1. 程式人生 > >Unity3D課程學習筆記(三)

Unity3D課程學習筆記(三)

1.Fantasy Skybox FREE 構建自己的遊戲場景

其實這個操作很簡單,只要在Asset Store中搜索Fantasy Skybox FREE,然後在Camera中新增Component,然後新增Skybox,再將相應的Skybox圖案新增上去,就能夠完成了。


新增天空盒具體的操作可以參照下面左圖的圖示,在主攝像機Main Camara中新增元件Component->Rendering->Skybox.

    

將這個元件新增以後,能夠看到如上邊右圖所示的屬性欄,其中有一個Custom Skybox的屬性,將自己喜歡的天空盒模型拖拽到裡面,然後按執行,就能夠得到自己喜歡的場景了,看到一幅比較逼真的畫面,比上次製作太陽系製作的盒子創造的背景好看多了,同時也省去了下面的牧師與魔鬼的背景載入器。

效果圖如下所視:


這是在添加了Terrain後的場景示意圖,可以在Terrain中創造一些自己的場景,具體的一些方法可以在下面的圖示中看到,在這些設定中,添加了自己的assets以後,就能夠創造出自己喜歡的樹,草,地板等樣式,這樣一個遊戲環境就能夠大概的創造出來,而這個過程中並沒有自己去寫程式碼。

2.遊戲物件總結

遊戲物件主要包括:空物件,攝像機,光線,3D物體,聲音,UI基於事件的new UI系統和粒子系統與特效
- 空物件(Empty):不顯示卻是最常用的物件之一
- 攝像機(Camara):觀察遊戲世界的視窗
- 光線(Light):遊戲世界的光源,讓遊戲世界富有魅力
- 3D物體 :3D遊戲中的重要組成部分,可以改變其網格和材質,三角網格是遊戲物體表面的唯一形式
- 聲音(Audio):3D遊戲中的聲音來源

3.程式設計實踐,牧師與魔鬼動作分離版

本次作業需要在上次作業的基礎上,將場記中的動作分離出來,首先要建立動作管理器

動作管理是遊戲內容的重要內容,這次作業要搞清楚簡單動作的組合問題,實現動作管理器,實現基礎動作類,回撥實現動作完成通知(經典方法),其規劃與設計的UML圖如下所示:


動作基類(SSAction)的設計,在下面程式碼中

ScriptableObject是不需要繫結GameObject物件的可程式設計基類。這些物件受Unity引擎場景管理

protected SSAction()是防止使用者自己new物件

使用virtual申明虛方法,通過重寫實現多型。這樣繼承者就能明確使用Start和Update程式設計遊戲物件行為

利用介面實現訊息通知,避免與動作管理者直接依賴

SSAction的相關程式碼如下所示:

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() {}

    public virtual void Start() {
        throw new System.NotImplementedException();
    }

    public virtual void Update() {
        throw new System.NotImplementedException();
    }
}

在動作分離版本的牧師與惡魔中,通過寫具體的動作類來實現動作的分離,這些動作類都會通過動作管理器來管理,然後對傳入其中的物件進行動作分配。

在下面程式碼中,通過重寫Update和Start來實現多型,在動作完成後,將這個SSAction的destroy設定為true,並且將訊息傳遞通知管理者,就能夠將動作銷燬回收。

CCMoveToAction的作用主要是遊戲中移動的動作,通過傳入遊戲物件的位置和設定好的動作,就能夠使遊戲物件移動起來。

CCMoveToAction的相關程式碼如下所示:

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);
		if(this.transform.position == target) {
			this.destroy = true;
			this.callback.SSActionEvent(this);
		}
	}

	public override void Start() {}
}

接下來是組合動作實現的類CCSequenceAction,這個類建立一個動作順序執行序列,在下面的程式碼中,repeat的值為-1表示動作無限迴圈,而start則表示動作開始

Update的重寫則是表示執行當前的動作

而SSActionEvent則是一個回撥通知的動作,當收到當前動作執行完成後,則推下一個動作,如果完成一次迴圈,則減少它的次數。如果當所有動作完成,就通知動作的管理者,將其銷燬。

而Start的重寫則是表示,在執行動作前,為每個動作注入當前動作的遊戲物件,並將自己作為動作事件的接收者。

SSActionEvent的相關程式碼如下所示:

public class CCSequenceAction : SSAction, ISSActionCallback {
    public List<SSAction> sequence;
    public int repeat = -1; //repeat forever
    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 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 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);
            }
            else {
                sequence[start].Start();
            }
        }
    }

    private void OnDestroy() {
        //destory
    }
}

接下來是動作事件介面的程式碼分析,在定義了時間處理介面以後,所有的事件管理者都必須實現這個介面來實現事件排程。所以,組合事件需要實現它,事件管理器也必須實現它。

SSActionEventType的相關程式碼如下所示:

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);
}

然後就到動作管理器的設計了,在動作管理器中RunAction的作用是提供了一個新動作的方法,並且該方法把遊戲物件與動作繫結,並繫結該動作事件的訊息接收者。相關程式碼如下所示:

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>();

    void Start() {}

    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.destroy) {
                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;
        action.transform = gameobject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();
    }
}

最後就是關鍵的事例,個人理解,上面的內容都相當於是模版一樣,真正要做到實現應用的還是下面這個CCActionManager,這個能夠具體將相關的物件執行起來

例如moveBoat動作,原來是在FirstController裡面的通過呼叫Move()方法來實現的,但是在動作分離後,動作管理者管理著Move這個動作,在CCActionManager中通過返回一個SSAction的例項,將相應運動的引數傳送到這個動作例項裡面,然後通過RunAction把遊戲物件和動作聯絡起來,從而達到遊戲物件運動的效果。

而牧師或者魔鬼的移動則是分開為兩部分執行的,其中一部分,例如上傳動作,則是先水平移動到船的上方,然後再垂直移動,因此有兩個動作action1,action2,將其加入到動作列表中,1表示只執行一次,0則表示開始。其他部分與單個動作的執行過程相同。

public class CCActionManager : SSActionManager, ISSActionCallback
{
    public FirstController sceneController;
    public CCMoveToAction MoveBoatAction;
    public CCSequenceAction MoveCharacterAction;

    // Use this for initialization
    protected void Start () {
        sceneController = (FirstController)Director.getInstance().currentScenceController;
        sceneController.actionManager = this;
    }

    public void moveBoat(GameObject boat, Vector3 target, float speed) {
        MoveBoatAction = CCMoveToAction.GetSSAction(target,speed); //將相應的引數傳遞給SSAction例項
        this.RunAction(boat, MoveBoatAction, this);//通過呼叫RunAction將遊戲物件和動作聯絡起來,實現動作
    }

    public void moveCharacter(GameObject character, Vector3 middle, Vector3 end, float speed) {
        SSAction MoveToMiddle = CCMoveToAction.GetSSAction(middle,speed);
        SSAction MoveToEnd = CCMoveToAction.GetSSAction(end,speed);
        MoveCharacterAction = CCSequenceAction.GetSSAction(1, 0, new List<SSAction>{ MoveToMiddle, MoveToEnd });
        this.RunAction(character, MoveCharacterAction, this);
    } 

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
        int intParam = 0, string strParam = null, Object objectParam = null) {}
}

其它部分的程式碼,其實跟上一次作業的程式碼差不多,只不過是把moveablescript中相關的程式碼去掉,交給動作管理器來管理,然後我把上次的程式碼的類做了一些合併操作。具體程式碼可以看我的Github

最後上傳一張遊戲的效果圖,其實和上次的作業差不多,但是多了幾個bug,其實這次作業是真的難,很多程式碼都要借鑑別人的,要是自己一個人寫還真寫不出來,很多地方還是不太理解。