AnimationComponent.java revision 3c1e67e433728684b5f228c5d4f3e5b1457bb271
1/* 2 * Copyright (C) 2010 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 17package com.replica.replicaisland; 18 19 20import com.replica.replicaisland.CollisionParameters.HitType; 21import com.replica.replicaisland.GameObject.ActionType; 22import com.replica.replicaisland.SoundSystem.Sound; 23 24/** 25 * Player Animation game object component. Responsible for selecting an animation to describe the 26 * player's current state. Requires the object to contain a SpriteComponent to play animations. 27 */ 28public class AnimationComponent extends GameComponent { 29 30 public enum PlayerAnimations { 31 IDLE, 32 MOVE, 33 MOVE_FAST, 34 BOOST_UP, 35 BOOST_MOVE, 36 BOOST_MOVE_FAST, 37 STOMP, 38 HIT_REACT, 39 DEATH, 40 FROZEN 41 } 42 43 private static final float MIN_ROCKET_TIME = 0.0f; 44 private static final float FLICKER_INTERVAL = 0.15f; 45 private static final float FLICKER_DURATION = 3.0f; 46 private static final float LAND_THUMP_DELAY = 0.5f; 47 48 private SpriteComponent mSprite; 49 private SpriteComponent mJetSprite; 50 private SpriteComponent mSparksSprite; 51 52 private PlayerComponent mPlayer; 53 private float mLastFlickerTime; 54 private boolean mFlickerOn; 55 private float mFlickerTimeRemaining; 56 57 private GameObject.ActionType mPreviousAction; 58 59 private float mLastRocketsOnTime; 60 private boolean mExplodingDeath; 61 62 private ChangeComponentsComponent mDamageSwap; 63 private Sound mLandThump; 64 private Sound mRocketSound; 65 private Sound mExplosionSound; 66 private float mLandThumpDelay; 67 private int mRocketSoundStream; 68 private boolean mRocketSoundPaused; 69 70 private int mLastRubyCount; 71 private Sound mRubySound1; 72 private Sound mRubySound2; 73 private Sound mRubySound3; 74 private InventoryComponent mInventory; 75 76 77 public AnimationComponent() { 78 super(); 79 reset(); 80 setPhase(ComponentPhases.ANIMATION.ordinal()); 81 } 82 83 @Override 84 public void reset() { 85 mPreviousAction = ActionType.INVALID; 86 mSprite = null; 87 mJetSprite = null; 88 mSparksSprite = null; 89 mPlayer = null; 90 mLastFlickerTime = 0.0f; 91 mFlickerOn = false; 92 mFlickerTimeRemaining = 0.0f; 93 mLastRocketsOnTime = 0.0f; 94 mExplodingDeath = false; 95 mDamageSwap = null; 96 mLandThump = null; 97 mLandThumpDelay = 0.0f; 98 mRocketSound = null; 99 mRocketSoundStream = -1; 100 mLastRubyCount = 0; 101 mInventory = null; 102 mExplosionSound = null; 103 } 104 105 @Override 106 public void update(float timeDelta, BaseObject parent) { 107 if (mSprite != null) { 108 109 GameObject parentObject = (GameObject) parent; 110 111 final float velocityX = parentObject.getVelocity().x; 112 final float velocityY = parentObject.getVelocity().y; 113 114 115 GameObject.ActionType currentAction = parentObject.getCurrentAction(); 116 117 if (mJetSprite != null) { 118 mJetSprite.setVisible(false); 119 } 120 121 if (mSparksSprite != null) { 122 mSparksSprite.setVisible(false); 123 } 124 125 126 final TimeSystem time = sSystemRegistry.timeSystem; 127 final float gameTime = time.getGameTime(); 128 129 if (currentAction != ActionType.HIT_REACT && mPreviousAction == ActionType.HIT_REACT) { 130 mFlickerTimeRemaining = FLICKER_DURATION; 131 } 132 133 134 final boolean touchingGround = parentObject.touchingGround(); 135 136 boolean boosting = mPlayer != null ? mPlayer.getRocketsOn() : false; 137 138 boolean visible = true; 139 140 SoundSystem sound = sSystemRegistry.soundSystem; 141 142 // It's usually not necessary to test to see if sound is enabled or not (when it's disabled, 143 // play() is just a nop), but in this case I have a stream that is maintained for the rocket 144 // sounds. So it's simpler to just avoid that code if sound is off. 145 if (sound.getSoundEnabled()) { 146 if (boosting) { 147 mLastRocketsOnTime = gameTime; 148 } else { 149 if (gameTime - mLastRocketsOnTime < MIN_ROCKET_TIME 150 && velocityY >= 0.0f) { 151 boosting = true; 152 } 153 } 154 155 if (mRocketSound != null) { 156 if (boosting) { 157 if (mRocketSoundStream == -1) { 158 mRocketSoundStream = sound.play(mRocketSound, true, SoundSystem.PRIORITY_HIGH); 159 mRocketSoundPaused = false; 160 } else if (mRocketSoundPaused) { 161 sound.resume(mRocketSoundStream); 162 mRocketSoundPaused = false; 163 } 164 } else { 165 sound.pause(mRocketSoundStream); 166 mRocketSoundPaused = true; 167 } 168 } 169 } 170 171 // Normally, for collectables like the coin, we could just tell the object to play 172 // a sound when it is collected. The gems are a special case, though, as we 173 // want to pick a different sound depending on how many have been collected. 174 if (mInventory != null && mRubySound1 != null && mRubySound2 != null && mRubySound3 != null) { 175 InventoryComponent.UpdateRecord inventory = mInventory.getRecord(); 176 final int rubyCount = inventory.rubyCount; 177 if (rubyCount != mLastRubyCount) { 178 mLastRubyCount = rubyCount; 179 switch (rubyCount) { 180 case 1: 181 sound.play(mRubySound1, false, SoundSystem.PRIORITY_NORMAL); 182 break; 183 case 2: 184 sound.play(mRubySound2, false, SoundSystem.PRIORITY_NORMAL); 185 break; 186 case 3: 187 sound.play(mRubySound3, false, SoundSystem.PRIORITY_NORMAL); 188 break; 189 } 190 191 } 192 } 193 194 // Turn on visual effects (smoke, etc) when the player's life reaches 1. 195 if (mDamageSwap != null) { 196 if (parentObject.life == 1 && !mDamageSwap.getCurrentlySwapped()) { 197 mDamageSwap.activate(parentObject); 198 } else if (parentObject.life != 1 && mDamageSwap.getCurrentlySwapped()) { 199 mDamageSwap.activate(parentObject); 200 } 201 } 202 203 float opacity = 1.0f; 204 205 if (currentAction == ActionType.MOVE) { 206 InputSystem input = sSystemRegistry.inputSystem; 207 if (input.getRollDirection().x < 0.0f) { 208 parentObject.facingDirection.x = -1.0f; 209 } else if (input.getRollDirection().x > 0.0f) { 210 parentObject.facingDirection.x = 1.0f; 211 } 212 213 // TODO: get rid of these magic numbers! 214 if (touchingGround) { 215 216 if (Utils.close(velocityX, 0.0f, 30.0f)) { 217 mSprite.playAnimation(PlayerAnimations.IDLE.ordinal()); 218 } else if (Math.abs(velocityX) > 300.0f) { 219 mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal()); 220 } else { 221 mSprite.playAnimation(PlayerAnimations.MOVE.ordinal()); 222 } 223 224 if (input.getClickPressed() || 225 (input.getTouchPressed() && input.getTouchedWithinRegion( 226 ButtonConstants.STOMP_BUTTON_REGION_X, 227 ButtonConstants.STOMP_BUTTON_REGION_Y, 228 ButtonConstants.STOMP_BUTTON_REGION_WIDTH, 229 ButtonConstants.STOMP_BUTTON_REGION_HEIGHT))) { 230 // charge 231 final float pressedTime = gameTime - input.getLastClickTime(); 232 final float wave = (float)Math.cos(pressedTime * (float)Math.PI * 2.0f); 233 opacity = (wave * 0.25f) + 0.75f; 234 } 235 236 } else { 237 if (boosting) { 238 if (mJetSprite != null) { 239 mJetSprite.setVisible(true); 240 } 241 242 if (Math.abs(velocityX) < 100.0f && velocityY > 10.0f) { 243 mSprite.playAnimation(PlayerAnimations.BOOST_UP.ordinal()); 244 } else if (Math.abs(velocityX) > 300.0f) { 245 mSprite.playAnimation(PlayerAnimations.BOOST_MOVE_FAST.ordinal()); 246 } else { 247 mSprite.playAnimation(PlayerAnimations.BOOST_MOVE.ordinal()); 248 } 249 } else { 250 251 if (Utils.close(velocityX, 0.0f, 1.0f)) { 252 mSprite.playAnimation(PlayerAnimations.IDLE.ordinal()); 253 } else if (Math.abs(velocityX) > 300.0f) { 254 mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal()); 255 } else { 256 mSprite.playAnimation(PlayerAnimations.MOVE.ordinal()); 257 } 258 } 259 260 } 261 } else if (currentAction == ActionType.ATTACK) { 262 mSprite.playAnimation(PlayerAnimations.STOMP.ordinal()); 263 if (touchingGround && gameTime > mLandThumpDelay) { 264 if (mLandThump != null && sound != null) { 265 // modulate the sound slightly to avoid sounding too similar 266 sound.play(mLandThump, false, SoundSystem.PRIORITY_HIGH, 1.0f, 267 (float)(Math.random() * 0.5f) + 0.75f); 268 mLandThumpDelay = gameTime + LAND_THUMP_DELAY; 269 } 270 } 271 } else if (currentAction == ActionType.HIT_REACT) { 272 mSprite.playAnimation(PlayerAnimations.HIT_REACT.ordinal()); 273 274 if (velocityX > 0.0f) { 275 parentObject.facingDirection.x = -1.0f; 276 } else if (velocityX < 0.0f) { 277 parentObject.facingDirection.x = 1.0f; 278 } 279 280 if (mSparksSprite != null) { 281 mSparksSprite.setVisible(true); 282 } 283 } else if (currentAction == ActionType.DEATH) { 284 if (mPreviousAction != currentAction) { 285 if (mExplosionSound != null) { 286 sound.play(mExplosionSound, false, SoundSystem.PRIORITY_NORMAL); 287 } 288 // by default, explode when hit with the DEATH hit type. 289 boolean explodingDeath = parentObject.lastReceivedHitType == HitType.DEATH; 290 // or if touching a death tile. 291 HotSpotSystem hotSpot = sSystemRegistry.hotSpotSystem; 292 if (hotSpot != null) { 293 // TODO: HACK! Unify all this code. 294 if (hotSpot.getHotSpot(parentObject.getCenteredPositionX(), 295 parentObject.getPosition().y + 10.0f) == HotSpotSystem.HotSpotType.DIE) { 296 explodingDeath = true; 297 } 298 } 299 if (explodingDeath) { 300 mExplodingDeath = true; 301 GameObjectFactory factory = sSystemRegistry.gameObjectFactory; 302 GameObjectManager manager = sSystemRegistry.gameObjectManager; 303 if (factory != null && manager != null) { 304 GameObject explosion = factory.spawnEffectExplosionGiant(parentObject.getPosition().x, parentObject.getPosition().y); 305 if (explosion != null) { 306 manager.add(explosion); 307 } 308 } 309 } else { 310 mSprite.playAnimation(PlayerAnimations.DEATH.ordinal()); 311 mExplodingDeath = false; 312 } 313 314 mFlickerTimeRemaining = 0.0f; 315 if (mSparksSprite != null) { 316 if (!mSprite.animationFinished()) { 317 mSparksSprite.setVisible(true); 318 } 319 } 320 } 321 if (mExplodingDeath) { 322 visible = false; 323 } 324 } else if (currentAction == ActionType.FROZEN) { 325 mSprite.playAnimation(PlayerAnimations.FROZEN.ordinal()); 326 } 327 328 if (mFlickerTimeRemaining > 0.0f) { 329 mFlickerTimeRemaining -= timeDelta; 330 if (gameTime > mLastFlickerTime + FLICKER_INTERVAL) { 331 mLastFlickerTime = gameTime; 332 mFlickerOn = !mFlickerOn; 333 } 334 mSprite.setVisible(mFlickerOn); 335 if (mJetSprite != null && mJetSprite.getVisible()) { 336 mJetSprite.setVisible(mFlickerOn); 337 } 338 } else { 339 mSprite.setVisible(visible); 340 mSprite.setOpacity(opacity); 341 } 342 343 mPreviousAction = currentAction; 344 } 345 } 346 347 public void setSprite(SpriteComponent sprite) { 348 mSprite = sprite; 349 } 350 351 public void setJetSprite(SpriteComponent sprite) { 352 mJetSprite = sprite; 353 } 354 355 public void setSparksSprite(SpriteComponent sprite) { 356 mSparksSprite = sprite; 357 } 358 359 public void setPlayer(PlayerComponent player) { 360 mPlayer = player; 361 } 362 363 public final void setDamageSwap(ChangeComponentsComponent damageSwap) { 364 mDamageSwap = damageSwap; 365 } 366 367 public void setLandThump(Sound land) { 368 mLandThump = land; 369 } 370 371 public void setRocketSound(Sound sound) { 372 mRocketSound = sound; 373 } 374 375 public void setRubySounds(Sound one, Sound two, Sound three) { 376 mRubySound1 = one; 377 mRubySound2 = two; 378 mRubySound3 = three; 379 } 380 381 public void setInventory(InventoryComponent inventory) { 382 mInventory = inventory; 383 } 384 385 public void setExplosionSound(Sound sound) { 386 mExplosionSound = sound; 387 } 388} 389