本篇討論IrrlichtDevice,主要探討以下幾個問題:
->irrlicht對裝置做了哪些抽象
->如何做到跨平台
->win32裝置的實現細節
1)引擎的基石 - IrrlichtDevice
irrlicht 所指的裝置是什麼呢?探究IrrlichtDevice介面,irrlicht裝置是對應用程式視窗環境的抽象,實際上對應了在各個平台上的應用程式架構。在IrrlichtDevice介面中,有run(),closeDevice(),yield(),sleep()這樣的應用程式生命週期操作,也有setResizeAble(),setWindowCaption這樣對視窗的操作,另外裝置還持有了video driver,scene manager,gui enviroment, file system等對象,從介面看應該在實現的具體裝置中完成這些對象的構造並管理這些對象。IrrlichtDevice可以算是引擎的基石,支撐起在目標平台上3D引擎的運行,如果我們設計的引擎想要跨平台則必須要做這樣的抽象。
2)如何?裝置跨平台
使用irrlicht引擎時,首先要調用的一個全域函數:createDevice(),他的實現在源檔案Irrlicht.cpp中,實際上真正完成工作的是 createDeviceEx(),這個函數的聲明和createDevice()一樣都在irrlicht.h中,但實現在不同的平台的 CIrrDeviceXXX.cpp中。irrlicht通過編譯選項保證只有一個平台的相關代碼被編譯,所以不會出現多次 createDeviceEx()的定義。在windows平台上,_IRR_USE_WINDOWS_DEVICE_被定義,而其他如 _IRR_USE_LINUX_DEVICE_是沒有定義的,所以裝置相關的起作用的代碼檔案是CIrrDeviceWin32.h/.cpp。在 IrrCompileConfig.h中可以發現,如果定義了_IRR_USE_SDL_DEVICE_,就不會定義 _IRR_USE_WINDOWS_DEVICE_,此時將是SDL裝置起作用。總之,只有一個平台會被定義,只有一種裝置會被建立,只有一份 createDeviceEx()存在。通過編譯選項和宏定義,編譯器只會選擇編譯某一平台的代碼,構建某一平台的irr裝置。
3)Win32裝置源碼剖析
上面說了,在win32平台上,只有CIrrDeviceWin32.h/.cpp會被編譯,查看原始碼,首先發現:
class CIrrDeviceWin32 : public CIrrDeviceStub
查詞典,“電腦編程中,RMI中將客戶輔助對象稱之為Stub”,CIrrDeviceStub是個所有裝置類型都能用上的公用功能的集合,是個輔助的基類。那麼先看下這個類,源碼 CIrrDeviceStub.h/.cpp。這個類不會建立一個具體的IrrDevice(很顯然,因為他是各平台共用的輔助類,當然不會關心某個具體的裝置),沒有驚訝,這個類持有並管理了一些IrrDevice介面上可以看出的對象,如FileSystem,當然FileSystem本身就是跨平台的實現,這是一種形式,另一種形式是stub提供了建立函數,但自己並沒有調用它,具體的就是createGUIAndScene(),會建立 GUIEnvironment和SceneManager,這個函數會在子類中調用。另外stub還實現了getXXX()系列的介面。
stub只是盡量提取了一些各平台共有的操作,真正的東西還是需要各自平台自己實現。
下面分析win32裝置。
<1>裝置建立
首先尋找createDeviceEx(),其中只是new了一個CIrrDeviceWin32對象,並檢查是否成功,主要的建立還是在 CIrrDeviceWin32的建構函式中。建構函式中主要的工作是建立視窗(也可以使用外部傳入的視窗HWND),建立driver,建立GUI和 SceneMgr(調用stub中的createGUIAndScene())。這裡的重點是建立driver,irrlicht可以同時支援多種 driver並在運行前選擇,也就是說這些driver都是編譯好的(而不是像device那樣只編譯了一個),在createDriver()中,會根據CreateParams決定建立哪種driver,其中建立硬體driver,如DX,OpenGL是在平台的代碼中的,而建立軟體driver和 NULL driver是在stub中。在Linux版本的device中我們發現只有OpenGL這種硬體driver,當然了,Linux並不支援DX。
<2>運行和關閉裝置
從最簡短的例子可以看到,通過判斷裝置是否run()而進行遊戲的主迴圈。win32版的run()如下:
view plaincopy to clipboardprint?
bool CIrrDeviceWin32::run()
{
os::Timer::tick();
MSG msg;
bool quit = false;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
if (ExternalWindow && msg.hwnd == HWnd)
WndProc(HWnd, msg.message, msg.wParam, msg.lParam);
else
DispatchMessage(&msg);
if (msg.message == WM_QUIT)
quit = true;
}
if (!quit)
resizeIfNecessary();
if(!quit)
pollJoysticks();
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
return !quit;
}
bool CIrrDeviceWin32::run()
{
os::Timer::tick();
MSG msg;
bool quit = false;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
if (ExternalWindow && msg.hwnd == HWnd)
WndProc(HWnd, msg.message, msg.wParam, msg.lParam);
else
DispatchMessage(&msg);
if (msg.message == WM_QUIT)
quit = true;
}
if (!quit)
resizeIfNecessary();
if(!quit)
pollJoysticks();
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
return !quit;
}
os::Timer::tick() 用來記錄系統時間,這是全域的,irrlicht引擎是基於時間而不是基於固定幀的,後面會有一篇專門分析這個問題。再下面是一個常用的 PeekMessage式windows訊息迴圈。重點是傳回值,只有視窗收到WM_QUIT訊息,run()才返回false,而視窗的quit既可以是直接關閉視窗,也可以是遊戲調用device的closeDevice():
view plaincopy to clipboardprint?
void CIrrDeviceWin32::closeDevice()
{
MSG msg;
PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
PostQuitMessage(0);
PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
DestroyWindow(HWnd);
}
void CIrrDeviceWin32::closeDevice()
{
MSG msg;
PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
PostQuitMessage(0);
PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
DestroyWindow(HWnd);
}
<3>其他介面
win32 device中內含的視窗訊息處理函數WndProc中,會處理如鍵盤滑鼠這樣的事件,並轉換成irrlicht的訊息。device還實現了 IImagePresenter介面,這個介面只有一個方法present,只有軟體driver會使用它,將buffer繪製到螢幕上。另外還有 sleep(),yield()這樣的線程式控制制方法。
小結:一個遊戲有一個device對象,device抽象了不同的遊戲平台作業系統,提供了運行和關閉的介面,封裝了driver的建立,儲存了driver, gui, scene manager, file system等全域唯一的對象。試想,如果我們要給irrlicht增加一個新的遊戲平台,比如ps3,我們要做什麼呢?當然是符合irr語義的實現這些介面即可,程式會照樣的run起來,maybe you want to try.
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/n5/archive/2009/07/07/4329603.aspx