在上次做的牧师和魔鬼的游戏中,场记基本负责了所有的工作,加载资源,移动游戏对象,很明显这样的游戏结构很难维护,很难拓展,很不面向对象,所以这次的工作就是利用工厂模式来生产动作。弥补了上次没有上船动作的缺点。
先上UML图:
UML图实在是花的丑,有空再改。
工厂模式解释:
CCAction, CCSequenceAction等就是工厂,他的产品就是SSAction,由SSActionManager统一管理。具体的动作都是继承SSAction进行自定义。这里我定义了两个动作,一个是MoveToAction 用于船的移动,因为船是直线移动同时确定终点起点,第二个是MoveAction,用于上船的运动,因为魔鬼和牧师出发的位置不一定,所以要根据具体位置进行计算重点,但是其移动的距离和方向却是确定的,再利用CCSequenceAction进行简单动作的组合。
每个SSAction都包含一个callback属性,在完成后通知动作的管理者。
值得说明的是,在编写这个游戏的过程中,我遇到了一个bug解决了两天。就是一个动作只能执行一次,下一次就不能正常的执行了。我排查了许久,终于发现是action.destroy 和 action.enable没有进行初始化。
关键代码讲解:
SSActionManager.cs
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() {
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions) {
SSAction ac = kv.Value;
if (ac.destory) {
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;
// just let it move relative to their father
action.transform = gameobject.transform;
action.callback = manager;
/* !!!!!!!!!!!!!!!!!!!!!!!!!!
* bug happen here --20170320 2306
* if this action is used twice, we need to reset
* action.destory and action.enable
* But this is not a good convention
* -- by BowenWu
*/
action.destory = false;
action.enable = true;
waitingAdd.Add(action);
action.Start();
}
protected void Start() {}
}
RunAction方法:
这个方法负责所有动作的执行,其他动作在需要执行的时候就调用这个方法。这个方法会将需要执行的动作放入集合中,在每个Update统一管理,调用Update。这是因为其他动作的类并没有继承MonoBehavior并不会在每帧自动执行,而这个manger是会在每帧自动执行。
waitingAdd队列:
由于动作可能在任何时候进入,同时他进入的时候并不一定会是在一帧刚好结束时,这其中情况很多,不可预料,所以将其先放入waitingAdd队列中,然后在update下一次执行时再使其加入到actions字典中。
waitingDelete队列:
当游戏规模变大时,游戏资源、对象相关的依赖性增加,如果立即销毁对象,可能导致离散引擎并发的行为之间依赖关系产生不可预知错误。所以在渲染前才删去。
以下代码为这次的内容,上次的代码也需要进行少部分改变,那些内容不是很难,如果需要的可以给我留言。
将CCActionManger和GenGameobject挂在空对象上,就可以运行啦!
SSAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject {
public bool enable = true;
public bool destory = false;
public GameObject gameobject {get; set; }
public Transform transform {get; set; }
public ISSActionCallback callback {get; set;}
protected SSAction () {}
public virtual void Start() {
throw new System.NotImplementedException();
}
public virtual void Update() {
throw new System.NotImplementedException();
}
}
SSActionManager.cs
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() {
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions) {
SSAction ac = kv.Value;
if (ac.destory) {
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;
// just let it move relative to their father
action.transform = gameobject.transform;
action.callback = manager;
/* !!!!!!!!!!!!!!!!!!!!!!!!!!
* bug happen here --20170320 2306
* if this action is used twice, we need to reset
* action.destory and action.enable
* But this is not a good convention
* -- by BowenWu
*/
action.destory = false;
action.enable = true;
waitingAdd.Add(action);
action.Start();
}
protected void Start() {}
}
ISSActionCallback.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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);
}
CCActionManager.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback {
public CCMoveToAction boat_to_end, boat_to_begin;
// simple moves used to make a sequence move
public CCMoveAction move_up, move_down;
public CCMoveToAction move_to_boat_left_begin, move_to_boat_right_begin;
public CCMoveToAction move_to_boat_left_end, move_to_boat_right_end;
public CCSequenceAction get_on_boat_left_begin, get_on_boat_right_begin;
public CCSequenceAction get_on_boat_left_end, get_on_boat_right_end;
// public CCSequenceAction get_off_boat;
private float object_speed;
private float boat_speed;
public GenGameObjects sceneController;
protected new void Start() {
object_speed = 4.0f;
boat_speed = 4.0f;
sceneController = (GenGameObjects)SSDirector.getInstance().currentSceneController;
sceneController.actionManager = this;
move_up = CCMoveAction.GetSSAction(new Vector3(0, 1, 0), object_speed);
move_down = CCMoveAction.GetSSAction(new Vector3(0, -1, 0), object_speed);
move_to_boat_left_begin = CCMoveToAction.GetSSAction(new Vector3(-2.3f, 2, 0), object_speed);
move_to_boat_right_begin = CCMoveToAction.GetSSAction(new Vector3(-1.2f, 2, 0), object_speed);
move_to_boat_left_end = CCMoveToAction.GetSSAction (new Vector3 (0.7f, 2, 0), object_speed);
move_to_boat_right_end = CCMoveToAction.GetSSAction (new Vector3 (1.8f, 2, 0), object_speed);
get_on_boat_left_begin = CCSequenceAction.GetSSAction(0, 0, new List<SSAction> {
move_up, move_to_boat_left_begin, move_down
});
get_on_boat_right_begin = CCSequenceAction.GetSSAction(0, 0, new List<SSAction> {
move_up, move_to_boat_right_begin, move_down
});
get_on_boat_left_end = CCSequenceAction.GetSSAction (0, 0, new List<SSAction> {
move_up,
move_to_boat_left_end,
move_down
});
get_on_boat_right_end = CCSequenceAction.GetSSAction (0, 0, new List<SSAction> {
move_up,
move_to_boat_right_end,
move_down
});
boat_to_end = CCMoveToAction.GetSSAction (new Vector3 (1.7f, 0, 0), boat_speed);
boat_to_begin = CCMoveToAction.GetSSAction (new Vector3 (-1.7f, 0, 0), boat_speed);
}
protected new void Update() {
base.Update();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null) {
Debug.Log ("change back Game_state");
sceneController.game_state = GenGameObjects.State.normal;
// sceneController.game_state = sceneController.State.moving;
}
}
CCMoveToAction.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveToAction : SSAction {
public Vector3 target;
public float speed;
public static CCMoveToAction GetSSAction(Vector3 target, float speed) {
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
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.destory = true;
this.callback.SSActionEvent(this);
}
}
public override void Start() {
Debug.Log ("MoveToAction, target is " + target);
// make move on relative cordinate
}
}
CCMoveAction.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveAction : SSAction {
// this kind of action move towards the Vector3
// but not move to a target
public Vector3 distance;
private Vector3 target;
public float speed;
public static CCMoveAction GetSSAction(Vector3 distance, float speed) {
CCMoveAction action = ScriptableObject.CreateInstance<CCMoveAction>();
action.distance = distance;
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.destory = true;
this.callback.SSActionEvent(this);
}
}
public override void Start() {
target = this.transform.position + distance;
Debug.Log ("on MoveAction Start!");
Debug.Log ("target is :" + target);
}
}
CCSequenceAction.cs:
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.sequence = sequence;
action.start = start;
return action;
}
public override void Update() {
/*if (sequence.Count == 0) return;
if (start < sequence.Count) {
sequence[start].Update();
}*/
sequence [start].Update ();
}
public override void Start() {
Debug.Log ("on CCSequenceActionStart");
// Debug.Log (this.transform.parent);
foreach (SSAction action in sequence)
{
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
}
start = 0;
sequence [0].Start ();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null) {
source.destory = false;
this.start++;
if (this.start >= sequence.Count) {
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0) {
this.destory = true;
this.callback.SSActionEvent(this);
}
} else {
sequence [start].Start ();
}
}
}