Unity实战之牧师与魔鬼(动作分离版)
整体描述
本次项目在第一版牧师与魔鬼的基础上,将动作从场记中分离出来,并设计一个裁判类实时监测游戏进行的情况。这样改进的优点有很多:
- 降低了不同功能之间的耦合性,代码的复用性更好。
- 通过门面模式的设计,程序更加容易进行维护。
设计思路
本次改进参考了上课时介绍的cocos2d方案,它的UML图如下:
接下来将逐个介绍其中的类
动作管理(Action)
-
SSAction
动作基类。定义了两个虚函数
Start
和Update
,后续的动作类都继承动作基类并实现这两个虚函数。using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSAction : ScriptableObject { public bool enable = true; public bool destroy = false; public GameObject gameobject { get; set; } public Transform transform { get; set; } public ISSActionCallback callback { get; set;} protected SSAction() {} // Start is called before the first frame update public virtual void Start() { throw new System.NotImplementedException(); } // Update is called once per frame public virtual void Update() { throw new System.NotImplementedException(); } }
-
ISSActionCallback
回调函数。SSActionEvent
是回调函数,动作完成后需要通知主控制器完成结果,设计这样的类更加方便事件的调度。using System.Collections; using System.Collections.Generic; using UnityEngine; public enum SSActionEventType : int { Started, Competeted } public interface ISSActionCallback { // Start is called before the first frame update // void Start() // { // } // // Update is called once per frame // void Update() // { // } public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null); }
-
SSActionManager
动作管理基类在
Update
中实现了所有动作的基本管理。具体操作是遍历动作字典中的所有动作,查看它们的信息,进行相应的销毁或更新操作。using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSActionManager : MonoBehaviour { private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); private List<SSAction> waitingAdd = new List<SSAction> (); private List<int> waitingDelete = new List<int>(); // Start is called before the first frame update protected void Start() { } // Update is called once per frame 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); Destroy(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(); } }
-
CCMoveToAction
简单移动实现。using System.Collections; using System.Collections.Generic; using UnityEngine; public class CCMoveToAction : SSAction { public Vector3 target; // 移动后的目标位置 public float speed; private CCMoveToAction(){ } // Start is called before the first frame update public override void Start() { } // Update is called once per frame public override void Update() { this.transform.localPosition = Vector3.MoveTowards(this.transform.localPosition, target, speed * Time.deltaTime); // 如果游戏对象不存在或者当前位置已在目标位置上,则不移动 if(this.transform.localPosition == target || this.gameobject == null){ this.destroy = true; // 标记为销毁 this.callback.SSActionEvent(this); // 回调函数 return; } } public static CCMoveToAction GetSSAction(Vector3 target, float speed){ CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>(); action.target = target; action.speed = speed; return action; } }
-
CCSequenceAction
组合动作实现通过一个列表存储组合动作中的各个子动作,并按存放的顺序依次执行。根据参数
repeat
判断是否要重复执行组合动作。using System.Collections; using System.Collections.Generic; using UnityEngine; public class CCSequenceAction : SSAction, ISSActionCallback { public List<SSAction> sequence; public int repeat = -1; public int start = 0; // Start is called before the first frame update public override void Start() { // 初始化列表中的动作 foreach(SSAction action in sequence){ action.gameobject = this.gameobject; action.transform = this.transform; action.callback = this; action.Start(); } } // Update is called once per frame public override void Update() { if(sequence.Count <= 0){ return; } if(sequence.Count > 0 && start < sequence.Count){ sequence[start].Update(); } else{ return; } } public static CCSequenceAction GetSSAction(int repeat, int start, List<SSAction> sequence){ CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>(); action.repeat = repeat; action.start = start; action.sequence = sequence; return action; } public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null){ source.destroy = false; this.start++; if(this.start >= sequence.Count){ this.start = 0; if(this.repeat > 0){ this.repeat--; } else{ this.destroy = true; this.callback.SSActionEvent(this); } } } void OnDestroy(){ } }
-
CCActionManager
动作组合MoveBoat
移动船,这是单独的一个动作,对应为CCMoveToAction
。MoveRole
移动人物,这是一个组合动作(折线运动),对应为CCSequenceAction
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CCActionManager : SSActionManager, ISSActionCallback { public CCMoveToAction boatMovement; public CCSequenceAction roleMovement; public FirstController controller; private bool isMoving = false; protected new void Start() { controller = (FirstController)SSDirector.GetInstance().CurrentSceneController; controller.actionManager = this; } public bool CheckMoving() { return isMoving; } public void MoveBoat(GameObject boat, Vector3 target, float speed) { if (isMoving) return; isMoving = true; boatMovement = CCMoveToAction.GetSSAction(target, speed); this.RunAction(boat, boatMovement, this); } public void MoveRole(GameObject role, Vector3 middle_pos, Vector3 target, float speed) { if (isMoving) return; isMoving = true; SSAction ac1 = CCMoveToAction.GetSSAction(middle_pos, speed); SSAction ac2 = CCMoveToAction.GetSSAction(target, speed); roleMovement = CCSequenceAction.GetSSAction(0, 0, new List<SSAction> {CCMoveToAction.GetSSAction(middle_pos, speed), CCMoveToAction.GetSSAction(target, speed)}); this.RunAction(role, roleMovement, this); } public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null) { isMoving = false; } }
控制器(Controller)
-
JudgeController
裁判类(新增)-
裁判类的作用是判断游戏进行的状态并通知主控制器,简单来说就是将原来
FirstController
中的Check
抽离出来用一个类单独实现。需要在每一帧判断当前游戏的输赢,因此需要输入相关的变量进行判断。 -
在
Update
中实现判断的逻辑,以达到实施监测的效果。 -
这里有一个问题,我们需要利用到判断的结果在主控制器中进行相应的处理(打印信息等等),但
Update
函数不返回任何值(void类型)。解决的方法是,在FirstCotroller
中设计一个回调函数JudgeResultCallBack
,在裁判类中调用JudgeResultCallBack
以返回判断结果。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class JudgeController : MonoBehaviour { public FirstController sceneController; public Land rightLand; public Land leftLand; public Boat boat; // Start is called before the first frame update void Start() { this.sceneController = (FirstController)SSDirector.GetInstance().CurrentSceneController; this.rightLand = sceneController.rightLandController.GetLand(); this.leftLand = sceneController.leftLandController.GetLand(); this.boat = sceneController.boatController.GetBoatModel(); } // Update is called once per frame void Update() { if(sceneController.isRunning == false){ return; } this.gameObject.GetComponent<UserGUI>().gameMessage = ""; if(rightLand.priestCount == 3){ // win, callback sceneController.JudgeResultCallBack("You win!!"); return; } else{ int leftPriestCount, rightPriestCount, leftDevilCount, rightDevilCount; leftPriestCount = leftLand.priestCount + (boat.isRight ? 0 : boat.priestCount); rightPriestCount = 3 - leftPriestCount; leftDevilCount = leftLand.devilCount + (boat.isRight ? 0: boat.devilCount); rightDevilCount = 3 - leftDevilCount; if((leftPriestCount != 0 && leftPriestCount < leftDevilCount) || (rightPriestCount != 0 && rightPriestCount < rightDevilCount)){ // lose sceneController.JudgeResultCallBack("Game over!!"); return; } } } }
-
-
FirstController
场景控制器与上一次项目的主要不同是,这次的场景控制器并不具体实现移动和检查的逻辑,而是交由相应的功能类实现。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FirstController : MonoBehaviour, ISceneController, IUserAction { public LandControl leftLandController, rightLandController; public River river; public BoatControl boatController; public RoleControl[] roleControllers; public MoveCtrl moveController; public bool isRunning; public float time; public CCActionManager actionManager; public float speed = 5; public void LoadResources() { roleControllers = new RoleControl[6]; for (int i = 0; i < 6; ++i) { roleControllers[i] = new RoleControl(); roleControllers[i].CreateRole(Position.role_land[i], i < 3 ? true : false, i); } leftLandController = new LandControl(); leftLandController.CreateLand(Position.left_land); leftLandController.GetLand().land.name = "left_land"; rightLandController = new LandControl(); rightLandController.CreateLand(Position.right_land); rightLandController.GetLand().land.name = "right_land"; foreach (RoleControl roleController in roleControllers) { roleController.GetRoleModel().role.transform.localPosition = leftLandController.AddRole(roleController.GetRoleModel()); } boatController = new BoatControl(); boatController.CreateBoat(Position.left_boat); river = new River(Position.river); moveController = new MoveCtrl(); isRunning = true; time = 60; } public void MoveBoat() { if (isRunning == false || actionManager.CheckMoving() == true) return; Vector3 target; if(boatController.GetBoatModel().isRight){ target = Position.left_boat; } else{ target = Position.right_boat; } actionManager.MoveBoat(boatController.GetBoatModel().boat, target, speed); boatController.GetBoatModel().isRight = !boatController.GetBoatModel().isRight; } public void MoveRole(Role roleModel) { if (isRunning == false || actionManager.CheckMoving() == true) return; Vector3 middle_pos; Vector3 target; if(roleModel.inBoat){ if(boatController.GetBoatModel().isRight){ target = rightLandController.AddRole(roleModel); } else{ target = leftLandController.AddRole(roleModel); } // if(roleModel.role.transform.localPosition.y > target.y){ // middle_pos = new Vector3(target.x, roleModel.role.transform.localPosition.y, target.z); // } // else{ // middle_pos = new Vector3(roleModel.role.transform.localPosition.x, target.y, target.z); // } middle_pos = new Vector3(roleModel.role.transform.localPosition.x, target.y, target.z); actionManager.MoveRole(roleModel.role, middle_pos, target, speed); roleModel.onRight = boatController.GetBoatModel().isRight; boatController.RemoveRole(roleModel); } else{ if (boatController.GetBoatModel().isRight == roleModel.onRight){ if (roleModel.onRight) { rightLandController.RemoveRole(roleModel); } else { leftLandController.RemoveRole(roleModel); } target = boatController.AddRole(roleModel); // if(roleModel.role.transform.localPosition.y > target.y){ // middle_pos = new Vector3(target.x, roleModel.role.transform.localPosition.y, target.z); // } // else{ // middle_pos = new Vector3(roleModel.role.transform.localPosition.x, target.y, target.z); // } middle_pos = new Vector3(target.x, roleModel.role.transform.localPosition.y, target.z); actionManager.MoveRole(roleModel.role, middle_pos, target, speed); } } } public void Check() { } public void JudgeResultCallBack(string result){ this.gameObject.GetComponent<UserGUI>().gameMessage = result; this.isRunning = false; } void Awake() { SSDirector.GetInstance().CurrentSceneController = this; LoadResources(); this.gameObject.AddComponent<UserGUI>(); this.gameObject.AddComponent<CCActionManager>(); this.gameObject.AddComponent<JudgeController>(); } void Update() { } }
以上完整介绍了牧师与魔鬼动作分离版本的主要变动和新增的函数,这一个版本和初始版本相比,增加了动作管理器,提升了代码的可复用性。