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 import com.replica.replicaisland.GameObject.ActionType; 20 import com.replica.replicaisland.HotSpotSystem.HotSpotType; 21 22 /** 23 * This component implements the "patrolling" behavior for AI characters. Patrolling characters 24 * will walk forward on the map until they hit a direction hot spot or a wall, in which case they 25 * may change direction. Patrollers can also be configured via this component to attack the player 26 * if appropriate conditions are met. 27 */ 28 public class PatrolComponent extends GameComponent { 29 private float mMaxSpeed; 30 private float mAcceleration; 31 private boolean mAttack; 32 private float mAttackAtDistance; 33 private boolean mAttackStopsMovement; 34 private float mAttackDuration; 35 private float mAttackDelay; 36 private boolean mTurnToFacePlayer; 37 private boolean mFlying; 38 39 private float mLastAttackTime; 40 Vector2 mWorkingVector; 41 Vector2 mWorkingVector2; 42 43 44 public PatrolComponent() { 45 super(); 46 mWorkingVector = new Vector2(); 47 mWorkingVector2 = new Vector2(); 48 49 reset(); 50 setPhase(GameComponent.ComponentPhases.THINK.ordinal()); 51 } 52 53 @Override 54 public void reset() { 55 mTurnToFacePlayer = false; 56 mMaxSpeed = 0.0f; 57 mAcceleration = 0.0f; 58 mAttack = false; 59 mAttackAtDistance = 0.0f; 60 mAttackStopsMovement = false; 61 mAttackDuration = 0.0f; 62 mAttackDelay = 0.0f; 63 mWorkingVector.zero(); 64 mWorkingVector2.zero(); 65 mFlying = false; 66 } 67 68 @Override 69 public void update(float timeDelta, BaseObject parent) { 70 GameObject parentObject = (GameObject) parent; 71 72 if (parentObject.getCurrentAction() == ActionType.INVALID 73 || parentObject.getCurrentAction() == ActionType.HIT_REACT) { 74 parentObject.setCurrentAction(GameObject.ActionType.MOVE); 75 } 76 77 if ((mFlying || parentObject.touchingGround()) && parentObject.life > 0) { 78 GameObjectManager manager = sSystemRegistry.gameObjectManager; 79 GameObject player = null; 80 if (manager != null) { 81 player = manager.getPlayer(); 82 } 83 84 if (mAttack) { 85 updateAttack(player, parentObject); 86 } 87 88 89 if (parentObject.getCurrentAction() == GameObject.ActionType.MOVE 90 && mMaxSpeed > 0.0f) { 91 int hotSpot = HotSpotSystem.HotSpotType.NONE; 92 HotSpotSystem hotSpotSystem = sSystemRegistry.hotSpotSystem; 93 if (hotSpotSystem != null) { 94 // TODO: ack, magic number 95 hotSpot = hotSpotSystem.getHotSpot(parentObject.getCenteredPositionX(), 96 parentObject.getPosition().y + 10.0f); 97 } 98 final float targetVelocityX = parentObject.getTargetVelocity().x; 99 final float targetVelocityY = parentObject.getTargetVelocity().y; 100 101 boolean goLeft = (parentObject.touchingRightWall() 102 || hotSpot == HotSpotType.GO_LEFT) && targetVelocityX >= 0.0f; 103 104 boolean goRight = (parentObject.touchingLeftWall() 105 || hotSpot == HotSpotType.GO_RIGHT) && targetVelocityX <= 0.0f; 106 107 boolean pause = (mMaxSpeed == 0.0f) || hotSpot == HotSpotType.GO_DOWN; 108 109 if (mTurnToFacePlayer && player != null && player.life > 0) { 110 final float horizontalDelta = player.getCenteredPositionX() 111 - parentObject.getCenteredPositionX(); 112 final int targetFacingDirection = Utils.sign(horizontalDelta); 113 final float closestDistance = player.width / 2.0f; 114 115 if (targetFacingDirection < 0.0f) { // we want to turn to the left 116 if (goRight) { 117 goRight = false; 118 pause = true; 119 } else if (targetFacingDirection 120 != Utils.sign(parentObject.facingDirection.x)) { 121 goLeft = true; 122 } 123 } else if (targetFacingDirection > 0.0f) { // we want to turn to the right 124 if (goLeft) { 125 goLeft = false; 126 pause = true; 127 } else if (targetFacingDirection 128 != Utils.sign(parentObject.facingDirection.x)) { 129 goRight = true; 130 } 131 } 132 133 if (Math.abs(horizontalDelta) < closestDistance) { 134 goRight = false; 135 goLeft = false; 136 pause = true; 137 } 138 } 139 140 if (!mFlying) { 141 if (!pause && !goLeft && !goRight && targetVelocityX == 0.0f) { 142 if (parentObject.facingDirection.x < 0.0f) { 143 goLeft = true; 144 } else { 145 goRight = true; 146 } 147 } 148 149 150 if (goRight) { 151 parentObject.getTargetVelocity().x = mMaxSpeed; 152 parentObject.getAcceleration().x = mAcceleration; 153 } else if (goLeft) { 154 parentObject.getTargetVelocity().x = -mMaxSpeed; 155 parentObject.getAcceleration().x = mAcceleration; 156 } else if (pause) { 157 parentObject.getTargetVelocity().x = 0; 158 parentObject.getAcceleration().x = mAcceleration; 159 } 160 } else { 161 final boolean goUp = (parentObject.touchingGround() && targetVelocityY < 0.0f) 162 || hotSpot == HotSpotType.GO_UP; 163 164 final boolean goDown = (parentObject.touchingCeiling() && targetVelocityY > 0.0f) 165 || hotSpot == HotSpotType.GO_DOWN; 166 167 if (goUp) { 168 parentObject.getTargetVelocity().x = 0.0f; 169 parentObject.getTargetVelocity().y = mMaxSpeed; 170 parentObject.getAcceleration().y = mAcceleration; 171 parentObject.getAcceleration().x = mAcceleration; 172 173 } else if (goDown) { 174 parentObject.getTargetVelocity().x = 0.0f; 175 parentObject.getTargetVelocity().y = -mMaxSpeed; 176 parentObject.getAcceleration().y = mAcceleration; 177 parentObject.getAcceleration().x = mAcceleration; 178 179 } else if (goRight) { 180 parentObject.getTargetVelocity().x = mMaxSpeed; 181 parentObject.getAcceleration().x = mAcceleration; 182 parentObject.getAcceleration().y = mAcceleration; 183 parentObject.getTargetVelocity().y = 0.0f; 184 } else if (goLeft) { 185 parentObject.getTargetVelocity().x = -mMaxSpeed; 186 parentObject.getAcceleration().x = mAcceleration; 187 parentObject.getAcceleration().y = mAcceleration; 188 parentObject.getTargetVelocity().y = 0.0f; 189 } 190 } 191 } 192 } else if (!mFlying && !parentObject.touchingGround() && parentObject.life > 0) { 193 // A non-flying unit is in the air. In this case, just watch for bounces off walls. 194 if (Utils.sign(parentObject.getTargetVelocity().x) != Utils.sign(parentObject.getVelocity().x)) { 195 // Todo: maybe the physics code should adjust target velocity instead in this case? 196 parentObject.getTargetVelocity().x *= -1.0f; 197 } 198 } 199 } 200 201 private void updateAttack(GameObject player, GameObject parentObject) { 202 TimeSystem time = sSystemRegistry.timeSystem; 203 final float gameTime = time.getGameTime(); 204 205 boolean visible = true; 206 CameraSystem camera = sSystemRegistry.cameraSystem; 207 ContextParameters context = sSystemRegistry.contextParameters; 208 final float dx = 209 Math.abs(parentObject.getCenteredPositionX() - camera.getFocusPositionX()); 210 final float dy = 211 Math.abs(parentObject.getCenteredPositionY() - camera.getFocusPositionY()); 212 if (dx > context.gameWidth / 2.0f || dy > context.gameHeight / 2.0f) { 213 visible = false; 214 } 215 if (visible && parentObject.getCurrentAction() == GameObject.ActionType.MOVE) { 216 boolean closeEnough = false; 217 boolean timeToAttack = (gameTime - mLastAttackTime) > mAttackDelay; 218 if (mAttackAtDistance > 0 && player != null && player.life > 0 219 && timeToAttack) { 220 // only attack if we are facing the player 221 if (Utils.sign(player.getPosition().x - parentObject.getPosition().x) 222 == Utils.sign(parentObject.facingDirection.x)) { 223 mWorkingVector.set(parentObject.getPosition()); 224 mWorkingVector.x = parentObject.getCenteredPositionX(); 225 mWorkingVector2.set(player.getPosition()); 226 mWorkingVector2.x = player.getCenteredPositionX(); 227 if (mWorkingVector2.distance2(mWorkingVector) < 228 mAttackAtDistance * mAttackAtDistance) { 229 closeEnough = true; 230 } 231 } 232 } else { 233 closeEnough = true; // If no distance has been set, don't worry about 234 // the player's position. 235 } 236 237 if (timeToAttack && closeEnough) { 238 // Time to attack. 239 parentObject.setCurrentAction(GameObject.ActionType.ATTACK); 240 mLastAttackTime = gameTime; 241 if (mAttackStopsMovement) { 242 parentObject.getVelocity().zero(); 243 parentObject.getTargetVelocity().zero(); 244 } 245 } 246 } else if (parentObject.getCurrentAction() == GameObject.ActionType.ATTACK) { 247 if (gameTime - mLastAttackTime > mAttackDuration) { 248 parentObject.setCurrentAction(GameObject.ActionType.MOVE); 249 if (mAttackStopsMovement) { 250 parentObject.getTargetVelocity().x = 251 mMaxSpeed * Utils.sign(parentObject.facingDirection.x); 252 parentObject.getAcceleration().x = mAcceleration; 253 } 254 } 255 } 256 } 257 258 public void setMovementSpeed(float speed, float acceleration) { 259 mMaxSpeed = speed; 260 mAcceleration = acceleration; 261 } 262 263 public void setupAttack(float distance, float duration, float delay, boolean stopMovement) { 264 mAttack = true; 265 mAttackAtDistance = distance; 266 mAttackStopsMovement = stopMovement; 267 mAttackDuration = duration; 268 mAttackDelay = delay; 269 } 270 271 public void setTurnToFacePlayer(boolean turn) { 272 mTurnToFacePlayer = turn; 273 } 274 275 public void setFlying(boolean flying) { 276 mFlying = flying; 277 } 278 } 279