/* * 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); } }