3D Computer Game Programming-Note 7
1、智能巡逻兵
- 游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
- 程序设计要求:
- 必须使用订阅与发布模式传消息
- 使用工厂模式生产巡逻兵
【结构设计】
使用订阅与发布模式传消息,即有一个事件发布者来发布不同类型的事件消息,有多个订阅者可以关注该发布者,当事件发布者触发事件通知时,订阅者会第一时间接收到消息,进而触发其他相关事件。
设计程序的 UML 图:

【编程实现】
注:本博客重点讲述动画状态机和订阅/发布模式,代码细节参见🔗github
- GameEventManager.cs: 使用事件——代理机制,
delegate关键字定义了关于得分和游戏结果事件的代理类型,同时申明两个静态变量ScoreChange和GameoverChange,当它们分别被调用时,代理事件会被触发,订阅者从代理事件那里得到消息
public class GameEventManager : MonoBehaviour {public delegate void ScoreEvent();public static event ScoreEvent ScoreChange;public delegate void GameoverEvent();public static event GameoverEvent GameoverChange;public void PlayerEscape() {if (ScoreChange != null){ScoreChange();}}public void PlayerGameover() {if (GameoverChange != null){GameoverChange();}}}
- FirstController.cs:
FirstController为订阅者,加赋值表示订阅,减赋值表示取消订阅,这样,在订阅后FirstController可以随时关注到游戏事件的动态变化,将信息的传递和控制进一步分离出来
public class FirstController : MonoBehaviour, IUserAction, ISceneController {/* ... */void OnEnable() {GameEventManager.ScoreChange += AddScore;GameEventManager.GameoverChange += Gameover;}void OnDisable() {GameEventManager.ScoreChange -= AddScore;GameEventManager.GameoverChange -= Gameover;}void AddScore() {recorder.AddScore();}public void Gameover() {game_over = true;patrol_factory.StopPatrol();action_manager.DestroyAllAction();}}
- PatrolCollider.cs: 通过实例化事件发布类的一个实例发布消息,所有订阅了该事件的订阅者都会接收到这个消息
public class PatrolCollider : MonoBehaviour {/* ... */void OnCollisionEnter(Collision obj) {if (obj.gameObject.tag == "Player") {obj.gameObject.GetComponent<Animator>().SetTrigger("death");this.GetComponent<Animator>().SetTrigger("shoot");Singleton<GameEventManager>.Instance.PlayerGameover();}}}
- GoPatrolAction.cs: 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址,即每次确定下一个目标位置,用自己当前位置为原点计算;当巡逻兵碰撞到障碍物(墙壁时),自动选择下一个点为目标
public class GoPatrolAction : SSAction {private enum Dirction { EAST, NORTH, WEST, SOUTH };/* ... */void Gopatrol() {if (move_sign) {switch (dirction) {case Dirction.EAST:pos_x -= move_length;break;case Dirction.NORTH:pos_z += move_length;break;case Dirction.WEST:pos_x += move_length;break;case Dirction.SOUTH:pos_z -= move_length;break;}move_sign = false;}this.transform.LookAt(new Vector3(pos_x, 0, pos_z));float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));if (distance > 0.9) {transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);}else {dirction = dirction + 1;if (dirction > Dirction.SOUTH){dirction = Dirction.EAST;}move_sign = true;}}}
- PatrolZoneCollider: 巡逻兵在设定范围内感知到玩家时,将玩家设置为追击目标和碰撞检测对象,自动追击玩家
public class PatrolZoneCollider : MonoBehaviour{/* ... */void OnTriggerEnter(Collider collider){if (collider.gameObject.tag == "Player"){this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = true;this.gameObject.transform.parent.GetComponent<PatrolData>().player = collider.gameObject;}}}
- PatrolFollowAction.cs: 实现巡逻兵追击玩家的动作。当失去玩家目标后,继续巡逻
public class PatrolFollowAction : SSAction {private float speed = 2f;private GameObject player;private PatrolData data;public override void Update() {/* ... *///若不存在玩家目标或玩家目标不在巡逻兵感知范围内if (!data.follow_player || data.wall_sign != data.sign){this.destroy = true;this.callback.SSActionEvent(this, 1, this.gameobject);}}/* ... */}
使用素材包 Toony Tiny People,内有多种人物预制动画可选,根据游戏需要选用 idle、run 和 shoot。
为 Patrol 和 Player 制作动画状态机:


对于巡逻兵而言,当感知到玩家时条件 run 为真,巡逻兵从闲散的巡逻状态过渡到奔跑状态,感知消失则回到 Idle 状态;当与玩家碰撞时 shoot 事件被触发,巡逻兵向玩家开枪,游戏结束。


对于玩家而言,当接收到键盘方向键输入时条件 run 为真,玩家从闲散状态过渡到奔跑运动状态,无输入时则回到 Idle 状态;当与巡逻兵碰撞时 death 事件被触发,玩家倒地不能移动,游戏结束。
【游戏效果】



