Unity Practical Priest and Devil (action separation version)

Unity Practical Priest and Devil (action separation version)

project link



overall description

Based on the first version of the Priest and the Devil, this project separates the action from the record, and designs a referee class to monitor the progress of the game in real time. The advantages of this improvement are many:

  • The coupling between different functions is reduced, and the code reusability is better.
  • Through the design of the facade mode, the program is easier to maintain.




Design ideas

This improvement refers to the cocos2d solution introduced in class, and its UML diagram is as follows:

insert image description here



Next, we will introduce the classes one by one.





Action Management (Action)

  • SSActionAction base class.

    Two virtual functions Startand Update, and subsequent action classes inherit the action base class and implement these two virtual functions.

    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();
        }
    }
    



  • ISSActionCallbackCallback.

    SSActionEventIt is a callback function. After the action is completed, the main controller needs to be notified of the completion result. Designing such a class is more convenient for event scheduling.

    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);
    }
    



  • SSActionManagerAction management base class

    UpdateBasic management of all actions is implemented in . The specific operation is to traverse all actions in the action dictionary, view their information, and perform corresponding destroy or update operations.

    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();
        }
    }
    



  • CCMoveToActionSimple mobile implementation.

    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;
        }
    }
    



  • CCSequenceActionCombination action realization

    Each sub-action in the combined action is stored in a list, and executed in sequence according to the stored order. According to the parameters repeat, it is judged whether to execute the combination action repeatedly.

    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(){
    
        }
    }
    



  • CCActionManageraction combination

    MoveBoatMove the boat, which is a single action, corresponding to CCMoveToAction.

    MoveRoleMove the character, this is a combined action (fold line movement), corresponding toCCSequenceAction

    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

  • JudgeControllerReferee (new)

    1. The role of the referee class is to judge the status of the game and notify the main controller. In simple terms, it is FirstControllerto Checkextract the original from and implement it with a single class. It is necessary to judge the winning or losing of the current game at each frame, so it is necessary to input relevant variables for judgment.

    2. UpdateRealize the logic of judgment in to achieve the effect of monitoring.

    3. There is a problem here, we need to use the results of the judgment to perform corresponding processing in the main controller (printing information, etc.), but Updatethe function does not return any value (void type). The solution is to FirstCotrollerdesign a callback function in JudgeResultCallBackand call in the referee class JudgeResultCallBackto return the judgment result.

    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;
                }
            }
    
        }
    
    }
    



  • FirstControllerscene controller

    The main difference from the previous project is that this time the scene controller does not implement the logic of moving and checking, but is implemented by the corresponding functional class.

    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() {
    
        }
    }
    



The above fully introduces the main changes and new functions of the separation version of priest and devil actions. Compared with the initial version, this version adds an action manager and improves the reusability of the code.

Guess you like

Origin blog.csdn.net/weixin_51930942/article/details/127703949