void setTarget(GameObject target) {
    if (target != null && mTarget != target) {
      mPreInterpolateCameraPosition.set(mCurrentCameraPosition);
      mPreInterpolateCameraPosition.subtract(target.getPosition());
      if (mPreInterpolateCameraPosition.length2()
          < MAX_INTERPOLATE_TO_TARGET_DISTANCE * MAX_INTERPOLATE_TO_TARGET_DISTANCE) {
        final TimeSystem time = sSystemRegistry.timeSystem;
        mTargetChangedTime = time.getGameTime();
        mPreInterpolateCameraPosition.set(mCurrentCameraPosition);
      } else {
        mTargetChangedTime = 0.0f;
        mCurrentCameraPosition.set(target.getPosition());
      }
    }

    mTarget = target;
  }
  @Override
  public void update(float timeDelta, BaseObject parent) {

    mShakeOffsetY = 0.0f;

    if (mShakeTime > 0.0f) {
      mShakeTime -= timeDelta;
      mShakeOffsetY = (float) (Math.sin(mShakeTime * SHAKE_FREQUENCY) * mShakeMagnitude);
    }

    if (mTarget != null) {
      mTargetPosition.set(mTarget.getCenteredPositionX(), mTarget.getCenteredPositionY());
      final Vector2 targetPosition = mTargetPosition;

      if (mTargetChangedTime > 0.0f) {
        final TimeSystem time = sSystemRegistry.timeSystem;
        final float delta = time.getGameTime() - mTargetChangedTime;

        mCurrentCameraPosition.x =
            Lerp.ease(
                mPreInterpolateCameraPosition.x,
                targetPosition.x,
                INTERPOLATE_TO_TARGET_TIME,
                delta);

        mCurrentCameraPosition.y =
            Lerp.ease(
                mPreInterpolateCameraPosition.y,
                targetPosition.y,
                INTERPOLATE_TO_TARGET_TIME,
                delta);

        if (delta > INTERPOLATE_TO_TARGET_TIME) {
          mTargetChangedTime = -1;
        }
      } else {

        // Only respect the bias if the target is moving.  No camera motion without
        // player input!
        if (mBias.length2() > 0.0f && mTarget.getVelocity().length2() > 1.0f) {
          mBias.normalize();
          mBias.multiply(BIAS_SPEED * timeDelta);
          mCurrentCameraPosition.add(mBias);
        }

        final float xDelta = targetPosition.x - mCurrentCameraPosition.x;
        if (Math.abs(xDelta) > X_FOLLOW_DISTANCE) {
          mCurrentCameraPosition.x = targetPosition.x - (X_FOLLOW_DISTANCE * Utils.sign(xDelta));
        }

        final float yDelta = targetPosition.y - mCurrentCameraPosition.y;
        if (yDelta > Y_UP_FOLLOW_DISTANCE) {
          mCurrentCameraPosition.y = targetPosition.y - Y_UP_FOLLOW_DISTANCE;
        } else if (yDelta < -Y_DOWN_FOLLOW_DISTANCE) {
          mCurrentCameraPosition.y = targetPosition.y + Y_DOWN_FOLLOW_DISTANCE;
        }
      }

      mBias.zero();
    }

    mFocalPosition.x = (float) Math.floor(mCurrentCameraPosition.x);
    mFocalPosition.x = snapFocalPointToWorldBoundsX(mFocalPosition.x);

    mFocalPosition.y = (float) Math.floor(mCurrentCameraPosition.y + mShakeOffsetY);
    mFocalPosition.y = snapFocalPointToWorldBoundsY(mFocalPosition.y);
  }
  @Override
  public void update(float timeDelta, BaseObject parent) {
    if (mSprite != null) {

      GameObject parentObject = (GameObject) parent;

      final float velocityX = parentObject.getVelocity().x;
      final float velocityY = parentObject.getVelocity().y;

      GameObject.ActionType currentAction = parentObject.getCurrentAction();

      if (mJetSprite != null) {
        mJetSprite.setVisible(false);
      }

      if (mSparksSprite != null) {
        mSparksSprite.setVisible(false);
      }

      final TimeSystem time = sSystemRegistry.timeSystem;
      final float gameTime = time.getGameTime();

      if (currentAction != ActionType.HIT_REACT && mPreviousAction == ActionType.HIT_REACT) {
        mFlickerTimeRemaining = FLICKER_DURATION;
      }

      final boolean touchingGround = parentObject.touchingGround();

      boolean boosting = mPlayer != null ? mPlayer.getRocketsOn() : false;

      boolean visible = true;

      SoundSystem sound = sSystemRegistry.soundSystem;

      // It's usually not necessary to test to see if sound is enabled or not (when it's disabled,
      // play() is just a nop), but in this case I have a stream that is maintained for the rocket
      // sounds.  So it's simpler to just avoid that code if sound is off.
      if (sound.getSoundEnabled()) {
        if (boosting) {
          mLastRocketsOnTime = gameTime;
        } else {
          if (gameTime - mLastRocketsOnTime < MIN_ROCKET_TIME && velocityY >= 0.0f) {
            boosting = true;
          }
        }

        if (mRocketSound != null) {
          if (boosting) {
            if (mRocketSoundStream == -1) {
              mRocketSoundStream = sound.play(mRocketSound, true, SoundSystem.PRIORITY_HIGH);
              mRocketSoundPaused = false;
            } else if (mRocketSoundPaused) {
              sound.resume(mRocketSoundStream);
              mRocketSoundPaused = false;
            }
          } else {
            sound.pause(mRocketSoundStream);
            mRocketSoundPaused = true;
          }
        }
      }

      // Normally, for collectables like the coin, we could just tell the object to play
      // a sound when it is collected.  The gems are a special case, though, as we
      // want to pick a different sound depending on how many have been collected.
      if (mInventory != null && mRubySound1 != null && mRubySound2 != null && mRubySound3 != null) {
        InventoryComponent.UpdateRecord inventory = mInventory.getRecord();
        final int rubyCount = inventory.rubyCount;
        if (rubyCount != mLastRubyCount) {
          mLastRubyCount = rubyCount;
          switch (rubyCount) {
            case 1:
              sound.play(mRubySound1, false, SoundSystem.PRIORITY_NORMAL);
              break;
            case 2:
              sound.play(mRubySound2, false, SoundSystem.PRIORITY_NORMAL);
              break;
            case 3:
              sound.play(mRubySound3, false, SoundSystem.PRIORITY_NORMAL);
              break;
          }
        }
      }

      // Turn on visual effects (smoke, etc) when the player's life reaches 1.
      if (mDamageSwap != null) {
        if (parentObject.life == 1 && !mDamageSwap.getCurrentlySwapped()) {
          mDamageSwap.activate(parentObject);
        } else if (parentObject.life != 1 && mDamageSwap.getCurrentlySwapped()) {
          mDamageSwap.activate(parentObject);
        }
      }

      float opacity = 1.0f;

      if (currentAction == ActionType.MOVE) {
        InputGameInterface input = sSystemRegistry.inputGameInterface;
        final InputXY dpad = input.getDirectionalPad();
        if (dpad.getX() < 0.0f) {
          parentObject.facingDirection.x = -1.0f;
        } else if (dpad.getX() > 0.0f) {
          parentObject.facingDirection.x = 1.0f;
        }

        // TODO: get rid of these magic numbers!
        if (touchingGround) {

          if (Utils.close(velocityX, 0.0f, 30.0f)) {
            mSprite.playAnimation(PlayerAnimations.IDLE.ordinal());
          } else if (Math.abs(velocityX) > 300.0f) {
            mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal());
          } else {
            mSprite.playAnimation(PlayerAnimations.MOVE.ordinal());
          }

          final InputButton attackButton = input.getAttackButton();

          if (attackButton.getPressed()) {
            // charge
            final float pressedTime = gameTime - attackButton.getLastPressedTime();
            final float wave = (float) Math.cos(pressedTime * (float) Math.PI * 2.0f);
            opacity = (wave * 0.25f) + 0.75f;
          }

        } else {
          if (boosting) {
            if (mJetSprite != null) {
              mJetSprite.setVisible(true);
            }

            if (Math.abs(velocityX) < 100.0f && velocityY > 10.0f) {
              mSprite.playAnimation(PlayerAnimations.BOOST_UP.ordinal());
            } else if (Math.abs(velocityX) > 300.0f) {
              mSprite.playAnimation(PlayerAnimations.BOOST_MOVE_FAST.ordinal());
            } else {
              mSprite.playAnimation(PlayerAnimations.BOOST_MOVE.ordinal());
            }
          } else {

            if (Utils.close(velocityX, 0.0f, 1.0f)) {
              mSprite.playAnimation(PlayerAnimations.IDLE.ordinal());
            } else if (Math.abs(velocityX) > 300.0f) {
              mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal());
            } else {
              mSprite.playAnimation(PlayerAnimations.MOVE.ordinal());
            }
          }
        }
      } else if (currentAction == ActionType.ATTACK) {
        mSprite.playAnimation(PlayerAnimations.STOMP.ordinal());
        if (touchingGround && gameTime > mLandThumpDelay) {
          if (mLandThump != null && sound != null) {
            // modulate the sound slightly to avoid sounding too similar
            sound.play(
                mLandThump,
                false,
                SoundSystem.PRIORITY_HIGH,
                1.0f,
                (float) (Math.random() * 0.5f) + 0.75f);
            mLandThumpDelay = gameTime + LAND_THUMP_DELAY;
          }
        }
      } else if (currentAction == ActionType.HIT_REACT) {
        mSprite.playAnimation(PlayerAnimations.HIT_REACT.ordinal());

        if (velocityX > 0.0f) {
          parentObject.facingDirection.x = -1.0f;
        } else if (velocityX < 0.0f) {
          parentObject.facingDirection.x = 1.0f;
        }

        if (mSparksSprite != null) {
          mSparksSprite.setVisible(true);
        }
      } else if (currentAction == ActionType.DEATH) {
        if (mPreviousAction != currentAction) {
          if (mExplosionSound != null) {
            sound.play(mExplosionSound, false, SoundSystem.PRIORITY_NORMAL);
          }
          // by default, explode when hit with the DEATH hit type.
          boolean explodingDeath = parentObject.lastReceivedHitType == HitType.DEATH;
          // or if touching a death tile.
          HotSpotSystem hotSpot = sSystemRegistry.hotSpotSystem;
          if (hotSpot != null) {
            // TODO: HACK!  Unify all this code.
            if (hotSpot.getHotSpot(
                    parentObject.getCenteredPositionX(), parentObject.getPosition().y + 10.0f)
                == HotSpotSystem.HotSpotType.DIE) {
              explodingDeath = true;
            }
          }
          if (explodingDeath) {
            mExplodingDeath = true;
            GameObjectFactory factory = sSystemRegistry.gameObjectFactory;
            GameObjectManager manager = sSystemRegistry.gameObjectManager;
            if (factory != null && manager != null) {
              GameObject explosion =
                  factory.spawnEffectExplosionGiant(
                      parentObject.getPosition().x, parentObject.getPosition().y);
              if (explosion != null) {
                manager.add(explosion);
              }
            }
          } else {
            mSprite.playAnimation(PlayerAnimations.DEATH.ordinal());
            mExplodingDeath = false;
          }

          mFlickerTimeRemaining = 0.0f;
          if (mSparksSprite != null) {
            if (!mSprite.animationFinished()) {
              mSparksSprite.setVisible(true);
            }
          }
        }
        if (mExplodingDeath) {
          visible = false;
        }
      } else if (currentAction == ActionType.FROZEN) {
        mSprite.playAnimation(PlayerAnimations.FROZEN.ordinal());
      }

      if (mFlickerTimeRemaining > 0.0f) {
        mFlickerTimeRemaining -= timeDelta;
        if (gameTime > mLastFlickerTime + FLICKER_INTERVAL) {
          mLastFlickerTime = gameTime;
          mFlickerOn = !mFlickerOn;
        }
        mSprite.setVisible(mFlickerOn);
        if (mJetSprite != null && mJetSprite.getVisible()) {
          mJetSprite.setVisible(mFlickerOn);
        }
      } else {
        mSprite.setVisible(visible);
        mSprite.setOpacity(opacity);
      }

      mPreviousAction = currentAction;
    }
  }
  @Override
  public void update(float timeDelta, BaseObject parent) {
    if (mRenderComponent != null) {
      final TimeSystem time = sSystemRegistry.timeSystem;
      final float currentTime = time.getGameTime();

      // Support repeating "phases" on top of the looping fade itself.
      // Complexity++, but it lets this component handle several
      // different use cases.
      if (mActivateTime == 0.0f) {
        mActivateTime = currentTime;
        mInitialDelayTimer = mInitialDelay;
      } else if (mPhaseDuration > 0.0f && currentTime - mActivateTime > mPhaseDuration) {
        mActivateTime = currentTime;
        mInitialDelayTimer = mInitialDelay;
        mStartTime = 0.0f;
      }

      if (mInitialDelayTimer > 0.0f) {
        mInitialDelayTimer -= timeDelta;
      } else {
        if (mStartTime == 0) {
          mStartTime = currentTime;
        }
        float elapsed = currentTime - mStartTime;
        float opacity = mInitialOpacity;
        if (mLoopType != LOOP_TYPE_NONE && elapsed > mDuration) {
          final float endTime = mStartTime + mDuration;
          elapsed = endTime - currentTime;
          mStartTime = endTime;
          if (mLoopType == LOOP_TYPE_PING_PONG) {
            float temp = mInitialOpacity;
            mInitialOpacity = mTargetOpacity;
            mTargetOpacity = temp;
          }
        }

        if (elapsed > mDuration) {
          opacity = mTargetOpacity;
        } else if (elapsed != 0.0f) {
          if (mFunction == FADE_LINEAR) {
            opacity = Lerp.lerp(mInitialOpacity, mTargetOpacity, mDuration, elapsed);
          } else if (mFunction == FADE_EASE) {
            opacity = Lerp.ease(mInitialOpacity, mTargetOpacity, mDuration, elapsed);
          }
        }

        if (mTexture != null) {
          // If a texture is set then we supply a drawable to the render component.
          // If not, we take whatever drawable the renderer already has.
          final DrawableFactory factory = sSystemRegistry.drawableFactory;
          if (factory != null) {
            GameObject parentObject = ((GameObject) parent);
            DrawableBitmap bitmap = factory.allocateDrawableBitmap();
            bitmap.resize((int) mTexture.width, (int) mTexture.height);
            // TODO: Super tricky scale.  fix this!
            bitmap.setWidth((int) parentObject.width);
            bitmap.setHeight((int) parentObject.height);
            bitmap.setOpacity(opacity);
            bitmap.setTexture(mTexture);
            mRenderComponent.setDrawable(bitmap);
          }
        } else {
          DrawableObject drawable = mRenderComponent.getDrawable();
          // TODO: ack, instanceof!  Fix this!
          if (drawable != null && drawable instanceof DrawableBitmap) {
            ((DrawableBitmap) drawable).setOpacity(opacity);
          }
        }
      }
    }
  }