在上次做的牧師和魔鬼的遊戲中,場記基本負責了所有的工作,加載資源,移動遊戲對象,很明顯這樣的遊戲結構很難維護,很難拓展,很不面向對象,所以這次的工作就是利用工廠模式來生產動作。彌補了上次沒有上船動作的缺點。
先上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 ();
}
}
}