Unity3D學習筆記(四)牧師和魔鬼遊戲改進

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


先上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 ();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章