你想了解更多關(guān)于游戲引擎的知識(shí)、并自己來(lái)寫(xiě)一個(gè)嗎?
這可是非常牛皮的一件事。為了幫助你學(xué)習(xí),這里有一些C++庫(kù)和依賴(lài)項(xiàng)的推薦,可以幫助你快速上手。
游戲開(kāi)發(fā)一直是我的學(xué)生學(xué)習(xí)更高級(jí)計(jì)算機(jī)科學(xué)主題的好幫手。我的一位導(dǎo)師Sepi博士曾經(jīng)說(shuō)過(guò):
“有些人認(rèn)為游戲是孩子的東西,但游戲開(kāi)發(fā)是少數(shù)幾個(gè)幾乎使用了標(biāo)準(zhǔn)CS課程所有內(nèi)容的領(lǐng)域之一?!? Sepideh Chakaveh博士
她說(shuō)的完全不假!如果將隱藏在現(xiàn)代游戲開(kāi)發(fā)棧下的內(nèi)容暴露出來(lái),我們會(huì)發(fā)現(xiàn),它觸及了許多計(jì)算機(jī)科學(xué)專(zhuān)業(yè)學(xué)生所熟悉的概念。
算法與數(shù)據(jù)結(jié)構(gòu)
游戲程序員需要在內(nèi)存中表示游戲?qū)ο螅⒃诳紤]性能(數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)局部性)的前提下訪(fǎng)問(wèn)對(duì)應(yīng)的數(shù)據(jù)。
數(shù)學(xué)
游戲開(kāi)發(fā)需要不少應(yīng)用數(shù)學(xué)領(lǐng)域的助力。很多游戲都利用了離散數(shù)學(xué)、線(xiàn)性代數(shù)、微積分、概率、數(shù)值分析以及其他數(shù)學(xué)分支。
計(jì)算機(jī)體系結(jié)構(gòu)
引擎開(kāi)發(fā)人員需要很好地理解目標(biāo)機(jī)器的結(jié)構(gòu),以獲得盡可能快的速度。
圖形
圖形技術(shù)對(duì)游戲開(kāi)發(fā)至關(guān)重要。著色器、頂點(diǎn)、并行加速、2D&3D渲染和GPU api都是開(kāi)發(fā)者使用的圖形技術(shù)的典型例子。
編譯器和形式語(yǔ)言
引擎通常需要把腳本語(yǔ)言暴露給關(guān)卡設(shè)計(jì)師,這通常意味著解釋某種高級(jí)定制語(yǔ)法。
人工智能
游戲?qū)ο螅〝橙撕蚽pc)通常需要表現(xiàn)出類(lèi)似人類(lèi)的智能。典型例子是尋路算法、追蹤、機(jī)器學(xué)習(xí)和可見(jiàn)性方法。
網(wǎng)絡(luò)
多人游戲和社交整合在現(xiàn)代游戲中很常見(jiàn)。大多數(shù)游戲必須利用好設(shè)備之間的互聯(lián),通過(guò)網(wǎng)絡(luò)協(xié)議發(fā)送/接收多個(gè)數(shù)據(jù)包。
UI和UX
UI和UX也是游戲開(kāi)發(fā)技術(shù)的重要組成部分,這意味著向用戶(hù)展示信息以及提供合適的反饋。
根據(jù)你所制作的游戲的本質(zhì),你可能還需要深入到更專(zhuān)業(yè)的領(lǐng)域,例如分布式系統(tǒng)或人機(jī)交互。游戲開(kāi)發(fā)是一項(xiàng)嚴(yán)肅的業(yè)務(wù),它也完全可以成為學(xué)習(xí)嚴(yán)肅的CS概念的強(qiáng)大工具。
本文將介紹使用C++創(chuàng)建簡(jiǎn)單游戲引擎所需的一些基本構(gòu)建模塊。我將解釋游戲引擎中所需要的主要元素,并就如何從頭開(kāi)始編寫(xiě)游戲引擎給出一些個(gè)人建議。
話(huà)雖如此,這并非一個(gè)編程教程。我不會(huì)介紹太多的技術(shù)細(xì)節(jié),也不會(huì)解釋如何用代碼將所有這些元素粘合在一起。如果你正在尋找一本關(guān)于編寫(xiě)C++游戲引擎的綜合教程,這是一個(gè)很好的起點(diǎn):
什么是游戲引擎?
如果你正在閱讀這篇文章,那么你可能已經(jīng)很清楚什么是游戲引擎,甚至可能自己也用過(guò)。但為了達(dá)成共識(shí),我們來(lái)快速回顧一下什么是游戲引擎,以及它們能幫助我們實(shí)現(xiàn)什么。
游戲引擎是一組優(yōu)化游戲開(kāi)發(fā)的軟件工具。它們可以是小型且極簡(jiǎn)的,只提供一個(gè)游戲循環(huán)和一些渲染功能,也可以是大型且綜合性的,類(lèi)似于IDE類(lèi)應(yīng)用,在當(dāng)中開(kāi)發(fā)者可以編寫(xiě)腳本,調(diào)試,定制關(guān)卡邏輯,AI,設(shè)計(jì),發(fā)布,協(xié)作,并最終從頭到尾構(gòu)建游戲,整個(gè)過(guò)程不需要離開(kāi)引擎。
游戲引擎和游戲框架通常會(huì)將API暴露給用戶(hù)。API允許程序員調(diào)用引擎函數(shù)執(zhí)行困難的任務(wù),就像它們是黑盒一樣。
為了真正理解這樣的API是如何工作的,先來(lái)點(diǎn)鋪墊。例如,游戲引擎API把名為“IsColliding()”的函數(shù)暴露出來(lái)并不罕見(jiàn),開(kāi)發(fā)者可以調(diào)用該函數(shù)來(lái)檢查兩個(gè)游戲?qū)ο笫欠癜l(fā)生碰撞。程序員不需要知道這個(gè)函數(shù)是如何實(shí)現(xiàn)的,也不需要知道正確確定兩個(gè)形狀是否重疊所需的算法是什么。就我們所關(guān)心的而言,IsColliding函數(shù)就是一個(gè)黑盒,它做了一些神奇的事情,并在這些對(duì)象可能相互碰撞時(shí)正確地返回true或false。這是大多數(shù)游戲引擎都會(huì)向用戶(hù)暴露的功能。
if (IsColliding(player, bullet)) {
lives--;
if (lives == 0) {
GameOver();
}
}
除了編程API,游戲引擎的另一個(gè)重要職責(zé)是硬件抽象。例如,3D引擎通常構(gòu)建在專(zhuān)用的圖形API(例如OpenGL、Vulkan或Direct3D)之上。這些API為圖形處理單元(GPU)提供了抽象。
說(shuō)到硬件抽象,還有一些低級(jí)庫(kù)(如DirectX、OpenAL和SDL)提供了對(duì)許多其他硬件元素的抽象和多平臺(tái)訪(fǎng)問(wèn)。這些庫(kù)幫我們?cè)L問(wèn)和處理鍵盤(pán)事件、鼠標(biāo)移動(dòng)、網(wǎng)絡(luò)連接,甚至音頻。
游戲引擎的崛起
在游戲行業(yè)的早期,游戲是用自定義的渲染引擎構(gòu)建的,代碼的開(kāi)發(fā)是為了從較慢的機(jī)器上榨取盡可能多的性能。每個(gè)CPU時(shí)鐘周期都是至關(guān)重要的,因此代碼重用或適用于多種場(chǎng)景的通用函數(shù)并非開(kāi)發(fā)者可以負(fù)擔(dān)得起的奢侈品。
而隨著游戲、開(kāi)發(fā)團(tuán)隊(duì)的規(guī)模和復(fù)雜性的增長(zhǎng),大多數(shù)工作室最終都會(huì)在游戲之間重用功能和子程序。工作室開(kāi)發(fā)的內(nèi)部引擎基本上是處理低級(jí)任務(wù)的內(nèi)部文件和庫(kù)的集合。這些功能讓開(kāi)發(fā)團(tuán)隊(duì)的其他成員能夠?qū)W⒂谟螒蛲娣?、地圖創(chuàng)建和關(guān)卡定制等高級(jí)細(xì)節(jié)。
一些流行的經(jīng)典引擎包括id Tech、Build和AGI。創(chuàng)建這些引擎是為了幫助特定游戲的開(kāi)發(fā),它們讓團(tuán)隊(duì)其他成員能快速開(kāi)發(fā)新關(guān)卡,添加自定義資產(chǎn),以及動(dòng)態(tài)自定義地圖。這些自定義引擎也用于mod或?yàn)樵及姹緞?chuàng)建擴(kuò)展包。
Id Tech由Id Software開(kāi)發(fā)。Id Tech是不同引擎的集合,每次迭代都與不同的游戲相關(guān)聯(lián)。我們經(jīng)常聽(tīng)到開(kāi)發(fā)者將id Tech 0描述為“德軍總部3D引擎”,將id Tech 1描述為“毀滅戰(zhàn)士引擎”,id Tech 2描述為“Quake引擎”。
Build是另一個(gè)塑造90年代游戲歷史的引擎例子。它由Ken Silverman創(chuàng)造,用于幫助定制第一人稱(chēng)射擊游戲。與id Tech類(lèi)似,Build也隨著時(shí)間的推移而發(fā)展,其不同版本幫助程序員開(kāi)發(fā)了《毀滅公爵3D》、《影子武士》和《Blood》等游戲。它們可以說(shuō)是用Build引擎創(chuàng)建的最受歡迎的游戲,通常被稱(chēng)為“三巨頭”。
90年代的另一個(gè)游戲引擎例子是“Manic Mansion的腳本創(chuàng)建工具”(SCUMM)。SCUMM是LucasArts開(kāi)發(fā)的引擎,它是許多經(jīng)典的點(diǎn)擊類(lèi)游戲(如《猴島小英雄》和《極速天龍》)的基礎(chǔ)。
SCUMM腳本語(yǔ)言用于管理《極速天龍》中的對(duì)話(huà)和操作。
隨著機(jī)器進(jìn)化得越來(lái)越強(qiáng)大,游戲引擎也隨之發(fā)展?,F(xiàn)代引擎裝滿(mǎn)了功能豐富的工具,這些工具需要更快的處理器速度、巨量的內(nèi)存和專(zhuān)用顯卡。
有了多余的動(dòng)力,現(xiàn)代引擎用時(shí)鐘周期換取了更多的抽象。這種權(quán)衡意味著我們可以將現(xiàn)代游戲引擎視為以低成本和短開(kāi)發(fā)周期創(chuàng)作復(fù)雜游戲的通用工具。
問(wèn)題來(lái)了,我們?yōu)槭裁匆谱饔螒蛞婺兀?/p>
這是一個(gè)非常常見(jiàn)的問(wèn)題,不同的游戲程序員會(huì)根據(jù)所開(kāi)發(fā)游戲的性質(zhì)、業(yè)務(wù)需求和其他驅(qū)動(dòng)力,對(duì)這個(gè)議題有著自己的看法。
開(kāi)發(fā)者可以使用很多免費(fèi)、強(qiáng)大且專(zhuān)業(yè)的商業(yè)引擎來(lái)創(chuàng)作和部署自己的游戲。既然有這么多游戲引擎可供選擇,為什么還會(huì)有人不厭其煩地從頭開(kāi)始制作游戲引擎呢?
我在之前的一篇博文里,解釋了程序員決定從頭開(kāi)始制作游戲引擎的一些可能原因。在我看來(lái),最主要的原因是:
學(xué)習(xí)機(jī)會(huì):對(duì)游戲引擎工作原理的底層理解可以幫助你成長(zhǎng)為一名開(kāi)發(fā)者。
工作流控制:你可以更好地控制你自己游戲的特殊要素,并根據(jù)自己的工作流需求調(diào)整解決方案。
自定義:你將能夠?yàn)楠?dú)特的游戲需求量身定制解決方案。
極簡(jiǎn)化:較小的代碼庫(kù)可以減少大型游戲引擎帶來(lái)的開(kāi)銷(xiāo)。
創(chuàng)新:你可能需要實(shí)現(xiàn)一些全新的東西,或者瞄準(zhǔn)其他引擎不支持的非傳統(tǒng)硬件。
在接下來(lái)的討論中,我將假設(shè)你對(duì)游戲引擎在教育學(xué)習(xí)層面的吸引力感興趣。從頭開(kāi)始創(chuàng)造一個(gè)小型游戲引擎是我向所有CS學(xué)生強(qiáng)烈推薦的內(nèi)容。
如何制作游戲引擎
所以,在快速討論了使用和開(kāi)發(fā)游戲引擎的動(dòng)機(jī)之后,我們繼續(xù)來(lái)討論游戲引擎的一些組件,并學(xué)習(xí)如何自己編寫(xiě)一個(gè)游戲引擎。
1. 選擇編程語(yǔ)言
我們面臨的第一個(gè)抉擇,是挑選用于開(kāi)發(fā)核心引擎代碼的編程語(yǔ)言。我見(jiàn)過(guò)用原始匯編、C、C++以及高級(jí)語(yǔ)言(如c#、Java、Lua,甚至JavaScript)來(lái)開(kāi)發(fā)引擎!
編寫(xiě)游戲引擎最流行的語(yǔ)言之一是C++。C++編程語(yǔ)言將速度與運(yùn)用OOP及其他編程范式的能力相結(jié)合。這些編程范式能幫助開(kāi)發(fā)者組織和設(shè)計(jì)大型軟件項(xiàng)目。
當(dāng)我們開(kāi)發(fā)游戲時(shí),性能通常是非常重要的,C++具有編譯語(yǔ)言的優(yōu)勢(shì)。編譯語(yǔ)言意味著最終的可執(zhí)行文件將在目標(biāo)機(jī)器的處理器上原生運(yùn)行。還有許多專(zhuān)門(mén)的C++庫(kù)和開(kāi)發(fā)工具包,適用于大多數(shù)現(xiàn)代主機(jī),如PlayStation或Xbox。
說(shuō)到性能,我個(gè)人不推薦使用虛擬機(jī)、字節(jié)碼或任何其他中間層的語(yǔ)言。除了C++,一些適合編寫(xiě)核心游戲引擎代碼的現(xiàn)代替代方法是Rust、Odin和Zig。
在本文的剩余部分,我的建議將假設(shè)讀者希望使用C++編程語(yǔ)言構(gòu)建一個(gè)簡(jiǎn)單的游戲引擎。
2. 硬件訪(fǎng)問(wèn)
在老式的操作系統(tǒng)(如MS-DOS)中,我們通??梢灾苯佣ㄎ坏絻?nèi)存地址并訪(fǎng)問(wèn)映射到不同硬件組件的特殊位置。例如,我要做的就是用表示VGA調(diào)色板正確顏色的數(shù)字來(lái)加載一個(gè)特殊的內(nèi)存地址,然后顯示驅(qū)動(dòng)程序?qū)⒏臑槲锢硐袼氐膬?nèi)容轉(zhuǎn)換到CRT監(jiān)視器中。
隨著操作系統(tǒng)的進(jìn)化,它們開(kāi)始負(fù)責(zé)保護(hù)硬件不受程序員的侵害?,F(xiàn)代操作系統(tǒng)不允許代碼修改操作系統(tǒng)允許給到進(jìn)程的地址之外的內(nèi)存位置。
例如,如果你使用的是Windows、macOS、Linux或BSD,則需要向操作系統(tǒng)請(qǐng)求正確的權(quán)限,以便在屏幕上繪制像素或與任何其他硬件組件對(duì)話(huà)。即使是形如“在操作系統(tǒng)桌面上打開(kāi)一個(gè)窗口”這樣的簡(jiǎn)單任務(wù),也必須通過(guò)操作系統(tǒng)API來(lái)執(zhí)行。
因此,運(yùn)行進(jìn)程、打開(kāi)窗口、在屏幕上呈現(xiàn)圖形、在窗口內(nèi)繪制像素,甚至從鍵盤(pán)讀取輸入事件都是特定于操作系統(tǒng)的任務(wù)。
SDL是一個(gè)非常流行的庫(kù),可以幫忙實(shí)現(xiàn)多平臺(tái)硬件抽象。我個(gè)人喜歡在教授游戲開(kāi)發(fā)課程時(shí)使用SDL,因?yàn)橛肧DL,我不需要為Windows學(xué)生創(chuàng)建一個(gè)版本的代碼,為macOS學(xué)生創(chuàng)建一個(gè)版本的代碼,又為L(zhǎng)inux學(xué)生創(chuàng)建另一個(gè)版本的代碼。SDL不僅是不同操作系統(tǒng)之間的橋梁,也是不同CPU架構(gòu)(Intel、ARM、Apple M1等)之間的橋梁。SDL庫(kù)抽象了底層硬件訪(fǎng)問(wèn),并“翻譯”了我們的代碼,以在這些不同的平臺(tái)上正確工作。
下面是“用SDL在操作系統(tǒng)上打開(kāi)一個(gè)窗口”的一小段代碼。下面的代碼對(duì)于Windows、macOS、Linux、BSD甚至RaspberryPi都是一樣的。
#include <SDL2/SDL.h>
void OpenNewWindow() {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("My Window", 0, 0, 800, 600, 0);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
}
但SDL只是我們可以用來(lái)實(shí)現(xiàn)這種多平臺(tái)硬件訪(fǎng)問(wèn)的庫(kù)的眾多例子之一。對(duì)于2D游戲和將現(xiàn)有代碼移植到不同平臺(tái),SDL是一個(gè)流行的選擇。另一個(gè)流行的多平臺(tái)庫(kù)選項(xiàng)是GLFW,主要用于3D游戲和3D引擎。GLFW庫(kù)與加速3D api(如OpenGL和Vulkan)之間的通信非常好。
3.游戲循環(huán)
打開(kāi)操作系統(tǒng)窗口后,我們需要?jiǎng)?chuàng)建一個(gè)可控制的游戲循環(huán)。
簡(jiǎn)而言之,我們通常希望游戲以每秒60幀的速度運(yùn)行。幀速率可能因游戲而異,但從整體來(lái)看,電影膠片拍攝的幀速率為24 FPS(每秒鐘閃過(guò)24張圖像)。
游戲循環(huán)在gameplay中持續(xù)運(yùn)行,在每次循環(huán)中,我們的引擎都需要跑一些重要的任務(wù)。傳統(tǒng)的游戲循環(huán)必須:
處理輸入事件,不阻塞
更新當(dāng)前幀的所有游戲?qū)ο蠹捌鋵傩?/p>
在屏幕上渲染出所有游戲?qū)ο蠛推渌匾畔?/p>
while (isRunning) {
Input();
Update();
Render();
}
這是一個(gè)很袖珍的while循環(huán)。完事兒了嗎?明顯沒(méi)有。
原始的C++循環(huán)對(duì)我們來(lái)說(shuō)還不夠好。游戲循環(huán)必須與現(xiàn)實(shí)世界的時(shí)間有某種關(guān)系。畢竟游戲中的敵人在任何機(jī)器上都應(yīng)該以相同的速度移動(dòng),不管它們的CPU時(shí)鐘速度怎樣。
控制幀率并將其設(shè)置為固定FPS,實(shí)際上是一個(gè)非常有趣的問(wèn)題。這通常需要我們跟蹤幀與幀之間的時(shí)間,并進(jìn)行一些合理的計(jì)算,以確保我們的游戲在至少30幀/秒的幀速率下平穩(wěn)運(yùn)行。
4. 輸入
我無(wú)法想象一款不用讀取用戶(hù)輸入事件的游戲。這些輸入可以來(lái)自鍵盤(pán)、鼠標(biāo)、手柄或VR設(shè)備。因此,我們必須在游戲循環(huán)中處理不同的輸入事件。
為了處理用戶(hù)輸入,我們必須請(qǐng)求訪(fǎng)問(wèn)硬件事件,而這必須通過(guò)操作系統(tǒng)API來(lái)執(zhí)行。好消息是,我們可以使用多平臺(tái)硬件抽象庫(kù)(SDL、GLFW、SFML等)來(lái)為我們處理用戶(hù)輸入。
如果我們用了SDL,則可以輪詢(xún)事件,并通過(guò)幾行代碼進(jìn)行相應(yīng)處理。
void Input() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_SPACE) {
ShootMissile();
}
break;
}
}
}
同樣,如果使用形如SDL這樣的跨平臺(tái)庫(kù)來(lái)處理輸入,我們不必太擔(dān)心針對(duì)特定操作系統(tǒng)的實(shí)現(xiàn)。不管目標(biāo)平臺(tái)是什么,我們的C++代碼都應(yīng)該是一樣的。
在擁有一個(gè)有效的游戲循環(huán)和處理用戶(hù)輸入的方法后,我們就該開(kāi)始考慮如何在內(nèi)存中組織游戲?qū)ο罅恕?/p>
5. 在內(nèi)存中表示游戲?qū)ο?/p>
在設(shè)計(jì)游戲引擎時(shí),需要設(shè)置數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)和訪(fǎng)問(wèn)咱們的游戲?qū)ο蟆?/p>
程序員在構(gòu)建游戲引擎時(shí)可以使用多種技術(shù)。一些引擎可能會(huì)使用簡(jiǎn)單的面向?qū)ο蟮姆椒ǎ?lèi)和繼承,而另一些引擎則可能將其對(duì)象組織為實(shí)體和組件。
如果你的目標(biāo)之一是學(xué)習(xí)更多關(guān)于算法和數(shù)據(jù)結(jié)構(gòu)的知識(shí),我建議你嘗試自己實(shí)現(xiàn)這些數(shù)據(jù)結(jié)構(gòu)。如果你使用C++,一種選擇是使用STL(標(biāo)準(zhǔn)模板庫(kù)),充分利用它自帶的許多數(shù)據(jù)結(jié)構(gòu)(向量、列表、隊(duì)列、堆棧、map、set等)。C++ STL在很大程度上依賴(lài)于模板,所以這是一個(gè)練習(xí)使用模板并在實(shí)際項(xiàng)目中接觸它們的好機(jī)會(huì)。
而當(dāng)你開(kāi)始閱讀更多關(guān)于游戲引擎架構(gòu)的內(nèi)容時(shí),你便會(huì)發(fā)現(xiàn),游戲中最受歡迎的設(shè)計(jì)模式是基于實(shí)體和組件。實(shí)體組件設(shè)計(jì)將游戲場(chǎng)景的對(duì)象組織為實(shí)體(Unity稱(chēng)之為“游戲?qū)ο蟆?,而虛幻稱(chēng)之為“actor”)和組件(我們能添加或附到實(shí)體上的數(shù)據(jù))。
要理解實(shí)體和組件如何協(xié)同工作,請(qǐng)考慮一個(gè)簡(jiǎn)單的游戲場(chǎng)景。實(shí)體將會(huì)是主要玩家、敵人、地板、拋射物,而組件將是我們“附加”到實(shí)體上的重要數(shù)據(jù)塊,如位置、速度、剛體碰撞器等等。
一種流行的游戲引擎設(shè)計(jì)模式便是將游戲元素組織為實(shí)體和組件
下面列出可以選擇附加到實(shí)體上的組件的部分例子:
位置組件:跟蹤實(shí)體在游戲世界中的x-y位置坐標(biāo)(或3D中的x-y-z)。
速度組件:跟蹤實(shí)體在x-y軸(或3D中的x-y-z軸)中的移動(dòng)速度。
精靈組件:它通常存儲(chǔ)我們應(yīng)該為某個(gè)實(shí)體呈現(xiàn)的PNG圖像。
動(dòng)畫(huà)組件:跟蹤實(shí)體的動(dòng)畫(huà)速度,以及動(dòng)畫(huà)幀如何隨時(shí)間變化。
碰撞器組件:這通常與剛體的物理特性有關(guān),并定義了實(shí)體的碰撞形狀(邊界框、邊界圓、網(wǎng)格碰撞器等)。
健康組件:存儲(chǔ)實(shí)體的當(dāng)前健康值。這通常只是一個(gè)數(shù)字,在某些情況下是一個(gè)百分比值(例如一個(gè)血條)。
腳本組件:有時(shí)我們可以將腳本組件附加到實(shí)體,這可能是引擎必須在幕后解釋和執(zhí)行的外部腳本文件(Lua, Python等)。
這是表示游戲?qū)ο蠛椭匾螒驍?shù)據(jù)的一種非常流行的方式。先是實(shí)體,然后會(huì)將不同的組件“插入”到實(shí)體中。
許多書(shū)籍和文章都探討了我們應(yīng)該如何實(shí)現(xiàn)實(shí)體組件設(shè)計(jì),以及在這樣的實(shí)現(xiàn)中應(yīng)該使用什么數(shù)據(jù)結(jié)構(gòu)。我們用到的數(shù)據(jù)結(jié)構(gòu)以及訪(fǎng)問(wèn)它們的方式直接影響著游戲性能。你可能經(jīng)常聽(tīng)到開(kāi)發(fā)者提到諸如“面向數(shù)據(jù)的設(shè)計(jì)”,“實(shí)體-組件-系統(tǒng)”(ECS),“數(shù)據(jù)本地性”和其他許多與游戲數(shù)據(jù)如何存儲(chǔ)在內(nèi)存中、以及如何有效訪(fǎng)問(wèn)這些數(shù)據(jù)有關(guān)的idea。
表示和訪(fǎng)問(wèn)內(nèi)存中的游戲?qū)ο罂梢允且粋€(gè)復(fù)雜的主題。在我看來(lái),你可以手動(dòng)編寫(xiě)一個(gè)簡(jiǎn)單的實(shí)體組件實(shí)現(xiàn),也可以簡(jiǎn)單使用現(xiàn)有的第三方ECS庫(kù)。
我們可以在C++項(xiàng)目中包含一些流行的現(xiàn)成ECS庫(kù),并開(kāi)始創(chuàng)建實(shí)體和附加組件,而不必?fù)?dān)心它們?cè)诘讓邮侨绾螌?shí)現(xiàn)的。形如EnTT和Flecs都是C++ ECS庫(kù)的一些典型例子。
我個(gè)人建議那些認(rèn)真對(duì)待編程的學(xué)生至少?lài)L試一次手動(dòng)實(shí)現(xiàn)簡(jiǎn)易版ECS。即使你的實(shí)現(xiàn)并不完美,從頭開(kāi)始編寫(xiě)ECS系統(tǒng)也會(huì)迫使你考慮底層數(shù)據(jù)結(jié)構(gòu)以及其性能。
現(xiàn)在,嚴(yán)肅來(lái)講,一旦你完成了自定義ECS實(shí)現(xiàn),我會(huì)鼓勵(lì)你使用一些流行的第三方ECS庫(kù)(EnTT、fecs等)。這些是經(jīng)過(guò)業(yè)界多年開(kāi)發(fā)和測(cè)試的專(zhuān)業(yè)庫(kù)。它們可能比我們自己從零開(kāi)始做的任何東西都要好得多。
總之,一個(gè)專(zhuān)業(yè)的ECS很難從頭開(kāi)始實(shí)現(xiàn)。這是一個(gè)有效的學(xué)術(shù)練習(xí),但一旦你完成了你的小型學(xué)習(xí)項(xiàng)目,那么不用多想,選擇一個(gè)經(jīng)過(guò)良好測(cè)試的第三方ECS庫(kù),并將其添加到你的游戲引擎代碼中。
6. 渲染
好吧,看起來(lái)我們的游戲引擎的復(fù)雜性正在緩慢增長(zhǎng)。既然已經(jīng)討論了在內(nèi)存中存儲(chǔ)和訪(fǎng)問(wèn)游戲?qū)ο蟮姆椒?,我們可能還需要討論如何在屏幕上渲染對(duì)象。
第一步是考慮我們將用引擎創(chuàng)造的游戲的性質(zhì)。我們是否創(chuàng)造了一個(gè)只用于開(kāi)發(fā)2D游戲的游戲引擎?如果是這種情況,我們就需要考慮渲染精靈、紋理、管理圖層,可能還需要利用顯卡加速。好消息是,2D游戲通常比3D游戲簡(jiǎn)單,2D數(shù)學(xué)也比3D數(shù)學(xué)簡(jiǎn)單得多。
如果你的目標(biāo)是開(kāi)發(fā)一個(gè)2D引擎,你可以使用SDL來(lái)幫助多平臺(tái)渲染。SDL抽象了加速GPU硬件,可以解碼和顯示PNG圖像,繪制精靈,并在游戲窗口內(nèi)渲染紋理。
而如果你的目標(biāo)是開(kāi)發(fā)一個(gè)3D引擎,那么我們需要定義如何向GPU發(fā)送一些額外的3D信息(頂點(diǎn)、紋理、shader等)。你可能想使用軟件抽象圖形硬件,最流行的選項(xiàng)是OpenGL、Direct3D、Vulkan和Metal。使用哪個(gè)API可能取決于你的目標(biāo)平臺(tái)。例如,Direct3D支持微軟的應(yīng)用程序,而Metal將只與蘋(píng)果產(chǎn)品兼容。
3D應(yīng)用程序通過(guò)圖形管線(xiàn)處理3D數(shù)據(jù)。該管線(xiàn)將指示你的引擎必須如何向GPU發(fā)送圖形信息(頂點(diǎn)、紋理坐標(biāo)、法線(xiàn)等)。圖形API和管線(xiàn)還將指示我們應(yīng)該如何編寫(xiě)可編程shader來(lái)變換和修改3D場(chǎng)景的頂點(diǎn)和像素。
可編程shader指示GPU應(yīng)該如何處理和顯示3D對(duì)象。我們可以為每個(gè)頂點(diǎn)和每個(gè)像素(片段)運(yùn)用不同的腳本,它們可以控制反射、平滑度、顏色、透明度等等。
說(shuō)到3D對(duì)象和頂點(diǎn),把讀取和解碼不同網(wǎng)格格式的任務(wù)委托給庫(kù)是個(gè)好主意。有許多流行的3D模型格式,大多數(shù)第三方3D引擎應(yīng)該都知道。文件的一些例子是. obj、Collada、FBX和DAE。我建議從. obj文件開(kāi)始。有一些經(jīng)過(guò)良好測(cè)試和支持的庫(kù)可以用C++處理OBJ加載。TinyOBJLoader和AssImp是很多游戲引擎都用的一個(gè)很棒的選擇。
7. 物理
當(dāng)我們向引擎添加實(shí)體時(shí),我們可能還希望它們?cè)趫?chǎng)景中移動(dòng)、旋轉(zhuǎn)和彈跳。這個(gè)游戲引擎的子系統(tǒng)就是物理模擬。它既可以手動(dòng)創(chuàng)建,也可以從現(xiàn)有的現(xiàn)成物理引擎導(dǎo)入。
在這里,我們還需要考慮我們想要模擬的物理類(lèi)型。2D物理通常比3D簡(jiǎn)單,但物理模擬的底層部分2D和3D引擎是非常相似的。
如果你只是想在你的項(xiàng)目中包含一個(gè)物理庫(kù),有幾個(gè)很好的選擇。
對(duì)于2D物理,我建議看看Box2D和Chipmunk2D。對(duì)于專(zhuān)業(yè)和穩(wěn)定的3D物理模擬,則可以瞅瞅像是PhysX和Bullet這樣的庫(kù)。如果物理穩(wěn)定性和開(kāi)發(fā)速度對(duì)你的項(xiàng)目至關(guān)重要,那么使用第三方物理引擎總是不錯(cuò)的選擇。
GIF
作為一名教育者,我堅(jiān)信,每個(gè)程序員都應(yīng)該在職業(yè)生涯中至少學(xué)習(xí)一次如何編寫(xiě)簡(jiǎn)單的物理引擎。同樣地,你不需要編寫(xiě)一個(gè)完美的物理模擬,但要專(zhuān)注于確保物體能夠正確加速,并確保不同類(lèi)型的力可以應(yīng)用到你的游戲物體上。一旦移動(dòng)搞定了,你還可以考慮實(shí)現(xiàn)一些簡(jiǎn)單的碰撞檢測(cè)和碰撞解決方案。
如果你想了解更多關(guān)于物理引擎的知識(shí),可以把一些好書(shū)和在線(xiàn)資源充分利用起來(lái)。對(duì)于2D剛體物理,你可以查看Box2D源代碼和Erin Catto的PPT。但如果你正在尋找一門(mén)關(guān)于游戲物理的綜合課程,《2D game physics from Scratch》可能是一個(gè)不錯(cuò)的開(kāi)始。
如果你想學(xué)習(xí)3D物理以及如何實(shí)現(xiàn)一個(gè)強(qiáng)大的物理模擬,另一個(gè)很好的資源是David Eberly的《Game physics》一書(shū)。
8. UI
一提到Unity或虛幻等現(xiàn)代游戲引擎,我們總會(huì)想到帶有各種面板、滑塊、拖放選項(xiàng)及其他幫用戶(hù)定制游戲場(chǎng)景的漂亮界面元素的復(fù)雜UI。UI能讓開(kāi)發(fā)者添加和刪除實(shí)體,動(dòng)態(tài)更改組件值,并輕松修改游戲變量。
需要明確的是,我們談?wù)摰氖怯糜诠ぞ叩挠螒蛞鎁I,而不是向玩家展示的用戶(hù)界面(如對(duì)話(huà)屏幕和菜單)。
請(qǐng)記住,游戲引擎不一定要嵌入編輯器,但由于游戲引擎通常用于提高工作效率,友好的UI會(huì)幫你和其他團(tuán)隊(duì)成員快速定制關(guān)卡及游戲場(chǎng)景的其他內(nèi)容。
從頭開(kāi)發(fā)UI框架可能是新手程序員嘗試添加到游戲引擎中的最煩人的任務(wù)之一。你必須創(chuàng)建按鈕、面板、對(duì)話(huà)框、滑塊、單選按鈕、管理顏色,還需要正確處理該UI的事件并始終保持其狀態(tài)。一點(diǎn)都不好玩。在引擎中添加UI工具還將會(huì)增加應(yīng)用程序的復(fù)雜性,并為源代碼添加大量的噪聲。
如果你的目標(biāo)是為你的引擎創(chuàng)建UI工具,我的建議是使用現(xiàn)有的第三方UI庫(kù)。在Google上搜一把會(huì)立刻出現(xiàn)最受歡迎的一些選擇,例如Dear ImGui, Qt和Nuklear。
GIF
Dear ImGui是我最喜歡的工具之一,因?yàn)樗屛覀兡転橐婀ぞ呖焖僭O(shè)置用戶(hù)界面。ImGui項(xiàng)目使用一種被稱(chēng)為“即時(shí)模式UI”的設(shè)計(jì)模式,它被廣泛用于游戲引擎,因?yàn)樗眉铀貵PU渲染能與3D應(yīng)用程序進(jìn)行良好的通信。
總之,如果你想在游戲引擎中添加UI工具,我的建議是使用Dear ImGui。
9. 腳本
隨著游戲引擎的發(fā)展,一個(gè)很流行的選擇是使用簡(jiǎn)單的腳本語(yǔ)言進(jìn)行關(guān)卡定制。
這個(gè)想法理念很簡(jiǎn)單:我們將腳本語(yǔ)言嵌入到原生C++應(yīng)用中,非專(zhuān)業(yè)程序員可以用這種更簡(jiǎn)單的腳本語(yǔ)言編寫(xiě)實(shí)體行為、AI邏輯、動(dòng)畫(huà)和游戲的其他重要元素。
一些流行的游戲腳本語(yǔ)言是Lua, Wren, C#,Python和JavaScript。所有這些語(yǔ)言的操作級(jí)別都比我們的原生C++代碼高得多。無(wú)論誰(shuí)使用腳本語(yǔ)言編寫(xiě)游戲行為,都不需要擔(dān)心內(nèi)存管理或核心引擎如何工作的其他底層細(xì)節(jié)。他們所需要做的就是編寫(xiě)關(guān)卡腳本,咱們的引擎知道如何解釋腳本并在幕后執(zhí)行困難的任務(wù)。
GIF
我最喜歡的腳本語(yǔ)言是Lua。Lua體積小、速度快,并且非常容易與C和C++原生代碼集成。此外,如果我使用Lua和“現(xiàn)代”C++,我喜歡用一個(gè)名為Sol的wrapper庫(kù)。Sol庫(kù)幫我地道地運(yùn)用Lua,并提供許多幫助函數(shù)來(lái)改進(jìn)傳統(tǒng)的Lua C API。
啟用腳本后,我們就可以開(kāi)始在游戲引擎中討論更高級(jí)的主題。腳本幫我們定義AI邏輯,定制動(dòng)畫(huà)框架和移動(dòng),以及其他不需要存在于原生C++代碼中、可以通過(guò)外部腳本輕松管理的游戲行為。
10. 音頻
另一個(gè)可以考慮為游戲引擎添加的支撐元素是音頻。
毫無(wú)疑問(wèn),若想要插入音頻值并發(fā)出聲音,我們需要通過(guò)操作系統(tǒng)訪(fǎng)問(wèn)音頻設(shè)備。同樣地(二回目),由于我們通常不會(huì)想編寫(xiě)針對(duì)特定操作系統(tǒng)的代碼,所以我建議用一個(gè)多平臺(tái)庫(kù)來(lái)抽象音頻硬件訪(fǎng)問(wèn)。
像SDL這樣的多平臺(tái)庫(kù)有各種擴(kuò)展組件,能幫你的引擎處理音樂(lè)和音效等內(nèi)容。
但是現(xiàn)在,嚴(yán)肅來(lái)講(二回目),我強(qiáng)烈建議在你的引擎的其他部分已經(jīng)協(xié)同工作之后再處理音頻。讓聲音文件響起來(lái)可能很容易實(shí)現(xiàn),但一旦我們開(kāi)始處理音頻同步,將音頻與動(dòng)畫(huà)、事件和其他游戲元素鏈接起來(lái),事情就會(huì)變得混亂。
如果你非常務(wù)實(shí)地全手動(dòng)處理,由于多線(xiàn)程管理的緣故,音頻部分可能會(huì)很棘手。它并非不能實(shí)現(xiàn),但如果你的目標(biāo)是編寫(xiě)一個(gè)簡(jiǎn)單的游戲引擎,那么這部分內(nèi)容是最值得委托給專(zhuān)業(yè)庫(kù)的。
你可以考慮將SDL_Mixer、SoLoud和FMOD等優(yōu)秀的音頻庫(kù)和工具整合到游戲引擎中。
《Tiny Combat Arena》就是一款用FMOD庫(kù)實(shí)現(xiàn)多普勒效應(yīng)和壓縮效應(yīng)等音頻效果的游戲。你可以聽(tīng)到加力燃燒器的聲音,以及其他噴氣機(jī)經(jīng)過(guò)時(shí)的3D效果。
11. 人工智能
我要討論的最后一個(gè)子系統(tǒng)是AI。可以通過(guò)腳本實(shí)現(xiàn)AI,這意味著我們能將AI邏輯委托給關(guān)卡設(shè)計(jì)師編寫(xiě)腳本。另一種選擇便是將適當(dāng)?shù)腁I系統(tǒng)嵌入到我們的游戲引擎核心原生代碼中。
在游戲中,AI是用來(lái)產(chǎn)生對(duì)游戲?qū)ο蟮捻憫?yīng)性、適應(yīng)性或智能行為。大多數(shù)AI邏輯被添加到非玩家角色(npc,敵人)中,以模擬類(lèi)似人類(lèi)的智能。
敵人是AI在游戲中應(yīng)用的一個(gè)常見(jiàn)例子。當(dāng)敵人在地圖上追逐目標(biāo)時(shí),游戲引擎可以通過(guò)尋路算法或有趣的類(lèi)人行為來(lái)創(chuàng)建抽象。
Ian Millington的《AI for games》是一本關(guān)于游戲人工智能理論和執(zhí)行的綜合性書(shū)籍。
不要試圖同時(shí)做所有的事情
好了,我們前開(kāi)你討論了一些重要的概念,你可以考慮將它們添加到你的簡(jiǎn)單C++游戲引擎中。但在開(kāi)始把這些東西粘在一起之前,我想提一點(diǎn)非常重要的事情。
開(kāi)發(fā)游戲引擎最困難的部分之一是,大多數(shù)開(kāi)發(fā)者不會(huì)設(shè)定明確的邊界,也沒(méi)有“終點(diǎn)線(xiàn)”的概念。換句話(huà)說(shuō),程序員會(huì)開(kāi)始一個(gè)游戲引擎項(xiàng)目,渲染對(duì)象,添加實(shí)體,添加組件,然后便走入不歸路了。如果沒(méi)有定義邊界,很容易添加越來(lái)越多的功能,而失去對(duì)大局的掌控。如果出現(xiàn)這種情況,游戲引擎很有可能永遠(yuǎn)都見(jiàn)不到天日。
除了缺乏邊界之外,當(dāng)我們看到代碼以迅雷不及掩耳盜鈴之勢(shì)在眼前增長(zhǎng)時(shí),我們很容易不知所措。游戲引擎項(xiàng)目的復(fù)雜性可能會(huì)在幾周內(nèi)迅速增長(zhǎng),你的C++項(xiàng)目可能會(huì)有多個(gè)依賴(lài)項(xiàng),需要一個(gè)復(fù)雜的構(gòu)建系統(tǒng),隨著引擎添加更多功能,你的代碼的整體可讀性也會(huì)下降。
我的最重要建議之一是,在編寫(xiě)一款實(shí)際游戲的同時(shí)編寫(xiě)游戲引擎。在開(kāi)始和完成引擎的第一次迭代時(shí),腦中要有一款真正的游戲。這將幫助你設(shè)定限制,為你需要完成的事情定義一條清晰的道路。盡你最大的努力堅(jiān)持下去,不要在中途改變需求。
慢慢來(lái),專(zhuān)注于基礎(chǔ)
如果你正在創(chuàng)建自己的游戲引擎作為學(xué)習(xí)練習(xí),那就盡情享受這樣的小小勝利吧!
大多數(shù)學(xué)生在項(xiàng)目開(kāi)始時(shí)都非常興奮,隨著時(shí)間的推移,焦慮開(kāi)始浮現(xiàn)。如果我們從頭開(kāi)始創(chuàng)造游戲引擎,特別是使用像C++這樣復(fù)雜的語(yǔ)言時(shí),我們很容易就會(huì)不知所措并失去動(dòng)力。
我想鼓勵(lì)你戰(zhàn)勝那種“與時(shí)間賽跑”的感覺(jué)。深呼吸,享受每一個(gè)微小的勝利。例如,當(dāng)你學(xué)會(huì)如何成功在屏幕上顯示PNG紋理時(shí),享受這一刻,并確保你明白你做了什么。如果你成功地檢測(cè)到了兩個(gè)物體之間的碰撞,那就再次享受這一刻,并反思你剛剛獲得的知識(shí)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-400844.html
專(zhuān)注于基礎(chǔ)并確保切實(shí)擁有這些知識(shí)。不管一個(gè)概念多小、多簡(jiǎn)單,都要切實(shí)擁有它??!其他一切都是浮云。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-400844.html
到了這里,關(guān)于如何制作自己的C++游戲引擎的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!