古之學者必有師,對于技術的提升,只靠自己的摸索雖然能得到深刻的經驗,但往往沒有較高的效率。筆者這些天學習了BV1eM4m1S74K“提瓦特幸存者”的C++開發,也是實現了該類型游戲的開發。
今天,就通過經驗總結,親手結束這一小段學習過程!
游戲的基本框架
所有游戲的底層框架都是通過一個主循環來刷新畫布,每個循環中都實現讀取操作、處理數據、繪制畫面這三個步驟。
int?main()?{while?(running) {//動態延時(記錄本次循環開始的時間)DWORD?start_time?=?GetTickCount(); ?//32位無符號整數,長度不隨編譯器變化//讀取操作區域while?(peekmessage(&msg)) {}//處理數據區域cleardevice();//渲染畫面區域FlushBatchDraw();//記錄循環結束時間DWORD?end_time?=?GetTickCount();DWORD?delta_time?=?end_time - start_time;//1秒144次刷新,即幀率為144,一個循環就是一幀if?(delta_time <?1000?/?144) {Sleep(1000?/?144?- delta_time);}}EndBatchDraw();return?0;
}
在這些操作執行完后,加上動態延時從而實現游戲開發中“幀”的概念。這也是游戲引擎設計的基本框架,是不是很有Update()函數的感覺?
關于渲染緩沖區:
為了隱藏渲染或其他處理的過程,BeginBatchDraw函數會讓后續的渲染操作在緩沖區中進行,像一塊閑置的畫布,當調用FlushBatchDraw或EndBatchDraw,即所有操作繪制結束時,直接代替當前畫布,達到無縫銜接的效果。
一些代碼細節:
視頻的創作者在許多地方都體現了C++的編碼細節,比如使用static靜態變量只在第一次調用時創建(后續調用會跳過)復用同一內存;使用TCHAR這一Windows兼容類型適應非英文環境;同樣使用寬字符串適應非英文環境……
頭文件依賴問題
在這次C++開發的過程中,我總是被所謂“出現未定義的變量名”給搞到破防。這是頭文件重復包含導致的。
一個項目各個區域的執行順序是全局變量->靜態全局變量->函數聲明->靜態函數聲明->類定義->靜態類成員定義->命名空間變量->函數定義……頭文件一般用來包含變量與函數的聲明、類的定義等重要信息。
頭文件的循環包含引發編譯錯誤的原因:
編譯器處理?#include?時,會把對應頭文件內容嵌入包含位置。若頭文件循環包含,其可能會陷入無限遞歸嘗試展開頭文件的情形。即便使用包含守衛(?#ifndef?三件套 )或?#pragma?once 規避重復展開,由于頭文件解析時需要對方類型完成自身聲明或定義,循環依賴會導致部分必要的聲明或定義無法在依賴解析階段正確處理。
類似A.h在第一行包含了B.h,同時B.h的第一行包含了A.h, B類使用的A類的定義,但是在頭文件依賴解析A.h時,先展開B.h,但B.h遍歷到A.h會因為包含守衛而跳過,這下B類里A就沒有了定義……
這里有三個編程習慣可以盡量避免頭文件重復包含的問題:
1、使用前向聲明,在使用A的頭文件中聲明聲明一下A類。不需要訪問具體成員時需要。
2、使用包含守衛,即?#ifndef、#define、#endif?連招。
3、使用?#pragma?once,可替代包含守衛,使頭文件在一個編譯單元中只包含一次。
但這些方法只能盡量避免我們遇到的問題,最重要的還是在一開始就規劃好項目的結構。保持頭文件聲明、源文件定義的好習慣,在必要時進行重構,讓文件包含的脈絡清晰,一目了然。
動畫與MCI工具
游戲中使用的動畫分為骨骼動畫(關鍵幀動畫)與序列幀動畫。序列幀動畫就是讓圖片素材以若干個幀為單位進行交替,從而達到動畫播放的效果。
這里使用自定義圖集類來批量載入名稱有規律的圖片素材。
Atlas::Atlas(LPCTSTR path,?int?num) {TCHAR path_file[256];for?(size_t?i =?0; i < num; ++i) {_stprintf_s(path_file, path, i);IMAGE* frame =?new?IMAGE();loadimage(frame, path_file);frame_list.push_back(frame);}
}
這種方法依次用從零開始的自然數代替圖集中的數字部分,實現圖片載入。
MCI工具(媒體控制接口)能夠讓我們以字符串的形式對windows系統發出指令,控制音樂的播放。
但在我的測試中,mp3文件在播放時,會明顯影響游戲的幀率。經過測試與查詢,這與MP3格式文件的特性有關:編解碼邊播放。而MCI解碼的消息可能會打斷Sleep,讓主循環提前醒來,導致幀率變高。
所以在加載音頻文件時,更推薦使用WAV格式:1、MCI 加載 MP3 資源時,底層會創建額外的線程或窗口,并且會向主消息隊列發送消息(比如 MM_MCINOTIFY),EasyX 的 peekmessage 也會處理這些消息。2、WAV 文件是無壓縮格式,處理簡單,不會影響主線程;而 MP3 需要解碼,可能會影響主線程的消息分發和定時精度。3、某些 Windows 環境下,MCI 加載MP3會讓Sleep變得不準確,主循環實際刷新頻率變高。
小結
雖然本篇圖文列出的點很少,但是這次學習經歷切切實實加深了游戲開發的理解。我想這些框架性的東西也可能成為游戲引擎開發的一個開端,而通過C++而不是依賴引擎的開發,更能深入底層邏輯,讓日后對代碼的優化的方向更清晰。
如有補充糾正歡迎留言。