基础MVC牧师与魔鬼:
一、动作分离
基本思想:场记(控制器)管的事太多,不仅要处理用户交互事件,还要游戏对象加载、游戏规则实现、运动实现等等,而显得非常臃肿。一个最直观的想法就是让更多的人(角色)来管理不同方面的工作。
所以有动作基类SSAction:
public class SSAction : ScriptableObject //动作
{
public bool enable = true; //是否正在进行此动作
public bool destroy = false; //是否需要被销毁
public GameObject gameobject; //动作对象
public Transform transform; //动作对象的transform
public ISSActionCallback callback; //回调函数
protected SSAction() { } //保证SSAction不会被new
public virtual void Start() //子类可以使用这两个函数
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
- 使用
virtual
申明虚方法,通过重写实现多态。这样继承者就明确使用 Start 和 Update 编程游戏对象行为 - 利用接口(
ISSACtionCallback
)实现消息通知,避免与动作管理者直接依赖
动作完成通知接口:
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);
}
简单动作SSMoveToAction:
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()
{
//移动动作建立时候不做任何事情
}
}
组合动作SequenceAction:
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()
{
//如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
}
}
动作管理基类 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);
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();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null)
{
//牧师与魔鬼的游戏对象移动完成后就没有下一个要做的动作了,所以回调函数为空
}
}
针对本游戏的移动的动作管理类
public class MySceneActionManager : SSActionManager //本游戏管理器
{
private SSMoveToAction moveBoatToEndOrStart; //移动船到结束岸,移动船到开始岸
private SequenceAction moveRoleToLandorBoat; //移动角色到陆地,移动角色到船上
public FirstController sceneController;
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);
}
}
他们之间的关系如此UML图所示:
这些添加在名为Actions的脚本下,这里代码讲解得比较简略,是因为注释比较详细了。
二、裁判类
public class Judge:MonoBehaviour
{
public FirstController sceneController;
public CoastModel end_coast;
public CoastModel start_coast;
public BoatModel boat;
private void Start()
{
this.sceneController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
this.end_coast = sceneController.end_coast;
this.start_coast = sceneController.start_coast;
this.boat = sceneController.boat;
}
private void Update()
{
int start_priest = (start_coast.GetRoleNum())[0];
int start_devil = (start_coast.GetRoleNum())[1];
int end_priest = (end_coast.GetRoleNum())[0];
int end_devil = (end_coast.GetRoleNum())[1];
if (end_priest + end_devil == 6) //获胜
sceneController.JudgeResultCallBack(2);
if (start_priest > 0 && start_priest < start_devil) //失败
{
sceneController.JudgeResultCallBack(1);
}
if (end_priest > 0 && end_priest < end_devil) //失败
{
sceneController.JudgeResultCallBack(1);
}
//未完成
}
}
裁判类负责判别当前游戏是否结束,是否输或者赢,并回调通知当前场记控制器
在场记那边的回调函数如下:
public void JudgeResultCallBack(int result)
{
user_gui.sign = result;
}
三、与基础版的区别
- 新加裁判类来判断游戏输赢,将之前在场记控制器的判断游戏输赢的Check()函数去掉,输赢逻辑由裁判类实现,场记的活减少
- 移动船和移动角色的动作有专门的动作管理者实现,场记控制器实现变简单
- Models中去掉了Move类,BoatModel、RoleModel这两个类去掉了直接操作移动函数,实现模型和玩家动作的解耦合
四、地址
基础版博客:https://blog.csdn.net/m0_67750738/article/details/133964258
代码地址:https://gitee.com/xiao-qunxi/priest-and-devil_version2
实现效果和基础版一样,动作分离主打在代码的简化和模型和动作之间的解耦合,视频地址在基础版中。