游戲代碼也寫了幾年, 有時候在想如果現在給剛入行的自己一點建議也許能有一點幫助。所以這篇主要是分享一下自己對於游戲編程入門的一些想法。希望對你們有幫助!
語言的選擇
在一開始更多是推薦從高級語言入門,比如 Java、C#、lua、python,javascript。在這個時期可以配合一些游戲引擎來學習,如果你是因為喜歡游戲才學習編程的可能不喜歡老是 print 一些文字,可能更喜歡輸出圖片。
可以直接從對應語言的游戲引擎開始做幾個小 Demo 了解一下游戲構造,但是游戲引擎有個弊端就是 API 太多入門有點難……
游戲引擎的優點是做游戲會比較快,但是引擎整體還是略龐大,不適合學習代碼之用。
之后可以使用 pico-8、love2d、pygame、SDL 或者直接 javascript 操作 canvas 畫布來制作嘗試制作游戲挑戰自己編程能力。
而 C 語言(包含 C++)可以放在后續研究,研究 C 語言可以對語言底層點的東西理解。像指針可以讓你直接訪問內存,這在其他語言中是很少見。可能一般感受不出來它的力量,但當你面對資源緊張的系統(需要手動內存管理),比如 GBA 和 NDS 上面的編程時候就繞不開 C 語言甚至匯編語言了。
一定要多動手
我在沒學習編程之前是個愛看書的小青年。所以在我剛開始學習寫代碼時候常常就一直看書沒有實踐,有些東西看似懂了,實際還是要上手操作才能掌握透徹。
在着手寫代碼時候最好是帶着問題去學習,編程其實就是把復雜問題分解。比如在制作一個小 Demo 時候可以考慮這幾個問題:
如何顯示一個圖片/精靈
如何播放幀動畫
如何控制兩個物體碰撞
兩個物體碰撞時候銷毀其中一個同時生成一個物體播放幀動畫特效然后播放完成之后銷毀
如何播放音樂音效
背景層滾動效果怎么做
如何處理存檔(數據持久化)
而之后可以考慮的問題可能有:
精靈的顯示如何分清楚哪個先渲染哪個后渲染(渲染層次)
每次播放特效都生成一個物體然后銷毀是不是有點浪費內存,可不可以一次生成多個重復利用(引入了對象池)
存檔時候如果 A 寫入存檔還沒結束時候 B 又寫入存檔會不會出問題(引入了文件系統使用單例)
不要執着於 Opengl
游戲行業比較流行圖形學,可能感覺起來高達上,但是對於初學者(非初學者其實也一樣)來說更多還是應該專注於 GamePlay。
我在剛開始寫代碼沒多久就跑去拿本 opengl 紅寶書在啃,寫了一堆代碼到頭來也沒學到什么。
一開始不要考慮3D 方面東西,從簡單的2d 開始比較不錯。這個時候我更多推薦看下代碼本色和游戲人工智能來鍛煉一下代碼能力。
甚至如果要學圖形學 Shader 的,我更多建議是先直接用 Unity 上面先學習如何寫一些 Shader(推薦Unity 着色器和屏幕特效開發秘笈),能寫一些比較特效之后然后再回頭去寫那些渲染管線 Opengl 那些會比較有感觸。
不要禁錮在游戲引擎中
現在游戲引擎非常方便,寫軟件的目的就是為了使其越來越容易使用。
但一直使用游戲引擎對於初學編程的人來說會比較難以提高編程水平。個人建議是可以從先從游戲引擎入門,然后嘗試不使用引擎使用上面提到一些底層點的框架或者工具來制作游戲。
造點小輪子有助於提高
很多人都說不要重復造輪子,但有時候別人的輪子比較重量級不夠輕。自己寫點適合自己的輕量級的輪子也對自己編程能力有一定提高,也可以更多享受編程的樂趣。
比如用上文提到的比較底層的框架(比如 SDL)來制作游戲然后自己寫個簡單的地圖編輯器、粒子系統、存檔文件保存讀取系統或者菜單系統。雖然你做的這個跟專業引擎制作的沒法比,但有一天你回到引擎里再來看待一些問題會有不一樣的解決思路。
注意數據結構的應用
數據結構剛學習時候會陷入這東西在游戲開發中能干什么的疑惑之中。除了比較常見尋路用到圖和網絡編程的消息隊列,其他的在我一開始學習數據結構時候都一無所知。
堆棧的應用:在處理菜單時候比如進入設置菜單時候把新菜單壓棧操作,返回時候出棧銷毀即可。在游戲場景進入到一個房間時候可以把當前游戲場景暫停然后把新的房間場景壓棧。
隊列的應用:在一些需要緩沖輸入時候(比如格斗游戲)可以使用隊列來控制輸入操作。在一些技能系統比如一回合有8個體力槽玩家來組合攻擊。在制作回放系統時候也可以使用隊列來制作。
樹的應用:基本是當你需要面對分支而且每個分支都有分支時候需要考慮。典型的比如劇情或者對話樹,在節點下有很多子物體,子物體又可以有很多子物體時候也是它的應用場景。
圖的應用:在可視化流程控制,有限狀態機和導航系統都可以找到它的影子。
游戲開發設計模式應用
這個其實有專門書籍進行闡述, 我也只是班門弄斧而已 ,我只談論幾個對於我初學時候比較有收獲的。
單例模式:在很多人剛開始接觸游戲引擎時候都會面臨一個問題是場景切換不銷毀數據,不推薦的做法也是一些初學者會犯的是把數據保存到本地,然后下個場景再讀取。這種直接進行讀寫操作是不可取而且當你要保存的是一個游戲對象(GameObject)時候就沒辦法了(當然其實也可以序列化對象保存到本地但仍然不可取)。這個時候比較可取的就是保存到一個全局的靜態對象/變量上,這就引入了單例模式。還有比如一些系統只能有一個入口不允許隨便使用不然會出錯,比如典型的文件 IO 和控制器輸入控制。
數據驅動:在 Unity 的 ScriptObject 中明顯就是數據驅動,其核心思想就是改動數據而不必改動代碼。而當你要使用數據驅動時候一定要分清楚什么可以硬編碼什么可以數據驅動,配置太多的話是很影響開發效率和可閱讀性。
降低指針跳轉消耗:我覺得這個也是 ECS 提出的原因之一,在大型游戲中面對的是巨量的游戲物體,這個時候游戲物體中又有各種指針跳轉的話造成的性能消耗就不能忽視了。所以在寫代碼時候要注意降低指針跳轉,盡量讓內存連續分布,比如使用結構體和數組。當然內存連續分布也可以減少內存碎片。
原型模式:這個一開始好像是在 Cocos2d-x 里面看到過,一個接口來實現克隆當前對象。在實際游戲中其實也挺常見,比如 GBA 的木葉戰記中,鳴人使用影分身可以生成一個新的人物但是攻擊力和血量減半。在 Unity 中 Prefab 從 Project 窗口拖動到 Scene 中時候也是使用類似思路(應該吧)。
注意代碼整潔
這個更多是經驗性的東西,我也難說做得很好。保持代碼可讀性是為了日后你或者別人回來看代碼能看得懂。保持函數的單一職責很有必要,當你發現有些代碼重復寫了幾次就應該思考這是否應該放到一個函數中。你必須明白的是 Bug 總是會有的,總有一天你或者別人會重新看這些代碼,如果在寫的時候稍加注意,后期的維護和復用就會相對簡單輕松。
具體可以看下代碼大全、代碼整潔之道和這個視頻。
最后,游戲制作很有趣,寫游戲代碼也很開心,希望你也能享受游戲編程的快樂!