| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.replica.replicaisland; |
| |
| /** |
| * Manages the position of the camera based on a target game object. |
| */ |
| public class CameraSystem extends BaseObject { |
| private GameObject mTarget; |
| private float mShakeTime; |
| private float mShakeMagnitude; |
| private float mShakeOffsetY; |
| private Vector2 mCurrentCameraPosition; |
| private Vector2 mFocalPosition; |
| private Vector2 mPreInterpolateCameraPosition; |
| private Vector2 mTargetPosition; |
| private Vector2 mBias; |
| private float mTargetChangedTime; |
| |
| private static final float X_FOLLOW_DISTANCE = 0.0f; |
| private static final float Y_UP_FOLLOW_DISTANCE = 90.0f; |
| private static final float Y_DOWN_FOLLOW_DISTANCE = 0.0f; |
| |
| private static final float MAX_INTERPOLATE_TO_TARGET_DISTANCE = 300.0f; |
| private static final float INTERPOLATE_TO_TARGET_TIME = 1.0f; |
| |
| private static int SHAKE_FREQUENCY = 40; |
| |
| private static float BIAS_SPEED = 400.0f; |
| |
| public CameraSystem() { |
| super(); |
| mCurrentCameraPosition = new Vector2(); |
| mFocalPosition = new Vector2(); |
| mPreInterpolateCameraPosition = new Vector2(); |
| mTargetPosition = new Vector2(); |
| mBias = new Vector2(); |
| } |
| |
| @Override |
| public void reset() { |
| mTarget = null; |
| mCurrentCameraPosition.zero(); |
| mShakeTime = 0.0f; |
| mShakeMagnitude = 0.0f; |
| mFocalPosition.zero(); |
| mTargetChangedTime = 0.0f; |
| mPreInterpolateCameraPosition.zero(); |
| mTargetPosition.zero(); |
| } |
| |
| 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; |
| |
| } |
| |
| public GameObject getTarget() { |
| return mTarget; |
| } |
| |
| void shake(float duration, float magnitude) { |
| mShakeTime = duration; |
| mShakeMagnitude = magnitude; |
| } |
| |
| public boolean shaking() { |
| return mShakeTime > 0.0f; |
| } |
| |
| @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); |
| } |
| |
| /** Returns the x position of the camera's look-at point. */ |
| public float getFocusPositionX() { |
| return mFocalPosition.x; |
| } |
| |
| /** Returns the y position of the camera's look-at point. */ |
| public float getFocusPositionY() { |
| return mFocalPosition.y; |
| } |
| |
| public boolean pointVisible(Vector2 point, float radius) { |
| boolean visible = false; |
| final float width = sSystemRegistry.contextParameters.gameWidth / 2.0f; |
| final float height = sSystemRegistry.contextParameters.gameHeight / 2.0f; |
| if (Math.abs(mFocalPosition.x - point.x) < (width + radius)) { |
| if (Math.abs(mFocalPosition.y - point.y) < (height + radius)) { |
| visible = true; |
| } |
| } |
| return visible; |
| } |
| |
| /** Snaps a coordinate against the bounds of the world so that it may not pass out |
| * of the visible area of the world. |
| * @param worldX An x-coordinate in world units. |
| * @return An x-coordinate that is guaranteed not to expose the edges of the world. |
| */ |
| public float snapFocalPointToWorldBoundsX(float worldX) { |
| float focalPositionX = worldX; |
| final float width = sSystemRegistry.contextParameters.gameWidth; |
| final LevelSystem level = sSystemRegistry.levelSystem; |
| if (level != null) { |
| final float worldPixelWidth = Math.max(level.getLevelWidth(), width); |
| final float rightEdge = focalPositionX + (width / 2.0f); |
| final float leftEdge = focalPositionX - (width / 2.0f); |
| |
| if (rightEdge > worldPixelWidth) { |
| focalPositionX = worldPixelWidth - (width / 2.0f); |
| } else if (leftEdge < 0) { |
| focalPositionX = width / 2.0f; |
| } |
| } |
| return focalPositionX; |
| } |
| |
| /** Snaps a coordinate against the bounds of the world so that it may not pass out |
| * of the visible area of the world. |
| * @param worldY A y-coordinate in world units. |
| * @return A y-coordinate that is guaranteed not to expose the edges of the world. |
| */ |
| public float snapFocalPointToWorldBoundsY(float worldY) { |
| float focalPositionY = worldY; |
| |
| final float height = sSystemRegistry.contextParameters.gameHeight; |
| final LevelSystem level = sSystemRegistry.levelSystem; |
| if (level != null) { |
| final float worldPixelHeight = Math.max(level.getLevelHeight(), sSystemRegistry.contextParameters.gameHeight); |
| final float topEdge = focalPositionY + (height / 2.0f); |
| final float bottomEdge = focalPositionY - (height / 2.0f); |
| |
| if (topEdge > worldPixelHeight) { |
| focalPositionY = worldPixelHeight - (height / 2.0f); |
| } else if (bottomEdge < 0) { |
| focalPositionY = height / 2.0f; |
| } |
| } |
| |
| return focalPositionY; |
| } |
| |
| public void addCameraBias(Vector2 bias) { |
| final float x = bias.x - mFocalPosition.x; |
| final float y = bias.y - mFocalPosition.y; |
| final float biasX = mBias.x; |
| final float biasY = mBias.y; |
| mBias.set(x, y); |
| mBias.normalize(); |
| mBias.add(biasX, biasY); |
| } |
| |
| |
| } |