學習3D遊戲程式設計與設計的第二講《離散模擬引擎基礎》。下載試用了軟體Unity3D,完成製作小遊戲井字棋。
本部落格分為三部分:
1. 解釋遊戲物件(GameObjects) 和 資源(Assets)的區別與聯絡。
- 遊戲物件表示某些資源的具體範例化,出現在遊戲的場景中,遊戲物件一般有敵人,場景,攝像機等非實體虛擬父類別,子類一般為遊戲內的實體 ;
- 資源表示硬碟中的檔案,儲存在Unity工程的Assets檔案中,資源可以被多個物件使用,也可以作為模板範例化成遊戲內的實體,一般劃分為材質,物件,場景,預設,聲音,指令碼,貼圖等子資料夾中。
區別:遊戲物件是具有一定屬性與功能的類的實體化,對應為Unity中具有對應職能與屬性的元件,例如遊戲中常見的玩家、怪物等;資源是預先準備好的模型、圖片、音樂等,可以直接並重復使用
聯絡:資源可以新增到遊戲物件作為其一部分,而遊戲物件可以儲存作為一種資源以便捷地重複使用
2. 下載幾個遊戲案例,分別總結資源、物件組織的結構(指資源的目錄組織結構與遊戲物件樹的層次結構)
從資源網站下載遊戲憤怒的小鳥、賽車遊戲、2048遊戲、畫素鳥遊戲等資源後,進行總結後,這裡以憤怒的小鳥為例。
遊戲資源的目錄組織結構一般包含:Animation、Matreias、Prefab、Scene、Scripts、Sprites。
遊戲物件樹的層次結構一般包含:Camera、Background、Light和一些自定義物件等。
3. 編寫一個程式碼,使用 debug 語句來驗證 MonoBehaviour 基本行為或事件觸發的條件
編寫如下程式碼並新增到Main Camere:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class test : MonoBehaviour
{
void Start()
{
Debug.Log("Start");
}
void Update()
{
Debug.Log("Update");
}
void Awake()
{
Debug.Log("Awake");
}
void FixedUpdate()
{
Debug.Log("FixedUpdate");
}
void LateUpdate()
{
Debug.Log("LateUpdate");
}
void OnGUI()
{
Debug.Log("OnGUI");
}
void OnDisable()
{
Debug.Log("OnDisable");
}
void OnEnable()
{
Debug.Log("OnEnable");
}
}
執行結果如下:
- Awake() :當一個指令碼範例被載入時被呼叫;
- Start() : 在所有update()函數被呼叫之前呼叫;
- Update():當行為啟用時其Update()在每一幀被呼叫;
- FixedUpdate():當行為啟動時其Update()在每一時間片被呼叫;
- LateUpdate():當行為啟用時,在所有Update()執行完後執行;
- Onable() :當物件變為可用或啟用狀態時此函數被呼叫,OnEnable不能用於協同程式(物體啟動時被呼叫);
- Ondisable() :當物件變為不可用或非啟用狀態時此函數被呼叫。當物體被銷燬時他被呼叫,並且可用於任意清理程式碼 。當指令碼編譯完成之後重新載入時,該函數被呼叫,OnEnable()在指令碼被載入後被呼叫。(物體被禁用時呼叫) ;
- OnGUI() :繪製GUI時觸發。一般在這個函數裡繪製GUI選單(GUI顯示函數只能在OnGUI中被呼叫)。
4. 查詢指令碼手冊,瞭解 GameObject,Transform,Component 物件
- 分別翻譯官方對三個物件的描述(Description)
GameObeject:Base class for all entities in Unity Scenes.
翻譯:Unity場景中所有實體的基礎類別。
Transform:Position, rotation and scale of an object.Every object in a Scene has a Transform. It’s used to store and manipulate the position, rotation and scale of the object. Every Transform can have a parent, which allows you to apply position, rotation and scale hierarchically.
翻譯:物件的位置旋轉和比例。場景中每個物件都有一個變換。他用於操作物件的位置,縮放和旋轉。每個轉換都可以擁有一個父類別,允許分層應用位置。
Component:Base class for everything attached to GameObjects.
翻譯:所有附加到GameObject的東西的基礎類別。
描述下圖中 table 物件(實體)的屬性、table 的 Transform 的屬性、 table 的部件
table:第一個選擇框時便籤Tag:當前選擇的是未加標籤;第二個選擇框是層,當前是預設屬性;第三個屬性是預設。
table的Transform:有三個屬性:Position、Rotation、Scale,分別表示位置,旋轉,比例
table的部件:Transform、Cube、Mesh Renderer、Box Colloder等。
用 UML 圖描述 三者的關係(請使用 UMLet 14.1.1 stand-alone版本出圖)
5. 資源預設(Prefabs)與 物件克隆 (clone)
使遊戲物件可重複使用,這個遊戲物件已經設定完成,可以直接加入到遊戲中去,提高了工作效率。
預設與克隆都可以由一個物件生成大量相同的物件,但是預設出的物件在本體改變時回隨著本體進行改變,而克隆出的物件之間沒有聯絡,可以隨意更改。
參考課件,製作table後將其拖入Assets,自動形成table預製。
將 table 預製資源範例化成遊戲物件,程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class createTable : MonoBehaviour
{
public GameObject table;
// Start is called before the first frame update
void Start()
{
GameObject temp = (GameObject)Instantiate(table, transform.position, transform.rotation);
temp.name = "instance";
}
void Update()
{
}
}
將createTable新增到Main Camera,並把之前做好的table預製拖入選擇框。
點選執行,效果如下圖:
井字棋——專案地址
實現功能:
- 共O、X兩種棋子,點選Start遊戲開始;
- O棋先手,點選方塊O、X棋子交替落棋;
- 兩種贏法:橫著三格連成,豎著三格連成;
- 三種結局:O棋贏、X棋贏、平局。
主要程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
private int[,] chessBoard = new int[3, 3];
private int turn = 1;//決定誰落子
void Start()
{
Reset();//初始化介面
}
int checkWin()//判斷勝負
{
for(int i = 1; i < 3; i++)
{
if (chessBoard[i, 0] == chessBoard[i, 1] && chessBoard[i, 0] == chessBoard[i, 2] && chessBoard[i, 0] != 0)
return chessBoard[i, 0];
}//豎著贏
for (int i = 0; i < 3; i++)
{
if (chessBoard[0, i] == chessBoard[1, i] && chessBoard[0, i] == chessBoard[2, i] && chessBoard[0, i] != 0)
return chessBoard[0, i];
}//橫著贏
if (chessBoard[0, 0] == chessBoard[1, 1] && chessBoard[0, 0] == chessBoard[2, 2] && chessBoard[0, 0] != 0)
return chessBoard[0, 0];//斜著贏
int count = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (chessBoard[i, j] != 0)
{
count++;
}
}
}
if (count == 9)//被填滿仍無人勝利,平局
return 0;
return 3;
}
void OnGUI()
{
if (GUI.Button(new Rect(300, 50, 150, 50), "Start Game"))
{
Reset();//點選Start Game按鈕重置遊戲
}
GUISkin skin = GUI.skin;
skin.button.normal.textColor = Color.green;
skin.button.hover.textColor = Color.blue;//設定按鈕樣式
int State = checkWin();//判斷當前狀態2:x贏1:O贏0:平局
if (State == 2)
{
skin.button.normal.textColor = Color.red;
GUI.Label(new Rect(300, 105, 50, 50), "X Win!");
}
else if (State == 1)
{
skin.button.normal.textColor = Color.red;
GUI.Label(new Rect(300, 105, 50, 50), "O Win");
}
else if (State == 0)
{
skin.button.normal.textColor = Color.red;
GUI.Label(new Rect(300, 105, 50, 50), "Tied");
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (chessBoard[i, j] == 1)
{
GUI.Button(new Rect(300 + 50 * i, 130 + j * 50, 50, 50), "O");
}
else if (chessBoard[i, j] == 2)
{
GUI.Button(new Rect(300 + 50 * i, 130 + j * 50, 50, 50), "X");
}
if (GUI.Button(new Rect(300 + 50 * i, 130 + j * 50, 50, 50), ""))
{//按鈕沒被點選時,被點選後改變棋盤狀態,然後根據棋盤狀態改變按鈕顯示
if (State == 3)
{
if (turn == 1)
chessBoard[i, j] = 1;
else if (turn == -1)
chessBoard[i, j] = 2;
turn = -turn;
}
}
}
}
}
void Reset()
{//重置棋盤
turn = 1;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
chessBoard[i, j] = 0;
}
}
}
void Update()
{
}
}
編寫上述程式碼檔案至預製指令碼,拖入Main Camere中,點選執行即可開始遊戲。實現效果如下:
- 微軟 XNA 引擎的 Game 物件遮蔽了遊戲迴圈的細節,並使用一組虛方法讓繼承者完成它們,我們稱這種設計為「模板方法模式」。
- 為什麼是「模板方法」模式而不是「策略模式」呢?
模板方法模式:定義一個演演算法的骨架,將骨架中的特定步驟延遲到子類中。模板方法模式使得子類可以不改變演演算法的結構即可重新定義該演演算法的某些特定步驟。
策略模式:屬於物件的行為模式。其用意是針對一組演演算法,將每一個演演算法封裝到具有共同介面的獨立的類中,從而使得它們可以相互替換。
模板方法可以通過不同的實現來達到不同的效果,而策略模式以不同的實現方法來達到相同的效果。
- 將遊戲物件組成樹型結構,每個節點都是遊戲物件(或數)。
- 嘗試解釋組合模式(Composite Pattern / 一種設計模式)。
- 使用 BroadcastMessage() 方法,向子物件傳送訊息。你能寫出 BroadcastMessage() 的虛擬碼嗎?
組合模式(Composite Pattern),又叫部分整體模式,是用於把一組相似的物件當作一個單一的物件。組合模式依據樹形結構來組合物件,用來表示部分以及整體層次。這種型別的設計模式屬於結構型模式,它建立了物件組的樹形結構。
foreach childObject
sendMessage();
Decorator模式
因為需要對物件動態的新增功能,採用繼承需要修改的程式碼過多,採用Decorate模式,可以在不修改原來程式碼的情況下增加新的功能核模組。且繼承容易導致類與類之間結構混亂。
參考資料:
模板方法模式和策略模式
組合模式
裝飾器模式