智能巡逻兵
-
游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
-
程序设计要求:
-
必须使用订阅与发布模式传消息
-
工厂模式生产巡逻兵
-
实验内容:
-
订阅与发布模式,图示如下:
-
在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
-
在本次作业中,玩家作为消息发布者,将自己的相关信息(如:生存状态,位置,所在区域)发送给订阅者,订阅者根据自己的需求,从所有信息中得到自己想要的部分(如:场景控制器判断游戏是否结束,巡逻兵判断是否在自己所在区域,是否追击),并根据信息判断决定下一步动作。
游戏设计过程中,订阅者有巡逻兵:根据玩家所在区域判断是否追击、根据玩家状态判断是否追击、根据玩家位置判断追击方向;场景控制器:根据玩家状态判断游戏是否结束;记分员:根据玩家所在区域判断是否得分。
-
-
工厂模式:
与之前的打飞碟游戏的工厂模式生成飞碟类似,不再做过多解释。 -
主要代码实现如下:
-
巡逻兵控制器PatrolController.cs:
实现巡逻兵对Hero的感知和追踪、在游戏场景中碰到障碍物墙壁改变方向以及追捕到Hero时判定游戏结束。
检测巡逻兵所处的位置变量的函数值,若与Hero相同,则触发追赶玩家的条件,巡逻兵追击Hero;对于直接接触的碰撞,游戏结束。代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PatrolController : MonoBehaviour { public SceneController sceneController; private Animator animator; void Start() { animator = GetComponent<Animator> (); sceneController = (SceneController)SSDirector.getInstance ().currentSceneController; } void FixedUpdate() { if (sceneController.hero.GetComponent<MoveData> ().planeNum == GetComponent<MoveData> ().planeNum && !sceneController.gameOver) { sceneController.actionManager.CatchRunAction (gameObject, sceneController.hero); } else { if (!shouldChangeState ()) sceneController.actionManager.MoveRunAction (gameObject, gameObject.GetComponent<MoveData> ().state); else { sceneController.actionManager.MoveRunAction (gameObject, (gameObject.GetComponent<MoveData> ().state + 3) % 4); } } animator.SetBool ("run", true); } bool shouldChangeState() { if (gameObject.transform.localPosition.x >= 4) { gameObject.transform.localPosition = new Vector3 (3.9f, gameObject.transform.localPosition.y, gameObject.transform.localPosition.z); return true; } else if (gameObject.transform.localPosition.x <= -4) { gameObject.transform.localPosition = new Vector3 (-3.9f, gameObject.transform.localPosition.y, gameObject.transform.localPosition.z); return true; } else if (gameObject.transform.localPosition.z >= 4) { gameObject.transform.localPosition = new Vector3 (gameObject.transform.localPosition.x, gameObject.transform.localPosition.y, 3.9f); return true; } else if (gameObject.transform.localPosition.z <= -4) { gameObject.transform.localPosition = new Vector3 (gameObject.transform.localPosition.x, gameObject.transform.localPosition.y, -3.9f); return true; } return false; } void OnCollisionStay(Collision e) { if (e.gameObject.Equals (sceneController.hero)) { sceneController.GameOver (); } } }
-
玩家控制器HeroController.cs:
实现W、A、S、D键对玩家移动的控制,并使用scoreKeep作为发布者,实现信息的发送和传递,代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class HeroController : MonoBehaviour{ //发布与订阅模式 public delegate void ScoreKeep(); public static event ScoreKeep scoreKeep; public SceneController sceneController; private Animator animator; void Start () { animator = GetComponent<Animator> (); sceneController = (SceneController)SSDirector.getInstance ().currentSceneController; } void FixedUpdate() { if (scoreKeep != null) { scoreKeep (); //计分 // 获取控制的方向 float KeyVertical = Input.GetAxis ("Vertical"); float KeyHorizontal = Input.GetAxis ("Horizontal"); if (KeyVertical == -1) { sceneController.actionManager.MoveRunAction (gameObject, 2); // 下 } else if (KeyVertical == 1) { sceneController.actionManager.MoveRunAction (gameObject, 0); // 上 } if (KeyHorizontal == 1) { sceneController.actionManager.MoveRunAction (gameObject, 1); // 右 } else if (KeyHorizontal == -1) { sceneController.actionManager.MoveRunAction (gameObject, 3); // 左 } if (KeyVertical == 0 && KeyHorizontal == 0) { animator.SetBool ("run", false); } else { animator.SetBool ("run", true); } } } }
-
Scorekeeper.cs:
作为发布者,当玩家得分时,实现信息向调度中心的传递,代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Scorekeeper { public SceneController sceneController { get; set; } private static Scorekeeper _instance; public static Scorekeeper getInstance(){ if (_instance == null) _instance = new Scorekeeper(); return _instance; } public int score = 0; public void reset(){ score = 0; } //更改plane加分 public void judge() { GameObject[] plane = sceneController.plane; for (int i = 0; i < plane.Length; ++i) { if (Vector3.Distance (plane [i].transform.position, sceneController.hero.transform.position) <= 2.8) { if (i != sceneController.hero.GetComponent<MoveData> ().planeNum) { sceneController.hero.GetComponent<MoveData> ().planeNum = i; ++score; Debug.Log (score); } return; } } //处于交界 sceneController.hero.GetComponent<MoveData>().planeNum = -1; } }
-
CameraFlow.cs:
实现玩家的游戏视角在Hero身上的绑定,提升游戏的交互体验,代码如下:
using UnityEngine; using System.Collections; public class CameraFlow : MonoBehaviour { public Transform target; private Vector3 offset; private Vector3 pos; public SceneController sceneController; public float speed = 2; void Start() { offset = target.position - this.transform.position; sceneController = (SceneController)SSDirector.getInstance ().currentSceneController; } void FixedUpdate() { if (sceneController.isGameOver ()) { pos = target.position + new Vector3(0,20,0); this.transform.position = Vector3.Lerp (this.transform.position, pos, speed * Time.deltaTime);//调整相机与玩家之间的距离 Quaternion angel = Quaternion.LookRotation (target.position - this.transform.position);//获取旋转角度 this.transform.rotation = Quaternion.Slerp (this.transform.rotation, angel, speed * Time.deltaTime); } else this.transform.position = target.position - offset; } }
-
PatrolFactory.cs:
以工厂的模式产生巡逻兵,代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PatrolFactory : System.Object { private static PatrolFactory _instance; public SceneController sceneController { get; set; } public List<GameObject> used; public List<GameObject> free; public static PatrolFactory getInstance(){ if (_instance == null) { _instance = new PatrolFactory(); _instance.used = new List<GameObject>(); _instance.free = new List<GameObject>(); } return _instance; } public GameObject getPatrol() { GameObject newPatrol; if (free.Count == 0) newPatrol = GameObject.Instantiate(Resources.Load("prefabs/Patrol")) as GameObject; else { newPatrol = free[0]; free.Remove(free[0]); } newPatrol.SetActive(true); used.Add(newPatrol); if (!newPatrol.GetComponent<Rigidbody> ()) { newPatrol.AddComponent<Rigidbody> (); } return newPatrol; } public void freePatrol(GameObject g) { for (int i = 0; i < used.Count; i++) { if (used[i] == g) { used.Remove(g); g.SetActive(false); free.Add(g); } } } public void hideAll() { for (int i = 0; i < used.Count; i++) used [i].SetActive (false); for (int i = 0; i < free.Count; i++) free [i].SetActive (false); } }
-
场景管理和动作管理的实现在前几次作业中已做过详细分析和说明,所以本次实验不再赘述。
-
-
运行效果截图: