Intelligent patrol
- Submission requirements:
- Game design requirements:
- Create a map and several patrols (using animation);
- Each patrol takes a convex polygon with 3 ~ 5 sides, and the position data is the relative address. That is, determine the next target position each time and calculate with your current position as the origin;
- If the patrol collides with an obstacle, it will automatically select the next point as the target;
- The patrolman will automatically chase the player if he senses the player within the set range;
- After losing the player's target, continue patrolling;
- Rule: players will win if they eat all gold coins, and fail if they collide with patrol soldiers.
- Program design requirements:
- You must use subscription and publish mode to deliver messages
- subject: OnLostGoal
- Publisher: GameEventManager
- Subscriber: SceneController
- Factory mode production patrol
- You must use subscription and publish mode to deliver messages
Game implementation
Creation of game map
Build a 3 * 2 maze map
public void LoadResources() { Instantiate(Resources.Load("Prefabs/Light")); Instantiate(Resources.Load("Prefabs/Entry")); blood = Instantiate(Resources.Load("Prefabs/Blood"), new Vector3(40, 40, 40), Quaternion.identity) as GameObject; role = Instantiate(Resources.Load("Prefabs/role")) as GameObject; float pos_z = 0, pos_x; int dir, sum = 0, size = 5; GameObject maze, temp; guardFactory gf = guardFactory.getInstance(); for (int i = 0; i < size; i++) { dir = Random.Range(0, 2); pos_x = i * 2.8f; for (int j = 0; j <= i; j++, sum++) { if (sum%3 == 0) maze = Instantiate(Resources.Load("Prefabs/g_maze"), new Vector3(pos_x, 0, pos_z), Quaternion.identity) as GameObject; else if (sum%3 == 1) maze = Instantiate(Resources.Load("Prefabs/y_maze"), new Vector3(pos_x, 0, pos_z), Quaternion.identity) as GameObject; else maze = Instantiate(Resources.Load("Prefabs/b_maze"), new Vector3(pos_x, 0, pos_z), Quaternion.identity) as GameObject; temp = gf.getNewGuard(pos_x - 0.9f, 0, pos_z - 0.9f); temp.transform.parent = maze.transform; if (j != i) if (dir == 0) pos_z += 2.8f; else pos_z -= 2.8f; } } }
Player action realization
The player's actions include forward, backward and left and right turns through the keyboard ⬆⬇➡⬅ To achieve. Monitor keyboard input and call relevant functions to complete the execution of relevant actions according to the input.
void Update() { float transitionX = Input.GetAxis("Horizontal"); float transitionZ = Input.GetAxis("Vertical"); //Mobile player action.MovePlayer(transitionX, transitionZ); timeCounter = action.GetTime(); } // SceneController.cs public void MovePlayer(float x, float z) { if (!gameOver) { //Move and rotate player.transform.Translate(0, 0, z * playerSpeed * Time.deltaTime); player.transform.Rotate(0, x * 135f * Time.deltaTime, 0); //Prevent movement caused by collision if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0) { player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0); } if (player.transform.position.y != 0.5f) { player.transform.position = new Vector3(player.transform.position.x, 0.5f, player.transform.position.z); } } }
patrol
The patrolman has two movement modes: Patrol mode and tracking mode. When the player's area is not under the jurisdiction of the patrolman, continue patrolling, otherwise switch to tracking mode. When patrolling, each patrolman keeps repeating the movement according to a simple line, and changes the direction of the patrolman every time he reaches the specified vertex. The factory mode is used for the creation of patrol soldiers. The factory is responsible for generating a certain number of patrol soldiers at predetermined positions, and assigning a certain jurisdiction to each patrol soldier.
public class PatrolFactory : MonoBehaviour { private GameObject patrolPrefab = null; private Vector3[] position = new Vector3[6]; //Record the position of the patrol private List<GameObject> patrols = new List<GameObject>(); public List<GameObject> GetPatrols() { int[] pos_x = { -10, 3, 18, -11, 3, 15 }; int[] pos_z = { -18, -15, -15, 12, 10, 13 }; for(int i = 0; i < 6; i++) { position[i] = new Vector3(pos_x[i], 0, pos_z[i]); patrolPrefab = Object.Instantiate(Resources.Load<GameObject>("Prefabs/patrol2"), position[i], Quaternion.identity); patrolPrefab.name = "patrol" + i; patrolPrefab.AddComponent<PatrolData>(); patrolPrefab.GetComponent<PatrolData>().manageFloor = i + 1; patrolPrefab.GetComponent<PatrolData>().initPosition = position[i]; patrols.Add(patrolPrefab); } return patrols; } public void Reset() { for(int i = 0; i < patrols.Count; i++) { patrols[i].transform.position = position[i]; patrols[i].GetComponent<Animator>().SetBool("shoot", false); } } }
Patrol action:
public class PatrolMoveAction : SSAction { private float posX, posZ; private float rectLength; // Side length private enum Dirction { EAST, NORTH, WEST, SOUTH }; // Four directions of patrol private float speed = 2f; // Patrol speed private bool reach = true; // Whether to reach the destination private Dirction dirction = Dirction.EAST; // Direction of movement private PatrolData data; // Patrol data public static PatrolMoveAction GetSSAction(Vector3 location) { PatrolMoveAction action = CreateInstance<PatrolMoveAction>(); action.posX = location.x; action.posZ = location.z; action.rectLength = Random.Range(5, 8); return action; } public override void Start() { data = this.gameObject.GetComponent<PatrolData>(); } public override void Update() { //Prevent rotation after collision if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0) { transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0); } if (transform.position.y != 0.5f) { transform.position = new Vector3(transform.position.x, 0.5f, transform.position.z); } // move Move(); // If the room is the same, destroy the current action and call back if (data.manageFloor == data.plyerFloor) { this.destory = true; this.callback.SSActionEvent(this, SSActionEventType.Compeleted, 0 ,"follow player", this.gameObject); } } public void Move() { if (reach) { // If you have arrived, change direction switch (dirction) { case Dirction.EAST: posX -= rectLength; break; case Dirction.NORTH: posZ += rectLength; break; case Dirction.WEST: posX += rectLength; break; case Dirction.SOUTH: posZ -= rectLength; break; } reach = false; } //Face the destination this.transform.LookAt(new Vector3(posX, 0.5f, posZ)); //Calculate distance float distance = Vector3.Distance(transform.position, new Vector3(posX, 0.5f, posZ)); if (distance > 1) { transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(posX, 0.5f, posZ), speed * Time.deltaTime); } else { dirction = dirction + 1; if (dirction > Dirction.SOUTH) { dirction = Dirction.EAST; } reach = true; } } }
public class PatrolFollowAction : SSAction { private GameObject player; // When you create an action, you pass in a player object to track the player private float speed = 3f; // Track player speed private PatrolData data; // Patrol data public static PatrolFollowAction GetSSAction(GameObject player) { PatrolFollowAction action = CreateInstance<PatrolFollowAction>(); action.player = player; return action; } public override void Start() { data = this.gameObject.GetComponent<PatrolData>(); } public override void Update() { if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0) { transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0); } if (transform.position.y != 0.5f) { transform.position = new Vector3(transform.position.x, 0.5f, transform.position.z); } transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime); //Face the player when tracking this.transform.LookAt(player.transform.position); //Lost target, stop tracking //If the Scout does not follow the target, or the player who needs to follow is not in the Scout's area if (data.manageFloor != data.plyerFloor) { this.destory = true; this.callback.SSActionEvent(this, SSActionEventType.Compeleted, 1, "stop follow", this.gameObject); } } }
Patrol action manager
The action manager is mainly responsible for letting the patrolmen start patrolling at the beginning of the game, and handling different situations through the callback function: switching between patrol action and tracking action according to different callback parameters.
public class PatrolActionManager : SSActionManager, ISSActionCallback { //Patrol action private PatrolMoveAction move; private SceneController sceneController; protected new void Start() { sceneController = SSDirector.GetInstance().CurrentSceneController as SceneController; } public void PatrolMove(GameObject patrol) { move = PatrolMoveAction.GetSSAction(patrol.transform.position); this.RunAction(patrol, move, this); } #region ISSActionCallback implementation public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Compeleted, int intParam = 0, string strParam = null, GameObject objectParam = null) { //Callback function, which is called after action execution. if (intParam == 0) { //Start following players PatrolFollowAction follow = PatrolFollowAction.GetSSAction(sceneController.player); this.RunAction(objectParam, follow, this); } else { //Lost target, continue patrolling PatrolMoveAction move = PatrolMoveAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().initPosition); this.RunAction(objectParam, move, this); //Player escape Singleton<GameEventManager>.Instance.PlayerEscape(); } } #endregion }
Collision and update
In order to detect the player's position so that the patrol can start the tracking mode, the player's position should be marked through the collision between the player and the ground of different maze rooms, so as to continuously update the player's position,
public class AreaCollide : MonoBehaviour { public int sign = 0; SceneController sceneController; private void Start() { sceneController = SSDirector.GetInstance().CurrentSceneController as SceneController; } void OnTriggerEnter(Collider collider) { //Mark players to enter their own area if (collider.gameObject.name == "player") { Debug.Log("player enter floor " + sign); sceneController.floorNumber = sign; } } }
Players eat gold coins
After the player eats the gold coin, set the gold coin invisible and release the message of gold coin reduction.
public class CoinCollide : MonoBehaviour { void OnTriggerEnter(Collider collider) { //Triggered when the player eats gold coins if (collider.gameObject.name == "player") { this.gameObject.SetActive(false); Singleton<GameEventManager>.Instance.RecudeCoinNum(); } } }
Player arrested
When a player collides with a patrol, it is marked as that the player is arrested and the message is released.
public class PatrolCollide : MonoBehaviour { void OnCollisionStay(Collision other) { //When scouts collide with players if (other.gameObject.name == "player") { other.gameObject.GetComponent<Animator>().SetBool("death", true); this.GetComponent<Animator>().SetBool("shoot", true); Singleton<GameEventManager>.Instance.PlayerArrested(); } } }
Subscription and publication mode
Publisher:
Publishers will post three types of messages: coins are eaten by players, the game is over, and scores
public class GameEventManager : MonoBehaviour { public delegate void ScoreEvent(); public static event ScoreEvent ScoreChange; public delegate void GameOverEvent(); public static event GameOverEvent GameOver; public delegate void CoinEvent(); public static event CoinEvent CoinNumberChange; //Player escape public void PlayerEscape() { if (ScoreChange != null) { ScoreChange(); } } //Player arrested public void PlayerArrested() { if (GameOver != null) { GameOver(); } } public void RecudeCoinNum() { if(CoinNumberChange != null) { CoinNumberChange(); } } public void TimeOut() { if (GameOver != null) { GameOver(); } } }
Subscriber: the subscriber is the scene controller
void OnEnable() { //Registration event GameEventManager.ScoreChange += AddScore; GameEventManager.GameOver += GameOver; GameEventManager.CoinNumberChange += ReduceCoinNumber; } void OnDisable() { //Unregister event GameEventManager.ScoreChange -= AddScore; GameEventManager.GameOver -= GameOver; GameEventManager.CoinNumberChange -= ReduceCoinNumber; } void AddScore() { scoreRecorder.AddScore(); } void GameOver() { gameOver = true; actionManager.DestroyAll(); } void ReduceCoinNumber() { coinNumberGet += 1; }