遊戲設計入門——遊戲程式框架設計
遊戲開發這個世界太廣闊了,這篇文章中,我只在程式實現的抽象邏輯上,開一個口子進行一些膚淺的闡述。
當我去設計一個遊戲,一個玩法,包含規則,遊戲物件等等一系列的遊戲系統,用紙可以寫下來,用嘴可以說出來,但是當我想要用程式去實現這一堆東西的時候,就有些無從下手。
比如我們大家都會下五子棋,它有一個明確的規則“一人一字交替放置棋子,率先5顆連成一線獲勝”,在現實世界中,我們不用考慮五子棋的硬體配置,拿一張白紙,畫上橫縱格子,兩人分別執筆就可以完成一場五子棋的遊戲,或者是使用正規的五子棋盤和棋子。無論用什麼玩,要實現“玩”是很簡單的。
但是在做一個五子棋視訊遊戲的時候,那就需要把“棋子”“棋盤”這些東西用軟體去實現,
進一步,作為抽象概念的“遊戲規則”“勝負判斷”也需要軟體去實現,這就需要更多的“用計算機思維去思考”。
於是我花了一些時間,憑藉有限的本科計算機基礎以及一些自我東拼西湊的自學,總結了一下游戲框架的一個通用設計方案。
一.隨便開個頭
首先闡述一下大的抽象概念,這裡也不用被“抽象”二字嚇到了,什麼是抽象,就是歸納總結出來的結論之類的東西,就像把自行車,飛機,汽車都抽象為“交通工具”一樣,這裡歸納總結一下這些玩意的共性,於是“交通工具”這一個抽象概念就誕生了。
視訊遊戲程式框架,我認為她(she)就是一個迴圈(Loop)過程性,“開始遊戲——殺敵/解密/闖關/…——遊戲勝利/遊戲失敗——開始遊戲”,對吧,關鍵字,迴圈(Loop)的,過程的。
進一步對此概念進行細化,衍生出GameState(遊戲狀態)和GameController(遊戲/規則控制器),至於為什麼會有這兩個概念?我是參考了UE4提供的GameState類和GameMode類,同時也參考了前輩們用Flash寫的遊戲邏輯,至此我總結為GS和GC兩大塊,至於更高階的學術闡述,受限於我學識水平目前還做不到。
二.GameState
GameState就是對遊戲狀態的描述,最基本的兩個,GamePlay(開始遊戲)GameOver(遊戲結束),這裡GameOver不能理解成玩家平時看見的GameOver,玩家平時看見的GameOver大多表示遊戲失敗,而在這裡,這個概念只是表示遊戲結束,因為無論你遊戲勝利還是遊戲失敗,遊戲都結束了(GameOver)。
在此之上,可以進行擴充套件,例如現在遊戲都會有主選單,也有暫停,或者有多人模式,遊戲設定等等,於是我們可以在GameState中新增MainMenu,GamePause,MultiPlay,GameOption等等。
使用GameState進行遊戲狀態的管理有什麼用?最重要的就是給你一個清晰的程式設計實現遊戲的思路,或者說給你一個入手點,至於方便管理遊戲程序,方便後續遊戲程式功能性擴充套件,降低遊戲程式各系統耦合性等等,實在太多了。
至於GameState如何使用?這東西就是用來切換的,作為標識遊戲目前狀態的一個Flag。
之前在閱讀《遊戲人工智慧程式設計案例精粹》這本書中,其中詳解了FSM(有限狀態機)在遊戲AI的應用和擴充套件,以此聯想,對於GameState的處理使用FSM豈不是再合適不過了。《lua遊戲開發實踐指南》中提到“對於Singleton,無論什麼情況下,只要它們提供方便就使用它們”,雖然這句話說的太滿,不太符合中國人的思維,也不符合辯證法(笑),但也某種程度上表明Singleton的實用性和廣泛性。所以更進一步,使用Singleton(單例模式)的FSM處理GameState,以我來看是極好的。
當然,以上是抽象概念的闡述,最終用到遊戲程式設計上,還得按照一些程式語言的語法和特性來實現,這裡,我也給出了一份Unity的C#的原型程式碼,可以當作虛擬碼吧。
Unity的C#的原型程式碼:
publicclassS_GameState : MonoBehaviour {
publicenumGameState
{
GamePlay,
GamePause,
GameOver,
GameReady,
GameInit
}
publicstaticS_GameState Instance;
privateGameState m_GameState;
void Awake()
{
Instance = this;
}
publicGameState GetGameState()
{
returnm_GameState;
}
publicvoid ToGamePlay()
{
m_GameState = GameState.GamePlay;
}
publicvoid ToGamePause()
{
m_GameState = GameState.GamePause;
}
publicvoid ToGameOver()
{
m_GameState = GameState.GameOver;
}
publicvoid ToGameInit()
{
m_GameState = GameState.GameInit;
}
}
補記:
至於FSM和Singleton,請教谷歌老師是一個很好方法,不過,這裡我稍微闡述一些FSM的應用,FSM早些年用於遊戲AI的構建,畢竟遊戲AI不同於科研領域的AI,遊戲AI不是為了讓玩家無法戰勝而設立的,基本上游戲AI就是一套規律的集合,比如《黑暗之魂3》的第一個BOSS——古達,其中一個規律就是半血之後會變身,變身時候有動畫,不會攻擊,玩家可以很安逸的砍空一條體力,然後全身而退,這個規律,打幾百遍古達都不會變。
回到原來話題,FSM廣泛應用於遊戲AI的地位現在基本被行為樹取代了,然後FSM現在就專職做起了角色動畫的管理,比如第三人稱玩家角色的各種動畫切換,UE4和Unity都使用FSM進行動畫狀態切換的管理。
三.GameController
說完了GameState,就該說一下GameController了
GameController就是用來管理遊戲規則的了,比如開始遊戲了,要生成玩家角色,生成敵人,生成地圖等等;玩家按了ESC,要暫停遊戲了;玩家通關了,妙極,GameOver;
所以GameController比起GameState要更加具體,基本上GameController就是要處理GameState切換之後一系列工作,依舊是五子棋,比如GameState從MainMenu切換到了GamePlay,那麼GameController就要載入一個載入畫面,載入結束後,消掉載入畫面,同時顯示一個棋盤等待。當然,OnLoading也可以作為一個GameState,至於要不要這個OnLoading的GameState,就歸到彈性選項裡面好了(笑)。
這裡就需要一個遊戲狀態的檢測,如果遊戲某一時刻切換了狀態,那麼作為GameController就得做點事情了,所以要快!準!狠!
於是在一個獨立執行緒裡面整個一while(true)來不斷迴圈判斷是否遊戲狀態進行了切換
當然GameController也可以使用一個Singleton來實現,我認為是不錯的,不僅可以非同步執行,而且很快,充分滿足快準狠的需要,而且分離了遊戲的渲染執行緒和邏輯執行緒,並行優化/劣化好像很不錯(笑)——對於一般的小型遊戲,多執行緒可能會殺雞用牛刀,反而劣化的遊戲效能。
不過現在遊戲引擎都體統了一個每幀呼叫的函式,所以將遊戲狀態檢測放在裡面也是很好的,以下,提供一個Unity的C#程式碼,以供參考。
Unity的C#的原型程式碼:
void Awake()
{
Instance = this;
}
void Start()
{
m_StartWait = newWaitForSeconds(m_StartDelay);
m_EndWait = newWaitForSeconds(m_EndDelay);
StartCoroutine(GameStateOperator());
}
IEnumeratorGameStateOperator()
{
while(true)
{
switch(S_GameState.Instance.GetGameState())
{
caseS_GameState.GameState.GameInit:
GameInit();
break;
caseS_GameState.GameState.GameReady:
GameReady();
break;
caseS_GameState.GameState.GamePlay:
GamePlay();
break;
caseS_GameState.GameState.GamePause:
GamePause();
break;
caseS_GameState.GameState.GameOver:
GameOver();
break;
}
yieldreturnnull;
}
}
補記:
這裡使用了Unity的偽執行緒——協程,方便使用多執行緒進行參考,同時也為了讓程式碼結構清晰一些。
四.狀態切換
GameState提供了遊戲狀態來切換,GameController提供了狀態切換後的工作處理,那麼問題來了,什麼玩意兒來切換GameState呢?
自然是遊戲物件了,GameObejct,玩家角色,Npc,某個子彈,某些觸發器,鍵盤操作等等。回到上面看看GameState的切換函式使用了public,也是這個原因,反正都是static了,稍微裸一點,我覺得頗為不錯(笑)。
回到原來話題,比如玩家角色也許有一個HP的屬性,當HP==0時,GameOver,呼叫一下GameState的狀態切換函式,將狀態切換稱GameOver,這時,GameController檢測到了GameState切換了,開始做出響應,很完美。
當然GameController也可以用一用狀態切換,比如一些類似關卡倒計時計算需要寫在GameController裡面,當倒計時為0是,遊戲結束,這時候就需要GameController呼叫切換函數了。同樣的,對於玩家角色的HP檢測放到GameController裡面也未嘗不可,不過這顯然不符合OOP的設計原則,這麼明顯的增加耦合性,軟體工程老師要氣死。但是我就是要氣死他(笑)。
補記:
對於遊戲物件的分析和設計,我覺得需要綜合OOP思想和設計原則,雖然設計主流遊戲型別,大多有原型了,比如UE4就很人性化,提供了各種型別遊戲原型,這裡UML之類工具使用起來也是極好的。當然不能忘了UI的設計,UI是要契合遊戲程式的,有效利用GameState是很好的入口點。下一篇我應該會說說UI設計的東西。