牧师与魔鬼动作分离版

牧师与魔鬼动作分离版

一、简介

这篇博客继承自牧师与魔鬼,在该游戏原有代码的基础上,实现动作及动作管理器与其他部分的分离。

二、代码框架结构变化

删除原来控制物体移动的move和moveController,替代为以下部分
在这里插入图片描述
另外增加一名裁判,以及对场记FirstController作修改(修改后依然只需要将FirstController脚本挂载到空对象上即可运行游戏)。

它们的关系如下:
在这里插入图片描述

三、代码解释

接下来对新增或修改的部分代码进行解释。

首先是Actions(动作)部分
1. SSAction & ISSActionCallback

动作基类和回调函数接口

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

SSAction是动作的基类,单个动作类和组合动作类都继承自它。
ScriptableObject 是不需要绑定 GameObject 对象的可编程基类,这些对象受 Unity 引擎场景管理。
所以需要在SSAction类的定义中增加对游戏对象的绑定。

SSAction还包含了一个回调函数的接口ISSActionCallback,该接口定义如下

public enum SSActionEventType:int {Started, Completed}
public interface ISSActionCallback
{
    //回调函数
    void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Completed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null);
}

这里简单解释一下这个接口的作用以及为什么要使用这个接口。

在我们的实现中有动作类和动作管理类,动作类与游戏对象绑定,控制游戏对象的移动,动作管理类则管理动作的产生和消除。

当一个动作结束时,动作管理器需要做出反应,比如将IsMoving设为false,这表示又可以接受新的动作了。有两种方式的实现:第一种实现是在动作结束时直接将isMoving设为false,另一种方式是动作本身不修改IsMoving,而是向动作管理器传递一个动作结束的消息,再由动作管理器来设置IsMoving的值。第一个版本的牧师与魔鬼采用了第一种方法,在我们这个版本中则采用了第二种方法。

采用消息传递的方式优点是显而易见的,一些当前的动作状态本该由动作管理器管理,而不应该交由某一个动作去控制,动作只需做好它本身的移动物体的任务即可。

所以,每一个SSAction都有一个ISSActionCallback接口callback,SSAction的管理者实现了ISSActionCallback接口,并且将自身赋给callback,当SSAction动作完成时,调用callback的SSActionEvent函数,即相当于调用动作管理者,令动作管理者做出相应的反应。

2. SSActionManager

动作管理者基类
动作管理者基类并不直接作为动作管理者使用,它的存在是为调用者提供较简单和通用的接口,我们真正用到的CCActionManager继承自它。

SSActionManager管理等待被加入的动作队列 ‘waitingAdd’ 和等待被删除的动作队列 ‘waitingDelete’ ,在每一次更新中主要做以下事情:

  • 将waitingAdd中的动作保存到字典中
  • 运行字典中的动作,将完成的动作加入到waitingDelete队列中

这里可能有个问题,同时运行字典中的所有动作?这不会导致混乱吗?

实际上在这个游戏中不会,因为动作管理器保证了队列中最多同时只有一个动作存在(如果isMoving为true即有动作正在进行,则新的动作会被丢弃)

//移动船
    public void MoveBoat(GameObject boat, Vector3 target, float speed)
    {
        if (isMoving)
            return;
        isMoving = true;
        moveBoatAction = CCMoveToAction.GetSSAction(target, speed);
        this.RunAction(boat, moveBoatAction, this);
    }

(其实这个游戏根本不需要用到这么复杂的框架,只是为了学习框架才用了起来)

  • 销毁waitingDelete队列中的动作实例对象
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>();

    protected void Update()
    {
        //将waitingAdd中的动作保存
        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();
            }
        }

        //销毁waitingDelete中的动作
        foreach (int key in waitingDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            Destroy(ac);
        }
        waitingDelete.Clear();
    }

    //准备运行一个动作,将动作初始化,并加入到waitingAdd
    public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback manager)
    {
        action.gameObject = gameObject;
        action.transform = gameObject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();
    }

    // Start is called before the first frame update
    protected void Start()
    {

    }

}

3. 单个动作与组合动作

如上所说,它们都继承自动作基类SSAction,各有各的表现。单个动作就是只有一个动作,比如船只的移动;组合动作是多个动作,比如角色的移动分为两个部分,横向移动和纵向移动。

public class CCMoveToAction : SSAction
{
    //目的地
    public Vector3 target;
    //速度
    public float speed;

    private CCMoveToAction()
    {

    }

    //生产函数(工厂模式)
    public static CCMoveToAction GetSSAction(Vector3 target, float speed)
    {
        CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
        action.target = target;
        action.speed = speed;
        return action;
    }

    // Start is called before the first frame update
    public override void Start()
    {
        
    }

    // Update is called once per frame
    public override void Update()
    {
        //判断是否符合移动条件
        if (this.gameObject == null || this.transform.localPosition == target)
        {
            this.destroy = true;
            this.callback.SSActionEvent(this);
            return;
        }
        //移动
        this.transform.localPosition = Vector3.MoveTowards(this.transform.localPosition, target, speed * Time.deltaTime);
    }
}

组合动作类同时还继承了上面说的回调函数接口ISSActionCallback,因为对于组合动作中的单个动作,组合动作对象就是它们的动作管理者,要为它们其中任何一个的结束做出反应(执行下一个动作,直到所有动作都执行完则再向它自己的动作管理器传递消息)。

public class CCSequenceAction : SSAction, ISSActionCallback
{
    //动作序列
    public List<SSAction> sequence;
    //重复次数
    public int repeat = -1;
    //动作开始指针
    public int start = 0;

    //生产函数(工厂模式)
    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 override void Start()
    {
        foreach (SSAction action in sequence)
        {
            action.gameObject = this.gameObject;
            action.transform = this.transform;
            action.callback = this;
            action.Start();
        }
    }

    //运行序列中的动作
    public override void Update()
    {
        if (sequence.Count == 0)
            return;
        if (start < sequence.Count)
        {
            sequence[start].Update();
        }
    }

    //回调处理,当有动作完成时触发
    public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Completed,
        int Param = 0,
        string strParam = null,
        Object objectParam = null)
    {
        source.destroy = false;
        this.start++;
        if (this.start >= sequence.Count)
        {
            this.start = 0;
            if (repeat > 0)
                repeat--;
            if (repeat == 0)
            {
                this.destroy = true;
                this.callback.SSActionEvent(this);
            }
        }
    }

    void OnDestroy()
    {

    }
}
4. 动作管理器CCActionManager

实现了移动船只和移动角色

public class CCActionManager : SSActionManager, ISSActionCallback
{
    //是否正在运动
    private bool isMoving = false;
    //船移动动作类
    public CCMoveToAction moveBoatAction;
    //人移动动作类(需要组合)
    public CCSequenceAction moveRoleAction;
    //控制器
    public FirstController controller;

    protected new void Start()
    {
        controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
        controller.actionManager = this;
    }

    public bool IsMoving()
    {
        return isMoving;
    }

    //移动船
    public void MoveBoat(GameObject boat, Vector3 target, float speed)
    {
        if (isMoving)
            return;
        isMoving = true;
        moveBoatAction = CCMoveToAction.GetSSAction(target, speed);
        this.RunAction(boat, moveBoatAction, this);
    }

    //移动人
    public void MoveRole(GameObject role, Vector3 mid_destination, Vector3 destination, int speed)
    {
        if (isMoving)
            return;
        isMoving = true;
        moveRoleAction = CCSequenceAction.GetSSAction(0, 0, new List<SSAction> { CCMoveToAction.GetSSAction(mid_destination, speed), CCMoveToAction.GetSSAction(destination, speed) });
        this.RunAction(role, moveRoleAction, this);
    }

    //回调函数
    public void SSActionEvent(SSAction source,
    SSActionEventType events = SSActionEventType.Completed,
    int intParam = 0,
    string strParam = null,
    Object objectParam = null)
    {
        isMoving = false;
    }
}
然后是控制部分
1. 裁判类

裁判类将原来FirstController中的check函数抽离出来,专门做游戏状态的检测,在游戏结束时通过回调通知FirstController场记。

public class JudgeController : MonoBehaviour
{
    public FirstController mainController;
    public Shore leftShoreModel;
    public Shore rightShoreModel;
    public Boat boatModel;
    // Start is called before the first frame update
    void Start()
    {
        mainController = (FirstController)SSDirector.GetInstance().CurrentSceneController;
        this.leftShoreModel = mainController.leftShoreController.GetShore();
        this.rightShoreModel = mainController.rightShoreController.GetShore();
        this.boatModel = mainController.boatController.GetBoatModel();
    }

    // Update is called once per frame
    void Update()
    {
        if (!mainController.isRunning)
            return;
        if (mainController.time <= 0)
        {
            mainController.JudgeCallback(false, "Game Over!");
            return;
        }
        this.gameObject.GetComponent<UserGUI>().gameMessage = "";
        //判断是否已经胜利
        if (rightShoreModel.priestCount == 3)
        {
            mainController.JudgeCallback(false, "You Win!");
            return;
        }
        else
        {
            
            int leftPriestNum, leftDevilNum, rightPriestNum, rightDevilNum;
            leftPriestNum = leftShoreModel.priestCount + (boatModel.isRight ? 0 : boatModel.priestCount);
            leftDevilNum = leftShoreModel.devilCount + (boatModel.isRight ? 0 : boatModel.devilCount);
            if (leftPriestNum != 0 && leftPriestNum < leftDevilNum)
            {
                mainController.JudgeCallback(false, "Game Over!");
                return;
            }
            rightPriestNum = rightShoreModel.priestCount + (boatModel.isRight ? boatModel.priestCount : 0);
            rightDevilNum = rightShoreModel.devilCount + (boatModel.isRight ? boatModel.devilCount : 0);
            if (rightPriestNum != 0 && rightPriestNum < rightDevilNum)
            {
                mainController.JudgeCallback(false, "Game Over!");
                return;
            }
        }
    }
}

2. FirstController

场记(主控制器)
在原来的基础上将check外包给裁判类(不再调用它)。
以及实现了裁判类的回调函数,当裁判类判定游戏结束时作出处理。
在初始化时,将动作管理器和裁判都加载到游戏对象上。

public class FirstController : MonoBehaviour, ISceneController, IUserAction {
    public CCActionManager actionManager;
    public ShoreCtrl leftShoreController, rightShoreController;
    public River river;
    public BoatCtrl boatController;
    public RoleCtrl[] roleControllers;
    //public MoveCtrl moveController;
    public bool isRunning;
    public float time;

    public void JudgeCallback(bool isRuning, string message)
    {
        this.gameObject.GetComponent<UserGUI>().gameMessage = message;
        this.gameObject.GetComponent<UserGUI>().time = (int)time;
        this.isRunning = isRunning;
    }
    public void LoadResources() {

        //role
        roleControllers = new RoleCtrl[6];
        for (int i = 0; i < 6; ++i) {
            roleControllers[i] = new RoleCtrl();
            roleControllers[i].CreateRole(Position.role_shore[i], i < 3 ? true : false, i);
        }

        //shore
        leftShoreController = new ShoreCtrl();
        leftShoreController.CreateShore(Position.left_shore);
        leftShoreController.GetShore().shore.name = "left_shore";
        rightShoreController = new ShoreCtrl();
        rightShoreController.CreateShore(Position.right_shore);
        rightShoreController.GetShore().shore.name = "right_shore";

        //将人物添加并定位至左岸  
        foreach (RoleCtrl roleController in roleControllers)
        {
            roleController.GetRoleModel().role.transform.localPosition = leftShoreController.AddRole(roleController.GetRoleModel());
        }
        //boat
        boatController = new BoatCtrl();
        boatController.CreateBoat(Position.left_boat);

        //river
        river = new River(Position.river);

        //move
        //moveController = new MoveCtrl();

        isRunning = true;
        time = 60;


    }

    public void MoveBoat() {
        if (isRunning == false || actionManager.IsMoving()) return;
        
        Vector3 destination = boatController.GetBoatModel().isRight ? Position.left_boat : Position.right_boat;
        actionManager.MoveBoat(boatController.GetBoatModel().boat, destination, 5);
        
        boatController.GetBoatModel().isRight = !boatController.GetBoatModel().isRight;
    
    }

    public void MoveRole(Role roleModel) {
        if (isRunning == false || actionManager.IsMoving()) return;
        Vector3 destination, mid_destination;
        if (roleModel.inBoat)
        {
            
            if (boatController.GetBoatModel().isRight)
                destination = rightShoreController.AddRole(roleModel);
            else
                destination = leftShoreController.AddRole(roleModel);
            if (roleModel.role.transform.localPosition.y > destination.y)
                mid_destination = new Vector3(destination.x, roleModel.role.transform.localPosition.y, destination.z);
            else
                mid_destination = new Vector3(roleModel.role.transform.localPosition.x, destination.y, destination.z);
            actionManager.MoveRole(roleModel.role, mid_destination, destination, 5);
            roleModel.onRight = boatController.GetBoatModel().isRight;
            boatController.RemoveRole(roleModel);
        }
        else
        {
            
            if (boatController.GetBoatModel().isRight == roleModel.onRight)
            {
                if (roleModel.onRight)
                {
                    rightShoreController.RemoveRole(roleModel);
                }
                else
                {
                    leftShoreController.RemoveRole(roleModel);
                }
                destination = boatController.AddRole(roleModel);
                if (roleModel.role.transform.localPosition.y > destination.y)
                    mid_destination = new Vector3(destination.x, roleModel.role.transform.localPosition.y, destination.z);
                else
                    mid_destination = new Vector3(roleModel.role.transform.localPosition.x, destination.y, destination.z);
                actionManager.MoveRole(roleModel.role, mid_destination, destination, 5);
            }
        }
    }

    public void Check() {
        
    }

    void Awake() {
        SSDirector.GetInstance().CurrentSceneController = this;
        LoadResources();
        this.gameObject.AddComponent<UserGUI>();
        this.gameObject.AddComponent<CCActionManager>();
        this.gameObject.AddComponent<JudgeController>();
    }

    void Update() {
        if (isRunning)
        {
            time -= Time.deltaTime;
            this.gameObject.GetComponent<UserGUI>().time = (int)time;
        }
    }
}
四、项目链接

github链接

以下是C#语言实现牧师与野人问题求解的代码和注释: ```csharp using System; using System.Collections.Generic; namespace MissionariesAndCannibals { class Program { static void Main(string[] args) { // 初始化状态 State initialState = new State(3, 3, true, 0, 0); // 初始化搜索树 SearchTree searchTree = new SearchTree(initialState); // 进行搜索 searchTree.BFS(); } } // 表示状态的类 class State { public int MissionariesLeft { get; set; } // 左岸传教士数量 public int CannibalsLeft { get; set; } // 左岸野人数量 public bool BoatPosition { get; set; } // 船的位置,true表示在左岸,false表示在右岸 public int MissionariesRight { get; set; } // 右岸传教士数量 public int CannibalsRight { get; set; } // 右岸野人数量 // 构造函数 public State(int missionariesLeft, int cannibalsLeft, bool boatPosition, int missionariesRight, int cannibalsRight) { MissionariesLeft = missionariesLeft; CannibalsLeft = cannibalsLeft; BoatPosition = boatPosition; MissionariesRight = missionariesRight; CannibalsRight = cannibalsRight; } // 判断状态是否合法 public bool IsValid() { if (MissionariesLeft < 0 || CannibalsLeft < 0 || MissionariesRight < 0 || CannibalsRight < 0) { return false; } if (MissionariesLeft > 3 || CannibalsLeft > 3 || MissionariesRight > 3 || CannibalsRight > 3) { return false; } if (CannibalsLeft > MissionariesLeft && MissionariesLeft > 0) { return false; } if (CannibalsRight > MissionariesRight && MissionariesRight > 0) { return false; } return true; } // 判断状态是否为目标状态 public bool IsGoal() { return MissionariesLeft == 0 && CannibalsLeft == 0; } // 判断两个状态是否相等 public override bool Equals(object obj) { State state = obj as State; if (state == null) { return false; } return MissionariesLeft == state.MissionariesLeft && CannibalsLeft == state.CannibalsLeft && BoatPosition == state.BoatPosition && MissionariesRight == state.MissionariesRight && CannibalsRight == state.CannibalsRight; } // 获取状态的哈希值 public override int GetHashCode() { return MissionariesLeft * 10000 + CannibalsLeft * 1000 + (BoatPosition ? 100 : 0) + MissionariesRight * 10 + CannibalsRight; } // 获取状态的字符串表示 public override string ToString() { return "MissionariesLeft: " + MissionariesLeft + ", CannibalsLeft: " + CannibalsLeft + ", BoatPosition: " + (BoatPosition ? "Left" : "Right") + ", MissionariesRight: " + MissionariesRight + ", CannibalsRight: " + CannibalsRight; } } // 表示搜索树的类 class SearchTree { private State _initialState; // 初始状态 private Queue<Node> _frontier; // 存放待扩展的节点的队列 private HashSet<State> _exploredSet; // 存放已扩展过的状态的集合 // 构造函数 public SearchTree(State initialState) { _initialState = initialState; _frontier = new Queue<Node>(); _frontier.Enqueue(new Node(_initialState, null)); _exploredSet = new HashSet<State>(); } // 广度优先搜索 public void BFS() { while (_frontier.Count > 0) { Node node = _frontier.Dequeue(); State state = node.State; if (state.IsGoal()) { // 找到了目标状态,输出路径 List<State> path = new List<State>(); while (node != null) { path.Insert(0, node.State); node = node.Parent; } foreach (State s in path) { Console.WriteLine(s); } return; } _exploredSet.Add(state); List<State> successors = GetSuccessors(state); foreach (State successor in successors) { if (!_exploredSet.Contains(successor)) { _frontier.Enqueue(new Node(successor, node)); } } } Console.WriteLine("No solution found."); } // 获取一个状态的所有合法后继状态 private List<State> GetSuccessors(State state) { List<State> successors = new List<State>(); if (state.BoatPosition) { // 船在左岸 for (int i = 0; i <= 2; i++) { for (int j = 0; j <= 2; j++) { if (i + j >= 1 && i + j <= 2) { State successor = new State(state.MissionariesLeft - i, state.CannibalsLeft - j, false, state.MissionariesRight + i, state.CannibalsRight + j); if (successor.IsValid()) { successors.Add(successor); } } } } } else { // 船在右岸 for (int i = 0; i <= 2; i++) { for (int j = 0; j <= 2; j++) { if (i + j >= 1 && i + j <= 2) { State successor = new State(state.MissionariesLeft + i, state.CannibalsLeft + j, true, state.MissionariesRight - i, state.CannibalsRight - j); if (successor.IsValid()) { successors.Add(successor); } } } } } return successors; } } // 表示搜索树中的节点的类 class Node { public State State { get; set; } // 节点对应的状态 public Node Parent { get; set; } // 父节点 // 构造函数 public Node(State state, Node parent) { State = state; Parent = parent; } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值