├── GameControllerSample ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── google │ │ └── fpl │ │ └── gamecontroller │ │ ├── GameState.java │ │ ├── GamepadController.java │ │ ├── MainActivity.java │ │ ├── PowerUp.java │ │ ├── ShapeBuffer.java │ │ ├── Spaceship.java │ │ ├── Utils.java │ │ ├── WallSegment.java │ │ └── particles │ │ ├── BackgroundParticleSystem.java │ │ ├── BaseParticle.java │ │ ├── ParticleCollisionGrid.java │ │ └── ParticleSystem.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_main.xml │ ├── raw │ ├── untextured_fs.glslf │ └── untextured_vs.glslv │ ├── values-sw600dp │ └── dimens.xml │ ├── values-sw720dp-land │ └── dimens.xml │ ├── values-v11 │ └── styles.xml │ ├── values-v14 │ └── styles.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ic_launcher-web.png ├── libs └── android-support-v4.jar ├── project.properties └── settings.gradle /GameControllerSample/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | buildscript { 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:0.9.+' 11 | } 12 | } 13 | 14 | apply plugin: 'android' 15 | 16 | 17 | dependencies { 18 | 19 | // Add the support lib that is appropriate for SDK 8 20 | compile "com.android.support:support-v4:19.1.+" 21 | compile "com.android.support:gridlayout-v7:19.1.+" 22 | 23 | 24 | } 25 | 26 | // The sample build uses multiple directories to 27 | // keep boilerplate and common code separate from 28 | // the main sample code. 29 | List dirs = [ 30 | 'main', // main sample code; look here for the interesting stuff. 31 | 'common', // components that are reused by multiple samples 32 | 'template'] // boilerplate code that is generated by the sample template process 33 | 34 | android { 35 | compileSdkVersion 19 36 | 37 | buildToolsVersion "19.0.3" 38 | 39 | sourceSets { 40 | main { 41 | dirs.each { dir -> 42 | java.srcDirs "src/${dir}/java" 43 | res.srcDirs "src/${dir}/res" 44 | } 45 | } 46 | androidTest.setRoot('tests') 47 | androidTest.java.srcDirs = ['tests/src'] 48 | 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/GameState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | import android.content.Context; 20 | import android.hardware.input.InputManager; 21 | import android.hardware.input.InputManager.InputDeviceListener; 22 | import android.opengl.GLES20; 23 | import android.opengl.GLSurfaceView; 24 | import android.opengl.GLSurfaceView.Renderer; 25 | import android.opengl.Matrix; 26 | import android.view.InputDevice; 27 | import android.view.InputEvent; 28 | import android.view.KeyEvent; 29 | import android.view.MotionEvent; 30 | 31 | import com.google.fpl.gamecontroller.particles.BackgroundParticleSystem; 32 | import com.google.fpl.gamecontroller.particles.ParticleSystem; 33 | 34 | import javax.microedition.khronos.egl.EGLConfig; 35 | import javax.microedition.khronos.opengles.GL10; 36 | 37 | /** 38 | * A singleton class that manages the OpenGL context, camera, players, etc. 39 | */ 40 | public class GameState extends GLSurfaceView implements Renderer, InputDeviceListener { 41 | 42 | // The "world" is everything that is visible on the screen. The world extends to the outside 43 | // edges of the walls that surround the world. 44 | // 45 | // These dimensions are somewhat arbitrary, and were chosen because they match the 16:9 46 | // aspect ratio of many tablets and TVs. The world dimensions do not map directly to pixels 47 | // on the screen. During rendering, the world is scaled so that it takes up as much of the 48 | // screen as possible, but still maintains the 16:9 aspect ratio. For displays with different 49 | // aspect ratios, some portions of the display will be empty. 50 | public static final int WORLD_WIDTH = 1280 / 4; 51 | public static final int WORLD_HEIGHT = 720 / 4; 52 | // Define the rectangle that bounds the world. The coordinate system used by the world 53 | // is centered around 0, 0. 54 | public static final int WORLD_TOP_COORDINATE = WORLD_HEIGHT / 2; 55 | public static final int WORLD_BOTTOM_COORDINATE = -WORLD_HEIGHT / 2; 56 | public static final int WORLD_LEFT_COORDINATE = -WORLD_WIDTH / 2; 57 | public static final int WORLD_RIGHT_COORDINATE = WORLD_WIDTH / 2; 58 | 59 | // Player's ships are identified with positive integers. This value indicates an invalid 60 | // or unassigned player. 61 | public static final int INVALID_PLAYER_ID = -1; 62 | 63 | // Mapping for Z values when projected into the world. 64 | private static final float WORLD_NEAR_PLANE = -1.0f; 65 | private static final float WORLD_FAR_PLANE = 1.0f; 66 | private static final float WORLD_ASPECT_RATIO = (float) WORLD_WIDTH / (float) WORLD_HEIGHT; 67 | 68 | // The thickness of the walls that bound the world. 69 | private static final int MAP_WALL_THICKNESS = 20; 70 | 71 | // The "map" is the area where ships can move. The map is bounded by the inside edges of 72 | // that walls that surround the world. 73 | public static final int MAP_WIDTH = WORLD_WIDTH - 2 * MAP_WALL_THICKNESS; 74 | public static final int MAP_HEIGHT = WORLD_HEIGHT - 2 * MAP_WALL_THICKNESS; 75 | // Define the rectangle that bounds the map. The map and the world share the same coordinate 76 | // system centered at 0, 0. 77 | public static final int MAP_TOP_COORDINATE = MAP_HEIGHT / 2; 78 | public static final int MAP_BOTTOM_COORDINATE = -MAP_HEIGHT / 2; 79 | public static final int MAP_LEFT_COORDINATE = -MAP_WIDTH / 2; 80 | public static final int MAP_RIGHT_COORDINATE = MAP_WIDTH / 2; 81 | 82 | // Set to "true" to print info about every controller event. 83 | private static final boolean CONTROLLER_DEBUG_PRINT = false; 84 | 85 | // An arbitrary frame-rate used to compute the "frameDelta" value that is passed to 86 | // update and other functions that require a time delta. This frame-rate does not have 87 | // any relationship to the actual rate at which the screen refreshes. 88 | // See onDrawFrame() and update() for more info on how this value is used. 89 | private static final float ANIMATION_FRAMES_PER_SECOND = 60.0f; 90 | 91 | // The maximum number of controllers supported by this game. 92 | private static final int MAX_PLAYERS = 4; 93 | 94 | // The number of "power-ups" to draw on the map. 95 | private static final int MAX_POWERUPS = 2; 96 | 97 | // The first player to join is red, second is green, etc. 98 | private static final Utils.Color PLAYER_COLORS[] = new Utils.Color[] { 99 | Utils.Color.RED, 100 | Utils.Color.GREEN, 101 | Utils.Color.YELLOW, 102 | Utils.Color.BLUE 103 | }; 104 | 105 | // The number of points a player must score to win a match. 106 | private static final int POINTS_PER_MATCH = 5; 107 | 108 | // Every particle needs to be checked every frame, even if it is not active. 109 | // Therefore, it is best for performance to not over-allocate particles. 110 | // Running out of background or explosion particles doesn't affect game play at all, 111 | // is likely to go unnoticed by players. 112 | private static final int MAX_EXPLOSION_PARTICLES = 2000; 113 | private static final int MAX_BACKGROUND_PARTICLES = 400; 114 | private static final int MAX_BULLET_PARTICLES = 500; 115 | 116 | // Singleton instance of the GameState. 117 | private static GameState sGameStateInstance = null; 118 | 119 | // A 4x4 matrix that represents the combined model, view, and projection matrices. 120 | private final float[] mMVPMatrix = new float[16]; 121 | 122 | // The window dimensions in pixels. 123 | private int mWindowWidth, mWindowHeight; 124 | 125 | // One Spaceship per controller. 126 | private Spaceship[] mPlayerList; 127 | 128 | // The animated background particles. 129 | private BackgroundParticleSystem mBackgroundParticles; 130 | 131 | // Manages the shots fired by the ships. 132 | private ParticleSystem mShots; 133 | 134 | // Manages explosion particles. 135 | private ParticleSystem mExplosions; 136 | 137 | // The walls and other obstacles in the world. 138 | private WallSegment[] mWallList; 139 | 140 | // All geometry for the frame goes into a single ShapeBuffer. 141 | private ShapeBuffer mShapeBuffer = null; 142 | 143 | // The list of power ups shown on the map. 144 | private PowerUp[] mPowerupList; 145 | 146 | // The system time (in milliseconds) of the last frame update. 147 | private long mLastUpdateTimeMillis; 148 | 149 | /** 150 | * @return The global GameState object. 151 | */ 152 | public static GameState getInstance() { 153 | return sGameStateInstance; 154 | } 155 | 156 | /** 157 | * Converts a duration in seconds to a duration in number of elapsed frames. 158 | */ 159 | public static float secondsToFrameDelta(float seconds) { 160 | return seconds * ANIMATION_FRAMES_PER_SECOND; 161 | } 162 | /** 163 | * Converts a duration in milliseconds to a duration in number of elapsed frames. 164 | */ 165 | public static float millisToFrameDelta(long milliseconds) { 166 | return secondsToFrameDelta((float) milliseconds / 1000.0f); 167 | } 168 | 169 | /** 170 | * Determines if the given point is within the bounds of the world. 171 | * 172 | * @param x the x coordinate of the point. 173 | * @param y the y coordinate of the point. 174 | * @return true of the point is inside the world. 175 | */ 176 | public static boolean inWorld(float x, float y) { 177 | return x >= GameState.WORLD_LEFT_COORDINATE 178 | && x <= GameState.WORLD_RIGHT_COORDINATE 179 | && y >= GameState.WORLD_BOTTOM_COORDINATE 180 | && y <= GameState.WORLD_TOP_COORDINATE; 181 | } 182 | 183 | /** 184 | * Set this class as renderer for this GLSurfaceView. 185 | * Request Focus and set if focusable in touch mode to 186 | * receive the Input from Screen 187 | * 188 | * @param context The Activity Context. 189 | */ 190 | public GameState(Context context) { 191 | super(context); 192 | sGameStateInstance = this; 193 | 194 | // Request GL ES 2.0 context. 195 | setEGLContextClientVersion(2); 196 | // Set this as Renderer. 197 | this.setRenderer(this); 198 | // Request focus. 199 | this.requestFocus(); 200 | this.setFocusableInTouchMode(true); 201 | 202 | // Create the lists of players and power-ups. 203 | mPlayerList = new Spaceship[MAX_PLAYERS]; 204 | for (int i = 0; i < mPlayerList.length; i++) { 205 | mPlayerList[i] = new Spaceship(this, i, PLAYER_COLORS[i]); 206 | } 207 | mPowerupList = new PowerUp[MAX_POWERUPS]; 208 | for (int i = 0; i < mPowerupList.length; i++) { 209 | mPowerupList[i] = new PowerUp(); 210 | } 211 | 212 | mBackgroundParticles = new BackgroundParticleSystem(MAX_BACKGROUND_PARTICLES); 213 | mExplosions = new ParticleSystem(MAX_EXPLOSION_PARTICLES, false); 214 | 215 | // The true here means we want collision tracking data. 216 | mShots = new ParticleSystem(MAX_BULLET_PARTICLES, true); 217 | 218 | mLastUpdateTimeMillis = System.currentTimeMillis(); 219 | 220 | InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); 221 | inputManager.registerInputDeviceListener(this, null); 222 | 223 | buildMap(); 224 | } 225 | 226 | /* ***** Listener Events ***** */ 227 | @Override 228 | public void onInputDeviceAdded(int arg0) { 229 | Utils.logDebug("Device added: " + arg0); 230 | } 231 | 232 | @Override 233 | public void onInputDeviceChanged(int arg0) { 234 | Utils.logDebug("Device changed: " + arg0); 235 | } 236 | 237 | @Override 238 | public void onInputDeviceRemoved(int arg0) { 239 | Utils.logDebug("Device removed: " + arg0); 240 | // Deactivate a player when their corresponding input device is removed. 241 | for (Spaceship player : mPlayerList) { 242 | if (player.isActive() && player.getController().getDeviceId() == arg0) { 243 | Utils.logDebug("Deactivated player: " + arg0); 244 | player.deactivateShip(); 245 | } 246 | } 247 | } 248 | 249 | public ParticleSystem getShots() { 250 | return mShots; 251 | } 252 | public ParticleSystem getExplosions() { 253 | return mExplosions; 254 | } 255 | public BackgroundParticleSystem getBackgroundParticles() { 256 | return mBackgroundParticles; 257 | } 258 | public Spaceship[] getPlayerList() { 259 | return mPlayerList; 260 | } 261 | public WallSegment[] getWallList() { 262 | return mWallList; 263 | } 264 | 265 | /** 266 | * The Surface is created/init() 267 | */ 268 | @Override 269 | public void onSurfaceCreated(GL10 unused, EGLConfig config) { 270 | // The ShapeBuffer creates OpenGl resources, so don't create it until after the 271 | // primary rendering surface has been created. 272 | mShapeBuffer = new ShapeBuffer(); 273 | mShapeBuffer.loadResources(); 274 | } 275 | 276 | /** 277 | * Here we do our drawing 278 | */ 279 | @Override 280 | public void onDrawFrame(GL10 unused) { 281 | // Clear the screen to black. 282 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 283 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 284 | 285 | // Don't try to draw if the shape buffer failed to initialize. 286 | if (!mShapeBuffer.isInitialized()) { 287 | return; 288 | } 289 | 290 | long currentTimeMillis = System.currentTimeMillis(); 291 | 292 | // Compute frame delta. frameDelta = # of "ideal" frames that have occurred since the 293 | // last update. "ideal" assumes a constant frame-rate (60 FPS or 16.7 milliseconds per 294 | // frame). Since the delta doesn't depend on the "real" frame-rate, the animations always 295 | // run at the same wall clock speed, regardless of what the real refresh rate is. 296 | // 297 | // frameDelta was used instead of a time delta in order to make the values passed 298 | // to update easier to understand when debugging the code. For example, a frameDelta 299 | // of "1.5" means that one and a half hypothetical frames have passed since the last 300 | // update. In wall time this would be 25 milliseconds or 0.025 seconds. 301 | float frameDelta = millisToFrameDelta(currentTimeMillis - mLastUpdateTimeMillis); 302 | 303 | update(frameDelta); 304 | draw(); 305 | mLastUpdateTimeMillis = currentTimeMillis; 306 | } 307 | 308 | /** 309 | * If the surface changes, reset the view size. 310 | */ 311 | @Override 312 | public void onSurfaceChanged(GL10 unused, int width, int height) { 313 | // Make sure the window dimensions are never 0. 314 | mWindowWidth = Math.max(width, 1); 315 | mWindowHeight = Math.max(height, 1); 316 | } 317 | 318 | /** 319 | * Indicate that the given player has scored a point. 320 | * 321 | * Will end the match if the scoring player has enough points to win. 322 | * 323 | * @param playerId the id of the scoring player. 324 | */ 325 | public void scorePoint(int playerId) { 326 | for (Spaceship player : mPlayerList) { 327 | if (player.isActive() && player.getPlayerId() == playerId) { 328 | player.changeScore(1); 329 | // See if the scoring player has enough points to win the match. 330 | if (player.getScore() >= POINTS_PER_MATCH) { 331 | endMatch(player); 332 | } 333 | } 334 | } 335 | } 336 | 337 | /** 338 | * Handles motion (joystick) input events. 339 | * 340 | * @param motionEvent The event to handle. 341 | * @return true if the event was handled. 342 | */ 343 | public boolean handleMotionEvent(MotionEvent motionEvent) { 344 | Spaceship player = mapInputEventToShip(motionEvent); 345 | if (player != null) { 346 | player.getController().handleMotionEvent(motionEvent); 347 | return true; 348 | } 349 | return false; 350 | } 351 | 352 | /** 353 | * Handles key input events. 354 | * 355 | * @param keyEvent The event to handle. 356 | * @return true if the event was handled. 357 | */ 358 | public boolean handleKeyEvent(KeyEvent keyEvent) { 359 | Spaceship player = mapInputEventToShip(keyEvent); 360 | if (player != null) { 361 | player.getController().handleKeyEvent(keyEvent); 362 | return true; 363 | } 364 | return false; 365 | } 366 | 367 | /** 368 | * Update positions, animations, etc. 369 | * 370 | * @param frameDelta The amount of time (in "frame units") that has elapsed since the last 371 | * call to update(). 372 | */ 373 | public void update(float frameDelta) { 374 | mBackgroundParticles.update(frameDelta); 375 | mExplosions.update(frameDelta); 376 | mShots.update(frameDelta); 377 | 378 | for (Spaceship player : mPlayerList) { 379 | // Only update the active players. 380 | if (player.isActive()) { 381 | player.update(frameDelta); 382 | } 383 | } 384 | 385 | for (WallSegment wall : mWallList) { 386 | wall.update(frameDelta); 387 | } 388 | for (PowerUp powerUp : mPowerupList) { 389 | powerUp.update(frameDelta); 390 | } 391 | } 392 | 393 | /** 394 | * Draws the world. 395 | */ 396 | public void draw() { 397 | // Each world element adds triangles to the shape buffer. No OpenGl calls are made 398 | // until after the whole scene has been added to the shape buffer. 399 | mShapeBuffer.clear(); 400 | mBackgroundParticles.draw(mShapeBuffer); 401 | for (WallSegment wall : mWallList) { 402 | wall.draw(mShapeBuffer); 403 | } 404 | for (PowerUp powerUp : mPowerupList) { 405 | powerUp.draw(mShapeBuffer); 406 | } 407 | 408 | mExplosions.draw(mShapeBuffer); 409 | for (Spaceship player : mPlayerList) { 410 | if (player.isActive()) { 411 | player.draw(mShapeBuffer); 412 | } 413 | } 414 | // Draw shots above everything else. 415 | mShots.draw(mShapeBuffer); 416 | 417 | // Prepare for rendering to the screen. 418 | updateViewportAndProjection(); 419 | 420 | // Send the triangles to OpenGl. 421 | mShapeBuffer.draw(mMVPMatrix); 422 | } 423 | 424 | /** 425 | * Builds the static map, including walls and obstacles. 426 | */ 427 | private void buildMap() { 428 | // The origin of the map coordinate system. 429 | final int mapCenterX = 0; 430 | final int mapCenterY = 0; 431 | 432 | final int mapTopWallCenterY = MAP_TOP_COORDINATE + MAP_WALL_THICKNESS / 2; 433 | final int mapBottomWallCenterY = MAP_BOTTOM_COORDINATE - MAP_WALL_THICKNESS / 2; 434 | final int mapRightWallCenterX = MAP_RIGHT_COORDINATE + MAP_WALL_THICKNESS / 2; 435 | final int mapLeftWallCenterX = MAP_LEFT_COORDINATE - MAP_WALL_THICKNESS / 2; 436 | 437 | final int rectangleShortEdgeLength = 20; 438 | final int rectangleLongEdgeLength = 60; 439 | 440 | final int squareEdgeLength = 20; 441 | 442 | mWallList = new WallSegment[]{ 443 | // Rectangles: 444 | // Rectangle touching top edge. 445 | new WallSegment( 446 | mapCenterX + 40, 447 | WORLD_TOP_COORDINATE - rectangleLongEdgeLength / 2, 448 | rectangleShortEdgeLength, 449 | rectangleLongEdgeLength), 450 | // Rectangle touching bottom edge. 451 | new WallSegment( 452 | mapCenterX - 40, 453 | WORLD_BOTTOM_COORDINATE + rectangleLongEdgeLength / 2, 454 | rectangleShortEdgeLength, 455 | rectangleLongEdgeLength), 456 | // Rectangle in center of map. 457 | new WallSegment( 458 | mapCenterX, 459 | mapCenterY, 460 | rectangleLongEdgeLength, 461 | rectangleShortEdgeLength), 462 | 463 | // Squares: one in each quadrant of the map. 464 | // Square in lower right. 465 | new WallSegment( 466 | mapCenterX + 80, 467 | mapCenterY - 50, 468 | squareEdgeLength, squareEdgeLength), 469 | // Square in upper left. 470 | new WallSegment( 471 | mapCenterX - 80, 472 | mapCenterY + 50, 473 | squareEdgeLength, 474 | squareEdgeLength), 475 | // Square in upper right. 476 | new WallSegment( 477 | mapCenterX + 110, 478 | mapCenterY + 30, 479 | squareEdgeLength, 480 | squareEdgeLength), 481 | // Square in lower left. 482 | new WallSegment( 483 | mapCenterX - 110, 484 | mapCenterY - 30, 485 | squareEdgeLength, 486 | squareEdgeLength), 487 | 488 | // Walls: around the edge of the map. 489 | // Top 490 | new WallSegment( 491 | mapCenterX, 492 | mapTopWallCenterY, 493 | WORLD_WIDTH, 494 | MAP_WALL_THICKNESS), 495 | // Bottom 496 | new WallSegment( 497 | mapCenterX, 498 | mapBottomWallCenterY, 499 | WORLD_WIDTH, 500 | MAP_WALL_THICKNESS), 501 | // Right 502 | new WallSegment( 503 | mapRightWallCenterX, 504 | mapCenterY, MAP_WALL_THICKNESS, 505 | WORLD_HEIGHT), 506 | // Left 507 | new WallSegment( 508 | mapLeftWallCenterX, 509 | mapCenterY, 510 | MAP_WALL_THICKNESS, 511 | WORLD_HEIGHT), 512 | }; 513 | } 514 | 515 | /** 516 | * Computes the view projections and sets the OpenGl viewport. 517 | */ 518 | private void updateViewportAndProjection() { 519 | // Assume a square viewport if the width and height haven't been initialized. 520 | float viewportAspectRatio = 1.0f; 521 | if ((mWindowWidth > 0) && (mWindowHeight > 0)) { 522 | viewportAspectRatio = (float) mWindowWidth / (float) mWindowHeight; 523 | } 524 | float viewportWidth = (float) mWindowWidth; 525 | float viewportHeight = (float) mWindowHeight; 526 | float viewportOffsetX = 0.0f; 527 | float viewportOffsetY = 0.0f; 528 | 529 | if (WORLD_ASPECT_RATIO > viewportAspectRatio) { 530 | // Our window is taller than the ideal aspect ratio needed to accommodate the world 531 | // without stretching. 532 | // Reduce the viewport height to match the aspect ratio of the world. The world 533 | // will fill the whole width of the screen, but have some empty space on the top and 534 | // bottom of the screen. 535 | viewportHeight = viewportWidth / WORLD_ASPECT_RATIO; 536 | // Center the viewport on the screen. 537 | viewportOffsetY = ((float) mWindowHeight - viewportHeight) / 2.0f; 538 | } else if (viewportAspectRatio > WORLD_ASPECT_RATIO) { 539 | // Our window is wider than the ideal aspect ratio needed to accommodate the world 540 | // without stretching. 541 | // Reduce the viewport width to match the aspect ratio of the world. The world 542 | // will fill the whole height of the screen, but have some empty space on the 543 | // left and right of the screen. 544 | viewportWidth = viewportHeight * WORLD_ASPECT_RATIO; 545 | // Center the viewport on the screen. 546 | viewportOffsetX = ((float) mWindowWidth - viewportWidth) / 2.0f; 547 | } 548 | 549 | Matrix.orthoM(mMVPMatrix, 0, 550 | WORLD_LEFT_COORDINATE, 551 | WORLD_RIGHT_COORDINATE, 552 | WORLD_BOTTOM_COORDINATE, 553 | WORLD_TOP_COORDINATE, 554 | WORLD_NEAR_PLANE, 555 | WORLD_FAR_PLANE); 556 | GLES20.glViewport((int) viewportOffsetX, (int) viewportOffsetY, 557 | (int) viewportWidth, (int) viewportHeight); 558 | } 559 | 560 | /** 561 | * Finds a player's Spaceship object that corresponds to a given input event. 562 | * 563 | * Events that do not come from game controllers are ignored. If the event is from a new 564 | * controller, the corresponding player's ship is activated. 565 | * 566 | * @param event The InputEvent to check. 567 | * @return a player's ship or null if the event was not from a controller. 568 | */ 569 | private Spaceship mapInputEventToShip(InputEvent event) { 570 | // getControllerNumber() will return "0" for devices that are not game controllers or 571 | // joysticks. 572 | int controllerNumber = InputDevice.getDevice(event.getDeviceId()).getControllerNumber() - 1; 573 | 574 | if (CONTROLLER_DEBUG_PRINT) { 575 | Utils.logDebug("----------------------------------------------"); 576 | Utils.logDebug("Input event: "); 577 | Utils.logDebug("Source: " + event.getSource()); 578 | Utils.logDebug("isFromSource(gamepad): " 579 | + event.isFromSource(InputDevice.SOURCE_GAMEPAD)); 580 | Utils.logDebug("isFromSource(joystick): " 581 | + event.isFromSource(InputDevice.SOURCE_JOYSTICK)); 582 | Utils.logDebug("isFromSource(touch nav): " 583 | + event.isFromSource(InputDevice.SOURCE_TOUCH_NAVIGATION)); 584 | Utils.logDebug("Controller: " + controllerNumber); 585 | Utils.logDebug("----------------------------------------------"); 586 | } 587 | 588 | if (controllerNumber >= 0 && controllerNumber < mPlayerList.length) { 589 | // Bind the device to the player's controller. 590 | mPlayerList[controllerNumber].getController().setDeviceId(event.getDeviceId()); 591 | return mPlayerList[controllerNumber]; 592 | } 593 | 594 | return null; 595 | } 596 | 597 | /** 598 | * Prepares the game for a new match. 599 | * 600 | * @param winningPlayer The winning ship from the last match. 601 | */ 602 | private void endMatch(Spaceship winningPlayer) { 603 | Utils.logDebug("Match over! - Player " + winningPlayer.getPlayerId() 604 | + " has " + winningPlayer.getScore() + " points!"); 605 | 606 | // Reset the score for each ship. 607 | for (Spaceship player : mPlayerList) { 608 | player.resetAtEndOfMatch(winningPlayer.getPlayerId()); 609 | } 610 | 611 | // Set the background color to the winning player's color until the next 612 | // match starts. 613 | mBackgroundParticles.flashWinningColor(winningPlayer.getColor()); 614 | } 615 | } 616 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/GamepadController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | import android.view.KeyEvent; 20 | import android.view.MotionEvent; 21 | 22 | /** 23 | * Handles input events from game pad controllers (includes joystick and button inputs). 24 | */ 25 | public class GamepadController { 26 | 27 | // The buttons on the game pad. 28 | public static final int BUTTON_A = 0; 29 | public static final int BUTTON_B = 1; 30 | public static final int BUTTON_X = 2; 31 | public static final int BUTTON_Y = 3; 32 | public static final int BUTTON_COUNT = 4; 33 | 34 | // The axes for joystick movement. 35 | public static final int AXIS_X = 0; 36 | public static final int AXIS_Y = 1; 37 | public static final int AXIS_COUNT = 2; 38 | 39 | // Game pads usually have 2 joysticks. 40 | public static final int JOYSTICK_1 = 0; 41 | public static final int JOYSTICK_2 = 1; 42 | public static final int JOYSTICK_COUNT = 2; 43 | 44 | // Keep track of button states for the current and previous frames. 45 | protected static final int FRAME_INDEX_CURRENT = 0; 46 | protected static final int FRAME_INDEX_PREVIOUS = 1; 47 | protected static final int FRAME_INDEX_COUNT = 2; 48 | 49 | // Positions of the two joysticks. 50 | private final float mJoystickPositions[][]; 51 | // The button states for the current and previous frames. 52 | private final boolean mButtonState[][]; 53 | // The device that we are tuned to. 54 | private int mDeviceId = -1; 55 | 56 | public GamepadController() { 57 | mButtonState = new boolean[BUTTON_COUNT][FRAME_INDEX_COUNT]; 58 | mJoystickPositions = new float[JOYSTICK_COUNT][AXIS_COUNT]; 59 | resetState(); 60 | } 61 | 62 | /** 63 | * Returns the controller to its default state. 64 | * 65 | * The histories for all joystick movements and button presses is also reset. 66 | */ 67 | private void resetState() { 68 | for (int button = 0; button < BUTTON_COUNT; ++button) { 69 | for (int frame = 0; frame < FRAME_INDEX_COUNT; ++frame) { 70 | mButtonState[button][frame] = false; 71 | } 72 | } 73 | for (int joystick = 0; joystick < JOYSTICK_COUNT; ++joystick) { 74 | for (int axis = 0; axis < AXIS_COUNT; ++axis) { 75 | mJoystickPositions[joystick][axis] = 0.0f; 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * @return The id of the associated controller device, or -1 if not assigned a device. 82 | */ 83 | public int getDeviceId() { 84 | return mDeviceId; 85 | } 86 | /** 87 | * Sets the physical device id for this controller. 88 | * 89 | * @param newId The physical device id, or -1 to indicate no assigned physical device. 90 | */ 91 | public void setDeviceId(int newId) { 92 | if (newId != mDeviceId) { 93 | mDeviceId = newId; 94 | if (newId != -1) { 95 | // Reset our button and axis state when a new physical device is attached. 96 | resetState(); 97 | } 98 | } 99 | } 100 | /** 101 | * @return true if this controller is assigned a physical device id. 102 | */ 103 | public boolean isActive() { 104 | return mDeviceId != -1; 105 | } 106 | 107 | /** 108 | * Returns the position of a joystick along a single axis. 109 | * 110 | * @param joystickIndex One of: JOYSTICK_1 or JOYSTICK_2. 111 | * @param axis One of: AXIS_X or AXIS_Y. 112 | * @return A value in the range -1 to 1, inclusive, where 0 represents the joystick's 113 | * center position. 114 | */ 115 | public float getJoystickPosition(int joystickIndex, int axis) { 116 | return mJoystickPositions[joystickIndex][axis]; 117 | } 118 | 119 | /** 120 | * Returns true if the given button is currently pressed. 121 | * 122 | * @param buttonId One of: BUTTON_A, BUTTON_B, BUTTON_X, or BUTTON_Y. 123 | * @return true if the given button is currently pressed. 124 | */ 125 | public boolean isButtonDown(int buttonId) { 126 | return mButtonState[buttonId][FRAME_INDEX_CURRENT]; 127 | } 128 | 129 | /** 130 | * Returns true if a button is down now, but wasn't last frame. 131 | * 132 | * @param buttonId One of: BUTTON_A, BUTTON_B, BUTTON_X, or BUTTON_Y. 133 | * @return true if a button is down now, but wasn't last frame. 134 | */ 135 | public boolean wasButtonPressed(int buttonId) { 136 | // Returns true if it's down now, but wasn't last frame. 137 | return mButtonState[buttonId][FRAME_INDEX_CURRENT] 138 | && !mButtonState[buttonId][FRAME_INDEX_PREVIOUS]; 139 | } 140 | 141 | /** 142 | * Returns true if it's up now, but wasn't last frame. 143 | * 144 | * @param buttonId One of: BUTTON_A, BUTTON_B, BUTTON_X, or BUTTON_Y. 145 | * @return true if it's up now, but wasn't last frame. 146 | */ 147 | public boolean wasButtonReleased(int buttonId) { 148 | return !mButtonState[buttonId][FRAME_INDEX_CURRENT] 149 | && mButtonState[buttonId][FRAME_INDEX_PREVIOUS]; 150 | } 151 | 152 | /** 153 | * Tells the controller to start tracking events for the next frame. 154 | */ 155 | public void advanceFrame() { 156 | // Copy the current button state to the previous frame. 157 | // We can't just toggle between both buffers because the buttons only update 158 | // when an event occurs (press or release), and not every frame. 159 | for (int i = 0; i < BUTTON_COUNT; i++) { 160 | mButtonState[i][FRAME_INDEX_PREVIOUS] = mButtonState[i][FRAME_INDEX_CURRENT]; 161 | } 162 | } 163 | 164 | /** 165 | * Updates the tracked state values of this controller in response to a motion input event. 166 | */ 167 | public void handleMotionEvent(MotionEvent motionEvent) { 168 | mJoystickPositions[JOYSTICK_1][AXIS_X] = motionEvent.getAxisValue(MotionEvent.AXIS_X); 169 | mJoystickPositions[JOYSTICK_1][AXIS_Y] = motionEvent.getAxisValue(MotionEvent.AXIS_Y); 170 | 171 | // The X and Y axes of the second joystick on a controller are mapped to the 172 | // MotionEvent AXIS_Z and AXIS_RZ values, respectively. 173 | mJoystickPositions[JOYSTICK_2][AXIS_X] = motionEvent.getAxisValue(MotionEvent.AXIS_Z); 174 | mJoystickPositions[JOYSTICK_2][AXIS_Y] = motionEvent.getAxisValue(MotionEvent.AXIS_RZ); 175 | } 176 | 177 | /** 178 | * Updates the tracked state values of this controller in response to a key input event. 179 | */ 180 | public void handleKeyEvent(KeyEvent keyEvent) { 181 | boolean keyIsDown = keyEvent.getAction() == KeyEvent.ACTION_DOWN; 182 | 183 | if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_A) { 184 | mButtonState[BUTTON_A][FRAME_INDEX_CURRENT] = keyIsDown; 185 | } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) { 186 | mButtonState[BUTTON_B][FRAME_INDEX_CURRENT] = keyIsDown; 187 | } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_X) { 188 | mButtonState[BUTTON_X][FRAME_INDEX_CURRENT] = keyIsDown; 189 | } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_Y) { 190 | mButtonState[BUTTON_Y][FRAME_INDEX_CURRENT] = keyIsDown; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import android.view.KeyEvent; 22 | import android.view.MotionEvent; 23 | 24 | /** 25 | * Application entry point. 26 | * 27 | * Forwards events to its GameState object. 28 | */ 29 | public class MainActivity extends Activity { 30 | /** 31 | * Manages the OpenGl context and all game mechanics. 32 | */ 33 | private GameState mGamestate; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | 39 | mGamestate = new GameState(this); 40 | // GameState is a GLSurfaceView. 41 | setContentView(mGamestate); 42 | } 43 | 44 | @Override 45 | protected void onResume() { 46 | super.onResume(); 47 | mGamestate.onResume(); 48 | } 49 | 50 | @Override 51 | protected void onPause() { 52 | super.onPause(); 53 | mGamestate.onPause(); 54 | } 55 | 56 | @Override 57 | public boolean dispatchGenericMotionEvent(MotionEvent event) { 58 | return mGamestate.handleMotionEvent(event); 59 | } 60 | 61 | @Override 62 | public boolean dispatchKeyEvent(KeyEvent event) { 63 | return mGamestate.handleKeyEvent(event); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/PowerUp.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | /** 20 | * Handles drawing, placing and detecting collisions with power-ups. 21 | */ 22 | public class PowerUp { 23 | 24 | // The number of frames to wait to wait after a powerup has been collected before 25 | // it respawns. 26 | private static final float RESPAWN_MIN_FRAME_COUNT = GameState.secondsToFrameDelta(3.0f); 27 | private static final float RESPAWN_MAX_FRAME_COUNT = GameState.secondsToFrameDelta(6.0f); 28 | 29 | private static final float ROTATION_RADIANS_PER_FRAME = 0.05f; 30 | 31 | // In radians, the amount the powerup's alpha value varies per frame. 32 | private static final float ALPHA_PULSE_RATE = 3.0f * ROTATION_RADIANS_PER_FRAME; 33 | // The minimum alpha value in the pulsing animation. 34 | private static final float ALPHA_PULSE_MIN_OPACITY = 2.0f / 3.0f; 35 | 36 | // When a ship gets within the powerup's COLLISION_RADIUS, it will collect the powerup. 37 | private static final float COLLISION_RADIUS = 5.0f; 38 | 39 | // Newly spawned powerups will not be placed too close to players. 40 | private static final float MIN_DISTANCE_TO_PLAYER = 10.0f; 41 | 42 | // The size of the powerup on screen. 43 | private static final float POWERUP_SCALE = 3.0f; 44 | 45 | // Parameters for the ring burst that is created when the power up spawns. 46 | private static final int RESPAWN_BURST_PARTICLE_COUNT = 50; 47 | private static final float RESPAWN_BURST_PARTICLE_SPEED = 1.5f; 48 | 49 | // Parameters for the steady stream of "steam" that comes from the powerup. 50 | private static final int STEAM_PARTICLE_COUNT = 1; 51 | private static final float STEAM_PARTICLE_MIN_SPEED = 0.075f; 52 | private static final float STEAM_PARTICLE_MAX_SPEED = 0.375f; 53 | 54 | // Don't try more than 10 times to find a valid location for the powerup. 55 | private static final int MAX_POWERUP_PLACEMENT_ATTEMPTS = 10; 56 | 57 | // The powerup should not be placed too close to the edge of the map. The number below 58 | // scales the map size to limit how close the power can get to the edge. 59 | private static final float USABLE_MAP_PERCENT = 0.95f; 60 | 61 | private float mPositionX, mPositionY; 62 | private float mTotalFrameCount = 0; 63 | 64 | // The respawn counter begins as a small positive number so that the first update will 65 | // trigger a respawn. 66 | private float mRespawnCounter = 1.0f; 67 | 68 | private final Utils.Color mColor = new Utils.Color(1.0f, 1.0f, 1.0f, 1.0f); 69 | private float mHeadingX; 70 | private float mHeadingY; 71 | 72 | public void update(float timeFactor) { 73 | GameState gameState = GameState.getInstance(); 74 | 75 | // See if it's time to respawn the powerup. 76 | if (mRespawnCounter > 0.0f) { 77 | mRespawnCounter -= timeFactor; 78 | if (mRespawnCounter <= 0.0f) { 79 | mRespawnCounter = 0.0f; 80 | pickNewLocation(); 81 | } 82 | if (mRespawnCounter <= 0.0f) { 83 | gameState.getExplosions().spawnRingBurst( 84 | mPositionX, mPositionY, 85 | Utils.Color.WHITE, 86 | RESPAWN_BURST_PARTICLE_SPEED, RESPAWN_BURST_PARTICLE_SPEED, 87 | RESPAWN_BURST_PARTICLE_COUNT); 88 | } 89 | } 90 | 91 | // Keep track of the total elapsed frame count for updating our cyclical animations 92 | // (spinning and pulsing). 93 | mTotalFrameCount += timeFactor; 94 | 95 | // Compute an alpha value that varies between fully opaque (1.0) 96 | // and ALPHA_PULSE_MIN_OPACITY (0.66). 97 | float alpha = (float) Math.sin(mTotalFrameCount * ALPHA_PULSE_RATE); 98 | alpha = alpha * (1.0f - ALPHA_PULSE_MIN_OPACITY) + ALPHA_PULSE_MIN_OPACITY; 99 | mColor.setAlpha(alpha); 100 | 101 | // Compute the current direction of the powerup. 102 | mHeadingX = (float) Math.sin(mTotalFrameCount * ROTATION_RADIANS_PER_FRAME); 103 | mHeadingY = (float) Math.cos(mTotalFrameCount * ROTATION_RADIANS_PER_FRAME); 104 | 105 | if (isSpawned()) { 106 | // The powerup throws off a steady stream of "steam" particles. 107 | // One particle is added each frame. 108 | gameState.getExplosions().spawnRingBurst( 109 | mPositionX, mPositionY, 110 | Utils.Color.WHITE, 111 | STEAM_PARTICLE_MIN_SPEED, STEAM_PARTICLE_MAX_SPEED, 112 | STEAM_PARTICLE_COUNT); 113 | // Check to see if any player is close enough to pick up the powerup. 114 | for (Spaceship player : gameState.getPlayerList()) { 115 | if (player.isActive()) { 116 | float distanceToPlayer = getDistanceToPoint(player.getPositionX(), 117 | player.getPositionY()); 118 | if (distanceToPlayer < COLLISION_RADIUS) { 119 | player.giveRandomWeapon(); 120 | mRespawnCounter = Utils.randFloatInRange( 121 | RESPAWN_MIN_FRAME_COUNT, 122 | RESPAWN_MAX_FRAME_COUNT); 123 | break; 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | public boolean isSpawned() { 131 | return mRespawnCounter == 0.0f; 132 | } 133 | 134 | 135 | public void draw(ShapeBuffer shapeBuffer) { 136 | if (isSpawned()) { 137 | shapeBuffer.add2DShape(mPositionX, mPositionY, mColor, 138 | Utils.SQUARE_SHAPE, POWERUP_SCALE, POWERUP_SCALE, 139 | mHeadingX, mHeadingY); 140 | } 141 | } 142 | 143 | 144 | public void pickNewLocation() { 145 | GameState gameState = GameState.getInstance(); 146 | 147 | boolean validLocation = false; 148 | // Tries to find a location that is not within a wall or too close to one of the players. 149 | // Gives up if it can't find a valid location after 10 tries. 150 | for (int tries = 0; tries < MAX_POWERUP_PLACEMENT_ATTEMPTS; ++tries) { 151 | mPositionX = Utils.randFloatInRange( 152 | GameState.MAP_LEFT_COORDINATE * USABLE_MAP_PERCENT, 153 | GameState.MAP_RIGHT_COORDINATE * USABLE_MAP_PERCENT); 154 | mPositionY = Utils.randFloatInRange( 155 | GameState.MAP_BOTTOM_COORDINATE * USABLE_MAP_PERCENT, 156 | GameState.MAP_TOP_COORDINATE * USABLE_MAP_PERCENT); 157 | // Assume it's true, until it fails... 158 | validLocation = true; 159 | // Don't spawn in a wall. 160 | for (WallSegment wall : gameState.getWallList()) { 161 | if (wall.isInWall(mPositionX, mPositionY)) { 162 | validLocation = false; 163 | break; 164 | } 165 | } 166 | for (Spaceship player : gameState.getPlayerList()) { 167 | // Don't spawn too close to a player. 168 | if (player.isActive()) { 169 | float distanceToPlayer = getDistanceToPoint(player.getPositionX(), 170 | player.getPositionY()); 171 | if (distanceToPlayer < MIN_DISTANCE_TO_PLAYER) { 172 | validLocation = false; 173 | break; 174 | } 175 | } 176 | } 177 | } 178 | 179 | if (!validLocation) { 180 | // Try again in a second. 181 | mRespawnCounter = GameState.secondsToFrameDelta(1.0f); 182 | } 183 | } 184 | 185 | /** 186 | * Convenience function to compute the distance between this powerup and the given point. 187 | */ 188 | private float getDistanceToPoint(float x, float y) { 189 | return Utils.distanceBetweenPoints(mPositionX, mPositionY, x, y); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/ShapeBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | import android.content.res.Resources; 20 | import android.opengl.GLES20; 21 | 22 | import java.io.BufferedReader; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.InputStreamReader; 26 | import java.nio.ByteBuffer; 27 | import java.nio.ByteOrder; 28 | import java.nio.FloatBuffer; 29 | import java.nio.IntBuffer; 30 | 31 | /** 32 | * Handles collecting and rendering of triangles. 33 | */ 34 | public class ShapeBuffer { 35 | // Number of coordinates per vertex in this array. 36 | private static final int COORDS_PER_VERTEX = 3; 37 | // 4 bytes per vertex. 38 | private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * 4; 39 | // Number of color components in each vertex. 40 | private static final int COLOR_CHANNELS_PER_VERTEX = 4; 41 | // Each color component is one byte. 42 | private static final int COLOR_STRIDE = COLOR_CHANNELS_PER_VERTEX; 43 | private static final int MAX_BUFFER_SIZE = 50000; 44 | 45 | // Names used in the vertex and fragment shaders. 46 | private static final String POSITION_ATTRIBUTE_NAME = "position"; 47 | private static final String COLOR_ATTRIBUTE_NAME = "color"; 48 | private static final String MVP_MATRIX_UNIFORM_NAME = "mvpMatrix"; 49 | 50 | private int mShaderProgram = 0; 51 | private int mCurrentIndex; 52 | private final float[] mVertexData; 53 | private final int[] mColorData; 54 | private final IntBuffer mColorBuffer; 55 | private final FloatBuffer mVertexBuffer; 56 | 57 | private int mPositionAttributeHandle; 58 | private int mColorAttributeHandle; 59 | private int mMVPMatrixUniformHandle; 60 | 61 | public ShapeBuffer() { 62 | mVertexData = new float[COORDS_PER_VERTEX * MAX_BUFFER_SIZE]; 63 | // All 4 color channels are packed into a single int, so only need one int per vertex. 64 | mColorData = new int[MAX_BUFFER_SIZE]; 65 | clear(); 66 | 67 | ByteBuffer bb; 68 | 69 | // size = buffer size x4 values per color, x1 bytes per color. 70 | bb = ByteBuffer.allocateDirect(MAX_BUFFER_SIZE * COLOR_STRIDE); 71 | bb.order(ByteOrder.nativeOrder()); 72 | mColorBuffer = bb.asIntBuffer(); 73 | 74 | // size = buffer size x3 values per coord, x4 bytes per float 75 | bb = ByteBuffer.allocateDirect(MAX_BUFFER_SIZE * VERTEX_STRIDE); 76 | bb.order(ByteOrder.nativeOrder()); 77 | mVertexBuffer = bb.asFloatBuffer(); 78 | } 79 | 80 | /** 81 | */ 82 | /** 83 | * Loads the shaders and looks up uniform and attribute locations. 84 | * 85 | * @return true if the shaders are successfully loaded and compiled. 86 | */ 87 | public boolean loadResources() { 88 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, 89 | getRawAsset(R.raw.untextured_vs)); 90 | int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, 91 | getRawAsset(R.raw.untextured_fs)); 92 | 93 | // Create empty OpenGL Program. 94 | int program = GLES20.glCreateProgram(); 95 | if (program != 0) { 96 | // Add the vertex shader to program. 97 | GLES20.glAttachShader(program, vertexShader); 98 | // Add the fragment shader to program. 99 | GLES20.glAttachShader(program, fragmentShader); 100 | // Create OpenGL program executables. 101 | GLES20.glLinkProgram(program); 102 | checkGlError("glLinkProgram"); 103 | 104 | // Get handle to vertex shader's position attribute. 105 | mPositionAttributeHandle = GLES20.glGetAttribLocation(program, POSITION_ATTRIBUTE_NAME); 106 | checkGlError("glGetAttribLocation - position"); 107 | 108 | // Get handle to vertex shader's color attribute. 109 | mColorAttributeHandle = GLES20.glGetAttribLocation(program, COLOR_ATTRIBUTE_NAME); 110 | checkGlError("glGetAttribLocation - color"); 111 | 112 | // Get handle to shape's transformation matrix. 113 | mMVPMatrixUniformHandle = GLES20.glGetUniformLocation(program, MVP_MATRIX_UNIFORM_NAME); 114 | checkGlError("glGetUniformLocation"); 115 | 116 | mShaderProgram = program; 117 | } 118 | return mShaderProgram != 0; 119 | } 120 | 121 | /** 122 | * Returns false if initialization has failed, for example if one of the shaders 123 | * failed to load. 124 | */ 125 | public boolean isInitialized() { 126 | return mShaderProgram != 0; 127 | } 128 | 129 | /** 130 | * Reset the buffer to start collecting new data. 131 | */ 132 | public void clear() { 133 | mCurrentIndex = 0; 134 | } 135 | 136 | public void add2DShape(float centerX, float centerY, Utils.Color color, 137 | float[] xyPositionOffsets, float scaleX, 138 | float scaleY, float headingX, float headingY) { 139 | // Normalize the heading vector. 140 | float magnitude = Utils.vector2DLength(headingX, headingY); 141 | if (magnitude == 0.0f) { 142 | headingX = 0.0f; 143 | headingY = 1.0f; 144 | } else { 145 | headingX /= magnitude; 146 | headingY /= magnitude; 147 | } 148 | 149 | final int packedABGRColor = color.getPackedABGR(); 150 | for (int i = 0; i < xyPositionOffsets.length - 1; i += 2) { 151 | final float positionX = xyPositionOffsets[i + 0]; 152 | final float positionY = xyPositionOffsets[i + 1]; 153 | 154 | float cx = (scaleX * positionX * headingX - scaleY * positionY * headingY); 155 | float cy = (scaleX * positionX * headingY + scaleY * positionY * headingX); 156 | 157 | // Compute the x and y positions. 158 | final int vertexOffset = mCurrentIndex * COORDS_PER_VERTEX; 159 | mVertexData[vertexOffset + 0] = cx + centerX; 160 | mVertexData[vertexOffset + 1] = cy + centerY; 161 | // Always set z to 0. 162 | mVertexData[vertexOffset + 2] = 0.0f; 163 | 164 | mColorData[mCurrentIndex] = packedABGRColor; 165 | ++mCurrentIndex; 166 | 167 | // If we're on the first or last point, repeat it for stiching. 168 | if (i == 0) { 169 | stitchingHelper(); 170 | } 171 | } 172 | stitchingHelper(); 173 | } 174 | 175 | /** 176 | * Draws all the triangles currently in the buffer. 177 | * 178 | * @param mvpMatrix the combined model, view, and projection matrices. 179 | */ 180 | public void draw(float[] mvpMatrix) { 181 | 182 | if (mCurrentIndex == 0) { 183 | // Nothing to draw. 184 | return; 185 | } 186 | 187 | // Load up our data: 188 | checkGlError("draw init"); 189 | 190 | mVertexBuffer.clear(); 191 | mVertexBuffer.put(mVertexData); 192 | mVertexBuffer.position(0); 193 | 194 | mColorBuffer.clear(); 195 | mColorBuffer.put(mColorData); 196 | mColorBuffer.position(0); 197 | 198 | GLES20.glEnable(GLES20.GL_BLEND); 199 | GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); 200 | checkGlError("draw start"); 201 | 202 | // Add program to OpenGL environment. 203 | GLES20.glUseProgram(mShaderProgram); 204 | checkGlError("glUseProgram"); 205 | 206 | // Enable a handle to the triangle vertices. 207 | GLES20.glEnableVertexAttribArray(mPositionAttributeHandle); 208 | // Prepare the triangle coordinate data. 209 | GLES20.glVertexAttribPointer(mPositionAttributeHandle, COORDS_PER_VERTEX, 210 | GLES20.GL_FLOAT, false, VERTEX_STRIDE, mVertexBuffer); 211 | checkGlError("glVertexAttribPointer - vert"); 212 | 213 | // Enable a handle to the triangle vertices. 214 | GLES20.glEnableVertexAttribArray(mColorAttributeHandle); 215 | // Prepare the color data. 216 | GLES20.glVertexAttribPointer(mColorAttributeHandle, COLOR_CHANNELS_PER_VERTEX, 217 | GLES20.GL_UNSIGNED_BYTE, true, COLOR_STRIDE, mColorBuffer); 218 | checkGlError("glVertexAttribPointer - color"); 219 | 220 | 221 | // Apply the projection and view transformation. 222 | GLES20.glUniformMatrix4fv(mMVPMatrixUniformHandle, 1, false, mvpMatrix, 0); 223 | checkGlError("glUniformMatrix4fv"); 224 | 225 | // Draw the buffers! 226 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mCurrentIndex); 227 | checkGlError("draw call"); 228 | 229 | // Disable vertex arrays. 230 | GLES20.glDisableVertexAttribArray(mPositionAttributeHandle); 231 | checkGlError("position attrib arrays disabled"); 232 | GLES20.glDisableVertexAttribArray(mColorAttributeHandle); 233 | checkGlError("vertex attrib arrays disabled"); 234 | } 235 | 236 | /** 237 | * Utility method for debugging OpenGL calls. Provide the name of the call 238 | * just after making it. 239 | * 240 | * If the operation is not successful, the check throws an error. 241 | * 242 | * @param glOperation Name of the OpenGL call to check. 243 | */ 244 | private static void checkGlError(String glOperation) { 245 | if (BuildConfig.DEBUG) { 246 | int error = GLES20.glGetError(); 247 | if (error != GLES20.GL_NO_ERROR) { 248 | Utils.logError(glOperation + ": glError " + error); 249 | throw new RuntimeException(glOperation + ": glError " + error); 250 | } 251 | } 252 | } 253 | 254 | /** 255 | * Loads the given raw resource into a string. 256 | * @param id the id of the resource to load. 257 | * @return the given resource as a string. 258 | */ 259 | private static String getRawAsset(int id) { 260 | Resources res = GameState.getInstance().getResources(); 261 | return fromStream(res.openRawResource(id)); 262 | } 263 | 264 | /** 265 | * Parses the given input stream into a string. 266 | * 267 | * @param in an InputStream. 268 | * @return a string. 269 | */ 270 | private static String fromStream(InputStream in) { 271 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 272 | StringBuilder out = new StringBuilder(); 273 | String newLine = System.getProperty("line.separator"); 274 | String line; 275 | try { 276 | while ((line = reader.readLine()) != null) { 277 | out.append(line); 278 | out.append(newLine); 279 | } 280 | } catch (IOException e) { 281 | Utils.logError(e.toString()); 282 | } 283 | return out.toString(); 284 | } 285 | 286 | /** 287 | * Creates an OpenGl shader object for the given shader source code. 288 | */ 289 | private static int loadShader(int type, String shaderCode) { 290 | // Create a vertex shader type (GLES20.GL_VERTEX_SHADER) 291 | // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER). 292 | int shader = GLES20.glCreateShader(type); 293 | 294 | // Add the source code to the shader and compile it. 295 | GLES20.glShaderSource(shader, shaderCode); 296 | GLES20.glCompileShader(shader); 297 | 298 | return shader; 299 | } 300 | 301 | /** 302 | * Duplicates the last point in our vertex list, to aid in stitching triangle strips. 303 | */ 304 | private void stitchingHelper() { 305 | // Copy the x, y, and x vertex components. 306 | mVertexData[3 * mCurrentIndex + 0] = mVertexData[3 * mCurrentIndex + 0 - 3]; 307 | mVertexData[3 * mCurrentIndex + 1] = mVertexData[3 * mCurrentIndex + 1 - 3]; 308 | mVertexData[3 * mCurrentIndex + 2] = mVertexData[3 * mCurrentIndex + 2 - 3]; 309 | 310 | // Copy the color component. 311 | mColorData[mCurrentIndex] = mColorData[mCurrentIndex - 1]; 312 | 313 | ++mCurrentIndex; 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/Spaceship.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | import com.google.fpl.gamecontroller.particles.BaseParticle; 20 | 21 | /** 22 | * Handles positioning, control, and spawning of user-controlled space ships. 23 | */ 24 | public class Spaceship { 25 | 26 | // The types of weapons available. 27 | private static final int WEAPON_BASEGUN = 0; 28 | private static final int WEAPON_MACHINEGUN = 1; 29 | private static final int WEAPON_SHOTGUN = 2; 30 | private static final int WEAPON_ARROWHEADS = 3; 31 | private static final int WEAPON_SCATTERGUN = 4; 32 | private static final int WEAPON_ROCKET = 5; 33 | private static final int WEAPON_COUNT = 6; 34 | 35 | // The 2D vertex positions used to render the ship. 36 | // The ship is shaped as a single triangle, with a total width of 2 units along the x-axis 37 | // and a height of 1 unit along the y-axis. 38 | private static final float[] SHIP_SHAPE = { 39 | -1.0f, 0.5f, 40 | -1.0f, -0.5f, 41 | 1.0f, 0.0f 42 | }; 43 | // Scales the above vertices into world-space units. 44 | private static final float SHIP_SIZE = 5.0f; 45 | 46 | // When the motion controller is released, the ship coasts to a stop. 47 | // The drag is expressed as a percentage of the ship's current velocity, so 0.05 drag 48 | // means that the ship will get 5% shower each frame. 49 | private static final float DRAG = 0.05f; 50 | 51 | // To prevent constant movement, the ship will stop moving if it's velocity drops below 52 | // this minimum velocity. 53 | private static final float MINIMUM_VELOCITY = 0.05f; 54 | 55 | // The minimum distance a joystick must move before it is considered to have moved. 56 | private static final float JOYSTICK_MOVEMENT_THRESHOLD = 0.1f; 57 | 58 | // The amount of time to wait after a ship has been destroyed before the ship 59 | // is spawned again. 60 | private static final float RESPAWN_FRAME_COUNT = GameState.millisToFrameDelta(2000); 61 | // When a ship spawns, it can not be killed while it is invincible. 62 | private static final float INVINCIBILITY_FRAME_COUNT = GameState.millisToFrameDelta(2000); 63 | // The amount of time to wait after the end of a match before starting a new match. 64 | private static final float NEW_MATCH_RESPAWN_FRAME_COUNT = GameState.millisToFrameDelta(6000); 65 | 66 | // The speed at which the various weapons can fire. 67 | private static final float GUN_FIREDELAY_BASEGUN = GameState.millisToFrameDelta(250); 68 | private static final float GUN_FIREDELAY_SHOTGUN = GameState.millisToFrameDelta(1000); 69 | private static final float GUN_FIREDELAY_MACHINEGUN = GameState.millisToFrameDelta(33); 70 | private static final float GUN_FIREDELAY_ARROWHEAD = GameState.millisToFrameDelta(500); 71 | private static final float GUN_FIREDELAY_ROCKET = GameState.millisToFrameDelta(750); 72 | private static final float GUN_FIREDELAY_SCATTERGUN = GameState.millisToFrameDelta(133); 73 | 74 | // Speeds for the various types of bullets. All speeds are in world-space units per frame. 75 | private static final float BULLET_SPEED_BASEGUN = 2.5f; 76 | private static final float BULLET_SPEED_MACHINEGUN = BULLET_SPEED_BASEGUN; 77 | private static final float BULLET_SPEED_SHOTGUN = BULLET_SPEED_BASEGUN; 78 | private static final float BULLET_SPEED_ARROWHEAD_CENTER = BULLET_SPEED_BASEGUN; 79 | private static final float BULLET_SPEED_SCATTERGUN = BULLET_SPEED_BASEGUN; 80 | private static final float BULLET_SPEED_SCATTERGUN_SECONDARY = 0.95f * BULLET_SPEED_SCATTERGUN; 81 | private static final float BULLET_SPEED_ROCKET = 2.0f; 82 | 83 | // The shotgun fires a volley of bullets along an arc. 84 | private static final int SHOTGUN_BULLET_COUNT = 20; 85 | private static final float SHOTGUN_BULLET_SPREAD_ARC_DEGREES = 20.0f; 86 | 87 | // Every other burst from the scatter gun fires a secondary set of bullets offset 88 | // from the central aiming direction. 89 | private static final int SCATTERGUN_SECONDARY_BULLET_COUNT = 2; 90 | private static final float SCATTERGUN_SECONDARY_BULLET_SPREAD_ARC_DEGREES = 15.0f; 91 | 92 | // The arrowhead weapon fires a triangular shaped volley of bullets. Each step 93 | // behind the center of the arrow has two bullets, one on either side of the center. 94 | private static final int ARROWHEAD_STEP_COUNT = 3; 95 | private static final int ARROWHEAD_STEP_BULLET_COUNT = 2; 96 | // Each step behind the central bullet travels slower than the one ahead of it. 97 | private static final float ARROWHEAD_STEP_SPEED_DECREMENT = 0.05f; 98 | // Each step behind the central bullet is offset further from the center. 99 | private static final float ARROWHEAD_STEP_SPREAD_INCREMENT = 3.0f; 100 | 101 | // The maximum lifetime for bullet particles. This is longer than it takes any bullet 102 | // to travel across the screen, so they will always hit something before timeing out. 103 | private static final float BULLET_LIFETIME_IN_SECONDS = 8.0f; 104 | 105 | // Attributes for bullet particles. 106 | private static final float BULLET_PARTICLE_SIZE = 0.75f; 107 | private static final float BULLET_PARTICLE_ASPECT_RATIO = 3.0f; 108 | private static final float BULLET_PARTICLE_INITIAL_POSITION_INCREMENT = 3.0f; 109 | 110 | // Attributes for rocket particles. 111 | private static final float ROCKET_PARTICLE_SIZE = 2.0f; 112 | private static final float ROCKET_PARTICLE_ASPECT_RATIO = 2.0f; 113 | private static final float ROCKET_PARTICLE_INITIAL_POSITION_INCREMENT = 3.0f; 114 | 115 | // The number of particles to spawn when creating a ring-burst around the ship. 116 | private static final int RINGBURST_PARTICLE_COUNT = 100; 117 | 118 | // Primary ring bursts have particles all moving at the same speed. 119 | private static final float RINGBURST_PRIMARY_MIN_SPEED = 1.5f; 120 | private static final float RINGBURST_PRIMARY_MAX_SPEED = 1.5f; 121 | 122 | // The secondary ring burst has particles moving at different speeds. 123 | private static final float RINGBURST_SECONDARY_MIN_SPEED = 0.75f; 124 | private static final float RINGBURST_SECONDARY_MAX_SPEED = 3.0f; 125 | 126 | // When a ship is invincible, it will flash a darker color. 127 | private static final float INVINCIBILITY_COLOR_DARKEN_FACTOR = 0.3f; 128 | // The number of frames between alternate colors while in invincible mode. 129 | private static final float INVINCIBILITY_COLOR_BLINK_RATE = GameState.millisToFrameDelta(200); 130 | 131 | private GameState mGameState; 132 | 133 | private float mPositionX, mPositionY; 134 | private int mPlayerId = GameState.INVALID_PLAYER_ID; 135 | private int mScore = 0; 136 | 137 | // The permanent color of the ship. 138 | private final Utils.Color mColor = new Utils.Color(); 139 | // The current color is updated each frame. It is the same as the permanent color, 140 | // unless the ship is invincible. 141 | private final Utils.Color mCurrentColor = new Utils.Color(); 142 | // Handles input events for this ship. 143 | private final GamepadController mController = new GamepadController(); 144 | // The vector describing this ship's speed and direction. If the ship is not moving, 145 | // it's velocity will be 0, but it's heading will point in the direction it was last moving. 146 | private float mVelocityX, mVelocityY; 147 | // The normalized direction of this ship. 148 | private float mHeadingX, mHeadingY; 149 | // The normalized direction vector along which bullets are fired. 150 | private float mAimX, mAimY; 151 | // If true, the aim direction was set by the secondary joystick. 152 | private boolean mJoystickAiming; 153 | // One of the available weapons. 154 | private int mCurrentGun; 155 | // The number of frames to wait before spawing this ship again. 0 when the ship is spawned. 156 | private float mRespawnTimer; 157 | // The number of frames this ship will remain invincible. 158 | private float mInvincibilityTimer; 159 | // The number of frames to wait before the gun can fire again. 160 | private float mGunRechargeTimer; 161 | 162 | // Used by the SCATTERGUN to determine how many shots to fire (every other shot fires 163 | // additional bullets). 164 | private int mFireCounter; 165 | 166 | public Spaceship(GameState gameState, int playerId, Utils.Color color) { 167 | resetPlayer(); 168 | this.mGameState = gameState; 169 | this.mHeadingX = 0.0f; 170 | this.mHeadingY = 1.0f; 171 | this.mColor.set(color); 172 | this.mPlayerId = playerId; 173 | 174 | // Set the respawn timer to something non-zero, so that a respawn event will trigger 175 | // in the next update. 176 | mRespawnTimer = 1.0f; 177 | } 178 | 179 | public float getPositionX() { 180 | return mPositionX; 181 | } 182 | public void setPositionX(float positionX) { 183 | this.mPositionX = Utils.clamp(positionX, GameState.MAP_LEFT_COORDINATE, 184 | GameState.MAP_RIGHT_COORDINATE); 185 | } 186 | public float getPositionY() { 187 | return mPositionY; 188 | } 189 | public void setPositionY(float positionY) { 190 | this.mPositionY = Utils.clamp(positionY, GameState.MAP_BOTTOM_COORDINATE, 191 | GameState.MAP_TOP_COORDINATE); 192 | } 193 | public int getPlayerId() { 194 | return mPlayerId; 195 | } 196 | public boolean isActive() { 197 | return mController.isActive(); 198 | } 199 | public int getScore() { 200 | return mScore; 201 | } 202 | public void changeScore(int pointDelta) { 203 | this.mScore = Math.max(0, mScore + pointDelta); 204 | } 205 | 206 | /** 207 | * Sets the player's score, weapon, etc. back to their default state. 208 | */ 209 | private void resetPlayer() { 210 | mScore = 0; 211 | 212 | mCurrentGun = WEAPON_BASEGUN; 213 | mRespawnTimer = 0.0f; 214 | mInvincibilityTimer = 0.0f; 215 | mFireCounter = 0; 216 | mGunRechargeTimer = 0.0f; 217 | } 218 | 219 | public Utils.Color getColor() { 220 | return mColor; 221 | } 222 | 223 | public void deactivateShip() { 224 | mController.setDeviceId(-1); 225 | } 226 | 227 | public void update(float frameDelta) { 228 | if (!updateStatus(frameDelta)) { 229 | // The ship is not active, so bail out now. 230 | return; 231 | } 232 | 233 | updateShipPosition(frameDelta); 234 | 235 | handleKeyPressesAndFiring(frameDelta); 236 | 237 | checkBulletCollisions(); 238 | 239 | // Tell the controller to start a new frame. This needs to be done after we're done 240 | // reading the controller's state for this frame. 241 | mController.advanceFrame(); 242 | } 243 | 244 | /** 245 | * Sets the current weapon to one of the power-up weapons (i.e. the new weapon will be any 246 | * of the weapons except WEAPON_BASEGUN). 247 | */ 248 | public void giveRandomWeapon() { 249 | mCurrentGun = Utils.randIntInRange(1, WEAPON_COUNT); 250 | spawnRingBurstAroundShip(RINGBURST_PRIMARY_MIN_SPEED, RINGBURST_PRIMARY_MAX_SPEED); 251 | } 252 | 253 | /** 254 | * Prepares a player for the start of a new match. 255 | * 256 | * @param winningPlayerId the Id of the winning player. 257 | */ 258 | public void resetAtEndOfMatch(int winningPlayerId) { 259 | resetPlayer(); 260 | if (isActive()) { 261 | if (mPlayerId != winningPlayerId) { 262 | // Explode the losing ships. 263 | spawnRingBurstAroundShip( 264 | RINGBURST_PRIMARY_MIN_SPEED, 265 | RINGBURST_PRIMARY_MAX_SPEED); 266 | spawnRingBurstAroundShip( 267 | RINGBURST_SECONDARY_MIN_SPEED, 268 | RINGBURST_SECONDARY_MAX_SPEED); 269 | } 270 | // Wait before starting next match. 271 | mRespawnTimer = NEW_MATCH_RESPAWN_FRAME_COUNT; 272 | } 273 | } 274 | 275 | public GamepadController getController() { 276 | return mController; 277 | } 278 | 279 | public boolean isSpawned() { 280 | return (mRespawnTimer <= 0); 281 | } 282 | 283 | public boolean isInvincible() { 284 | return (mInvincibilityTimer > 0); 285 | } 286 | 287 | /** 288 | * Draws the player's ship and 0 to 4 to indicate the player's score. 289 | */ 290 | public void draw(ShapeBuffer sb) { 291 | if (!isSpawned()) { 292 | // No drawing if we're not alive yet. 293 | return; 294 | } 295 | 296 | sb.add2DShape(mPositionX, mPositionY, mCurrentColor, SHIP_SHAPE, SHIP_SIZE, SHIP_SIZE, 297 | mHeadingX, mHeadingY); 298 | 299 | // Draw squares around the ship to indicate the score. 300 | for (int i = 0; i < Math.min(mScore, 4); ++i) { 301 | // Places the dots at the edges of a square the same size as the ship. 302 | sb.add2DShape( 303 | mPositionX + SHIP_SIZE * Utils.SQUARE_SHAPE[i * 2 + 0], 304 | mPositionY + SHIP_SIZE * Utils.SQUARE_SHAPE[i * 2 + 1], 305 | mColor, Utils.SQUARE_SHAPE, 1.0f, 1.0f, 0.0f, 1.0f); 306 | } 307 | if (mScore > 4) { 308 | // TODO: Implement a method to display more than 4 points per player. 309 | // For example, space the dots at equal intervals on a circle around the ship. 310 | Utils.logDebug("Scores higher than 4 are not displayed."); 311 | } 312 | } 313 | 314 | /** 315 | * Checks the aiming joystick position and computes the player's aim direction. 316 | */ 317 | protected void calculateAimDirection() { 318 | mAimX = mController.getJoystickPosition(GamepadController.JOYSTICK_2, 319 | GamepadController.AXIS_X); 320 | mAimY = -mController.getJoystickPosition(GamepadController.JOYSTICK_2, 321 | GamepadController.AXIS_Y); 322 | float magnitude = Utils.vector2DLength(mAimX, mAimY); 323 | 324 | if (magnitude > JOYSTICK_MOVEMENT_THRESHOLD) { 325 | // Normalize the direction vector. 326 | mAimX /= magnitude; 327 | mAimY /= magnitude; 328 | mJoystickAiming = true; 329 | } else { 330 | // The firing joystick is not being used, so fire any shots in the direction 331 | // the player is currently traveling. 332 | mAimX = mHeadingX; 333 | mAimY = mHeadingY; 334 | mJoystickAiming = false; 335 | } 336 | } 337 | 338 | /** 339 | * Fires one burst of the current weapon. 340 | */ 341 | protected void fireGun() { 342 | switch (mCurrentGun) { 343 | case WEAPON_BASEGUN: 344 | // Single bullet straight ahead. 345 | fireBullets(1, 0, BULLET_SPEED_BASEGUN, GUN_FIREDELAY_BASEGUN); 346 | break; 347 | case WEAPON_ARROWHEADS: 348 | // The center bullet of the arrowhead. 349 | fireBullets(1, 0, BULLET_SPEED_ARROWHEAD_CENTER, GUN_FIREDELAY_ARROWHEAD); 350 | 351 | // Fire the bullets that make up the steps behind the center bullet of the 352 | // arrowhead. 353 | for (int i = 1; i <= ARROWHEAD_STEP_COUNT; ++i) { 354 | // The bullets farther from the center go slower. 355 | float speedScale = 1.0f - i * ARROWHEAD_STEP_SPEED_DECREMENT; 356 | // Each step in the arrowhead has the bullets spread farther apart. 357 | float spread = i * ARROWHEAD_STEP_SPREAD_INCREMENT; 358 | fireBullets( 359 | ARROWHEAD_STEP_BULLET_COUNT, 360 | spread, 361 | speedScale * BULLET_SPEED_ARROWHEAD_CENTER, 362 | GUN_FIREDELAY_ARROWHEAD); 363 | } 364 | break; 365 | case WEAPON_SHOTGUN: 366 | // The shotgun fires a volley of bullets along an arc. 367 | fireBullets( 368 | SHOTGUN_BULLET_COUNT, 369 | SHOTGUN_BULLET_SPREAD_ARC_DEGREES, 370 | BULLET_SPEED_SHOTGUN, 371 | GUN_FIREDELAY_SHOTGUN); 372 | break; 373 | case WEAPON_MACHINEGUN: 374 | // Fire a single bullet straight ahead. 375 | fireBullets(1, 0, BULLET_SPEED_MACHINEGUN, GUN_FIREDELAY_MACHINEGUN); 376 | break; 377 | case WEAPON_SCATTERGUN: 378 | // Fire the first bullet straight ahead. 379 | fireBullets(1, 0, BULLET_SPEED_SCATTERGUN, GUN_FIREDELAY_SCATTERGUN); 380 | mFireCounter = (mFireCounter + 1) % 2; 381 | if (mFireCounter == 0) { 382 | // Every other burst from the scatter gun will have 2 extra bullets. 383 | fireBullets( 384 | SCATTERGUN_SECONDARY_BULLET_COUNT, 385 | SCATTERGUN_SECONDARY_BULLET_SPREAD_ARC_DEGREES, 386 | BULLET_SPEED_SCATTERGUN_SECONDARY, 387 | GUN_FIREDELAY_SCATTERGUN); 388 | } 389 | break; 390 | case WEAPON_ROCKET: 391 | fireRocket(); 392 | break; 393 | default: 394 | Utils.logDebug("Unhandled weapon type: " + mCurrentGun); 395 | break; 396 | } 397 | } 398 | 399 | /** 400 | * Creates a rocket "particle". 401 | */ 402 | protected void fireRocket() { 403 | mGunRechargeTimer = GUN_FIREDELAY_ROCKET; 404 | 405 | BaseParticle myShot = mGameState.getShots().spawnParticle(BULLET_LIFETIME_IN_SECONDS); 406 | if (myShot != null) { 407 | myShot.setPosition(mPositionX, mPositionY); 408 | myShot.setSpeed(mAimX * BULLET_SPEED_ROCKET, mAimY * BULLET_SPEED_ROCKET); 409 | myShot.setColor(mColor); 410 | myShot.setSize(ROCKET_PARTICLE_SIZE); 411 | myShot.setAspectRatio(ROCKET_PARTICLE_ASPECT_RATIO); 412 | myShot.setOwnerId(mPlayerId); 413 | myShot.setParticleType(BaseParticle.PARTICLE_TYPE_ROCKET); 414 | 415 | // Offset the rocket's starting position a few steps ahead of our position. 416 | myShot.incrementPosition(ROCKET_PARTICLE_INITIAL_POSITION_INCREMENT); 417 | } 418 | } 419 | 420 | /** 421 | * Checks to see if any bullets have collided with this ship. 422 | */ 423 | protected void checkBulletCollisions() { 424 | BaseParticle bullet = mGameState.getShots().checkForCollision(mPositionX, mPositionY, 425 | SHIP_SIZE); 426 | if (bullet != null && bullet.getOwnerId() != mPlayerId) { 427 | bullet.handleCollision(); 428 | if (!isInvincible()) { 429 | spawnRingBurstAroundShip( 430 | RINGBURST_PRIMARY_MIN_SPEED, 431 | RINGBURST_PRIMARY_MAX_SPEED); 432 | spawnRingBurstAroundShip( 433 | RINGBURST_SECONDARY_MIN_SPEED, 434 | RINGBURST_SECONDARY_MAX_SPEED); 435 | mRespawnTimer = RESPAWN_FRAME_COUNT; 436 | mGameState.scorePoint(bullet.getOwnerId()); 437 | changeScore(-1); 438 | } 439 | } 440 | } 441 | 442 | /** 443 | * Spawns a ring of particles radiating from the ship's current position. 444 | */ 445 | protected void spawnRingBurstAroundShip(float minSpeed, float maxSpeed) { 446 | mGameState.getExplosions().spawnRingBurst(mPositionX, mPositionY, mColor, minSpeed, 447 | maxSpeed, RINGBURST_PARTICLE_COUNT); 448 | } 449 | 450 | /** 451 | * Fires one or more bullets from the ship's current location. 452 | * @param bulletCount the number of bullets to fire. 453 | * @param spreadArc for multiple bullets, the arc, in degrees, over which the bullets 454 | * are spread out. 455 | * @param speed the speed of the bullets. 456 | * @param recharge the number of frames delay before the next shot can be fired. 457 | */ 458 | protected void fireBullets(int bulletCount, float spreadArc, float speed, 459 | float recharge) { 460 | mGunRechargeTimer = recharge; 461 | 462 | for (int i = 0; i < bulletCount; ++i) { 463 | float angleDegrees; 464 | if (bulletCount > 1) { 465 | // Compute this bullet's position along the spread arc. 466 | angleDegrees = 467 | -spreadArc / 2.0f + (float) i * spreadArc / ((float) bulletCount - 1.0f); 468 | } else { 469 | // Single bullets are always fired along the aiming direction. 470 | angleDegrees = 0; 471 | } 472 | float angleRadians = (float) Math.toRadians(angleDegrees); 473 | float angleSin = (float) Math.sin(angleRadians); 474 | float angleCos = (float) Math.cos(angleRadians); 475 | 476 | float shotDx = mAimX * angleCos - mAimY * angleSin; 477 | float shotDy = mAimX * angleSin + mAimY * angleCos; 478 | BaseParticle myShot = mGameState.getShots().spawnParticle(BULLET_LIFETIME_IN_SECONDS); 479 | if (myShot != null) { 480 | myShot.setPosition(mPositionX, mPositionY); 481 | myShot.setSpeed(shotDx * speed, shotDy * speed); 482 | myShot.setColor(mColor); 483 | myShot.setSize(BULLET_PARTICLE_SIZE); 484 | myShot.setAspectRatio(BULLET_PARTICLE_ASPECT_RATIO); 485 | myShot.setOwnerId(mPlayerId); 486 | 487 | // Offset the bullet's starting position a few steps ahead of our position. 488 | myShot.incrementPosition(BULLET_PARTICLE_INITIAL_POSITION_INCREMENT); 489 | } 490 | } 491 | } 492 | 493 | /** 494 | * Updates the ship's spawning state and invincibility state. 495 | */ 496 | private boolean updateStatus(float frameDelta) { 497 | updateSpawningStatus(frameDelta); 498 | if (!isSpawned()) { 499 | return false; 500 | } 501 | updateInvincibilityStatus(frameDelta); 502 | return true; 503 | } 504 | 505 | /** 506 | * Picks a new starting location when the ship is spawned. 507 | */ 508 | private void updateSpawningStatus(float frameDelta) { 509 | // Are we waiting to respawn. 510 | if (mRespawnTimer > 0.0f) { 511 | mRespawnTimer -= frameDelta; 512 | if (mRespawnTimer <= 0.0f) { 513 | // Time to respawn. 514 | mRespawnTimer = 0.0f; 515 | 516 | // Pick a new location. 517 | setPositionX(Utils.randFloatInRange(GameState.MAP_LEFT_COORDINATE, 518 | GameState.MAP_RIGHT_COORDINATE)); 519 | setPositionY(Utils.randFloatInRange(GameState.MAP_BOTTOM_COORDINATE, 520 | GameState.MAP_TOP_COORDINATE)); 521 | 522 | spawnRingBurstAroundShip(RINGBURST_PRIMARY_MIN_SPEED, RINGBURST_PRIMARY_MAX_SPEED); 523 | mInvincibilityTimer = INVINCIBILITY_FRAME_COUNT; 524 | 525 | // Newly spawned ships don't have any powerup weapons. 526 | mCurrentGun = WEAPON_BASEGUN; 527 | } 528 | } 529 | } 530 | 531 | /** 532 | * Keeps track of this ship's invincibility status. 533 | */ 534 | private void updateInvincibilityStatus(float frameDelta) { 535 | if (mInvincibilityTimer > 0.0f) { 536 | mInvincibilityTimer -= frameDelta; 537 | if (mInvincibilityTimer < 0.0f) { 538 | mInvincibilityTimer = 0.0f; 539 | } 540 | } 541 | 542 | mCurrentColor.set(mColor); 543 | 544 | // Flash the ship while it is invincible. 545 | if (isInvincible() 546 | && ((int) (mInvincibilityTimer / INVINCIBILITY_COLOR_BLINK_RATE) % 2 == 0)) { 547 | mCurrentColor.darken(INVINCIBILITY_COLOR_DARKEN_FACTOR); 548 | } 549 | } 550 | 551 | /** 552 | * Reads the movement joystick and updates the ship's position. 553 | */ 554 | private void updateShipPosition(float frameDelta) { 555 | float newHeadingX = mController.getJoystickPosition(GamepadController.JOYSTICK_1, 556 | GamepadController.AXIS_X); 557 | float newHeadingY = mController.getJoystickPosition(GamepadController.JOYSTICK_1, 558 | GamepadController.AXIS_Y); 559 | 560 | float magnitude = Utils.vector2DLength(newHeadingX, newHeadingY); 561 | if (magnitude > JOYSTICK_MOVEMENT_THRESHOLD) { 562 | // Normalize the direction vector. 563 | mHeadingX = newHeadingX / magnitude; 564 | mHeadingY = -newHeadingY / magnitude; 565 | 566 | // Compute the new speed. 567 | mVelocityX = newHeadingX; 568 | mVelocityY = -newHeadingY; 569 | 570 | if (magnitude > 1.0f) { 571 | // Limit the max speed to "1". If the movement joystick is moved less than 572 | // 1 unit from the center, the ship will move less than it's maximum speed. 573 | // If the joystick moves more than 1 unit from the center, dividing by 574 | // magnitude will limit the speed of the ship, but keep the direction of moment 575 | // correct. 576 | mVelocityX /= magnitude; 577 | mVelocityY /= magnitude; 578 | } 579 | 580 | // Create a particle trail (exhaust) behind the ship. 581 | GameState.getInstance().getExplosions().spawnExhaustTrail( 582 | mPositionX, mPositionY, 583 | mVelocityX, mVelocityY, 584 | mColor, 1); 585 | } 586 | 587 | setPositionX(mPositionX + mVelocityX * frameDelta); 588 | setPositionY(mPositionY + mVelocityY * frameDelta); 589 | 590 | // Use drag so that the ship will coast to a stop after the movement controller 591 | // is released. 592 | mVelocityX *= 1.0f - frameDelta * DRAG; 593 | mVelocityY *= 1.0f - frameDelta * DRAG; 594 | if (Utils.vector2DLength(mVelocityX, mVelocityY) < MINIMUM_VELOCITY) { 595 | mVelocityX = 0.0f; 596 | mVelocityY = 0.0f; 597 | } 598 | } 599 | 600 | /** 601 | * Checks for controller key presses and fires the gun. 602 | */ 603 | private void handleKeyPressesAndFiring(float frameDelta) { 604 | mGunRechargeTimer -= frameDelta; 605 | if (mGunRechargeTimer <= 0) { 606 | // The gun is ready to fire, so calculate the aim direction. 607 | calculateAimDirection(); 608 | if (mJoystickAiming || mController.isButtonDown(GamepadController.BUTTON_X)) { 609 | fireGun(); 610 | } 611 | } 612 | } 613 | 614 | } 615 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | import android.graphics.PointF; 20 | import android.graphics.RectF; 21 | import android.util.Log; 22 | 23 | /** 24 | * A few utility functions and classes used by this sample. 25 | */ 26 | public class Utils { 27 | /** 28 | * String used to identify log messages from this program. 29 | */ 30 | private static final String LOG_TAG = "GameControllerSample"; 31 | 32 | /** 33 | * The 2D vertex positions for a square centered around the origin. 34 | * 35 | * The vertices are ordered so that one triangle strip can be used to render 36 | * the square. 37 | * 38 | * Using scaling and rotation, these vertices can be used to render any rectangular shape. 39 | */ 40 | public static final float[] SQUARE_SHAPE = { 41 | 1.0f, 1.0f, 42 | -1.0f, 1.0f, 43 | 1.0f, -1.0f, 44 | -1.0f, -1.0f 45 | }; 46 | 47 | /** 48 | * Prints debugging messages to the console. 49 | * 50 | * Disabled for non-debug builds. 51 | * 52 | * @param message The message to print to the console. 53 | */ 54 | public static void logDebug(String message) { 55 | if (BuildConfig.DEBUG) { 56 | Log.d(LOG_TAG, message); 57 | } 58 | } 59 | 60 | /** 61 | * Prints an error message to the console. 62 | * 63 | * Disabled for non-debug builds. 64 | * 65 | * @param message The message to print to the console. 66 | */ 67 | public static void logError(String message) { 68 | if (BuildConfig.DEBUG) { 69 | Log.e(LOG_TAG, message); 70 | } 71 | } 72 | 73 | /** 74 | * Returns a random floating point value >= lowerBound and < upperBound. 75 | */ 76 | public static float randFloatInRange(float lowerBound, float upperBound) { 77 | return (float) (Math.random() * (upperBound - lowerBound) + lowerBound); 78 | } 79 | /** 80 | * Returns a random integer value >= lowerBound and < upperBound. 81 | */ 82 | public static int randIntInRange(int lowerBound, int upperBound) { 83 | return (int) randFloatInRange(lowerBound, upperBound); 84 | } 85 | 86 | /** 87 | * Returns a randomly chosen point inside the given rectangle. 88 | * 89 | * @param rect a rectangle bounding the location for the point. 90 | * @return a new PointF object. 91 | */ 92 | public static PointF randPointInRect(RectF rect) { 93 | float x = randFloatInRange(rect.left, rect.right); 94 | float y = randFloatInRange(rect.bottom, rect.top); 95 | 96 | return new PointF(x, y); 97 | } 98 | 99 | /** 100 | * Computes a randomly chosen direction vector. 101 | * 102 | * @return a new PointF object that is a normalized direction vector. 103 | */ 104 | public static PointF randDirectionVector() { 105 | // Pick a random point in a square centered about the origin. 106 | PointF direction = randPointInRect(new RectF(-1.0f, 1.0f, 1.0f, -1.0f)); 107 | 108 | // Turn the chosen point into a direction vector by normalizing it. 109 | normalizeDirectionVector(direction); 110 | 111 | return direction; 112 | } 113 | 114 | /** 115 | * Normalizes the given direction vector. 116 | * 117 | * Changes the length of the given vector so that it is "1". If the given direction 118 | * already has a length of "0", the direction will be set to "(1, 0)". 119 | * 120 | * @param direction the direction to normalize. 121 | */ 122 | public static void normalizeDirectionVector(PointF direction) { 123 | float length = direction.length(); 124 | if (length == 0.0f) { 125 | direction.set(1.0f, 0.0f); 126 | } else { 127 | direction.x /= length; 128 | direction.y /= length; 129 | } 130 | } 131 | 132 | /** 133 | * Determines the squared length of the given 2-component vector. 134 | * 135 | * @param x the x component of the vector. 136 | * @param y the y component of the vector. 137 | * @return the squared length of the vector. 138 | */ 139 | public static float vector2DLengthSquared(float x, float y) { 140 | return x * x + y * y; 141 | } 142 | 143 | /** 144 | * Determines the length of the given 2-component vector. 145 | * 146 | * @param x the x component of the vector. 147 | * @param y the y component of the vector. 148 | * @return the length of the vector. 149 | */ 150 | public static float vector2DLength(float x, float y) { 151 | return (float) Math.sqrt(vector2DLengthSquared(x, y)); 152 | } 153 | 154 | /** 155 | * Computes the distance between two 2-d points. 156 | * 157 | * @param x1 x position of the first point. 158 | * @param y1 y position of the first point. 159 | * @param x2 x position of the second point. 160 | * @param y2 y position of the second point. 161 | * @return the distance between the two points. 162 | */ 163 | public static float distanceBetweenPoints(float x1, float y1, float x2, float y2) { 164 | float xSquared = (x2 - x1); 165 | xSquared *= xSquared; 166 | 167 | float ySquared = (y2 - y1); 168 | ySquared *= ySquared; 169 | 170 | return (float) Math.sqrt(xSquared + ySquared); 171 | } 172 | 173 | /** 174 | * Ensures that the given value falls within the given range. 175 | * 176 | * @param value The value to clamp. 177 | * @param min The lower bound of the range. 178 | * @param max The upper bound of the range. 179 | * @return The clamped value. 180 | */ 181 | public static float clamp(float value, float min, float max) { 182 | return Math.max(min, Math.min(max, value)); 183 | } 184 | /** 185 | * Ensures that the given value falls within the given range. 186 | * 187 | * @param value The value to clamp. 188 | * @param min The lower bound of the range. 189 | * @param max The upper bound of the range. 190 | * @return The clamped value. 191 | */ 192 | public static float clamp(int value, int min, int max) { 193 | return Math.max(min, Math.min(max, value)); 194 | } 195 | 196 | /** 197 | * Class for storing and manipulating RGBA colors. 198 | * 199 | * The color components are stored internally in a 32-bit int, with 8 bits for each 200 | * component. 201 | * 202 | * This class is similar to android.graphics.Color, but differs in 2 main ways: 203 | * 1) This class is a container for color data, so it can be instantiated and used as 204 | * data type. android.graphics.Color is just a set of static functions for packing 205 | * color components into integers. 206 | * 2) android.graphics.Color only provides one component packing order, and that order 207 | * does not match what OpenGl/OpenGl ES expects. This class provides accessors 208 | * to get the colors packed in the order expected by OpenGl/OpenGl ES. 209 | */ 210 | public static class Color { 211 | // Some common colors for easy reference and readability. Add more as needed. 212 | public static final Color RED = new Color(1.0f, 0.0f, 0.0f); 213 | public static final Color GREEN = new Color(0.0f, 1.0f, 0.0f); 214 | public static final Color BLUE = new Color(0.0f, 0.0f, 1.0f); 215 | public static final Color YELLOW = new Color(1.0f, 1.0f, 0.0f); 216 | public static final Color WHITE = new Color(1.0f, 1.0f, 1.0f); 217 | 218 | // Masks and bit locations for each color component. 219 | private static final int RED_MASK = 0xffffff00; 220 | private static final int RED_SHIFT = 0; 221 | private static final int GREEN_MASK = 0xffff00ff; 222 | private static final int GREEN_SHIFT = 8; 223 | private static final int BLUE_MASK = 0xff00ffff; 224 | private static final int BLUE_SHIFT = 16; 225 | private static final int ALPHA_MASK = 0x00ffffff; 226 | private static final int ALPHA_SHIFT = 24; 227 | 228 | /** 229 | * The packed color data. 230 | * 231 | * Alpha is packed into the high-order byte and red is the low-order byte. This 232 | * matches the component packing order used by OpenGl/OpenGl ES. 233 | */ 234 | private int mABGR; 235 | 236 | /** 237 | * Converts a floating point color value in the range [0..1] to an integer in the 238 | * range [0..255]. 239 | * 240 | * @param normalizedColor A number in the range 0 to 1, inclusive. No range-checking 241 | * is performed. 242 | * @return An int in the range 0 to 255, inclusive. 243 | */ 244 | private static int normalizedColorToInt(float normalizedColor) { 245 | return (int) (255.0f * normalizedColor); 246 | } 247 | /** 248 | * Converts an integer color value in the range [0..255] to floating point number in the 249 | * range [0..1]. 250 | * 251 | * @param intColor A number in the range 0 to 255, inclusive. No range-checking 252 | * is performed. 253 | * @return A float in the range 0 to 1, inclusive. 254 | */ 255 | private static float intColorToNormalized(int intColor) { 256 | return (float) intColor / 255.0f; 257 | } 258 | 259 | /** 260 | * Packs 4 floating point color components into a single 32-bit integer. 261 | * 262 | * The color components must be in the range 0 to 1, inclusive. 263 | */ 264 | private static int packNormalizedRGBAToABGR(float red, float green, float blue, 265 | float alpha) { 266 | return packABGR( 267 | normalizedColorToInt(red), 268 | normalizedColorToInt(green), 269 | normalizedColorToInt(blue), 270 | normalizedColorToInt(alpha)); 271 | } 272 | 273 | /** 274 | * Packs 4 integer color components into a single 32-bit integer. 275 | * 276 | * The color components must be in the range 0 to 255, inclusive. 277 | */ 278 | private static int packABGR(int red, int green, int blue, int alpha) { 279 | return (red << RED_SHIFT) 280 | | (green << GREEN_SHIFT) 281 | | (blue << BLUE_SHIFT) 282 | | (alpha << ALPHA_SHIFT); 283 | } 284 | 285 | /** 286 | * Creates an uninitialized color object. 287 | */ 288 | public Color() {} 289 | 290 | /** 291 | * Creates a Color object with the given color components. 292 | * 293 | * Each component must be in the range 0 to 1, inclusive. 294 | */ 295 | public Color(float red, float green, float blue, float alpha) { 296 | set(red, green, blue, alpha); 297 | } 298 | /** 299 | * Creates a Color object with the given color components and an alpha of 1.0. 300 | * 301 | * Each component must be in the range 0 to 1, inclusive. 302 | */ 303 | public Color(float red, float green, float blue) { 304 | mABGR = packNormalizedRGBAToABGR(red, green, blue, 1.0f); 305 | } 306 | 307 | /** 308 | * Creates a copy of the given color. 309 | */ 310 | public Color(Utils.Color other) { 311 | set(other); 312 | } 313 | 314 | /** 315 | * Sets all of the color components. 316 | * 317 | * Each component must be in the range 0 to 1, inclusive. 318 | */ 319 | public void set(float red, float green, float blue, float alpha) { 320 | mABGR = packNormalizedRGBAToABGR(red, green, blue, alpha); 321 | } 322 | 323 | /** 324 | * Changes our color to match the given color. 325 | */ 326 | public void set(Color other) { 327 | this.mABGR = other.mABGR; 328 | } 329 | 330 | /** 331 | * Returns a packed integer representation of this color. 332 | * 333 | * @return The 32-bit packed color, with 8 bits per components. Alpha is in the 334 | * high-order bits, then blue, then green, and then red in the low-order bits. 335 | */ 336 | public int getPackedABGR() { 337 | return mABGR; 338 | } 339 | 340 | /** 341 | * Returns the red component of the color as a floating point number in the 342 | * range 0 to 1, inclusive. 343 | */ 344 | public float red() { 345 | return intColorToNormalized((mABGR >> RED_SHIFT) & 0xff); 346 | } 347 | /** 348 | * Sets the red component of the color. The given color component must be a 349 | * a floating point number in the range 0 to 1, inclusive. 350 | */ 351 | public void setRed(float red) { 352 | mABGR = (mABGR & RED_MASK) | (normalizedColorToInt(red) << RED_SHIFT); 353 | } 354 | /** 355 | * Returns the green component of the color as a floating point number in the 356 | * range 0 to 1, inclusive. 357 | */ 358 | public float green() { 359 | return intColorToNormalized((mABGR >> GREEN_SHIFT) & 0xff); 360 | } 361 | /** 362 | * Sets the green component of the color. The given color component must be a 363 | * a floating point number in the range 0 to 1, inclusive. 364 | */ 365 | public void setGreen(float green) { 366 | mABGR = (mABGR & GREEN_MASK) | (normalizedColorToInt(green) << GREEN_SHIFT); 367 | } 368 | /** 369 | * Returns the blue component of the color as a floating point number in the 370 | * range 0 to 1, inclusive. 371 | */ 372 | public float blue() { 373 | return intColorToNormalized((mABGR >> BLUE_SHIFT) & 0xff); 374 | } 375 | /** 376 | * Sets the blue component of the color. The given color component must be a 377 | * a floating point number in the range 0 to 1, inclusive. 378 | */ 379 | public void setBlue(float blue) { 380 | mABGR = (mABGR & BLUE_MASK) | (normalizedColorToInt(blue) << BLUE_SHIFT); 381 | } 382 | /** 383 | * Returns the alpha component of the color as a floating point number in the 384 | * range 0 to 1, inclusive. 385 | */ 386 | public float alpha() { 387 | return intColorToNormalized((mABGR >> ALPHA_SHIFT) & 0xff); 388 | } 389 | /** 390 | * Sets the alpha component of the color. The given color component must be a 391 | * a floating point number in the range 0 to 1, inclusive. 392 | */ 393 | public void setAlpha(float alpha) { 394 | mABGR = (mABGR & ALPHA_MASK) | (normalizedColorToInt(alpha) << ALPHA_SHIFT); 395 | } 396 | 397 | /** 398 | * Changes this color by multiplying each color component by the given factor. 399 | * 400 | * The alpha component of the color remains unchanged. 401 | * 402 | * @param factor A value in the range 0..1, inclusive, to indicate how much darkening 403 | * to apply. 0 sets the color to black, and 1 leaves the color unchanged. 404 | * Values outside of this range have undefined results. 405 | */ 406 | public void darken(float factor) { 407 | set(red() * factor, green() * factor, blue() * factor, alpha()); 408 | } 409 | 410 | /** 411 | * Sets this color to a color computed by interpolating between two other colors. 412 | * 413 | * The interpolated color is created by linearly interpolating each component 414 | * of the two given colors. 415 | * 416 | * @param colorA The first color to mix. 417 | * @param colorB The second color to mix. 418 | * @param factor A value between 0 and 1. A value of "0" will set this color to 419 | * colorA, and a value of "1" will set this color to colorB. 420 | */ 421 | public void setToLerp(Color colorA, Color colorB, float factor) { 422 | set(colorA.red() * factor + colorB.red() * (1.0f - factor), 423 | colorA.green() * factor + colorB.green() * (1.0f - factor), 424 | colorA.blue() * factor + colorB.blue() * (1.0f - factor), 425 | colorA.alpha() * factor + colorB.alpha() * (1.0f - factor)); 426 | } 427 | } 428 | } 429 | 430 | 431 | 432 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/WallSegment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller; 18 | 19 | import com.google.fpl.gamecontroller.particles.BaseParticle; 20 | 21 | /** 22 | * Handles drawing and collision-detection with map walls. 23 | */ 24 | public class WallSegment { 25 | private static final Utils.Color WALL_COLOR = Utils.Color.WHITE; 26 | private static final int EDGE_COUNT = 4; 27 | 28 | private final float mCenterX, mCenterY; 29 | private final float mWidth, mHeight; 30 | 31 | // The center points (x, y) for each edge of the wall. 32 | private final float[] mEdgeCenters = new float[EDGE_COUNT * 2]; 33 | // The scale values (x, y) for each edge of the wall. 34 | private final float[] mEdgeScales = new float[EDGE_COUNT * 2]; 35 | 36 | public WallSegment(float x, float y, float width, float height) { 37 | mCenterX = x; 38 | mCenterY = y; 39 | mWidth = width; 40 | mHeight = height; 41 | 42 | // Compute the center and scale needed to draw lines along each edge of the wall. 43 | int edgeIndex = 0; 44 | 45 | // Left edge. 46 | mEdgeCenters[edgeIndex + 0] = mCenterX - mWidth / 2.0f; 47 | mEdgeCenters[edgeIndex + 1] = mCenterY; 48 | mEdgeScales[edgeIndex + 0] = 1.0f + mHeight / 2.0f; 49 | mEdgeScales[edgeIndex + 1] = 1.0f; 50 | edgeIndex += 2; 51 | 52 | // Right edge. 53 | mEdgeCenters[edgeIndex + 0] = mCenterX + mWidth / 2.0f; 54 | mEdgeCenters[edgeIndex + 1] = mCenterY; 55 | mEdgeScales[edgeIndex + 0] = 1.0f + mHeight / 2.0f; 56 | mEdgeScales[edgeIndex + 1] = 1.0f; 57 | edgeIndex += 2; 58 | 59 | // Bottom edge. 60 | mEdgeCenters[edgeIndex + 0] = mCenterX; 61 | mEdgeCenters[edgeIndex + 1] = mCenterY - mHeight / 2.0f; 62 | mEdgeScales[edgeIndex + 0] = 1.0f; 63 | mEdgeScales[edgeIndex + 1] = 1.0f + mWidth / 2.0f; 64 | edgeIndex += 2; 65 | 66 | // Top edge. 67 | mEdgeCenters[edgeIndex + 0] = mCenterX; 68 | mEdgeCenters[edgeIndex + 1] = mCenterY + mHeight / 2.0f; 69 | mEdgeScales[edgeIndex + 0] = 1.0f; 70 | mEdgeScales[edgeIndex + 1] = 1.0f + mWidth / 2.0f; 71 | } 72 | 73 | public void draw(ShapeBuffer sb) { 74 | // Draw a line along each edge of the wall. 75 | for (int i = 0; i < EDGE_COUNT; ++i) { 76 | final int edgeIndex = i * 2; 77 | sb.add2DShape( 78 | mEdgeCenters[edgeIndex + 0], mEdgeCenters[edgeIndex + 1], // position 79 | WALL_COLOR, // color 80 | Utils.SQUARE_SHAPE, // vertices 81 | mEdgeScales[edgeIndex + 0], mEdgeScales[edgeIndex + 1], // scale 82 | 0.0f, 1.0f); // heading 83 | } 84 | } 85 | 86 | /** 87 | * Returns true if the given point is inside this wall. 88 | */ 89 | public boolean isInWall(float x, float y) { 90 | return x >= mCenterX - mWidth / 2 91 | && x <= mCenterX + mWidth / 2 92 | && y >= mCenterY - mHeight / 2 93 | && y <= mCenterY + mHeight / 2; 94 | } 95 | 96 | /** 97 | * Checks for collisions with this wall. 98 | */ 99 | public void update(float timeFactor) { 100 | GameState gameState = GameState.getInstance(); 101 | 102 | // First, check for collisions with bullets. 103 | BaseParticle[] possibleHits = gameState.getShots().getPotentialCollisions(mCenterX, 104 | mCenterY, mWidth, mHeight); 105 | 106 | BaseParticle currentBullet; 107 | // The possibleHits array will likely be longer than the number of entries returned. 108 | // Look for a null entry to indicate the end of the list. 109 | for (int i = 0; possibleHits[i] != null; i++) { 110 | currentBullet = possibleHits[i]; 111 | if (isInWall(currentBullet.getPositionX(), currentBullet.getPositionY())) { 112 | // Semi-hacky way to zero in on the collision point. 113 | // When we detect a bullet is inside the wall, iterate backwards and forwards 114 | // along the trajectory of the bullet by smaller and smaller steps. 115 | // This should get us fairly close to the intersection point. 116 | // 117 | // For better collision detection, we could compute the actual line 118 | // intersection point, instead of just approximating it. 119 | float stepSize = -0.5f; 120 | for (int j = 0; j < 3; ++j) { 121 | currentBullet.incrementPosition(stepSize); 122 | if (isInWall(currentBullet.getPositionX(), currentBullet.getPositionY())) { 123 | stepSize = -Math.abs(stepSize) * 0.5f; 124 | } else { 125 | stepSize = Math.abs(stepSize) * 0.5f; 126 | } 127 | } 128 | currentBullet.handleCollision(); 129 | } 130 | } 131 | 132 | // Now check for ship-wall collisions. 133 | for (Spaceship currentPlayer : gameState.getPlayerList()) { 134 | if (currentPlayer.isActive() 135 | && isInWall(currentPlayer.getPositionX(), currentPlayer.getPositionY())) { 136 | float xx = (currentPlayer.getPositionX() - mCenterX) * mHeight; 137 | float yy = (currentPlayer.getPositionY() - mCenterY) * mWidth; 138 | 139 | float epsilon = 0.1f; 140 | 141 | if (Math.abs(xx) > Math.abs(yy)) { 142 | if (xx >= 0) { 143 | currentPlayer.setPositionX(mCenterX + mWidth / 2 + epsilon); 144 | } else { 145 | currentPlayer.setPositionX(mCenterX - mWidth / 2 - epsilon); 146 | } 147 | } else { 148 | if (yy >= 0) { 149 | currentPlayer.setPositionY(mCenterY + mHeight / 2 + epsilon); 150 | } else { 151 | currentPlayer.setPositionY(mCenterY - mHeight / 2 - epsilon); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/particles/BackgroundParticleSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller.particles; 18 | 19 | import com.google.fpl.gamecontroller.GameState; 20 | import com.google.fpl.gamecontroller.Utils; 21 | 22 | /** 23 | * Manages particles that makeup the background of the map. 24 | */ 25 | public class BackgroundParticleSystem extends ParticleSystem { 26 | 27 | // Parameters used for creating the animated background particles. 28 | private static final float BACKGROUND_SQUARE_MIN_LIFETIME = 2.0f; // seconds 29 | private static final float BACKGROUND_SQUARE_MAX_LIFETIME = 5.0f; // seconds 30 | private static final float BACKGROUND_SQUARE_MIN_SIZE = 5.0f; 31 | private static final float BACKGROUND_SQUARE_MAX_SIZE = 30.0f; 32 | private static final float BACKGROUND_SQUARE_MIN_VELOCITY_Y = 0.5f; 33 | private static final float BACKGROUND_SQUARE_MAX_VELOCITY_Y = 1.5f; 34 | private static final float BACKGROUND_SQUARE_MAX_ALPHA = 0.25f; 35 | 36 | // The number of frames it takes to transition to the winning player's color. 37 | private static final float WINNING_ANIMATION_TRANSITION_FRAME_DELTA 38 | = GameState.secondsToFrameDelta(1.5f); 39 | // The number of frames the background will show the winning player's color. 40 | private static final float WINNING_ANIMATION_PAUSE_FRAME_DELTA 41 | = GameState.secondsToFrameDelta(4.0f); 42 | 43 | // Default background color is dark blue. 44 | private static final Utils.Color DEFAULT_COLOR = new Utils.Color(0.0f, 0.0f, 0.5f, 1.0f); 45 | 46 | // The color for this frame. 47 | private final Utils.Color mCurrentColor = new Utils.Color(DEFAULT_COLOR); 48 | // The background color before the animation started. 49 | private final Utils.Color mOriginalColor = new Utils.Color(DEFAULT_COLOR); 50 | // The color of the background at the end of the animation. 51 | private final Utils.Color mTargetColor = new Utils.Color(DEFAULT_COLOR); 52 | 53 | // The amount of time needed to transition from the original color to target color. 54 | private float mTotalTransitionFrameDelta = 0.0f; 55 | // The current elapsed time. 56 | private float mCurrentTransitionFrameDelta = 0.0f; 57 | // After transitioning to the target color, the background will return to its original 58 | // color after the pause time has expired. 59 | private float mPauseFrameDeltaRemaining = 0.0f; 60 | 61 | 62 | public BackgroundParticleSystem(int maxActiveParticles) { 63 | super(maxActiveParticles, false); 64 | } 65 | 66 | public void update(float frameDelta) { 67 | // Add a new square every frame (the particles time out after a few seconds, so this 68 | // replaces them). 69 | addRandomSquare(); 70 | 71 | if (mTotalTransitionFrameDelta > 0.0f 72 | && mCurrentTransitionFrameDelta < mTotalTransitionFrameDelta) { 73 | // We are transitioning from one color to another. 74 | mCurrentTransitionFrameDelta += frameDelta; 75 | if (mCurrentTransitionFrameDelta >= mTotalTransitionFrameDelta) { 76 | // The transition is complete, so clamp to the target color. 77 | mCurrentColor.set(mTargetColor); 78 | if (mPauseFrameDeltaRemaining <= 0.0f) { 79 | // We will not be returning to the original color, so mark the transition 80 | // as complete. 81 | mCurrentTransitionFrameDelta = 0.0f; 82 | mTotalTransitionFrameDelta = 0.0f; 83 | } 84 | } else { 85 | // Compute the interpolated color between the original and final colors. 86 | float transitionRatio = mCurrentTransitionFrameDelta / mTotalTransitionFrameDelta; 87 | mCurrentColor.setToLerp(mTargetColor, mOriginalColor, transitionRatio); 88 | } 89 | } else if (mPauseFrameDeltaRemaining > 0.0f) { 90 | // We have completed a transition and are pausing before switching back to our original 91 | // color. 92 | mPauseFrameDeltaRemaining -= frameDelta; 93 | if (mPauseFrameDeltaRemaining <= 0.0f) { 94 | // The pause is over, so kick off a transition back to our original color. 95 | // Use "0" for the pause duration so that the transition will stop after 96 | // the target color is reached. 97 | flashColorTo(mOriginalColor, mTotalTransitionFrameDelta, 0.0f); 98 | } 99 | } 100 | 101 | // Update all our particles to the current background color. 102 | for (BaseParticle square : mParticles) { 103 | if (square.isActive()) { 104 | square.getColor().set(mCurrentColor); 105 | } 106 | } 107 | 108 | super.update(frameDelta); 109 | } 110 | 111 | /** 112 | * Changes the color of the background particles over time. 113 | * 114 | * Calling this function while an animated transition is in progress has no effect. 115 | * 116 | * @param winningColor The new color for the background particles. 117 | */ 118 | public void flashWinningColor(Utils.Color winningColor) { 119 | // Make sure we're not already in the middle of an animation. 120 | if ((mTotalTransitionFrameDelta == 0.0f) && (mPauseFrameDeltaRemaining == 0.0f)) { 121 | flashColorTo(winningColor, WINNING_ANIMATION_TRANSITION_FRAME_DELTA, 122 | WINNING_ANIMATION_PAUSE_FRAME_DELTA); 123 | } else { 124 | Utils.logDebug("flashWinningColor() already in progress."); 125 | } 126 | } 127 | 128 | /** 129 | * Creates a new square particle. 130 | */ 131 | private void addRandomSquare() { 132 | float lifetimeInSeconds = Utils.randFloatInRange( 133 | BACKGROUND_SQUARE_MIN_LIFETIME, 134 | BACKGROUND_SQUARE_MAX_LIFETIME); 135 | BaseParticle square = spawnParticle(lifetimeInSeconds); 136 | 137 | if (square != null) { 138 | // The particles start towards the bottom of the map. 139 | float x = Utils.randFloatInRange(GameState.MAP_LEFT_COORDINATE, 140 | GameState.MAP_RIGHT_COORDINATE); 141 | float y = Utils.randFloatInRange( 142 | GameState.WORLD_BOTTOM_COORDINATE - BACKGROUND_SQUARE_MAX_SIZE, 0.0f); 143 | square.setPosition(x, y); 144 | 145 | // The particles move from the bottom of the screen towards the top of the screen. 146 | float velocityY = Utils.randFloatInRange( 147 | BACKGROUND_SQUARE_MIN_VELOCITY_Y, 148 | BACKGROUND_SQUARE_MAX_VELOCITY_Y); 149 | square.setSpeed(0.0f, velocityY); 150 | 151 | square.setColor(mCurrentColor); 152 | square.setMaxAlpha(BACKGROUND_SQUARE_MAX_ALPHA); 153 | square.setSize(Utils.randFloatInRange(BACKGROUND_SQUARE_MIN_SIZE, 154 | BACKGROUND_SQUARE_MAX_SIZE)); 155 | square.setDieOffscreen(false); 156 | } 157 | } 158 | 159 | /** 160 | * Changes the color of the background particles over time. 161 | * 162 | * @param targetColor The new color for the background particles. 163 | * @param transitionFrameDelta The number of frames over which the color change 164 | * will occur. 165 | * @param pauseFrameDelta If greater than zero, the number of frames to draw 166 | * before transitioning the background particles back to their 167 | * original color. If zero, the background particles will continue to 168 | * use targetColor after the transition is complete. 169 | */ 170 | private void flashColorTo(Utils.Color targetColor, float transitionFrameDelta, 171 | float pauseFrameDelta) { 172 | mTargetColor.set(targetColor); 173 | mOriginalColor.set(mCurrentColor); 174 | mTotalTransitionFrameDelta = Math.max(transitionFrameDelta, 0.0f); 175 | mCurrentTransitionFrameDelta = 0.0f; 176 | if (mTotalTransitionFrameDelta <= 0.0f) { 177 | // Make the transition instantaneous. 178 | mCurrentColor.set(mTargetColor); 179 | mTotalTransitionFrameDelta = 0.0f; 180 | } 181 | mPauseFrameDeltaRemaining = Math.max(pauseFrameDelta, 0.0f); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/particles/BaseParticle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller.particles; 18 | 19 | import com.google.fpl.gamecontroller.GameState; 20 | import com.google.fpl.gamecontroller.ShapeBuffer; 21 | import com.google.fpl.gamecontroller.Utils; 22 | 23 | /** 24 | * Base class used to handle particle effects. 25 | */ 26 | public class BaseParticle { 27 | public static final int PARTICLE_TYPE_NORMAL = 0; 28 | // Rocket particles have different collision behavior, produce a trail of exhaust, and 29 | // have an acceleration. 30 | public static final int PARTICLE_TYPE_ROCKET = 1; 31 | 32 | // Particles fade in and out over a 1 second period. 33 | private static final float FADE_FRAME_COUNT = GameState.secondsToFrameDelta(1.0f); 34 | private static final float FADE_DELTA_PER_FRAME = 1.0f / FADE_FRAME_COUNT; 35 | 36 | // The max velocity of rocket particles. 37 | private static final float ROCKET_MAX_SPEED_SQUARED = 6.0f * 6.0f; 38 | // Rocket acceleration per frame (expressed as a percentage increase increase over the 39 | // rocket's current speed). 40 | private static final float ROCKET_ACCELERATION = 0.05f; 41 | 42 | // Particles with an active frame count of 0 are not drawn or updated. 43 | private float mActiveFrameCountRemaining = 0.0f; 44 | 45 | // Either PARTICLE_TYPE_NORMAL or PARTICLE_TYPE_ROCKET. 46 | private int mParticleType; 47 | // Screen-space position of the center of the particle. 48 | private float mPositionX, mPositionY; 49 | // The distance this particle moves each frame. 50 | private float mVelocityX, mVelocityY; 51 | // Scales the coordinates Utils.SQUARE_SHAPE to create larger or smaller particles. 52 | private float mSize; 53 | // The color of the particle. 54 | private final Utils.Color mColor = new Utils.Color(); 55 | // A value between 0 and 1 used to scale the alpha value of the color. As particles fade 56 | // in and out, the alpha value in mColor changes. When a particle is not in the process of 57 | // fading, its transparency will be set to mMaxAlpha. 58 | private float mMaxAlpha; 59 | // The ratio of the particle's width to height. Particles that have an aspect ratio other 60 | // than 1.0 will automatically rotate to point in the direction they are traveling. 61 | private float mAspectRatio; 62 | // The id of the Spaceship that owns this particle. Used for bullet particles to award 63 | // points. 64 | private int mOwnerId; 65 | // If true, the particle will become inactive as soon as its center passes outside the 66 | // bounds of the world. 67 | private boolean mDieOffscreen; 68 | 69 | private final Utils.Color mCurrentColor = new Utils.Color(); 70 | 71 | /** 72 | * Returns a particle to its default state. 73 | * 74 | * This function is called to initialize newly spawned particles. 75 | * 76 | * @param lifetimeFrameCount the total number of frames this particle will be active. 77 | */ 78 | public void reset(float lifetimeFrameCount) { 79 | this.mActiveFrameCountRemaining = lifetimeFrameCount; 80 | this.mParticleType = PARTICLE_TYPE_NORMAL; 81 | this.mPositionX = 0.0f; 82 | this.mPositionY = 0.0f; 83 | this.mVelocityX = 0.0f; 84 | this.mVelocityY = 0.0f; 85 | this.mSize = 1.0f; 86 | this.mColor.set(1.0f, 1.0f, 1.0f, 1.0f); 87 | this.mMaxAlpha = 1.0f; 88 | this.mAspectRatio = 1.0f; 89 | this.mOwnerId = GameState.INVALID_PLAYER_ID; 90 | this.mDieOffscreen = true; 91 | // Set newly created particles to be transparent so that particles will not be visible 92 | // until they have had update() called on them. This avoids ordering issues that can 93 | // occur when particles are created during the update phase of the frame. 94 | this.mCurrentColor.setAlpha(0.0f); 95 | } 96 | 97 | public void setParticleType(int particleType) { 98 | this.mParticleType = particleType; 99 | } 100 | 101 | public void setPosition(float x, float y) { 102 | this.mPositionX = x; 103 | this.mPositionY = y; 104 | } 105 | public float getPositionX() { 106 | return mPositionX; 107 | } 108 | public float getPositionY() { 109 | return mPositionY; 110 | } 111 | 112 | public void setSpeed(float speedX, float speedY) { 113 | this.mVelocityX = speedX; 114 | this.mVelocityY = speedY; 115 | } 116 | 117 | public void setSize(float size) { 118 | this.mSize = size; 119 | } 120 | public float getSize() { 121 | return mSize; 122 | } 123 | 124 | public void setColor(Utils.Color color) { 125 | this.mColor.set(color); 126 | } 127 | public Utils.Color getColor() { 128 | return mColor; 129 | } 130 | 131 | public void setMaxAlpha(float maxAlpha) { 132 | this.mMaxAlpha = maxAlpha; 133 | } 134 | 135 | public void setAspectRatio(float aspectRatio) { 136 | this.mAspectRatio = aspectRatio; 137 | } 138 | 139 | public void setOwnerId(int ownerId) { 140 | this.mOwnerId = ownerId; 141 | } 142 | public int getOwnerId() { 143 | return mOwnerId; 144 | } 145 | 146 | public void setDieOffscreen(boolean dieOffscreen) { 147 | this.mDieOffscreen = dieOffscreen; 148 | } 149 | 150 | public void update(float frameDelta) { 151 | if (!isActive()) { 152 | return; 153 | } 154 | 155 | incrementPosition(frameDelta); 156 | mActiveFrameCountRemaining -= frameDelta; 157 | 158 | // Update the particle's alpha every frame. 159 | float newAlpha = mColor.alpha(); 160 | if (mActiveFrameCountRemaining < FADE_FRAME_COUNT) { 161 | // Particles that are about to die will fade out. 162 | newAlpha -= FADE_DELTA_PER_FRAME * frameDelta; 163 | } else { 164 | // Newly created particles fade in. 165 | newAlpha += FADE_DELTA_PER_FRAME * frameDelta; 166 | } 167 | mColor.setAlpha(Utils.clamp(newAlpha, 0.0f, 1.0f)); 168 | 169 | if (mDieOffscreen) { 170 | if (!GameState.inWorld(mPositionX, mPositionY)) { 171 | // The particle is outside the screen, so kill it. 172 | mActiveFrameCountRemaining = 0.0f; 173 | } 174 | } 175 | // Special update for rockets. 176 | if (mParticleType == PARTICLE_TYPE_ROCKET) { 177 | handleRocketUpdate(frameDelta); 178 | } 179 | 180 | mCurrentColor.set(mColor); 181 | mCurrentColor.setAlpha(mCurrentColor.alpha() * mMaxAlpha); 182 | } 183 | 184 | protected void handleRocketUpdate(float frameDelta) { 185 | float clampedX = Utils.clamp(mPositionX, GameState.MAP_LEFT_COORDINATE, 186 | GameState.MAP_RIGHT_COORDINATE); 187 | float clampedY = Utils.clamp(mPositionY, GameState.MAP_BOTTOM_COORDINATE, 188 | GameState.MAP_TOP_COORDINATE); 189 | if (clampedX != mPositionX || clampedY != mPositionY) { 190 | // The rocket hit the edge of the map, so make it explode. 191 | mPositionX = clampedX; 192 | mPositionY = clampedY; 193 | handleCollision(); 194 | } 195 | 196 | // The rocket particle will accelerate up to a maximum speed. 197 | float currentSpeedSquared = Utils.vector2DLengthSquared(mVelocityX, mVelocityY); 198 | if (currentSpeedSquared <= ROCKET_MAX_SPEED_SQUARED) { 199 | mVelocityX *= ROCKET_ACCELERATION * frameDelta + 1.0f; 200 | mVelocityY *= ROCKET_ACCELERATION * frameDelta + 1.0f; 201 | } 202 | 203 | // Create a particle trail (exhaust) behind the rocket. 204 | GameState.getInstance().getExplosions().spawnExhaustTrail( 205 | mPositionX, mPositionY, 206 | mVelocityX, mVelocityY, 207 | mColor, 1); 208 | } 209 | 210 | /** 211 | * Advance the particle's position by the given number of frames. 212 | */ 213 | public void incrementPosition(float frameDelta) { 214 | mPositionX += mVelocityX * frameDelta; 215 | mPositionY += mVelocityY * frameDelta; 216 | } 217 | 218 | public void draw(ShapeBuffer sb) { 219 | float headingX = 0.0f, headingY = 0.0f; 220 | if (mAspectRatio != 1.0f) { 221 | // Non-square particles point in the direction they are moving. 222 | headingX = mVelocityX; 223 | headingY = mVelocityY; 224 | } 225 | 226 | sb.add2DShape( 227 | mPositionX, mPositionY, 228 | mCurrentColor, 229 | Utils.SQUARE_SHAPE, 230 | mSize * mAspectRatio, mSize, 231 | headingX, headingY); 232 | } 233 | 234 | public void handleCollision() { 235 | if (mParticleType == PARTICLE_TYPE_ROCKET) { 236 | // Add the shrapnel particles to the "shots" layer so that they will be checked 237 | // for collisions with other players. 238 | GameState.getInstance().getShots().spawnShrapnelExplosion(mPositionX, mPositionY, 239 | mColor, 0.5f, 1.5f, mOwnerId, 100); 240 | } else { 241 | // Create a little bit of "smoke" when the particle hits something. 242 | GameState.getInstance().getExplosions().spawnRingBurst(mPositionX, mPositionY, mColor, 243 | 0.15f, 0.75f, 5); 244 | } 245 | mActiveFrameCountRemaining = 0.0f; 246 | } 247 | 248 | public boolean isActive() { 249 | return mActiveFrameCountRemaining > 0.0f; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/particles/ParticleCollisionGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller.particles; 18 | 19 | import com.google.fpl.gamecontroller.Utils; 20 | 21 | 22 | /** 23 | * Simple class for spatially partitioning particles into a 2-d grid structure. 24 | * 25 | * The world is partitioned into a 2-d grid of zones. As particles are inserted, they are 26 | * assigned to one of the zones. Every frame, the grid must be cleared and the particles 27 | * reinserted at their updated positions. 28 | * 29 | * Proximity queries (getRectPopulation()) can quickly determine the list of particles in 30 | * a given rectangular section of the world. 31 | * 32 | * Assumes that the origin is in the center of the screen. 33 | */ 34 | public class ParticleCollisionGrid { 35 | // The maximum number of particles that can be added to a single grid square. If too 36 | // many particles are added to a single zone, only the first 100 will be tracked. 37 | private static final int MAX_ENTITIES_PER_ZONE = 100; 38 | // The maximum number of particles returned for a proximity query (getRectPopulation()). 39 | private static final int MAX_RETURNED_VALUES = 4000; 40 | 41 | // Row and column sizes in world coordinates. 42 | private float mColumnWidth, mRowHeight; 43 | // The index of the last row and column in the grid. 44 | private int mColumnMax, mRowMax; 45 | // The total number of zones in the grid. 46 | private int mZoneCount; 47 | // Dimensions of the world. 48 | private float mWorldWidth, mWorldHeight; 49 | // Each zone has an array of particles. The zones are stored in row-major order in the first 50 | // dimension of mZoneArray (e.g. the particles in a given row and column can be found by 51 | // referencing mZoneArray[row + column * mColumnMax]). 52 | private final BaseParticle[][] mZoneArray; 53 | // Keeps track of the number of particles in each zone. Stored in row-major order, 54 | // like mZoneArray. 55 | private final int[] mZonePopulation; 56 | 57 | // To avoid the overhead of allocating a new list for each collision check, 58 | // this list is allocated once and reused for each query. 59 | private final BaseParticle[] mReturnValues; 60 | 61 | /** 62 | * Constructs a new collision grid. 63 | * 64 | * The origin of the world is at 0, 0. 65 | * 66 | * @param width the total width of the grid. 67 | * @param height the total height of the grid. 68 | * @param zoneSize the number of world units in each zone. Each zone is square, and 69 | * zoneSize is the length of the sides of the square. 70 | */ 71 | public ParticleCollisionGrid(float width, float height, float zoneSize) { 72 | mWorldWidth = width; 73 | mWorldHeight = height; 74 | mColumnMax = (int) Math.ceil(width / zoneSize); 75 | mRowMax = (int) Math.ceil(height / zoneSize); 76 | mColumnWidth = zoneSize; 77 | mRowHeight = zoneSize; 78 | mZoneCount = mColumnMax * mRowMax; 79 | 80 | mZoneArray = new BaseParticle[mZoneCount][MAX_ENTITIES_PER_ZONE]; 81 | mZonePopulation = new int[mZoneCount]; 82 | 83 | mReturnValues = new BaseParticle[MAX_RETURNED_VALUES]; 84 | } 85 | 86 | /** 87 | * Removes all the objects from the grid. 88 | */ 89 | public void clear() { 90 | for (int i = 0; i < mZoneCount; i++) { 91 | clearZone(i); 92 | } 93 | } 94 | 95 | /** 96 | * Insert a particle into the grid. 97 | */ 98 | public void addParticle(BaseParticle particle) { 99 | // The particles use world coordinates, which need to be converted to grid coordinates 100 | // before they can be inserted. 101 | addObjectHelper(particle, worldXToGridX(particle.getPositionX()), 102 | worldYToGridY(particle.getPositionY())); 103 | } 104 | 105 | /** 106 | * Returns an array containing all the particles in the given section of the grid. 107 | * 108 | * The returned particles will start at array element 0. The last valid element 109 | * will be followed by an element set to null. 110 | * 111 | * The returned array will become invalid next time getRectPopulation is called. 112 | */ 113 | public BaseParticle[] getRectPopulation(float x1, float y1, float x2, float y2) { 114 | int leftSlot, rightSlot, topSlot, bottomSlot; 115 | leftSlot = (int) Math.floor(worldXToGridX(x1) / mColumnWidth) - 1; 116 | if (leftSlot < 0) { 117 | leftSlot = 0; 118 | } 119 | rightSlot = (int) Math.floor(worldXToGridX(x2) / mColumnWidth) + 1; 120 | if (rightSlot >= mColumnMax) { 121 | rightSlot = mColumnMax - 1; 122 | } 123 | topSlot = (int) Math.floor(worldYToGridY(y1) / mRowHeight) - 1; 124 | if (topSlot < 0) { 125 | topSlot = 0; 126 | } 127 | bottomSlot = (int) Math.floor(worldYToGridY(y2) / mRowHeight) + 1; 128 | if (bottomSlot >= mRowMax) { 129 | bottomSlot = mRowMax - 1; 130 | } 131 | 132 | int returnedValueCount = 0; 133 | 134 | // Iterate through each zone covered by the given rectangle. 135 | for (int x = leftSlot; x <= rightSlot; ++x) { 136 | for (int y = topSlot; y <= bottomSlot; ++y) { 137 | int currentZone = x + y * mColumnMax; 138 | // Add all the particles in the zone. 139 | for (int i = 0; i < mZonePopulation[currentZone]; ++i) { 140 | mReturnValues[returnedValueCount] = mZoneArray[currentZone][i]; 141 | ++returnedValueCount; 142 | if (returnedValueCount >= MAX_RETURNED_VALUES - 1) { 143 | // Don't have enough room for more hits, so bail out. 144 | Utils.logDebug("Ran out of space in return array."); 145 | break; 146 | } 147 | } 148 | } 149 | } 150 | 151 | // Set the slot after the last particle to null. 152 | mReturnValues[returnedValueCount] = null; 153 | 154 | return mReturnValues; 155 | } 156 | 157 | /** 158 | * Converts a world coordinate to grid coordinate. 159 | * 160 | * The world coordinate system has the origin in the middle of the map and extends 161 | * mWorldWidth / 2 units to the left and right. The grid can not store negative values, so 162 | * world coordinates must be biased by mWorldWidth / 2 so that they are always positive. 163 | */ 164 | private float worldXToGridX(float worldX) { 165 | return worldX + mWorldWidth / 2.0f; 166 | } 167 | /** 168 | * Converts a world coordinate to grid coordinate. 169 | * 170 | * The world coordinate system has the origin in the middle of the map and extends 171 | * mWorldHeight / 2 units to the above and below the origin. 172 | * The grid can not store negative values, so world coordinates must be biased by 173 | * mWorldHeight / 2 so that they are always positive. 174 | */ 175 | private float worldYToGridY(float worldY) { 176 | return worldY + mWorldHeight / 2.0f; 177 | } 178 | 179 | private void addToZone(BaseParticle particle, int zone) { 180 | if (mZonePopulation[zone] < MAX_ENTITIES_PER_ZONE) { 181 | mZoneArray[zone][mZonePopulation[zone]] = particle; 182 | mZonePopulation[zone]++; 183 | } else { 184 | Utils.logDebug("Ran out of space in zone " + zone + "/" + mZoneCount); 185 | } 186 | } 187 | 188 | private void clearZone(int zone) { 189 | mZonePopulation[zone] = 0; 190 | // This next part is just to make sure garbage collection can happen. Not that it should. 191 | for (int i = 0; i < MAX_ENTITIES_PER_ZONE; i++) { 192 | mZoneArray[zone][i] = null; 193 | } 194 | } 195 | 196 | private void addObjectHelper(BaseParticle particle, float gridX, float gridY) { 197 | int zone = getZoneOnGrid(gridX, gridY); 198 | if (zone != -1) { 199 | addToZone(particle, zone); 200 | } 201 | } 202 | 203 | private int getZoneOnGrid(float gridX, float gridY) { 204 | int gridZoneX = (int) Math.floor(gridX / mColumnWidth); 205 | int gridZoneY = (int) Math.floor(gridY / mRowHeight); 206 | 207 | if (gridZoneX < 0) { 208 | gridZoneX = 0; 209 | } 210 | if (gridZoneY < 0) { 211 | gridZoneY = 0; 212 | } 213 | if (gridZoneX >= mColumnMax) { 214 | gridZoneX = mColumnMax - 1; 215 | } 216 | if (gridZoneY >= mRowMax) { 217 | gridZoneY = mRowMax - 1; 218 | } 219 | return (gridZoneX + gridZoneY * mColumnMax); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/java/com/google/fpl/gamecontroller/particles/ParticleSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.fpl.gamecontroller.particles; 18 | 19 | import android.graphics.PointF; 20 | 21 | import com.google.fpl.gamecontroller.GameState; 22 | import com.google.fpl.gamecontroller.ShapeBuffer; 23 | import com.google.fpl.gamecontroller.Utils; 24 | 25 | /** 26 | * Manages a group of particles. 27 | */ 28 | public class ParticleSystem { 29 | // Constants used to create the "ring burst" effect. 30 | private static final float RING_BURST_INITIAL_POSITION_INCREMENT = 0.0f; 31 | private static final float RING_BURST_MAX_ALPHA = 0.25f; 32 | private static final float RING_BURST_MIN_SIZE = 0.5f; 33 | private static final float RING_BURST_MAX_SIZE = 2.0f; 34 | private static final float RING_BURST_ASPECT_RATIO = 1.0f; 35 | private static final float RING_BURST_MIN_LIFETIME = 0.25f; 36 | private static final float RING_BURST_MAX_LIFETIME = 0.75f; 37 | private static final int RING_BURST_OWNER_ID = GameState.INVALID_PLAYER_ID; 38 | 39 | // Constants used to create the shrapnel effect. 40 | private static final float SHRAPNEL_INITIAL_POSITION_INCREMENT = 3.0f; 41 | private static final float SHRAPNEL_MAX_ALPHA = 1.0f; 42 | private static final float SHRAPNEL_MIN_SIZE = 0.75f; 43 | private static final float SHRAPNEL_MAX_SIZE = 0.75f; 44 | private static final float SHRAPNEL_ASPECT_RATIO = 4.0f; 45 | private static final float SHRAPNEL_MIN_LIFETIME = 0.08f; 46 | private static final float SHRAPNEL_MAX_LIFETIME = 0.75f; 47 | 48 | // Constants used to create exhaust particles. 49 | private static final float EXHAUST_TRAIL_MIN_LIFETIME = 0.25f; 50 | private static final float EXHAUST_TRAIL_MAX_LIFETIME = 1.0f; 51 | private static final float EXHAUST_TRAIL_SOURCE_VELOCITY_SCALE = -0.5f; 52 | private static final float EXHAUST_TRAIL_VELOCITY_VARIANCE = 0.1f; 53 | private static final float EXHAUST_TRAIL_SOURCE_OFFSET_STEPS = 2.0f; 54 | private static final float EXHAUST_TRAIL_MIN_SIZE = 1.0f; 55 | private static final float EXHAUST_TRAIL_MAX_SIZE = 2.0f; 56 | private static final float EXHAUST_TRAIL_MAX_ALPHA = 0.25f; 57 | 58 | private static final int COLLISION_GRID_ZONE_SIZE = 10; 59 | 60 | protected final BaseParticle[] mParticles; 61 | protected final ParticleCollisionGrid mCollisionGrid; 62 | protected int mLastOpenIndex = 0; 63 | 64 | /** 65 | * Constructs a new particle system. 66 | * 67 | * @param maxActiveParticles the most particles that will ever be active at once. 68 | * @param generateCollisionGrid - true to create a ParticleCollisionGrid structure 69 | * for intersection and proximity queries. 70 | */ 71 | public ParticleSystem(int maxActiveParticles, boolean generateCollisionGrid) { 72 | mParticles = new BaseParticle[maxActiveParticles]; 73 | for (int i = 0; i < mParticles.length; i++) { 74 | mParticles[i] = new BaseParticle(); 75 | } 76 | 77 | if (generateCollisionGrid) { 78 | mCollisionGrid = new ParticleCollisionGrid( 79 | GameState.WORLD_WIDTH, 80 | GameState.WORLD_HEIGHT, 81 | COLLISION_GRID_ZONE_SIZE); 82 | } else { 83 | mCollisionGrid = null; 84 | } 85 | } 86 | 87 | /** 88 | * Updates all the particles in the system. 89 | * 90 | * @param frameDelta the number of frames that have elapsed since the last update. 91 | */ 92 | public void update(float frameDelta) { 93 | if (mCollisionGrid != null) { 94 | mCollisionGrid.clear(); 95 | for (BaseParticle particle : mParticles) { 96 | particle.update(frameDelta); 97 | if (particle.isActive()) { 98 | mCollisionGrid.addParticle(particle); 99 | } 100 | } 101 | } else { 102 | for (BaseParticle particle : mParticles) { 103 | particle.update(frameDelta); 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Draws the system to the given shape buffer. 110 | */ 111 | public void draw(ShapeBuffer sb) { 112 | for (BaseParticle particle : mParticles) { 113 | if (particle.isActive()) { 114 | particle.draw(sb); 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * Spawns a new particle. 121 | * 122 | * @param lifetimeInSeconds the number of seconds this particle will be active. 123 | * @return the new particle, or null if too many particles have already been spawned. 124 | */ 125 | public BaseParticle spawnParticle(float lifetimeInSeconds) { 126 | int slot = getNextOpenIndex(); 127 | 128 | if (slot != -1) { 129 | mParticles[slot].reset(GameState.secondsToFrameDelta(lifetimeInSeconds)); 130 | return mParticles[slot]; 131 | } 132 | return null; 133 | } 134 | 135 | /** 136 | * Spawns a ring of particles around the given point. 137 | * 138 | * Useful for smoke and other effects that don't interact with the players' ships. 139 | * 140 | * @param centerX x center of the ring. 141 | * @param centerY y center of the ring. 142 | * @param color color of the ring particles. 143 | * @param minSpeed minimum particle speed. 144 | * @param maxSpeed maximum particle speed. 145 | * @param particleCount number of particles to create. 146 | */ 147 | public void spawnRingBurst(float centerX, float centerY, Utils.Color color, float minSpeed, 148 | float maxSpeed, int particleCount) { 149 | spawnGroupFromPoint( 150 | centerX, centerY, RING_BURST_INITIAL_POSITION_INCREMENT, 151 | color, RING_BURST_MAX_ALPHA, 152 | minSpeed, maxSpeed, 153 | RING_BURST_MIN_SIZE, RING_BURST_MAX_SIZE, RING_BURST_ASPECT_RATIO, 154 | RING_BURST_MIN_LIFETIME, RING_BURST_MAX_LIFETIME, 155 | RING_BURST_OWNER_ID, 156 | particleCount); 157 | } 158 | 159 | /** 160 | * Creates an explosion centered around the given point. 161 | * 162 | * Useful for creating fragments that can potentially collide with players' ships. 163 | * 164 | * @param centerX x center of the explosion. 165 | * @param centerY y center of the explosion. 166 | * @param color color of explosion particles. 167 | * @param minSpeed minimum particle speed. 168 | * @param maxSpeed maximum particle speed. 169 | * @param ownerId the owner of the new particles. 170 | * @param particleCount the number of particles to create. 171 | */ 172 | public void spawnShrapnelExplosion(float centerX, float centerY, Utils.Color color, 173 | float minSpeed, float maxSpeed, int ownerId, 174 | int particleCount) { 175 | spawnGroupFromPoint(centerX, centerY, SHRAPNEL_INITIAL_POSITION_INCREMENT, 176 | color, SHRAPNEL_MAX_ALPHA, 177 | minSpeed, maxSpeed, 178 | SHRAPNEL_MIN_SIZE, SHRAPNEL_MAX_SIZE, SHRAPNEL_ASPECT_RATIO, 179 | SHRAPNEL_MIN_LIFETIME, SHRAPNEL_MAX_LIFETIME, 180 | ownerId, 181 | particleCount); 182 | } 183 | 184 | /** 185 | * Creates a trail of particles behind the given source point. 186 | * 187 | * Useful for creating ship or rocket exhaust. 188 | * 189 | * @param sourceX x source of the exhaust. 190 | * @param sourceY y source of the exhaust. 191 | * @param sourceVelocityX the x velocity of the object creating exhaust. 192 | * @param sourceVelocityY the y velocity of the object creating exhaust. 193 | * @param color the color of the new particles. 194 | * @param particleCount the number of particles to create. 195 | */ 196 | public void spawnExhaustTrail(float sourceX, float sourceY, float sourceVelocityX, 197 | float sourceVelocityY, Utils.Color color, int particleCount) { 198 | for (int i = 0; i < particleCount; ++i) { 199 | float lifetime = Utils.randFloatInRange( 200 | EXHAUST_TRAIL_MIN_LIFETIME, 201 | EXHAUST_TRAIL_MAX_LIFETIME); 202 | BaseParticle trailParticle = spawnParticle(lifetime); 203 | if (trailParticle != null) { 204 | float velocityX = sourceVelocityX * EXHAUST_TRAIL_SOURCE_VELOCITY_SCALE; 205 | velocityX += Utils.randFloatInRange(-EXHAUST_TRAIL_VELOCITY_VARIANCE, 206 | EXHAUST_TRAIL_VELOCITY_VARIANCE); 207 | 208 | float velocityY = sourceVelocityY * EXHAUST_TRAIL_SOURCE_VELOCITY_SCALE; 209 | velocityY += Utils.randFloatInRange(-EXHAUST_TRAIL_VELOCITY_VARIANCE, 210 | EXHAUST_TRAIL_VELOCITY_VARIANCE); 211 | 212 | trailParticle.setPosition( 213 | sourceX - sourceVelocityX * EXHAUST_TRAIL_SOURCE_OFFSET_STEPS, 214 | sourceY - sourceVelocityY * EXHAUST_TRAIL_SOURCE_OFFSET_STEPS); 215 | trailParticle.setSpeed(velocityX, velocityY); 216 | trailParticle.setColor(color); 217 | trailParticle.setSize( 218 | Utils.randFloatInRange(EXHAUST_TRAIL_MIN_SIZE, EXHAUST_TRAIL_MAX_SIZE)); 219 | trailParticle.setMaxAlpha(EXHAUST_TRAIL_MAX_ALPHA); 220 | } 221 | } 222 | } 223 | 224 | /** 225 | * Treats the particle array as a circular list, and returns the next index after the given one. 226 | */ 227 | private int getNextIndex(int i) { 228 | return (i + 1) % mParticles.length; 229 | } 230 | 231 | /** 232 | * Returns the next available particle index, or -1 if all slots are in use. 233 | */ 234 | protected int getNextOpenIndex() { 235 | for (int i = getNextIndex(mLastOpenIndex); i != mLastOpenIndex; i = getNextIndex(i)) { 236 | if (!mParticles[i].isActive()) { 237 | mLastOpenIndex = i; 238 | return mLastOpenIndex; 239 | } 240 | } 241 | Utils.logDebug("Too many active particles: " + this.toString()); 242 | return -1; 243 | } 244 | 245 | /** 246 | * Returns the first particle that lies within the given circle. 247 | * 248 | * If more than one particle are in the circle, only one is returned. The returned 249 | * particle is not necessarily the one closest to the center of the circle. 250 | * 251 | * Returns null if no particle is in the given circle or if this system does not have 252 | * collision detection enabled. 253 | * 254 | * @param x x center of the circle. 255 | * @param y y center of the circle. 256 | * @param radius the radius of the circle to test. 257 | * @return the first particle that lies within the given circle. 258 | */ 259 | public BaseParticle checkForCollision(float x, float y, float radius) { 260 | if (mCollisionGrid == null) { 261 | return null; 262 | } 263 | // Get the list of all possible hits. 264 | BaseParticle[] possibleHits = 265 | mCollisionGrid.getRectPopulation(x - radius, y - radius, x + radius, y + radius); 266 | 267 | BaseParticle currentParticle; 268 | final float radiusSquared = radius * radius; 269 | // Look for the first particle that meets our criteria (within the given circle). 270 | for (int i = 0; possibleHits[i] != null; i++) { 271 | currentParticle = possibleHits[i]; 272 | float xx = x - currentParticle.getPositionX(); 273 | float yy = y - currentParticle.getPositionY(); 274 | if (Utils.vector2DLengthSquared(xx, yy) <= radiusSquared) { 275 | return currentParticle; 276 | } 277 | } 278 | 279 | return null; 280 | } 281 | 282 | /** 283 | * Returns a list of particles that might fall within the given rectangle. 284 | * 285 | * The list of particles returned is a super-set of the particles in the given 286 | * rectangle. Finer-grained checking is needed to know exactly which particles are 287 | * in the rectangle. 288 | * 289 | * @param x the center of the rectangle. 290 | * @param y the center of the rectangle. 291 | * @param width the width of the rectangle. 292 | * @param height the height of the rectangle. 293 | * @return All the particles that may be within the given rectangle. 294 | */ 295 | public BaseParticle[] getPotentialCollisions(float x, float y, float width, float height) { 296 | if (mCollisionGrid == null) { 297 | return null; 298 | } 299 | final float left = x - width / 2.0f; 300 | final float right = x + width / 2.0f; 301 | final float bottom = y - height / 2.0f; 302 | final float top = y + height / 2.0f; 303 | return mCollisionGrid.getRectPopulation(left, bottom, right, top); 304 | } 305 | 306 | /** 307 | * Helper function for spawning a group of particles at or near a given point. 308 | * 309 | * @param centerX x center of the group. 310 | * @param centerY y center of the group. 311 | * @param initialPositionIncrement the number of frame increments to move the particle 312 | * from its initial position. 313 | * @param color color of the particles. 314 | * @param maxAlpha maximum alpha value for new particles. 315 | * @param minSpeed minimum particle speed. 316 | * @param maxSpeed maximum particle speed. 317 | * @param minSize minimum particle size. 318 | * @param maxSize maximum particle size. 319 | * @param aspectRatio aspect ration for new particles. 320 | * @param minLifetime minimum lifetime for new particles. 321 | * @param maxLifetime maximum lifetime for new particles. 322 | * @param ownerId the owner of the new particles. 323 | * @param particleCount the number of particles to create. 324 | */ 325 | private void spawnGroupFromPoint(float centerX, float centerY, float initialPositionIncrement, 326 | Utils.Color color, float maxAlpha, 327 | float minSpeed, float maxSpeed, 328 | float minSize, float maxSize, float aspectRatio, 329 | float minLifetime, float maxLifetime, 330 | int ownerId, 331 | int particleCount) { 332 | for (int i = 0; i < particleCount; i++) { 333 | PointF direction = Utils.randDirectionVector(); 334 | float speed = Utils.randFloatInRange(minSpeed, maxSpeed); 335 | float size = Utils.randFloatInRange(minSize, maxSize); 336 | float lifetime = Utils.randFloatInRange(minLifetime, maxLifetime); 337 | 338 | BaseParticle particle = spawnParticle(lifetime); 339 | if (particle != null) { 340 | particle.setPosition(centerX, centerY); 341 | particle.setSpeed(direction.x * speed, direction.y * speed); 342 | particle.setColor(color); 343 | particle.setMaxAlpha(maxAlpha); 344 | particle.setSize(size); 345 | particle.setAspectRatio(aspectRatio); 346 | particle.setOwnerId(ownerId); 347 | 348 | // Potentially offset the particle so it does not start exactly on the 349 | // source location. 350 | particle.incrementPosition(initialPositionIncrement); 351 | } 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/androidtv-GameController/a8b7afaaf054a26041be25405b8ef3ea87eb8069/GameControllerSample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/androidtv-GameController/a8b7afaaf054a26041be25405b8ef3ea87eb8069/GameControllerSample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/androidtv-GameController/a8b7afaaf054a26041be25405b8ef3ea87eb8069/GameControllerSample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/androidtv-GameController/a8b7afaaf054a26041be25405b8ef3ea87eb8069/GameControllerSample/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/raw/untextured_fs.glslf: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Simple fragment shader that copies the vertex color to the fragment. 18 | 19 | precision mediump float; 20 | 21 | // Vertex color from the vertex shader. 22 | varying vec4 voutColor; 23 | 24 | void main() { 25 | gl_FragColor = voutColor; 26 | } 27 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/raw/untextured_vs.glslv: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Simple vertex shader that performs an MVP transformation and copies a per-vertex color 18 | // to the fragment shader. 19 | 20 | // The combined model, view, and projection matrices. 21 | uniform mat4 mvpMatrix; 22 | 23 | // The source vertex position and color data. 24 | attribute vec4 position; 25 | attribute vec4 color; 26 | 27 | // Color sent to the fragment shader. 28 | varying vec4 voutColor; 29 | 30 | void main() { 31 | // Send the vertex color to the fragment shader. 32 | voutColor = color; 33 | 34 | // Transform the position into clip space. 35 | gl_Position = mvpMatrix * position; 36 | } 37 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | 10 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GameController 5 | 6 | 7 | -------------------------------------------------------------------------------- /GameControllerSample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This sample has been deprecated/archived meaning it's read-only and it's no longer actively maintained (more details on archiving can be found [here][1]). 2 | 3 | For other related samples, check out the new [github.com/android/tv][2] repo. Thank you! 4 | 5 | [1]: https://help.github.com/en/articles/about-archiving-repositories 6 | [2]: https://github.com/android/tv 7 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/androidtv-GameController/a8b7afaaf054a26041be25405b8ef3ea87eb8069/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/androidtv-GameController/a8b7afaaf054a26041be25405b8ef3ea87eb8069/ic_launcher-web.png -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/androidtv-GameController/a8b7afaaf054a26041be25405b8ef3ea87eb8069/libs/android-support-v4.jar -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | # target=android-18 15 | target=Google Inc.:Google APIs:19 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | include 'GameControllerSample' 5 | --------------------------------------------------------------------------------