Unity3d入门之路-牧师与魔鬼 动作分离版

牧师与魔鬼 动作分离版

  • 【2019新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束

实现过程

什么是动作分离版呢?还记得我们上个版本的牧师与魔鬼为了实现character与boat的移动,实现了一个Moveable的脚本,并把此脚本作为组件加到character与boat上了。然而这样的实现方法是不完全符合MVC的分离的,动作的控制应该交由Control模块管理,而不是在Model模块中。所以这次为了实现动作分离版,我们实现一个Action模块,专门管理Model的动作,实际使用上与Moveable差别不大。为了有个更清晰的理解,先来看一下UML图
在这里插入图片描述

Action模块
  1. ISSActionCallback(动作事件接口)
    这是一个接口,用以实现callback。也就是当某个动作完成之后,使用callback回调到调用此函数的类中实现的此接口的函数,如SSActionEvent中。
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);
}
  1. SSAction (动作基类)
    这是所有动作的基类,所有动作继承该类以实现各种动作以及组合动作。ScriptableObject 是不需要绑定 GameObject 对象的可编程基类,这些对象受 Unity 引擎场景管理。这个类更关键的是利用接口(ISSACtionCallback)实现消息通知,避免了与动作管理者直接依赖。
public class SSAction : ScriptableObject            //动作
{

    public bool enable = true;                      //是否正在进行此动作
    public bool destroy = false;                    //是否需要被销毁

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

    protected SSAction() { }                        //保证SSAction不会被new

    public virtual void Start()                    //子类可以使用这两个函数
    {
        throw new System.NotImplementedException();
    }

    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}
  1. SequenceAction(组合动作实现)
    该类继承了SSAction类,实现多个单动作的组合成一个动作,组合动作同时也是一个动作。该类利用一个List管理多个动作,并在接口方法中实现了动作的执行顺序。
public class SequenceAction : SSAction, ISSActionCallback
{
    public List<SSAction> sequence;    //动作的列表
    public int repeat = -1;            //-1就是无限循环做组合中的动作
    public int start = 0;              //当前做的动作的索引

    public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence)
    {
        SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();//让unity自己创建一个SequenceAction实例
        action.repeat = repeat;
        action.sequence = sequence;
        action.start = start;
        return action;
    }

    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); //告诉组合动作的管理对象组合做完了
            }
        }
    }

    public override void Start()
    {
        foreach (SSAction action in sequence)
        {
            action.gameobject = this.gameobject;
            action.transform = this.transform;
            action.callback = this;                //组合动作的每个小的动作的回调是这个组合动作
            action.Start();
        }
    }

    void OnDestroy()
    {
        //如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
    }
}
  1. SSMoveToAction(移动动作实现)
    这是一个具体的动作实现——移动动作,当然可以实现各种各样的动作,该游戏场景只需要移动,即将某个游戏对象移动到指定的position上,且动作执行完会回调到调用者上。
public class SSMoveToAction : SSAction                        //移动
{
    public Vector3 target;        //移动到的目的地
    public float speed;           //移动的速度

    private SSMoveToAction() { }
    public static SSMoveToAction GetSSAction(Vector3 target, float speed)
    {
        SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();//让unity自己创建一个MoveToAction实例,并自己回收
        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 override void Start()
    {
        //移动动作建立时候不做任何事情
    }
}
  1. SSActionManager(动作管理基类)
    这是个动作管理者,在此游戏唯一的场景动作管理者就继承于此类,实现该场景的动作管理。动作管理管理着所有动作,控制着每个动作的执行顺序以及执行的对象等。
public class SSActionManager : MonoBehaviour, ISSActionCallback                      //action管理器
{

    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();    //将执行的动作的字典集合,int为key,SSAction为value
    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;                                      //获取动作实例的ID作为key
        }
        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);
            DestroyObject(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();
    }

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null)
    {
        //牧师与魔鬼的游戏对象移动完成后就没有下一个要做的动作了,所以回调函数为空
    }
}
  1. MySceneActionManager(移动动作管理实现)
    最后这就是这个场景的动作管理者,可以知道这个场景中主要有moveBoat和moveCharacter两个动作,也就是之前版本Moveable脚本实现的两个动作。通过那么复杂终于实现了动作的管理,由于这个游戏只有两个小动作,所以使用这样的管理难免会觉得繁琐。然而,如果游戏的复杂性上升,动作多了起来,使用这样一个动作管理器可以将动作集中起来共同管理,利用管理器效率就会更高。
public class MySceneActionManager : SSActionManager  //本游戏管理器
{

    private SSMoveToAction moveBoatToEndOrStart;     //移动船到结束岸,移动船到开始岸
    private SequenceAction moveRoleToLandorBoat;     //移动角色到陆地,移动角色到船上

    public FirstController sceneController;

    protected new void Start()
    {
        sceneController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
        sceneController.actionManager = this;
    }
    public void moveBoat(GameObject boat, Vector3 target, float speed)
    {
        moveBoatToEndOrStart = SSMoveToAction.GetSSAction(target, speed);
        this.RunAction(boat, moveBoatToEndOrStart, this);
    }

    public void moveRole(GameObject role, Vector3 middle_pos, Vector3 end_pos, float speed)
    {
        SSAction action1 = SSMoveToAction.GetSSAction(middle_pos, speed);
        SSAction action2 = SSMoveToAction.GetSSAction(end_pos, speed);
        moveRoleToLandorBoat = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
        this.RunAction(role, moveRoleToLandorBoat, this);
    }
}

除此之外,在该版本中还新增一个裁判类(Judge)以判断游戏是否结束。该功能通过场景控制者(FirstController)调用该类的判断函数,如果游戏结束裁判类会传递结束的信息回来。

Judge实现
  1. Judge(裁判类)
//Judge.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Judge
{
    public LandModel fromLand;
    public LandModel toLand;
    public BoatModel boat;

    public Judge(LandModel _fromLand, LandModel _toLand, BoatModel _boat)
    {
        fromLand = _fromLand;
        toLand = _toLand;
        boat = _boat;
    }

    public  int isOver()
    {

        int fromPriest = 0;
        int fromDevil = 0;
        int toPriest = 0;
        int toDevil = 0;

        int[] fromCount = fromLand.GetCharacterNum();
        fromPriest += fromCount[0];
        fromDevil += fromCount[1];

        int[] toCount = toLand.GetCharacterNum();
        toPriest += toCount[0];
        toDevil += toCount[1];

        if (toPriest + toDevil == 6)      // 赢了
            return 2;

        int[] boatCount = boat.GetCharacterNum();
        if (boat.GetToOrFrom() == -1)
        {
            toPriest += boatCount[0];
            toDevil += boatCount[1];
        }
        else
        {
            fromPriest += boatCount[0];
            fromDevil += boatCount[1];
        }
        if (fromPriest < fromDevil && fromPriest > 0) //输了
        {
            return 1;
        }
        if (toPriest < toDevil && toPriest > 0)
        {
            return 1;
        }
        return 0;			// 游戏未结束
    }
}

  1. 在FirstController中
    private int isOver()
    {
        return judge.isOver();
    }

代码与视频

Github: https://github.com/colorfuljor/3D-Game/tree/master/homework/hw4/src/Priests%20and%20Devils%20v2
牧师与魔鬼演示视频:https://v.youku.com/v_show/id_XNDM3NzczOTE3Ng==.html?spm=a2hzp.8244740.0.0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值