一、 项目配置
-
首先创建一个新项目,选择3D模板
-
将Asset文件替换原有的Asset文件,作业场景在mySence中
-
此次作业人物模型使用官网上的3DCharacterDummy资源包,人物动作使用官网上的Standard Assets资源包
-
在摄像机上挂载以下脚本
-
玩家角色添加刚体和碰撞器属性
-
巡逻兵模型
-
组件即需要挂载的脚本
-
巡逻兵模型还需要额外多一个碰撞器,表示其巡逻范围,称之为检测碰撞器
-
-
地图模型
-
出口配置
-
区域检测配置
-
-
Animator
-
PlayerController
-
PatrolController
-
二、 实现过程和方法
1. 总体设计思路
此次作业要实现玩家摆脱巡逻兵的追捕,游戏规则为:
玩家通过方向键控制游戏角色的移动,地图分为九个区域,每个区域中有一个巡逻兵在巡逻,当玩家进入巡逻兵的巡逻范围时巡逻兵会追捕玩家,若玩家被追捕到则游戏失败,玩家逃出巡逻兵的巡逻范围或逃出对应的巡逻区域时即可摆脱巡逻兵的追捕。玩家每逃脱一个区域时分数会加一,地图中有一个出口,当玩家到达出口时游戏胜利。
涉及到的知识包括:
-
通过键盘控制玩家的移动以及玩家动画的变化
-
通过Animator定义游戏角色——玩家和巡逻兵的动作
-
自定义地图、玩家、巡逻兵预制,在他们之上添加碰撞器组件
-
碰撞器检测碰撞后通过订阅与发布模式传递消息产生对应的响应,如玩家进入巡逻兵巡逻范围时巡逻兵由巡逻状态改为追捕状态,玩家抵达出口时游戏胜利,玩家触碰巡逻兵时游戏失败。
游戏代码主要可以分为:
-
巡逻兵模块
-
玩家模块
-
碰撞检测模块
-
分数记录模块
-
其它模块
2. 模块分析
首先,在Interface.cs中定义了需要实现的接口
using System.Collections; using System.Collections.Generic; using UnityEngine; public interface ISceneController { //加载场景资源 void LoadResources(); } public interface IUserAction { //获取当前分数 int GetScore(); //得到游戏结束标志 bool GetGameover(); //重新开始 void Restart(); //获取游戏胜利标志 bool GetIsWin(); } public interface ISSActionCallback { void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null); }
-
ISceneController接口是场景控制器接口
-
IUserAction接口是响应玩家动作的接口
-
ISSActionCallback接口是动作回调接口
巡逻兵部分
-
PatrolData
-
首先通过一个PatrolData类存放巡逻兵的一些基本属性,因此该脚本需要挂载在巡逻兵的预制上
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PatrolData : MonoBehaviour { public int sign; //标志巡逻兵在哪一块区域 public bool follow_player = false; //是否跟随玩家 public int wall_sign = -1; //当前玩家所在区域标志 public GameObject player; //玩家游戏对象 public Vector3 start_position; //当前巡逻兵初始位置 }
巡逻兵有两个动作——巡逻动作和追捕动作,可以利用之前作业用到的动作框架来进行实现
-
-
SSAction
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSAction : ScriptableObject { public bool enable = true; //是否正在进行此动作 public bool destroy = false; //是否需要被销毁 public GameObject gameobject; //动作对象 public Transform transform; //动作对象的transform public ISSActionCallback callback; //动作完成后的消息通知者 protected SSAction() { } //子类可以使用下面这两个函数 public virtual void Start() { throw new System.NotImplementedException(); } public virtual void Update() { throw new System.NotImplementedException(); } }
-
SSActionManager
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSActionManager : MonoBehaviour, ISSActionCallback { private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //将执行的动作的字典集合 private List<SSAction> waitingAdd = new List<SSAction>(); //等待去执行的动作列表 private List<int> waitingDelete = new List<int>(); //等待删除的动作的key protected void Update() { foreach (SSAction ac in waitingAdd) { actions[ac.GetInstanceID()] = ac; } waitingAdd.Clear(); foreach (KeyValuePair<int, SSAction> kv in actions) { SSAction ac = kv.Value; if (ac.destroy) { waitingDelete.Add(ac.GetInstanceID()); } else if (ac.enable) { //运动学运动更新 ac.Update(); } } foreach (int key in waitingDelete) { SSAction ac = actions[key]; actions.Remove(key); DestroyObject(ac); } waitingDelete.Clear(); } public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) { action.gameobject = gameobject; action.transform = gameobject.transform; action.callback = manager; waitingAdd.Add(action); action.Start(); } public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null) { if (intParam == 0) { //侦查兵跟随玩家 PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player); this.RunAction(objectParam, follow, this); } else { //侦察兵按照初始位置开始继续巡逻 PatrolWalkAction move = PatrolWalkAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position); this.RunAction(objectParam, move, this); } } public void DestroyAll() { foreach (KeyValuePair<int, SSAction> kv in actions) { SSAction ac = kv.Value; ac.destroy = true; } } }
SSActionManager通过实现ISSActionCallback接口,在SSActionEvent函数中定义两个动作结束对应的回调函数——巡逻动作结束时便开始追捕玩家,追捕动作结束时又回到巡逻动作。
-
PatrolActionManager
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PatrolActionManager : SSActionManager { private PatrolWalkAction go_patrol; //巡逻兵巡逻 public void GoPatrol(GameObject patrol) { go_patrol = PatrolWalkAction.GetSSAction(patrol.transform.position); this.RunAction(patrol, go_patrol, this); } //停止所有动作 public void DestroyAllAction() { DestroyAll(); } }
巡逻兵的动作管理器,负责在初始化巡逻兵之后让他们执行巡逻动作。
-
PatrolWalkAction
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PatrolWalkAction : SSAction { private enum Dirction { EAST, NORTH, WEST, SOUTH }; private float pos_x, pos_z; //移动前的初始x和z方向坐标 private float move_length; //移动的长度 private float move_speed = 1.2f; //移动速度 private bool move_sign = true; //是否到达目的地 private Dirction dirction = Dirction.EAST; //移动的方向 private PatrolData data; //侦察兵的数据 private PatrolWalkAction() { } public static PatrolWalkAction GetSSAction(Vector3 location) { PatrolWalkAction action = CreateInstance<PatrolWalkAction>(); action.pos_x = location.x; action.pos_z = location.z; //设定移动矩形的边长 action.move_length = Random.Range(4, 7); return action; } 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) { transform.position = new Vector3(transform.position.x, 0, transform.position.z); } //侦察移动 Gopatrol(); //如果侦察兵需要跟随玩家并且玩家就在侦察兵所在的区域,侦查动作结束 if (data.follow_player && data.wall_sign == data.sign) { this.destroy = true; this.callback.SSActionEvent(this, 0, this.gameobject); } } public override void Start() { this.gameobject.GetComponent<Animator>().SetBool("walk", true); data = this.gameobject.GetComponent<PatrolData>(); } 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; } } }
这是巡逻兵的巡逻动作,每个巡逻兵以一个随机长度边长的矩形作为其巡逻的路径,通过在Update()函数中不断调用Vector3.MoveTowards()函数使巡逻兵进行移动,再通过transform.LookAt()控制转向。
-
PatrolFollowAction
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PatrolFollowAction : SSAction { private float speed = 0.2f; //跟随玩家的速度 private GameObject player; //玩家 private PatrolData data; //侦查兵数据 private PatrolFollowAction() { } public static PatrolFollowAction GetSSAction(GameObject player) { PatrolFollowAction action = CreateInstance<PatrolFollowAction>(); action.player = player; return action; } 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) { transform.position = new Vector3(transform.position.x, 0, transform.position.z); } Follow(); //如果侦察兵没有跟随对象,或者需要跟随的玩家不在侦查兵的区域内 if (!data.follow_player || data.wall_sign != data.sign) { this.gameobject.GetComponent<Animator>().SetBool("run", false); this.destroy = true; this.callback.SSActionEvent(this, 1, this.gameobject); } } public override void Start() { this.gameobject.GetComponent<Animator>().SetBool("run", true); data = this.gameobject.GetComponent<PatrolData>(); } void Follow() { transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime); this.transform.LookAt(player.transform.position); } }
这是巡逻兵的追捕动作,与巡逻动作主要的不同在于Update()每次调用Vector3.MoveTowards()函数时目标地点为玩家的位置,由此能够跟随玩家的移动,从而实现追捕玩家。
-
PropFactory
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PropFactory : MonoBehaviour { private GameObject patrol = null; //巡逻兵 private List<GameObject> used = new List<GameObject>(); //正在被使用的巡逻兵 private Vector3[] vec = new Vector3[9]; //保存每个巡逻兵的初始位置 public FirstSceneController sceneControler; //场景控制器 public List<GameObject> GetPatrols() { int[] pos_x = { -6, 4, 13 }; int[] pos_z = { -4, 6, -13 }; int index = 0; //生成不同的巡逻兵初始位置 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { vec[index] = new Vector3(pos_x[i], 0, pos_z[j]); index++; } } for (int i = 0; i < 9; i++) { patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol")); patrol.transform.position = vec[i]; patrol.GetComponent<PatrolData>().sign = i + 1; patrol.GetComponent<PatrolData>().start_position = vec[i]; used.Add(patrol); } return used; } public void StopPatrol() { //切换所有侦查兵的动画 for (int i = 0; i < used.Count; i++) { used[i].gameObject.GetComponent<Animator>().SetBool("run", false); used[i].gameObject.GetComponent<Animator>().SetBool("walk", false); } } }
此部分体现了工厂模式的使用,负责巡逻兵的生成,加载预制在特定位置生成九个巡逻兵
玩家部分
玩家部分是通过UserGUI接收用户的输入,然后将输入传到场景控制器,由场景控制器调用专门负责玩家动作控制的类对象来实现控制。
-
PlayerActionController
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerActionController : MonoBehaviour { public FirstSceneController sceneController; public float player_speed = 2f; //玩家移动速度 public float rotate_speed = 135f; //玩家旋转速度 private GameObject player; void Start() { sceneController = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController; player = sceneController.player; } public void MovePlayer(float translationX, float translationZ) { player.transform.Translate(0, 0, translationZ * player_speed * Time.deltaTime); player.transform.Rotate(0, translationX * rotate_speed * Time.deltaTime, 0); //防止碰撞带来的移动 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) { player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z); } } public void PlayerRun() { player.GetComponent<Animator>().SetBool("run", true); } public void PlayerStopRun() { player.GetComponent<Animator>().SetBool("run", false); } public void PlayerBackward() { player.GetComponent<Animator>().SetBool("walk", true); } public void PlayerJumpBack() { player.GetComponent<Animator>().SetTrigger("jump"); } public void PlayerTurnLeft() { player.GetComponent<Animator>().SetTrigger("left"); } public void PlayerTurnRight() { player.GetComponent<Animator>().SetTrigger("right"); } }
-
UserGUI
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UserGUI : MonoBehaviour { private FirstSceneController sceneController; private GUIStyle score_style = new GUIStyle(); private GUIStyle text_style = new GUIStyle(); private GUIStyle over_style = new GUIStyle(); void Start() { sceneController = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController; text_style.fontSize = 16; score_style.fontSize = 16; over_style.fontSize = 25; } void Update() { //获取方向键的偏移量 float translationX = Input.GetAxis("Horizontal"); float translationZ = Input.GetAxis("Vertical"); //移动玩家 sceneController.player_action_controller.MovePlayer(translationX, translationZ); if (Input.GetKeyDown(KeyCode.W)) { sceneController.player_action_controller.PlayerRun(); } if (Input.GetKeyUp(KeyCode.W)) { sceneController.player_action_controller.PlayerStopRun(); } if (Input.GetKeyDown(KeyCode.S)) { sceneController.player_action_controller.PlayerJumpBack(); } if (Input.GetKeyDown(KeyCode.A)) { sceneController.player_action_controller.PlayerTurnLeft(); } if (Input.GetKeyDown(KeyCode.D)) { sceneController.player_action_controller.PlayerTurnRight(); } } private void OnGUI() { GUI.Label(new Rect(10, 5, 200, 50), "分数:", text_style); GUI.Label(new Rect(55, 5, 200, 50), sceneController.GetScore().ToString(), score_style); GUI.Label(new Rect(Screen.width / 2 - 120, 10, 100, 100), "按住w键向前移动,按A或D键进行转向,按S键后跳", text_style); GUI.Label(new Rect(Screen.width / 2 - 120, 50, 100, 100), "快寻找出口,逃出生天!", text_style); if (sceneController.GetGameover() && sceneController.GetIsWin()) { GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "游戏胜利!", over_style); if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "重新开始")) { sceneController.Restart(); } } else if (sceneController.GetGameover()) { GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "游戏失败!", over_style); if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "重新开始")) { sceneController.Restart(); } } } }
碰撞器部分
-
AreaCollide
using System.Collections; using System.Collections.Generic; using UnityEngine; public class AreaCollide : MonoBehaviour { public int sign = 0; //区域标号 FirstSceneController sceneController; private void Start() { sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController; } void OnTriggerEnter(Collider collider) { //玩家进入自己的区域 if (collider.gameObject.tag == "Player") { sceneController.wall_sign = sign; } } void OnTriggerExit(Collider collider) { //玩家逃出自己的区域 if (collider.gameObject.tag == "Player") { Singleton<GameEventManager>.Instance.PlayerEscape(); } } }
-
挂载在每个区域的碰撞器脚本,负责检测玩家角色,sign表示所挂载的碰撞器对应区域标号,因此在为每个区域的碰撞器挂载此脚本时需要同时设置sign值
-
当玩家角色进入某个区域时,会更新玩家当前所在位置
-
当玩家角色逃出某个区域时,会通过订阅-发布模式通知场记,进行得分的更新。
-
-
PatrolCollide
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PatrolCollide : 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; } } void OnTriggerExit(Collider collider) { //玩家逃出侦察兵追捕范围 if (collider.gameObject.tag == "Player") { this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = false; this.gameObject.transform.parent.GetComponent<PatrolData>().player = null; } } }
挂载在巡逻兵上的脚本,当巡逻兵的检测碰撞器组件发生碰撞时触发
-
PlayerCollide
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerCollide : MonoBehaviour { void OnCollisionEnter(Collision other) { //当玩家与巡逻兵相撞 if (other.gameObject.tag == "Player") { Singleton<GameEventManager>.Instance.PlayerGameover(); } } }
挂载在玩家角色上的脚本,当玩家角色的碰撞器组件发生碰撞时触发,当触碰到巡逻兵时,通过订阅-发布模式通知场记游戏结束。
-
DoorCollide
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DoorCollide : MonoBehaviour { void OnTriggerEnter(Collider collider) { if (collider.gameObject.tag == "Player") { Singleton<GameEventManager>.Instance.PlayerGameover(); Singleton<GameEventManager>.Instance.PlayerWin(); } } }
挂载在出口上的脚本,当出口的碰撞器组件发生碰撞时触发,当触碰到玩家角色时,通过订阅-发布模式通知场记游戏结束。
其它部分
-
CameraFollow
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraFollow : MonoBehaviour { public GameObject follow; //跟随的物体 public float smothing = 5f; //相机跟随的速度 Vector3 offset; //相机与物体相对偏移位置 void Start() { offset = transform.position - follow.transform.position; } void FixedUpdate() { Vector3 target = follow.transform.position + offset; //摄像机自身位置到目标位置平滑过渡 transform.position = Vector3.Lerp(transform.position, target, smothing * Time.deltaTime); transform.LookAt(follow.transform); } }
挂载在摄像机上,使得摄像机可以跟随玩家角色的移动而移动,其实现的原理是在一开始先计算摄像机初始位置和玩家角色初始位置之间的偏移,在往后的更新中会保持摄像机与玩家角色之间的偏移位置不变,由此实现摄像机可以跟随玩家角色的移动而移动。再通过LookAt()函数使得摄像机始终看向玩家角色
-
Singleton
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Singleton<T> : MonoBehaviour where T : MonoBehaviour { protected static T instance; public static T Instance { get { if (instance == null) { instance = (T)FindObjectOfType(typeof(T)); if (instance == null) { Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none."); } } return instance; } } }
单例模式的模板类,用于构造单例对象
-
SSDirector
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSDirector : System.Object { private static SSDirector _instance; //导演类的实例 public ISceneController CurrentScenceController { get; set; } public static SSDirector GetInstance() { if (_instance == null) { _instance = new SSDirector(); } return _instance; } }
导演类的实现与之前一样
-
FirstSceneController
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController { public PropFactory patrol_factory; //巡逻者工厂 public ScoreRecorder recorder; //分数记录器 public PatrolActionManager action_manager; //运动管理器 public PlayerActionController player_action_controller; public int wall_sign = -1; //当前玩家所处哪个格子 public GameObject player; //玩家 public Camera main_camera; //主相机 private List<GameObject> patrols; //场景中巡逻者列表 private bool game_over = false; //游戏结束 private bool is_win = false; //游戏是否胜利 void Update() { //更新每个巡逻兵,以判断玩家角色是否在其巡逻的区域中 for (int i = 0; i < patrols.Count; i++) { patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign; } } void Start() { SSDirector director = SSDirector.GetInstance(); director.CurrentScenceController = this; patrol_factory = Singleton<PropFactory>.Instance; action_manager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager; player_action_controller = Singleton<PlayerActionController>.Instance; LoadResources(); main_camera.GetComponent<CameraFollow>().follow = player; recorder = Singleton<ScoreRecorder>.Instance; } public void LoadResources() { //加载地图 Instantiate(Resources.Load<GameObject>("Prefabs/Plane")); //加载玩家角色 player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject; //通过工厂模式获得巡逻兵 patrols = patrol_factory.GetPatrols(); //巡逻兵开始巡逻 for (int i = 0; i < patrols.Count; i++) { action_manager.GoPatrol(patrols[i]); } } public bool GetGameover() { return game_over; } public void Restart() { SceneManager.LoadScene("Scenes/mySence"); } public bool GetIsWin() { return is_win; } public int GetScore() { return recorder.GetScore(); } //订阅消息 void OnEnable() { GameEventManager.ScoreChange += AddScore; GameEventManager.GameoverChange += Gameover; GameEventManager.PlayerStatusChange += PlayerWin; } void OnDisable() { GameEventManager.ScoreChange -= AddScore; GameEventManager.GameoverChange -= Gameover; GameEventManager.PlayerStatusChange -= PlayerWin; } void AddScore() { recorder.AddScore(); } //游戏结束 void Gameover() { game_over = true; patrol_factory.StopPatrol(); action_manager.DestroyAllAction(); } void PlayerWin() { is_win = true; } }
场记主要负责资源的加载,以及作为订阅者,当收到订阅的消息之后进行相应的处理。
-
GameEventManager
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameEventManager : MonoBehaviour { //分数变化 public delegate void ScoreEvent(); public static event ScoreEvent ScoreChange; //游戏结束变化 public delegate void GameoverEvent(); public static event GameoverEvent GameoverChange; //玩家状态变化 public delegate void PlayerStatusEvent(); public static event PlayerStatusEvent PlayerStatusChange; //玩家逃脱 public void PlayerEscape() { if (ScoreChange != null) { ScoreChange(); } } //游戏结束 public void PlayerGameover() { if (GameoverChange != null) { GameoverChange(); } } //玩家胜利 public void PlayerWin() { if (PlayerStatusChange != null) { PlayerStatusChange(); } } }
实现订阅-发布模式的关键所在,定义了事件代理,当定义的函数被调用时,会自动通知所有订阅者。
-
ScoreRecorder
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ScoreRecorder : MonoBehaviour { public FirstSceneController sceneController; public int score = 0; //分数 // Use this for initialization void Start() { sceneController = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController; sceneController.recorder = this; } public int GetScore() { return score; } public void AddScore() { score++; } }
负责玩家得分的管理
三、总结
通过此次作业,学到了订阅-发布模式的使用,订阅-发布模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在C#中对此模式做了语言级别的实现。称为事件-代理机制,我们只需要按照该模型即可简单地实现订阅-发布模式。作业中订阅-发布模式的使用主要在于碰撞器检测,当检测到碰撞时通过订阅-发布模式发送消息通知订阅者,即场景控制器,由此实现事件的响应。
此外,此次作业另一个重点在于动画,通过选择合适的人物模型,添加Animator组件,在AnimatorController中添加animation动画,定义好动作的状态图,通过代码控制状态转换的条件,即可实现对人物动画的控制。