Unity牧师与魔鬼小游戏(动作分离版)

Unity牧师与魔鬼小游戏(动作分离版)

前言

这是中大计算机学院3D游戏编程课的一次作业,在这里分享一下设计思路。
主要代码上传到了gitee上,请按照后文的操作运行。
项目地址:https://gitee.com/cuizx19308024/unity-games/tree/master/hw3
上一次的项目地址:https://gitee.com/cuizx19308024/unity-games/tree/master/hw2
成果视频:https://www.bilibili.com/video/BV16g411F7yF?spm_id_from=333.999.0.0
上一篇的博客地址:https://blog.csdn.net/kiriyama23333/article/details/120625461?spm=1001.2014.3001.5501

改动说明

在上一个项目中,我们把所有的动作、裁判和游戏控制全部交给了Controller来完成。但实际上,当游戏规模较大时,这种架构会变得难以维护。因此,本次的改动是将动作单独分离出来实现,将这些动作通过门面模式交给动作控制管理器来管理,外界可以通过调用动作管理器中的函数来控制动作的发生,将管理和实现分离。此外,还增加了一个裁判类,用于作游戏结束的判定。

新增ActionController

回调函数接口

这里采用接口来表示,每一个动作完成后,调用者会进行的操作,这一操作被放置在了回调函数里。

public enum SSActionEventType : int { Started, Competeted }

//接口
public interface ISSActionCallback {
    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null);
}

动作基类

这里通过基类和虚函数来规定子类需要实现的操作,通过“可进行”和“可删除”两个变量来控制动作。

//动作基类
public class SSAction : ScriptableObject {
    public bool enable = true;                      //是否可进行
    public bool destroy = false;                    //是否已完成

    public GameObject gameobject;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //回调函数

    /*防止用户自己new对象*/
    protected SSAction() { }                      

    public virtual void Start() {
        throw new System.NotImplementedException();
    }

    public virtual void Update() {
        throw new System.NotImplementedException();
    }
}

单个动作

注意单个动作的以下两个操作:

//这里利用Unity自带的构造函数创建对象,并初始化
public static SSMoveToAction GetSSAction(Vector3 _target, float _speed) {
    SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();
    action.target = _target;
    action.speed = _speed;
    return action;
}

public override void Update() {
    this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
    //动作完成,通知动作管理者或动作组合
    if (this.transform.position == target) {
        this.destroy = true;
        this.callback.SSActionEvent(this);      
    }
}

组合动作

组合动作包括:

public List<SSAction> sequence;    //动作的列表
public int repeat = -1;            //-1就是无限循环
public int start = 0;              //当前做的动作的标号

注意以下几个函数:

//让序列中的每一个动作都执行
public override void Update() {
    if (sequence.Count == 0) return;
    if (start < sequence.Count) {
        sequence[start].Update();               //执行之后,通过回调函数让start值递增
    }
}

//回调函数
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 (repeat > 0) repeat--;               
        if (repeat == 0) {                      
            this.destroy = true;                //删除
            this.callback.SSActionEvent(this);  //通知管理者
        }
    }
}

动作管理器基类

动作管理器是一个门面,它可以自己实现对于动作的控制,并可以提供接口给外部函数调用。
尤其注意这里的等待添加和等待删除的队列,由于每次update的过程中不可能立刻对新增动作和已执行的动作进行处理,因此需要一个队列来进行缓冲。被删除的动作对象也由Unity的Destroy进行操作。
对于门面模式,这里提供了函数RunAction(),外界仅需指定游戏对象、动作和回调处理器即可由这个类完成这些动作的实现。

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>();                              //等待删除的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);
            Object.Destroy(ac);//让Unity帮着删除
        }
        waitingDelete.Clear();
    }

    //外界只需要调用动作管理类的RunAction函数即可完成动作。
    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 moveBoat(GameObject boat, Vector3 end, float speed) {
    boatMove = SSMoveToAction.GetSSAction(end, speed);
    this.RunAction(boat, boatMove, this);
}

public void moveRole(GameObject role, Vector3 middle, Vector3 end, float speed) {
    //两段移动
    SSAction action1 = SSMoveToAction.GetSSAction(middle, speed);
    SSAction action2 = SSMoveToAction.GetSSAction(end, speed);
    //两个动作,都只重复一次
    roleMove = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
    this.RunAction(role, roleMove, this);
}

新增GameCheck

只需将原有的判断函数移动到类中即可,作一些变量的修改:

public class GameCheck : MonoBehaviour {
    public Controller sceneController;

    protected void Start() {
        sceneController = (Controller)SSDirector.GetInstance().CurrentScenceController;
    }

    public int GameJudge()
    {
        int src_priest = sceneController.src_land.GetTotal(0);
        int src_ghost = sceneController.src_land.GetTotal(1);
        int des_priest = sceneController.des_land.GetTotal(0);
        int des_ghost = sceneController.des_land.GetTotal(1);

        if (des_priest + des_ghost == 6)
        {    //全部到终点,获胜
            return 1;
        }

        if (sceneController.boat.GetBoatMark() == 1)//由于在这一边船还没开,因此只需检测另一边的数量即可。
        {
            if (des_priest < des_ghost && des_priest > 0)
            {//失败
                return -1;
            }
        }
        else
        {
            if (src_priest < src_ghost && src_priest > 0)
            {//失败
                return -1;
            }
        }

        return 0;//游戏继续进行
    }
}

修改原有代码

  1. 删除了Movable类,因为已经实现了控制器,不需要将其设计为属性。
  2. 替换了BoatModelRoleModel类中的Movable相关操作,全部改为用GhostBoatActionManager类的对象进行操作。Model只需指定地点和速度,调用这个类中的相关函数进行移动即可。
  3. 将原有的GameJudge()操作替换为checker.GameJudge(),删除类中原有的GameJudge()函数。此外,还需要修改接口IUserAction,因为判断游戏结束已经交给裁判决定了。
  4. Controller类初始化时新增了对GhostBoatActionManager对象和GameCheck对象的初始化,和UserGUI一样,都是作为一个属性被添加到这个游戏对象上面,相当于给这个游戏对象附上了那些脚本。

实验结果

与上次的实验结果相同,参见展示视频

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值