終...終於到了實作的部分了,鴿了這麼久,想必讀者也習慣了吧(不過有沒有人看我也不知道)。到今天也差不多體會到鐵人賽的驚險了?!沒屯稿的話每天被文章追著跑。
那事不宜遲,我們開始吧。首先要先提到的是遊戲引擎的架構,我們的解決方案中會需要兩個專案。
Engine
首先我們會需要一個動態庫,這個函式庫將會包含我們整個引擎。遊戲本體會透過動態連接的方式引用我們的引擎。至於為什麼是使用動態庫而非靜態庫,我們稍後會在下面做說明。
Sandbox
接著我們需要一個應用程式,這是我們的遊戲本體。
LIB
靜態庫是一種副檔名為.lib的二進位文件,編譯與Linking時Linker會從函式庫裡面複製Function和Data與應用程式組合併生成最後的可執行檔案.exe,這種做法只需要可執行檔案就能執行,不需要一併附上靜態庫.lib檔案。
DLL
動態庫編譯時會生成兩個文件,.lib檔、.dll檔。而引用動態庫與靜態庫有頗大的區別,.lib檔包含DLL導出的函數和變數,.dll檔案則是包含完整實際的Function和Data。編譯與Linking時只需要Linking .lib檔,而.dll檔則是在應用程式運行時才會加載DLL,動態的訪問函式庫去導出所需要的Function或Data。這樣輸出時需要可執行檔案和DLL才能執行。
因此我們可以知道,靜態函式庫與動態函式庫雖然都可以達到一樣的效果,但是使用動態函式庫時需要一併附上DLL檔案,若是DLL檔案遺失則會造成無法執行的錯誤;但靜態函式庫裡會將所有需要的Function或Data包含在最終的執行檔中,造成檔案會比較大。那在於我們的專案中會使用許多的第三方函式庫,若是以靜態庫的方式去連接會造成遊戲本體非常的龐大,這也是我們選擇使用動態函式庫連接的原因。
接著可以打開前幾天示範使用的test資料夾。我們可以先將前幾天的src資料夾刪除,我們等等會來重新配置專案。
接下來我們可以來重新配置CMakeLists,這裡的include是幾天前CMake章節中沒有提到的部分,他會尋找並執行你給定檔案的CMake code,這麼做的用意是可以更有系統的去配置你的專案,也更易讀。由於我們需要一個引擎的動態庫與一個名為Sandbox的應用程式,也先建立兩個以此為名的資料夾與同名的CMake檔
檔案配置如下
test
├─ CMakeLists.txt
├─ GenerateProject.bat
├─ engine
│ └─ engine.cmake
└─ sandbox
└─ sandbox.cmake
先將CMakeLists做修改。
cmake_minimum_required(VERSION 3.21)
project(Engine
VERSION 0.0.0
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 17)
include(engine/engine.cmake)
include(sandbox/sandbox.cmake)
接著先寫一段動態庫的CMake模板。
# 這裡等等會隨著進度加上函式庫的原始碼與標頭檔
set(SOURCES
)
set(HEADERS
)
# 建立專案,名為engine,並將
add_library(engine SHARED ${SOURCES} ${HEADERS})
# 未來添加第三方函式庫時需要
target_link_libraries(engine
)
# 未來添加include目錄時需要
target_include_directories(engine PRIVATE
)
# 未來添加definitions時需要
target_compile_definitions(engine PRIVATE
)
接著建立一段應用程式的模板。可以發現這裡與上面動態庫的模板多了一個add_dependencises,這是要將應用程式與動態庫建立依賴,提醒編譯器要先生成依賴項再生成應用程式以免出錯。
# 這裡等等會隨著進度加上應用程式的原始碼與標頭檔
set(SOURCES
)
set(HEADERS
)
# 建立應用程式專案
add_executable(sandbox ${SOURCES} ${HEADERS})
# 新增依賴項
add_dependencies(sandbox
engine
)
# 未來添加第三方函式庫時需要
target_link_libraries(sandbox
engine
)
# 未來添加include目錄時需要
target_include_directories(sandbox PRIVATE
)
# 未來添加definitions時需要
target_compile_definitions(sandbox PRIVATE
)
首先,我們先在Engine底下建立src/Core資料夾作為核心部分原始碼儲存部分,然後建立一個Application Class作為我們引擎的本體。而這個Class最後將在Sandbox中使用,因此我們需要加上__declspec(dllexport)
的關鍵字,用於從 DLL 匯出資料、函數、類別或類別成員函式的關鍵字。在Class裡面,我們定義了Run(),用於啟動應用程式。與一個會回傳Application指標的Function CreateApplication(),他將會在應用程式宣告時被定義。
#pragma once /* 預處理的指令,保證標頭檔只被編譯一次 */
/* 引擎相關的都會被放在Engine namespace */
namespace Engine {
class __declspec(dllexport) Application
{
public:
Application();
~Application();
void Run();
};
Application* CreateApplication();
}
接著,我們在實現的部分。Run()為啟動引擎,就先簡單的跑一個無限迴圈。
#include "Application.h"
namespace Engine {
Application::Application() {}
Application::~Application() {}
void Application::Run()
{
while (true)
{
/* code */
}
};
}
最後是Entry Point的部分,引擎的進入點、也是整個動態函式庫的進入點。extern是引用外部變數的意思,因為我們的CreateApplication()並非定義在這個檔案中。而我們在啟動app之前也能先簡單的用標準輸出來印個"Welcome to Engine!\n"。
#pragma once
extern Engine::Application* Engine::CreateApplication();
int main(int argc,char** argv)
{
printf("Welcome to Engine!\n");
auto app = Engine::CreateApplication();
app->Run();
delete app;
}
這裡我們也寫一個包含所有引擎內容的標頭檔。注意這裡的順序,由於EntryPoint使用了print函式,因此stdio的header必須在其前面。
#pragma once
#include<stdio.h>
#include "Core/Application.h"
#include "Core/EntryPoint.h"
然後我們到Sandbox底下建立src資料夾,並建立一個App。這裡就先include Engine 的all Header。並且將CreateApplication()定義,這個檔案以後我們在擴充時再細講他的用途。
#include <Engine.h>
Engine::Application* Engine::CreateApplication()
{
return new Engine::Application();
}
寫完這幾個檔案後我們的資料夾架構會是這樣。
test
├─ CMakeLists.txt
├─ GenerateProject.bat
├─ engine
│ ├─ engine.cmake
│ ├─ Engine.h
│ └─ src
│ └─ Core
│ ├─ Application.cpp
│ ├─ Application.h
│ └─ EntryPoint.h
└─ sandbox
├─ sandbox.cmake
└─ src
└─ App.cpp
到這裡還並沒有結束,我們新增了不少檔案,我們將它們加進去CMake中。
set(SOURCES
engine/src/Core/Application.cpp
engine/src/Core/Application.h
engine/src/Core/EntryPoint.h
)
set(HEADERS
engine/Engine.h
)
add_library(engine SHARED ${SOURCES} ${HEADERS})
target_link_libraries(engine
)
target_include_directories(engine PRIVATE
${CMAKE_SOURCE_DIR}/engine
${CMAKE_SOURCE_DIR}/engine/src
)
target_compile_definitions(engine PRIVATE
)
set(SOURCES
sandbox/src/App.cpp
)
set(HEADERS
)
add_executable(sandbox ${SOURCES} ${HEADERS})
add_dependencies(sandbox
engine
)
target_link_libraries(sandbox
engine
)
target_include_directories(sandbox PRIVATE
${CMAKE_SOURCE_DIR}/engine
${CMAKE_SOURCE_DIR}/engine/src
)
target_compile_definitions(sandbox PRIVATE
)
完成後我們就能建置與執行了。
今天完成了基礎的CMake的設置與應用程式的進入點,我們明天開始就慢慢加東西進去吧!