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 17 package com.replica.replicaisland; 18 19 20 import com.replica.replicaisland.CollisionParameters.HitType; 21 import com.replica.replicaisland.GameObject.ActionType; 22 import 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 */ 28 public 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 InputGameInterface input = sSystemRegistry.inputGameInterface; 207 final InputXY dpad = input.getDirectionalPad(); 208 if (dpad.getX() < 0.0f) { 209 parentObject.facingDirection.x = -1.0f; 210 } else if (dpad.getX() > 0.0f) { 211 parentObject.facingDirection.x = 1.0f; 212 } 213 214 // TODO: get rid of these magic numbers! 215 if (touchingGround) { 216 217 if (Utils.close(velocityX, 0.0f, 30.0f)) { 218 mSprite.playAnimation(PlayerAnimations.IDLE.ordinal()); 219 } else if (Math.abs(velocityX) > 300.0f) { 220 mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal()); 221 } else { 222 mSprite.playAnimation(PlayerAnimations.MOVE.ordinal()); 223 } 224 225 final InputButton attackButton = input.getAttackButton(); 226 227 if (attackButton.getPressed()) { 228 // charge 229 final float pressedTime = gameTime - attackButton.getLastPressedTime(); 230 final float wave = (float)Math.cos(pressedTime * (float)Math.PI * 2.0f); 231 opacity = (wave * 0.25f) + 0.75f; 232 } 233 234 } else { 235 if (boosting) { 236 if (mJetSprite != null) { 237 mJetSprite.setVisible(true); 238 } 239 240 if (Math.abs(velocityX) < 100.0f && velocityY > 10.0f) { 241 mSprite.playAnimation(PlayerAnimations.BOOST_UP.ordinal()); 242 } else if (Math.abs(velocityX) > 300.0f) { 243 mSprite.playAnimation(PlayerAnimations.BOOST_MOVE_FAST.ordinal()); 244 } else { 245 mSprite.playAnimation(PlayerAnimations.BOOST_MOVE.ordinal()); 246 } 247 } else { 248 249 if (Utils.close(velocityX, 0.0f, 1.0f)) { 250 mSprite.playAnimation(PlayerAnimations.IDLE.ordinal()); 251 } else if (Math.abs(velocityX) > 300.0f) { 252 mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal()); 253 } else { 254 mSprite.playAnimation(PlayerAnimations.MOVE.ordinal()); 255 } 256 } 257 258 } 259 } else if (currentAction == ActionType.ATTACK) { 260 mSprite.playAnimation(PlayerAnimations.STOMP.ordinal()); 261 if (touchingGround && gameTime > mLandThumpDelay) { 262 if (mLandThump != null && sound != null) { 263 // modulate the sound slightly to avoid sounding too similar 264 sound.play(mLandThump, false, SoundSystem.PRIORITY_HIGH, 1.0f, 265 (float)(Math.random() * 0.5f) + 0.75f); 266 mLandThumpDelay = gameTime + LAND_THUMP_DELAY; 267 } 268 } 269 } else if (currentAction == ActionType.HIT_REACT) { 270 mSprite.playAnimation(PlayerAnimations.HIT_REACT.ordinal()); 271 272 if (velocityX > 0.0f) { 273 parentObject.facingDirection.x = -1.0f; 274 } else if (velocityX < 0.0f) { 275 parentObject.facingDirection.x = 1.0f; 276 } 277 278 if (mSparksSprite != null) { 279 mSparksSprite.setVisible(true); 280 } 281 } else if (currentAction == ActionType.DEATH) { 282 if (mPreviousAction != currentAction) { 283 if (mExplosionSound != null) { 284 sound.play(mExplosionSound, false, SoundSystem.PRIORITY_NORMAL); 285 } 286 // by default, explode when hit with the DEATH hit type. 287 boolean explodingDeath = parentObject.lastReceivedHitType == HitType.DEATH; 288 // or if touching a death tile. 289 HotSpotSystem hotSpot = sSystemRegistry.hotSpotSystem; 290 if (hotSpot != null) { 291 // TODO: HACK! Unify all this code. 292 if (hotSpot.getHotSpot(parentObject.getCenteredPositionX(), 293 parentObject.getPosition().y + 10.0f) == HotSpotSystem.HotSpotType.DIE) { 294 explodingDeath = true; 295 } 296 } 297 if (explodingDeath) { 298 mExplodingDeath = true; 299 GameObjectFactory factory = sSystemRegistry.gameObjectFactory; 300 GameObjectManager manager = sSystemRegistry.gameObjectManager; 301 if (factory != null && manager != null) { 302 GameObject explosion = factory.spawnEffectExplosionGiant(parentObject.getPosition().x, parentObject.getPosition().y); 303 if (explosion != null) { 304 manager.add(explosion); 305 } 306 } 307 } else { 308 mSprite.playAnimation(PlayerAnimations.DEATH.ordinal()); 309 mExplodingDeath = false; 310 } 311 312 mFlickerTimeRemaining = 0.0f; 313 if (mSparksSprite != null) { 314 if (!mSprite.animationFinished()) { 315 mSparksSprite.setVisible(true); 316 } 317 } 318 } 319 if (mExplodingDeath) { 320 visible = false; 321 } 322 } else if (currentAction == ActionType.FROZEN) { 323 mSprite.playAnimation(PlayerAnimations.FROZEN.ordinal()); 324 } 325 326 if (mFlickerTimeRemaining > 0.0f) { 327 mFlickerTimeRemaining -= timeDelta; 328 if (gameTime > mLastFlickerTime + FLICKER_INTERVAL) { 329 mLastFlickerTime = gameTime; 330 mFlickerOn = !mFlickerOn; 331 } 332 mSprite.setVisible(mFlickerOn); 333 if (mJetSprite != null && mJetSprite.getVisible()) { 334 mJetSprite.setVisible(mFlickerOn); 335 } 336 } else { 337 mSprite.setVisible(visible); 338 mSprite.setOpacity(opacity); 339 } 340 341 mPreviousAction = currentAction; 342 } 343 } 344 345 public void setSprite(SpriteComponent sprite) { 346 mSprite = sprite; 347 } 348 349 public void setJetSprite(SpriteComponent sprite) { 350 mJetSprite = sprite; 351 } 352 353 public void setSparksSprite(SpriteComponent sprite) { 354 mSparksSprite = sprite; 355 } 356 357 public void setPlayer(PlayerComponent player) { 358 mPlayer = player; 359 } 360 361 public final void setDamageSwap(ChangeComponentsComponent damageSwap) { 362 mDamageSwap = damageSwap; 363 } 364 365 public void setLandThump(Sound land) { 366 mLandThump = land; 367 } 368 369 public void setRocketSound(Sound sound) { 370 mRocketSound = sound; 371 } 372 373 public void setRubySounds(Sound one, Sound two, Sound three) { 374 mRubySound1 = one; 375 mRubySound2 = two; 376 mRubySound3 = three; 377 } 378 379 public void setInventory(InventoryComponent inventory) { 380 mInventory = inventory; 381 } 382 383 public void setExplosionSound(Sound sound) { 384 mExplosionSound = sound; 385 } 386 } 387