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/** 20 * Manages the position of the camera based on a target game object. 21 */ 22public class CameraSystem extends BaseObject { 23 private GameObject mTarget; 24 private float mShakeTime; 25 private float mShakeMagnitude; 26 private float mShakeOffsetY; 27 private Vector2 mCurrentCameraPosition; 28 private Vector2 mFocalPosition; 29 private Vector2 mPreInterpolateCameraPosition; 30 private Vector2 mTargetPosition; 31 private Vector2 mBias; 32 private float mTargetChangedTime; 33 34 private static final float X_FOLLOW_DISTANCE = 0.0f; 35 private static final float Y_UP_FOLLOW_DISTANCE = 90.0f; 36 private static final float Y_DOWN_FOLLOW_DISTANCE = 0.0f; 37 38 private static final float MAX_INTERPOLATE_TO_TARGET_DISTANCE = 300.0f; 39 private static final float INTERPOLATE_TO_TARGET_TIME = 1.0f; 40 41 private static int SHAKE_FREQUENCY = 40; 42 43 private static float BIAS_SPEED = 400.0f; 44 45 public CameraSystem() { 46 super(); 47 mCurrentCameraPosition = new Vector2(); 48 mFocalPosition = new Vector2(); 49 mPreInterpolateCameraPosition = new Vector2(); 50 mTargetPosition = new Vector2(); 51 mBias = new Vector2(); 52 } 53 54 @Override 55 public void reset() { 56 mTarget = null; 57 mCurrentCameraPosition.zero(); 58 mShakeTime = 0.0f; 59 mShakeMagnitude = 0.0f; 60 mFocalPosition.zero(); 61 mTargetChangedTime = 0.0f; 62 mPreInterpolateCameraPosition.zero(); 63 mTargetPosition.zero(); 64 } 65 66 void setTarget(GameObject target) { 67 if (target != null && mTarget != target) { 68 mPreInterpolateCameraPosition.set(mCurrentCameraPosition); 69 mPreInterpolateCameraPosition.subtract(target.getPosition()); 70 if (mPreInterpolateCameraPosition.length2() < 71 MAX_INTERPOLATE_TO_TARGET_DISTANCE * MAX_INTERPOLATE_TO_TARGET_DISTANCE) { 72 final TimeSystem time = sSystemRegistry.timeSystem; 73 mTargetChangedTime = time.getGameTime(); 74 mPreInterpolateCameraPosition.set(mCurrentCameraPosition); 75 } else { 76 mTargetChangedTime = 0.0f; 77 mCurrentCameraPosition.set(target.getPosition()); 78 } 79 } 80 81 mTarget = target; 82 83 } 84 85 public GameObject getTarget() { 86 return mTarget; 87 } 88 89 void shake(float duration, float magnitude) { 90 mShakeTime = duration; 91 mShakeMagnitude = magnitude; 92 } 93 94 public boolean shaking() { 95 return mShakeTime > 0.0f; 96 } 97 98 @Override 99 public void update(float timeDelta, BaseObject parent) { 100 101 mShakeOffsetY = 0.0f; 102 103 if (mShakeTime > 0.0f) { 104 mShakeTime -= timeDelta; 105 mShakeOffsetY = (float) (Math.sin(mShakeTime * SHAKE_FREQUENCY) * mShakeMagnitude); 106 } 107 108 if (mTarget != null) { 109 mTargetPosition.set(mTarget.getCenteredPositionX(), mTarget.getCenteredPositionY()); 110 final Vector2 targetPosition = mTargetPosition; 111 112 if (mTargetChangedTime > 0.0f) { 113 final TimeSystem time = sSystemRegistry.timeSystem; 114 final float delta = time.getGameTime() - mTargetChangedTime; 115 116 mCurrentCameraPosition.x = Lerp.ease(mPreInterpolateCameraPosition.x, 117 targetPosition.x, INTERPOLATE_TO_TARGET_TIME, delta); 118 119 mCurrentCameraPosition.y = Lerp.ease(mPreInterpolateCameraPosition.y, 120 targetPosition.y, INTERPOLATE_TO_TARGET_TIME, delta); 121 122 if (delta > INTERPOLATE_TO_TARGET_TIME) { 123 mTargetChangedTime = -1; 124 } 125 } else { 126 127 // Only respect the bias if the target is moving. No camera motion without 128 // player input! 129 if (mBias.length2() > 0.0f && mTarget.getVelocity().length2() > 1.0f) { 130 mBias.normalize(); 131 mBias.multiply(BIAS_SPEED * timeDelta); 132 mCurrentCameraPosition.add(mBias); 133 } 134 135 final float xDelta = targetPosition.x - mCurrentCameraPosition.x; 136 if (Math.abs(xDelta) > X_FOLLOW_DISTANCE) { 137 mCurrentCameraPosition.x = targetPosition.x - (X_FOLLOW_DISTANCE * Utils.sign(xDelta)); 138 } 139 140 141 final float yDelta = targetPosition.y - mCurrentCameraPosition.y; 142 if (yDelta > Y_UP_FOLLOW_DISTANCE) { 143 mCurrentCameraPosition.y = targetPosition.y - Y_UP_FOLLOW_DISTANCE; 144 } else if (yDelta < -Y_DOWN_FOLLOW_DISTANCE) { 145 mCurrentCameraPosition.y = targetPosition.y + Y_DOWN_FOLLOW_DISTANCE; 146 } 147 148 } 149 150 mBias.zero(); 151 152 } 153 154 mFocalPosition.x = (float) Math.floor(mCurrentCameraPosition.x); 155 mFocalPosition.x = snapFocalPointToWorldBoundsX(mFocalPosition.x); 156 157 mFocalPosition.y = (float) Math.floor(mCurrentCameraPosition.y + mShakeOffsetY); 158 mFocalPosition.y = snapFocalPointToWorldBoundsY(mFocalPosition.y); 159 } 160 161 /** Returns the x position of the camera's look-at point. */ 162 public float getFocusPositionX() { 163 return mFocalPosition.x; 164 } 165 166 /** Returns the y position of the camera's look-at point. */ 167 public float getFocusPositionY() { 168 return mFocalPosition.y; 169 } 170 171 public boolean pointVisible(Vector2 point, float radius) { 172 boolean visible = false; 173 final float width = sSystemRegistry.contextParameters.gameWidth / 2.0f; 174 final float height = sSystemRegistry.contextParameters.gameHeight / 2.0f; 175 if (Math.abs(mFocalPosition.x - point.x) < (width + radius)) { 176 if (Math.abs(mFocalPosition.y - point.y) < (height + radius)) { 177 visible = true; 178 } 179 } 180 return visible; 181 } 182 183 /** Snaps a coordinate against the bounds of the world so that it may not pass out 184 * of the visible area of the world. 185 * @param worldX An x-coordinate in world units. 186 * @return An x-coordinate that is guaranteed not to expose the edges of the world. 187 */ 188 public float snapFocalPointToWorldBoundsX(float worldX) { 189 float focalPositionX = worldX; 190 final float width = sSystemRegistry.contextParameters.gameWidth; 191 final LevelSystem level = sSystemRegistry.levelSystem; 192 if (level != null) { 193 final float worldPixelWidth = Math.max(level.getLevelWidth(), width); 194 final float rightEdge = focalPositionX + (width / 2.0f); 195 final float leftEdge = focalPositionX - (width / 2.0f); 196 197 if (rightEdge > worldPixelWidth) { 198 focalPositionX = worldPixelWidth - (width / 2.0f); 199 } else if (leftEdge < 0) { 200 focalPositionX = width / 2.0f; 201 } 202 } 203 return focalPositionX; 204 } 205 206 /** Snaps a coordinate against the bounds of the world so that it may not pass out 207 * of the visible area of the world. 208 * @param worldY A y-coordinate in world units. 209 * @return A y-coordinate that is guaranteed not to expose the edges of the world. 210 */ 211 public float snapFocalPointToWorldBoundsY(float worldY) { 212 float focalPositionY = worldY; 213 214 final float height = sSystemRegistry.contextParameters.gameHeight; 215 final LevelSystem level = sSystemRegistry.levelSystem; 216 if (level != null) { 217 final float worldPixelHeight = Math.max(level.getLevelHeight(), sSystemRegistry.contextParameters.gameHeight); 218 final float topEdge = focalPositionY + (height / 2.0f); 219 final float bottomEdge = focalPositionY - (height / 2.0f); 220 221 if (topEdge > worldPixelHeight) { 222 focalPositionY = worldPixelHeight - (height / 2.0f); 223 } else if (bottomEdge < 0) { 224 focalPositionY = height / 2.0f; 225 } 226 } 227 228 return focalPositionY; 229 } 230 231 public void addCameraBias(Vector2 bias) { 232 final float x = bias.x - mFocalPosition.x; 233 final float y = bias.y - mFocalPosition.y; 234 final float biasX = mBias.x; 235 final float biasY = mBias.y; 236 mBias.set(x, y); 237 mBias.normalize(); 238 mBias.add(biasX, biasY); 239 } 240 241 242} 243