3D 遊戲編程與設計 - 離散仿真引擎基礎作業

簡答題

1.

解釋 遊戲對象(GameObjects) 和 資源(Assets)的區別與聯繫。

GameObject 是運行時在場景中被渲染的實體對象。而 Asset 是遊戲的資源,包括預設、圖片、腳本、場景等靜態文件。
遊戲的 GameObject 是遊戲資源的載體,GameObject 由預設產生,可以算是預設的實例;GameObject 還可以添加組件,掛載腳本,此時掛載的腳本還可以綁定現有的材質。也就是說,其實 GameObject 就是遊戲資源的組裝成品以及設置。
特別的,GameObject 也可以產生 Prefab,Prefab 可以產生 GameObject。

2.

下載幾個遊戲案例,分別總結資源、對象組織的結構(指資源的目錄組織結構與遊戲對象樹的層次結構)

3.

編寫一個代碼,使用 debug 語句來驗證 MonoBehaviour 基本行爲或事件觸發的條件

  • 基本行爲包括 Awake() Start() Update() FixedUpdate() LateUpdate()
  • 常用事件包括 OnGUI() OnDisable() OnEnable()
    添加腳本 TestScript 並掛載到 Directional Light 上:
using UnityEngine;
public class TestScript : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Awake");
    }

    void Start()
    {
        Debug.Log("Start");
    }

    void FixedUpdate()
    {
        Debug.Log("FixedUpdate");
    }

    void Update()
    {
        Debug.Log("Update");
    }

    void LateUpdate()
    {
        Debug.Log("LateUpdate");
    }

    void OnGUI()
    {
        Debug.Log("OnGUI");
    }

    void OnEnable()
    {
        Debug.Log("OnEnable");
    }

    void OnDisable()
    {
        Debug.Log("OnDisable");
    }

    void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}

運行後可以看到如下輸出:發現調用順序爲 AwakeOnEnableStartUpdateLateUpdateOnGUIOnDisableOnDestroy
在這裏插入圖片描述
點擊停止按鈕後的日誌如下:
在這裏插入圖片描述
可以看到和官方網站提供的 MonoBehaviour 的生命週期一致:
在這裏插入圖片描述

4.

查找腳本手冊,瞭解 GameObject,Transform,Component 對象

  • 分別翻譯官方對三個對象的描述(Description)
  • 描述下圖中 table 對象(實體)的屬性、table 的 Transform 的屬性、 table 的部件
    • 本題目要求是把可視化圖形編程界面與 Unity API 對應起來,當你在 Inspector 面板上每一個內容,應該知道對應 API。
    • 例如:table 的對象是 GameObject,第一個選擇框是 activeSelf 屬性。
  • 用 UML 圖描述 三者的關係(請使用 UMLet 14.1.1 stand-alone版本出圖)

GameObject

GameObjects are the fundamental objects in Unity that represent characters, props and scenery. They do not accomplish much in themselves but they act as containers for Components, which implement the real functionality.
Class GameObject is the base class for all entities in Unity Scenes.

也就是說 GameObject 是 Unity 中表示遊戲玩家、場景、物品的基礎對象。GameObject 是 Prefab 的實例,GameObject 還是 Component 的容器,一個 GameObject 上可以掛載多個 Component 定製 GameObject 的行爲。

Transform

The Transform component determines the Position, Rotation, and Scale of each object in the scene. Every GameObject has a Transform.

表示 GameObject 的位置、方向、縮放。

Component

Components are the nuts & bolts of objects and behaviors in a game. They are the functional pieces of every GameObject. If you don’t yet understand the relationship between Components and GameObjects, read the GameObjects page before going any further.

Component 是描述 GameObject 行爲的對象,負責控制 GameObject 的生命週期以及行爲,一個 GameObject 上可以有多個 Component。

5.

整理相關學習資料,編寫簡單代碼驗證以下技術的實現:

  • 查找對象
  • 添加子對象
  • 遍歷對象樹
  • 清除所有子對象

查找對象

// Finds a GameObject by name and returns it.
public static GameObject Find(string name);
// Returns a list of active GameObjects tagged tag. Returns empty array if no GameObject was found.
// 參數:
//   tag:
//     The name of the tag to search GameObjects for.
public static GameObject[] FindGameObjectsWithTag(string tag);
public static GameObject FindGameObjectWithTag(string tag);
// Returns one active GameObject tagged tag. Returns null if no GameObject was found.
// 參數:
//   tag:
//     The tag to search for.
public static GameObject FindWithTag(string tag);

驗證:

// SceneBehaviour,這個腳本綁定在了場景中的某一個對象上
void Start()
{
	GameObject table = GameObject.Find("Table");
	Debug.Log(table.GetHashCode());
}

// TableBehaviour,這個腳本綁定在了 Table 對象上
void Start()
{
	Debug.Log(gameObject.GetHashCode());
}

可以看到兩次的輸出是一致的,說明找到了同一個對象。
注:C# 默認的 GetHashCode() 函數返回的是對象的地址,可以反映是否是同一個對象。
在這裏插入圖片描述

添加子對象

void Start()
{
    GameObjecttable = GameObject.Find("Table");
    GameObject chair5 = GameObject.CreatePrimitive(PrimitiveType.Cube);
    chair5.name = "Chair5";
    chair5.transform.position = new Vector3(0, 2, 0);
    chair5.transform.localScale = new Vector3(1, 0.2f, 1);
    chair5.transform.parent = table.transform;
}

可以看到新創建了一個方塊:
在這裏插入圖片描述

遍歷對象樹

void Traverse(Transform transform)
{
	foreach (Transform child in transform)
	{
		Debug.Log(child.name);
		Traverse(child);
	}
}

清除所有子對象

foreach (Transform child in parent.transform)
{
    Destroy(child.gameObject);
}

6.

資源預設(Prefabs)與 對象克隆 (clone)

  • 預設(Prefabs)有什麼好處?
  • 預設與對象克隆 (clone or copy or Instantiate of Unity Object) 關係?
  • 製作 table 預製,寫一段代碼將 table 預製資源實例化成遊戲對象

預設有什麼好處?

可以複用相似的遊戲對象,減少建模的複雜度。而且複用可以使得批量更改所有的遊戲對象。

預設與對象克隆關係?

預設的修改會影響相關的所有遊戲對象。但對象克隆後兩者其實沒有關係,修改其中一個不會影響另一個。

寫一段代碼將 table 預製資源實例化成遊戲對象

編程實踐,小遊戲

遊戲內容: 井字棋 或 貸款計算器 或 簡單計算器 等等
技術限制: 僅允許使用 IMGUI 構建 UI
作業目的:提升 debug 能力提升閱讀 API 文檔能力

遊戲截圖

在這裏插入圖片描述

遊戲實現

UML 結構圖

在這裏插入圖片描述

GuiComponent

GuiComponent 爲所有 2D 元素的基類,因此按鈕、計時器、數字顯示屏、棋盤都是 GuiComponent 的派生類。

遊戲全部使用 IMGUI 構建,由於 IMGUI 過於原始,我們需要自己封裝 Gui 類來幫助我們完成工作。

GuiComponent 本身使用了組合模式,每個 GuiComponent 有自己的孩子,每個孩子的定位相對於父組件定位,最後組件形成了組件樹。

GuiTileBoard

GuiTile 爲棋盤的每一個方塊,並維護方塊當前狀態是未被揭開、已經揭開、插旗等。還負責自己的點擊事件的處理。

GuiTileBoard 實現了遊戲的邏輯,負責計算當前局面是勝利還是失敗。GuiTileBoard 每次都會查詢所有的 GuiTile 並維護當前局面形勢。

思考題

1

微軟 XNA 引擎的 Game 對象屏蔽了遊戲循環的細節,並使用一組虛方法讓繼承者完成它們,我們稱這種設計爲“模板方法模式”。

  • 爲什麼是“模板方法”模式而不是“策略模式”呢?

模板方法模式:通過基類定義算法流程,並將算法的實現細節步驟通過抽象方法的方式要求子類實現補全算法。強調補全算法細節,以及強調原本算法本身的結構以及執行流程。
策略模式:允許運行時修改對象行爲。強調修改的行爲本身的獨立性。
此處遊戲循環本身是一個算法流程,強調了先初始化,再循環,最後退出的算法流程,我們的任務是補全該算法,實現特定的程序。而這些流程之間是有聯繫的,是整個算法不可分割的一部分。因此強調行爲獨立性的策略模式在此不適用。

2

將遊戲對象組成樹型結構,每個節點都是遊戲對象(或數)。

  • 嘗試解釋組合模式(Composite Pattern / 一種設計模式)。
  • 使用 BroadcastMessage() 方法,向子對象發送消息。你能寫出 BroadcastMessage() 的僞代碼嗎?

組合模式

組合模式(Composite Pattern),又叫部分整體模式,是用於把一組相似的對象當作一個單一的對象。組合模式依據樹形結構來組合對象,用來表示部分以及整體層次。這種類型的設計模式屬於結構型模式,它創建了對象組的樹形結構。

BroadcastMessage

我們爲 Table 添加 TableScript 如下:

public class TableScript : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            BroadcastMessage("SayHello");
        }
    }
}

Chair 添加 ChairScript 如下:

public class ChairScript : MonoBehaviour
{
    void SayHello()
    {
        Debug.Log("Hello! " + transform.position.x + " " + transform.position.y + " " + transform.position.z);
    }
}

運行後點擊鼠標可以發現 4 個 chair 對象的座標都已經被打印出來了。
在這裏插入圖片描述

自己實現 BroadcastMessage

爲了實現 BroadcastMessage,並支持調用相應的重載方法,我們需要遍歷所有的函數,並且確保相應函數可以被調用:

public class TableScript : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            MyBroadcastMessage("SayHello", this);
            MyBroadcastMessage("SayHello", 123);
        }
    }

    public void MyBroadcastMessage(string methodName, params object[] parameters)
    {
        foreach (Transform child in this.transform)
        {
            foreach (MonoBehaviour behaviour in child.gameObject.GetComponents<MonoBehaviour>())
            {
                foreach (var method in behaviour.GetType().GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public))
                {
                    if (method.Name == methodName && method.GetParameters().Length == parameters.Length)
                    {
                        bool ok = true;
                        for (int i = 0; i < parameters.Length; ++i)
                        {
                            if (!method.GetParameters()[i].ParameterType.IsAssignableFrom(parameters[i].GetType()))
                            {
                                ok = false;
                                break;
                            }
                        }
                        if (ok)
                        {
                            method.Invoke(behaviour, parameters);
                        }
                    }
                }
            }
        }
    }
}

運行之後可以看到兩個 SayHello 函數都能被正常調用
在這裏插入圖片描述

3

一個遊戲對象用許多部件描述不同方面的特徵。我們設計坦克(Tank)遊戲對象不是繼承於GameObject對象,而是 GameObject 添加一組行爲部件(Component)。

  • 這是什麼設計模式?
  • 爲什麼不用繼承設計特殊的遊戲對象?

這是什麼設計模式?

我認爲這合用了模板模式策略模式
其中,Componet 本身使用了模板模式,是完成對象的遊戲循環算法的類型,此外,遊戲對象本身可以掛載多個 Component,這表明,遊戲對象本身使用了策略模式,允許通過 Component 修改 GameObject 本身的行爲。

爲什麼不用繼承設計特殊的遊戲對象?

  1. Delegation 允許動態修改遊戲對象行爲,而繼承不允許,因此繼承的靈活性差
  2. Delegation 性能比繼承好
  3. Delegation 允許組合,而繼承不能
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章