游戏简介与MVC版实现
unity 3D 牧师与魔鬼小游戏MVC.Ver-CSDN博客
基本框架
设计思路如下:
- 通过门面模式(控制器模式)输出组合好的几个动作,共原来程序调用。
- 好处,动作如何组合变成动作模块内部的事务
- 这个门面就是 CCActionManager
- 通过组合模式实现动作组合,按组合模式设计方法
- 必须有一个抽象事物表示该类事物的共性,例如 SSAction,表示动作,不管是基本动作或是组合后动作
- 基本动作,用户设计的基本动作类。 例如:CCMoveToAction
- 组合动作,由(基本或组合)动作组合的类。例如:CCSequenceAction
- 接口回调(函数回调)实现管理者与被管理者解耦
- 如组合对象实现一个事件抽象接口(ISSCallback),作为监听器(listener)监听子动作的事件
- 被组合对象使用监听器传递消息给管理者。至于管理者如何处理就是实现这个监听器的人说了算了
- 例如:每个学生做完作业通过邮箱发消息给学委,学委是谁,如何处理,学生就不用操心了
- 通过模板方法,让使用者减少对动作管理过程细节的要求
- SSActionManager 作为 CCActionManager 基类
最终,程序员可以方便的定义动作并实现动作的自由组合,做到:
- 程序更能适应需求变化
- 对象更容易被复用
- 程序更易于维护
删除原来控制物体移动的move和moveController,替代为以下部分
另外增加一名裁判JudgeController,以及对场记FirstController作修改(修改后依然只需要将FirstController脚本挂载到空对象上即可运行游戏)。
代码修改
1. SSAction & ISSActionCallback
动作基类和回调函数接口
SSAction是动作的基类,单个动作类和组合动作类都继承自它。
SSAction还包含了一个回调函数的接口ISSActionCallback.
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();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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);
}
2. SSActionManager
动作管理者基类
动作管理者基类并不直接作为动作管理者使用,它的存在是为调用者提供较简单和通用的接口,我们真正用到的CCActionManager继承自它。
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>();
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. 单个动作CCMoveToAction与组合动作CCSequenceAction
如上所说,它们都继承自动作基类SSAction,各有各的表现。单个动作就是只有一个动作,比如船只的移动;组合动作是多个动作,比如角色的移动分为两个部分,横向移动和纵向移动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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);
}
}
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;
//生产函数(工厂模式)
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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;
}
}
控制部分:裁判类与FirstController场记(主控制器)
裁判类将原来FirstController中的check函数抽离出来,专门做游戏状态的检测,在游戏结束时通过回调通知FirstController场记。
FirstController在原来的基础上将check外包给裁判类(不再调用它)。
以及实现了裁判类的回调函数,当裁判类判定游戏结束时作出处理。
在初始化时,将动作管理器和裁判都加载到游戏对象上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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;
}
}
}
}
using UnityEngine;
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 isRunning, 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;
}
}
}
游戏演示与参考博客
游戏演示:与mvc版效果无异
https://www.bilibili.com/video/BV1Tw411F7DW/?vd_source=6ce8b142a8043023f7a4a519f42f3e4f