Unity实战之牧师与魔鬼(动作分离版)

Unity实战之牧师与魔鬼(动作分离版)

项目链接



整体描述

本次项目在第一版牧师与魔鬼的基础上,将动作从场记中分离出来,并设计一个裁判类实时监测游戏进行的情况。这样改进的优点有很多:

  • 降低了不同功能之间的耦合性,代码的复用性更好。
  • 通过门面模式的设计,程序更加容易进行维护。




设计思路

本次改进参考了上课时介绍的cocos2d方案,它的UML图如下:

在这里插入图片描述



接下来将逐个介绍其中的类





动作管理(Action)

  • SSAction 动作基类。

    定义了两个虚函数 StartUpdate,后续的动作类都继承动作基类并实现这两个虚函数。

    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 裁判类(新增)

    1. 裁判类的作用是判断游戏进行的状态并通知主控制器,简单来说就是将原来 FirstController 中的 Check 抽离出来用一个类单独实现。需要在每一帧判断当前游戏的输赢,因此需要输入相关的变量进行判断。

    2. Update 中实现判断的逻辑,以达到实施监测的效果。

    3. 这里有一个问题,我们需要利用到判断的结果在主控制器中进行相应的处理(打印信息等等),但 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() {
    
        }
    }
    



以上完整介绍了牧师与魔鬼动作分离版本的主要变动和新增的函数,这一个版本和初始版本相比,增加了动作管理器,提升了代码的可复用性。

猜你喜欢

转载自blog.csdn.net/weixin_51930942/article/details/127703949
今日推荐