對(duì)2D游戲引擎設(shè)計(jì)的一些思考
發(fā)布時(shí)間:2008-03-29 閱讀數(shù): 次 來(lái)源:網(wǎng)樂(lè)原科技
前不久用模擬器玩了SFC上的一個(gè)經(jīng)典SLG——圣龍戰(zhàn)記后,突然對(duì)它出色的表現(xiàn)有了濃厚的興趣,尤其是在那種硬件平臺(tái)下,僅僅3M的游戲竟然能夠有如此出色的表現(xiàn)!不僅是畫(huà)面表現(xiàn)得極致,而且整個(gè)游戲的系統(tǒng),情節(jié)相對(duì)當(dāng)今的大多數(shù)游戲來(lái)說(shuō),實(shí)在是有過(guò)之而無(wú)不及!~實(shí)在是佩服萬(wàn)分~!
確實(shí),現(xiàn)在的硬件條件都比以前好多了,做一個(gè)游戲也越來(lái)越簡(jiǎn)單了(雖然我沒(méi)有在DOS下寫(xiě)過(guò)程序,但是經(jīng)過(guò)兩年多的編程,對(duì)各個(gè)方面都有些了解,仍能體會(huì)到在DOS下寫(xiě)游戲的痛苦:)), 現(xiàn)在各種各樣的游戲開(kāi)發(fā)包也越來(lái)越多了,不說(shuō)別的,就直接用DirectX SDK吧,做一個(gè)小的游戲比如飛機(jī)類(lèi)也不會(huì)花幾天時(shí)間(以前我花了3天做過(guò)一個(gè)^_^),開(kāi)發(fā)簡(jiǎn)單了,自然有些東西就不那么講究了,比如說(shuō)現(xiàn)在的商業(yè)游戲的容量,無(wú)論是什么都先比光盤(pán)多少,你的3CD,我來(lái)5CD,除去里面"免費(fèi)贈(zèng)送"的一些"原聲大蝶" 阿,"官方資料"阿之類(lèi)的東西,一個(gè)游戲至少也有1個(gè)G,(大概是現(xiàn)在的硬盤(pán)在大家的眼中不怎么值錢(qián)了吧,可能商家是這么認(rèn)為的,也可能是大眾心理:東西越多越好嘛:)),真正有用的數(shù)據(jù)有多少?估計(jì)也只有商家才清楚~先不說(shuō)某些游戲連壓縮都沒(méi)壓縮過(guò)就裸用一大堆的24Bit BMP( 沒(méi)錯(cuò),就是標(biāo)準(zhǔn)的位圖)來(lái)做游戲中的資源(僅僅做了一個(gè)未壓縮的資源包,很輕松就能提取出全部資源:)),畫(huà)面看起來(lái)效果好么?確實(shí),不過(guò)那沒(méi)有什么,反正就是美工的表現(xiàn)嘛!還對(duì)機(jī)器要求至少有PIII 500、128M以上的RAM~真是Faint!
赫赫,也許大多數(shù)人不在乎上面提到的東西~可是作為一個(gè)游戲開(kāi)發(fā)者,一個(gè)游戲程序設(shè)計(jì)者,就要在能力范圍內(nèi)對(duì)游戲程序做盡可能的優(yōu)化(先不提商業(yè)制作的一些"無(wú)奈"的原因的阻礙),就比如說(shuō)星際,大概是我所見(jiàn)到的PC上的商業(yè)游戲中做的最好的一個(gè)了:)
好了,廢話(huà)了一大堆,下面來(lái)談?wù)匋c(diǎn)正式的。
如今2D PC游戲上,最流行的就是16位色的顯示方式(主要是從速度和內(nèi)存消耗以及顯示質(zhì)量這些方面上來(lái)綜合),16位色上基本上是565的顯示方式(我到現(xiàn)在還從來(lái)沒(méi)有見(jiàn)到一臺(tái)555顯示的機(jī)器或一塊555的顯卡),所以我只討論16bit下565 模式。
下面的方法是由于'調(diào)色板'而來(lái)的靈感~
(先申明,這種方法絕對(duì)不適合主流技術(shù),基于上面我所說(shuō)的游戲----圣龍戰(zhàn)記,可以做類(lèi)似的游戲~不適合基于象素的游戲,對(duì)TILE類(lèi)游戲比較實(shí)用)
由于TILE類(lèi)游戲用到的TILE顏色相對(duì)都比較固定,顏色種類(lèi)比較少,所以我們可以選取一個(gè)固定的調(diào)色板,里面能容納大部分的TILE顏色,這樣所有TILE的數(shù)據(jù)都可以用這個(gè)調(diào)色板的索引來(lái)表示,當(dāng)然為了方便,256種顏色最好不過(guò),這樣每個(gè)點(diǎn)只占8位(也許有人會(huì)說(shuō),這樣不就干脆創(chuàng)建一個(gè)8位色的游戲不就行了?嘿嘿,稍安勿躁,馬上解釋),在內(nèi)存消耗上就有了很大的優(yōu)勢(shì)~如果再壓縮一下,嘿嘿........
從速度上來(lái)說(shuō),由于游戲里面需要大量的特效,比如最常用的半透明效果,色彩飽和效果、陰影效果、灰度化等等效果,所以從這方面來(lái)考慮。
由于只用到了256色,混合后的顏色也在256種顏色內(nèi),所以考慮用查表方式,
這256種顏色從16Bit 565模式共65536種顏色的色彩空間中提取出來(lái),這樣就算是32級(jí)的Alpha混合也就只占用256*256*32*8bit=2M的內(nèi)存。
但是用16級(jí)或者12級(jí)我就覺(jué)得夠了,這樣就有 256*256*16*8Bit = 1M 或者 256*256*12*8Bit = 768K
色彩飽和表就只需要 256*256*8Bit = 64K
陰影表也就只要 256*256*8Bit = 64K
灰度表只要 256*8Bit = 0.256K
一共加起來(lái)也就1M左右,呵呵夠少吧!
如下:
static unsigned char BDI_AlphaBlendTable[16][256][256]; //16級(jí)Alpha混和表
static unsigned char BDI_AdditiveTable[256][256]; //Additive表
static unsigned char BDI_SubTable[256][256]; //陰影表
static unsigned char BDI_GrayTable[256]; //灰度表
那么,哪256種顏色可以很好的描述大部分圖片的顏色呢?
嘗試過(guò)幾個(gè)不同的調(diào)色板后,最后發(fā)現(xiàn)下面這個(gè)調(diào)色板效果最好(并且還有附加的優(yōu)勢(shì)!稍后看到)
如下,
unsigned short wPal[256];
for(int i=0;i<256;i++)
{
wPal[i]=i|(i<<8);
}
也就是說(shuō)這個(gè)調(diào)色板的高8位和低8位是相同的,嘿嘿,想到什么了?(趕快用10秒鐘猜猜,下面回答)
當(dāng)然這樣一來(lái)也就不能直接用DirectDraw里面的Blt之類(lèi)的東西啦~,另外,由于我們的數(shù)據(jù)保留的是調(diào)色板的索引,所以,不能直接Blt到BackSurface上,自己分配一個(gè)緩沖區(qū),大小和BackSurface一樣大,不過(guò)用byte類(lèi)型就夠啦~
自己寫(xiě)幾個(gè)Blt吧:
比如一個(gè)Alpha混合的操作:(代碼取自我給出的Demo)
void GBDI::DrawToScreenAdditiveSrcColorKey(unsigned char*pBufDest,int nDestWidth,unsigned char*pBufSour,int nLine,int nRow)
{
unsigned char*pDestAddr = pBufDest;
unsigned char*pSourAddr = pBufSour;
for(register int i=0;i<nRow;i++)
{
for(register int j=0;j<nLine;j++)
{
if(*pSourAddr != m_byColorKeyIndex)
{
*pDestAddr = GBDI::BDI_AdditiveTable[*pSourAddr][*pDestAddr];
//這個(gè)地方極大的節(jié)省了大量的數(shù)學(xué)運(yùn)算
}
pDestAddr++;
pSourAddr++;
}
pBufDest += nDestWidth;
pBufSour += m_nWidth;
pDestAddr = pBufDest;
pSourAddr = pBufSour;
}
}
上面的操作是經(jīng)過(guò)裁減過(guò)后的顯示,裁減代碼如下:
RECT rtDest = {m_position.x,m_position.y,m_position.x+pScreen->GetWidth(),m_position.y+pScreen->GetHeight()};
RECT rtSour = m_rtShowArea;
if(rtDest.top<0)
{
rtSour.top -= rtDest.top;
rtDest.top = 0;
}
if(rtDest.left<0)
{
rtSour.left -= rtDest.left;
rtDest.left = 0;
}
if(rtDest.left+rtSour.right-rtSour.left>pScreen->GetWidth())
{
rtSour.right = rtSour.left+pScreen->GetWidth()-rtDest.left;
}
if(rtDest.top+rtSour.bottom-rtSour.top>pScreen->GetHeight())
{
rtSour.bottom = rtSour.top+pScreen->GetHeight()-rtDest.top;
}
unsigned char*pBufDest = pScreen->GetBuffer()+pScreen->GetWidth()*rtDest.top+rtDest.left;//目標(biāo)地址
unsigned char*pBufSour = m_pData+m_nWidth*rtSour.top+rtSour.left;//源地址
int nLine = rtSour.right-rtSour.left;
int nRow = rtSour.bottom-rtSour.top;
各種參數(shù)的含義都比較明顯,了解E文的并且寫(xiě)過(guò)代碼的應(yīng)該都能看懂,看不懂的如果有興趣的話(huà),自己去看完整源代碼,好了,如何才能在屏幕上正確的顯示呢? 這個(gè)問(wèn)題就很簡(jiǎn)單了,當(dāng)然最最直接的方法就是:
for(緩沖區(qū)上的每一個(gè)點(diǎn))
BackSurface上的每一個(gè)點(diǎn) = 緩沖區(qū)上的每一個(gè)點(diǎn)所代表的調(diào)色板的值
嘿嘿,別忘記了,上面說(shuō)過(guò)用到的調(diào)色板是什么來(lái)的?
低8位和高8位相同!
如果了解mmx的話(huà),就應(yīng)該知道這一條指令:punpcklbw
哈哈!如何?知道優(yōu)化的方法了吧?
下面是我的Demo中的代碼:
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
hr = m_pDSBack->Lock(NULL,&ddsd,DDLOCK_WAIT,NULL);
while(DD_OK!=hr)
{
if(DDERR_SURFACELOST==hr)
RestoreSurface();
else
return;
hr=m_pDSBack->Lock(NULL,&ddsd,DDLOCK_WAIT,NULL);
}
unsigned char*pSourBuf = (unsigned char*)m_pBuffer;
if(m_bDefaultPal)//如果是采用了默認(rèn)的調(diào)色板(高8位==低8位)
{
//由于初學(xué)mmx,還不會(huì)作mmx指令的優(yōu)化~代碼見(jiàn)笑了~
unsigned long dwResPitch = ddsd.lPitch-(m_nWidth<<1);
unsigned char*pBuf = (unsigned char*)ddsd.lpSurface;
unsigned long dwHeight = m_nHeight;
unsigned long loopTime = m_nWidth>>5; //一次處理32個(gè)索引點(diǎn)
{
_asm
{
mov esi,pSourBuf;
mov edi,pBuf;
mov edx,dwHeight;
rowLoop:
cmp edx,0;
je end;
mov ecx,loopTime;
mmxdraw:
movq mm0,[esi]; //8個(gè)索引點(diǎn)
movq mm2,[esi+8]; //后8個(gè)索引點(diǎn)
movq mm4,[esi+16];
movq mm6,[esi+24];
movq mm1,mm0;
movq mm3,mm2;
movq mm5,mm4;
movq mm7,mm6;
punpcklbw mm0,mm0; //0-3個(gè)索引的值
punpckhbw mm1,mm1; //4-7
punpcklbw mm2,mm2; //8-11
punpckhbw mm3,mm3; //12-15
punpcklbw mm4,mm4;
punpckhbw mm5,mm5;
punpcklbw mm6,mm6;
punpckhbw mm7,mm7;
movq [edi],mm0;
movq [edi+8],mm1;
movq [edi+16],mm2;
movq [edi+24],mm3;
movq [edi+32],mm4;
movq [edi+40],mm5;
movq [edi+48],mm6;
movq [edi+56],mm7;
add esi,32;
add edi,64;
loop mmxdraw;
dec edx;
add edi,dwResPitch;
jmp rowLoop;
end:
emms;
}
}
}
else
{
unsigned long dwResPitch = (ddsd.lPitch>>1)-m_nWidth;
unsigned short*pBuf = (unsigned short*)ddsd.lpSurface;
for(register int i=0;i<m_nHeight;i++)
{
for(register int j=0;j<m_nWidth;j++)
{
*pBuf = m_pPal[*pSourBuf];
pBuf++;
pSourBuf++;
}
pBuf += dwResPitch;
}
}
m_pDSBack->Unlock(NULL);
嘿嘿,最后最最重要的一點(diǎn)就是:效果如何呢?
這一點(diǎn)我無(wú)權(quán)評(píng)論,大家可以看看demo再說(shuō)在我的機(jī)器( CII 950 + 256M SDR)上FPS最高能到200左右。
附帶一點(diǎn),這個(gè)Demo中,我運(yùn)用了類(lèi)似模擬器上的圖層管理的方法,其思想就是分n個(gè)layer,每個(gè)layer上的圖元都有一個(gè)高度,高度范圍為m,然后每一個(gè)layer上的圖元全部由m個(gè)鏈表連接起來(lái),畫(huà)圖順序?yàn)椋鹤钕犬?huà)的圖層是0號(hào)圖層,最先畫(huà)的是0號(hào)鏈表,直到n個(gè)layer和m個(gè)高度(這樣就可以隨意關(guān)閉或者打開(kāi)第幾個(gè)layer,就像模擬器一樣,并且很容易的實(shí)現(xiàn)流水線渲染具體情況看我的demo代碼)。
最后說(shuō)明,現(xiàn)在的游戲都使用的是即時(shí)計(jì)算來(lái)進(jìn)行渲染(包括我正在寫(xiě)的一個(gè)engine),并且使用3d加速來(lái)做特效上面這種方式雖然簡(jiǎn)單高效,但是只是在TILE方式下~~有興趣研究Tile方式的游戲的朋友們不妨try一下~
好了,就到這里,浪費(fèi)大家的寶貴時(shí)間了,多有得罪~