本次实验的任务为:在Lab5已完成的内容上进行改进。改进要求如下:
1.将所有动作(上下船、船的移动)分离出来
2.设计一个裁判类,能够判定游戏是否结束并通知给场景控制器Controller。
若要查看Lab5中的牧师与魔鬼游戏设计,请点击:
以下是改进内容:
一,动作分离
相较于初始版本中将动作设计为一个class的方式(public class Move : MonoBehaviour),本次改进中将所有动作都分离封装到另一个单独的 C# script中,命名为Action.cs。
1.所有动作的父类
在这里,我们需要先设计好两个动作(移动船和移动角色)的父类,以便后续使用。代码如下:
public class SSAction : ScriptableObject
{
public bool enable = true; // 是否正在进行此动作
public bool destroy = false; // 用于在动作执行完毕之后销毁该动作对象
public GameObject gameobject; // 执行动作的实际对象(船或角色)
public Transform transform; // 动作对象的transform
public ISSActionCallback callback; // 回调函数
protected SSAction() { } // protected保证SSAction不会被new
// 子类可以使用这两个函数
public virtual void Start() {
throw new System.NotImplementedException();
}
public virtual void Update() {
throw new System.NotImplementedException();
}
}
其中ISSActionCallback将会在后续实现。
2.点到点移动的动作类
该动作子类继承于上面的动作父类,设计用于单段直线运动,参数仅有目的地坐标和速度。
public class SSMoveToAction : SSAction // 移动
{
public Vector3 target; // 移动到的目的地
public float speed; // 移动的速度
private SSMoveToAction() { }
public static SSMoveToAction GetSSAction(Vector3 target, float speed) {
// 让unity自己创建一个MoveToAction实例,并自己回收
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 override void Start() {
// 移动动作建立时候不做任何事情
}
}
3.动作的队列——用于多段移动的存储
该动作子类继承于上面的动作父类,设计用于角色移动这样的多段移动。
public class SequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence; // 动作的列表
public int repeat = -1; // 判定是否做完列表中的所有动作
public int start = 0; // 当前做的动作的索引
public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence) {
// 让unity自己创建一个SequenceAction实例
SequenceAction action = ScriptableObject.CreateInstance<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(); // 在Update函数中会有start++
}
public void SSActionEvent(SSAction source, 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() {
// 如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
}
}
4.回调函数
这个函数初始化为接口,被动作的父类初始化为一个参数,而函数在动作队列、动作管理器这些类中重构,用处在于让 动作 向 动作队列/动作管理器 发送信息,告知动作已结束。
public interface ISSActionCallback
{
void SSActionEvent(SSAction source, /*SSActionEventType events = SSActionEventType.Competeted,*/
int intParam = 0, string strParam = null, Object objectParam = null);
}
5.动作管理器和游戏管理器
动作管理器如下:
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 Controllor sceneController;
protected void Start() {
sceneController = (Controllor)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);
}
}
至此,动作的分离就设计好啦,只需要在场景控制器Controller中调用以上方法,即可操作游戏。
二,裁判类的设计
裁判类的作用即为判定游戏是否开始、是否已结束等状态,并将游戏状态返回给场景控制器Controller的usergui.sign这一参数中,从而使usergui其显示对应内容。代码如下:
public class Judger
{
LandModel start_land; // 初始化两岸和船对应的变量
LandModel end_land; // 用于对岸上和船上的牧师和魔鬼计数
BoatModel boat; // 从而判定游戏状态
//将游戏内初始化好的两岸和船赋值给裁判类的变量
public Judger(LandModel bl, LandModel el, BoatModel b) {
start_land = bl;
end_land = el;
boat = b;
}
public int Check() {
int start_priest = (start_land.GetRoleNum())[0];
int start_devil = (start_land.GetRoleNum())[1];
int end_priest = (end_land.GetRoleNum())[0];
int end_devil = (end_land.GetRoleNum())[1];
if (end_priest + end_devil == 6) // 游戏胜利
return 3;
int[] boat_role_num = boat.GetRoleNum();
if (boat.GetBoatSign() == 1) { // 船在开始岸时,开始岸的计数要包括船上
start_priest += boat_role_num[0];
start_devil += boat_role_num[1];
} else { // 船在结束岸时,结束岸的计数要包括船上
end_priest += boat_role_num[0];
end_devil += boat_role_num[1];
}
if ((start_priest > 0 && start_priest < start_devil) || (end_priest > 0 && end_priest < end_devil)) { //游戏失败
return 2;
}
return 1; //游戏正在进行中
}
}
三.总结
以上就是动作分离版牧师和魔鬼的核心改动内容。完整内容请看以下链接: