自製遊戲引擎 - 0 - Hello world

自我介紹:Tanki,SMU Guildhall,Game Design Master Programming track 在讀。本系列用以記錄自己的開發過程,(也可以作為興趣愛好者的科普讀物),歡迎大家各種姿勢吐槽。

在進入 Guildhall 之前,也曾經幾度想嘗試著手寫一個引擎。雖然讀了《遊戲引擎架構》,但是仍然發現在寫代碼時候無從著手。最近發現遊戲引擎從某方面來說和 Web development 中的前後端框架並沒有什麼差別,意義還是在於能夠更好的服務業務(遊戲)開發本身。而相比較互聯網公司的各種業務而言,各個 Game Studio 對於遊戲引擎的要求更加差異化,所以遊戲引擎的定製程度都比較高。

我的遊戲引擎將圍繞遊戲開發本身進行拓展,以能夠實現遊戲各項 Feature 為目標來實現引擎的各項功能。

在開發時,將遵循以下幾個原則:

  • 迭代開發,不考慮沒有遇到的性能問題(但是會遵循一些簡單原則)
  • 盡量剋制開發暫時用不到的功能,防止過度設計

技術選型:C++ 配合 MSBuild

在語言的選擇上毫無疑問還是選擇了 C++,原因有很多,但是最大的兩點:高效,跨平台。而 Visual Studio 作為遊戲業界標準,可以基本絲滑實現大多數平台包括主機的構建發布,在此不多做介紹,之後會專門寫一篇文章來作說明。

編碼標準:insomniac Game Coding standard

第一個遊戲:小行星(Asteroids)遊戲

下圖演示了遊戲的基本需求:

以及這是一個豪華版(Polished w/ Juice):

渲染循環

對於圖形化操作系統,因為每一幀變換的內容事實上很少,所以採用矩陣失效(Matrix

Invalidation)的方法更新 UI 界面,僅重繪製部分變換內容。對於遊戲系統來說,由於各種內部外部因素,場景內容變換很大,傾向於使用類似於電影的手法,既連續播放單幀靜止圖像。由於遊戲每一幀內容依賴於實時接受各種外部的輸入信號,無法採用離線渲染(Offline Rendering),使用實時渲染(Real-time Rendering)渲染循環(Render Loop)是遊戲實現的核心過程,可以簡單用以下代碼表示:

while(g_notQuitting) { beforeFrame(); update(); render(); afterFrame();}

其中,beforeFrame 用來處理各種系統調用,接受並更新各個系統的狀態。update 將負責更新場景內物體,攝像機等的相關物理狀態,進行一系列的相關運算,例如更新物體的位置(position),運動速度(velocity),朝向(Orientation)等。render 中將不作任何運算(事實上,我們暫時可以認為render中調用的方法在函數簽名最後都跟有const),僅根據各項參數在窗口中進行圖形繪製。afterFrame 將處理現場,更新相關狀態。

Hello Engine

對於引擎中各個子系統的構造將在之後的文章中介紹。在此之前,我們希望能夠創造一個能夠運行的程序。

從網上可以找到許多 OpenGL 版本的 Hello world 程序(參考),這裡不多做描述。但是需要能做到以下內容:

  • 創建窗口,並進行正確的(符合預期的)繪製
  • 能夠順利接受用戶的鍵盤輸入,按esc可以退出程序。

Vector2

首先我將在引擎中加入一些基本的 primitive type。Vector 系列類型是遊戲開發中最常見的基礎數據結構,最常見的構造方式為一組 float 類型的變數集合。由於暫時僅僅需要使用二維空間,所以暫時只創建了 Vector2 類型:

class Vector2 {public: // Construction/Destruction ~Vector2 () {} Vector2 () {} Vector2 (const Vector2& copyFrom); explicit Vector2 (float initialX, float initialY); // Operators const Vector2 operator+(const Vector2& vecToAdd) const; // vec2 + vec2 const Vector2 operator-(const Vector2& vecToSubtract) const; // vec2 - vec2 const Vector2 operator*(float uniformScale) const; // vec2 * float const Vector2 operator/(float inverseScale) const; // vec2 / float void operator+=(const Vector2& vecToAdd); // vec2 += vec2 void operator-=(const Vector2& vecToSubtract); // vec2 -= vec2 void operator*=(const float uniformScale); // vec2 *= float void operator/=(const float uniformDivisor); // vec2 /= float void operator=(const Vector2& copyFrom); // vec2 = vec2 bool operator==(const Vector2& compare) const; // vec2 == vec2 bool operator!=(const Vector2& compare) const; // vec2 != vec2 float getLength() const; float getLengthSquared() const; // faster than GetLength() since it skips the sqrtf() float normalizeAndGetLength(); // set my new length to 1.0f; keep my direction Vector2 getNormalized() const; // return a new vector, which is a normalized copy of me float getOrientationDegrees() const; // return 0 for east (5,0), 90 for north (0,8), etc. static Vector2 makeDirectionAtDegrees(float degrees); // create vector at angle friend const Vector2 operator*(float uniformScale, const Vector2& vecToScale); // float * vec2public: float x; float y;};

MathUtils

另外還需要創建一系列常用的工具方法:

#include <stdint.h>#define PI (3.1415926535897932384626433832795f)class Vector2;class Disc2;float convertRadiansToDegrees (float radians);float convertDegreesToRadians (float degrees);float cosDegrees (float degrees);float sinDegrees (float degrees);float getRandomFromZerotoOne();float getRandomInRange(float minInclusive, float maxInclusive);int32_t getRandomInt32InRange(int32_t minInclusive, int32_t maxInclusive);float getSquaredDistance(const Vector2& a, const Vector2& b);float getDistance(const Vector2& a, const Vector2& b);

需要注意的是關於幾個隨機函數的實現,下列代碼是 getRandomFromZerotoOne

的實現:

float getRandomFromZerotoOne() { return (float)rand() / (float)RAND_MAX;}

需要在 cpp 文件中 include <Math.h>,基於該函數實現,getRandomInRange 只需要對值域區間作初等變換即可。而對於getRandomInt32InRange,採用 rand() % (max -min) + min 來實現,雖然比較粗暴並且存在一些邊界問題並不是非常準確,但是對於我們的小行星遊戲來說已經足夠了。(Random 本身存在很多性能問題,並且我們的確有很多方法可以生成隨機數,這些將在以後涉及時進行優化)

至此,我們已經擁有了一些基礎的設施,進行一些簡單的封裝後,就可以在畫布上實現一些簡單功能:

  • 按某個鍵在隨機位置生成正多邊形(稍作改動後就可以變成隕石了)
  • 按某個鍵刪除一個多邊形

在下一篇文章中,我(暫時決定)將介紹:

  • C++ 多文件管理,How to include
  • 全局坐標系與局部坐標系
  • Entity class 以及相關子類
  • 讓世界動起來

還在不斷學習中,歡迎各位讀者指出文中的錯誤。謝謝閱讀!


推薦閱讀:

[CppCon14] How Ubisoft Montreal develops games formulticore – before and after C++11
從零開始寫引擎(OPENGL)(四)-後處理實現
[GDC16] Optimizing the Graphics Pipeline with Compute
[翻譯]Metal Gear Solid V – Graphics Study

TAG:遊戲引擎 | 遊戲開發 |