對2D遊戲引擎設計的一些思考(轉)

post0發表於2007-08-12
對2D遊戲引擎設計的一些思考(轉)[@more@]

  前不久用模擬器玩了SFC上的一個經典SLG――聖龍戰記後,突然對它出色的表現有了濃厚的興趣,尤其是在那種硬體平臺下,僅僅3M的遊戲竟然能夠有如此出色的表現!不僅是畫面表現得極致,而且整個遊戲的系統,情節相對當今的大多數遊戲來說,實在是有過之而無不及!~實在是佩服萬分~!

  

  確實,現在的硬體條件都比以前好多了,做一個遊戲也越來越簡單了(雖然我沒有在DOS下寫過程式,但是經過兩年多的程式設計,對各個方面都有些瞭解,仍能體會到在DOS下寫遊戲的痛苦:)), 現在各種各樣的遊戲開發包也越來越多了,不說別的,就直接用DirectX SDK吧,做一個小的遊戲比如飛機類也不會花幾天時間(以前我花了3天做過一個^_^),開發簡單了,自然有些東西就不那麼講究了,比如說現在的商業遊戲的容量,無論是什麼都先比光碟多少,你的3CD,我來5CD,除去裡面"免費贈送"的一些"原聲大蝶" 阿,"官方資料"阿之類的東西,一個遊戲至少也有1個G,(大概是現在的硬碟在大家的眼中不怎麼值錢了吧,可能商家是這麼認為的,也可能是大眾心理:東西越多越好嘛:)),真正有用的資料有多少?估計也只有商家才清楚~先不說某些遊戲連壓縮都沒壓縮過就裸用一大堆的24Bit BMP( 沒錯,就是標準的點陣圖)來做遊戲中的資源(僅僅做了一個未壓縮的資源包,很輕鬆就能提取出全部資源:)),畫面看起來效果好麼?確實,不過那沒有什麼,反正就是美工的表現嘛!還對機器要求至少有PIII 500、128M以上的RAM~真是Faint!

  

  赫赫,也許大多數人不在乎上面提到的東西~可是作為一個遊戲開發者,一個遊戲程式設計者,就要在能力範圍內對遊戲程式做盡可能的最佳化(先不提商業製作的一些"無奈"的原因的阻礙),就比如說星際,大概是我所見到的PC上的商業遊戲中做的最好的一個了:)

  

  好了,廢話了一大堆,下面來談談點正式的。

  如今2D PC遊戲上,最流行的就是16位色的顯示方式(主要是從速度和記憶體消耗以及顯示質量這些方面上來綜合),16位色上基本上是565的顯示方式(我到現在還從來沒有見到一臺555顯示的機器或一塊555的顯示卡),所以我只討論16bit下565 模式。

  

  下面的方法是由於'調色盤'而來的靈感~

  (先申明,這種方法絕對不適合主流技術,基於上面我所說的遊戲----聖龍戰記,可以做類似的遊戲~不適合基於象素的遊戲,對TILE類遊戲比較實用)

  

  由於TILE類遊戲用到的TILE顏色相對都比較固定,顏色種類比較少,所以我們可以選取一個固定的調色盤,裡面能容納大部分的TILE顏色,這樣所有TILE的資料都可以用這個調色盤的索引來表示,當然為了方便,256種顏色最好不過,這樣每個點只佔8位(也許有人會說,這樣不就乾脆建立一個8位色的遊戲不就行了?嘿嘿,稍安勿躁,馬上解釋),在記憶體消耗上就有了很大的優勢~如果再壓縮一下,嘿嘿........

  

  從速度上來說,由於遊戲裡面需要大量的特效,比如最常用的半透明效果,色彩飽和效果、陰影效果、灰度化等等效果,所以從這方面來考慮。

  

  由於只用到了256色,混合後的顏色也在256種顏色內,所以考慮用查表方式,

  這256種顏色從16Bit 565模式共65536種顏色的色彩空間中提取出來,這樣就算是32級的Alpha混合也就只佔用256*256*32*8bit=2M的記憶體。

  但是用16級或者12級我就覺得夠了,這樣就有 256*256*16*8Bit = 1M 或者 256*256*12*8Bit = 768K

  色彩飽和表就只需要 256*256*8Bit = 64K

  

  陰影表也就只要 256*256*8Bit = 64K

  灰度表只要 256*8Bit = 0.256K

  一共加起來也就1M左右,呵呵夠少吧!

  如下:

  static unsigned char BDI_AlphaBlendTable[16][256][256]; //16級Alpha混和表

  static unsigned char BDI_AdditiveTable[256][256];    //Additive表

  static unsigned char BDI_SubTable[256][256];      //陰影表

  static unsigned char BDI_GrayTable[256];        //灰度表

  

  那麼,哪256種顏色可以很好的描述大部分圖片的顏色呢?

  嘗試過幾個不同的調色盤後,最後發現下面這個調色盤效果最好(並且還有附加的優勢!稍後看到)

  如下,

  unsigned short wPal[256];

  for(int i=0;i<256;i++)

  {

  wPal[i]=i|(i<<8);

  }

  

  也就是說這個調色盤的高8位和低8位是相同的,嘿嘿,想到什麼了?(趕快用10秒鐘猜猜,下面回答)

  

  當然這樣一來也就不能直接用DirectDraw裡面的Blt之類的東西啦~,另外,由於我們的資料保留的是調色盤的索引,所以,不能直接Blt到BackSurface上,自己分配一個緩衝區,大小和BackSurface一樣大,不過用byte型別就夠啦~

  

  自己寫幾個Blt吧:

  

  比如一個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  {

  for(register int j=0;j  {

  if(*pSourAddr != m_byColorKeyIndex)

  {

  *pDestAddr = GBDI::BDI_AdditiveTable[*pSourAddr][*pDestAddr];

  //這個地方極大的節省了大量的數學運算

  }

  pDestAddr++;

  pSourAddr++;

  }

  pBufDest += nDestWidth;

  pBufSour += m_nWidth;

  pDestAddr = pBufDest;

  pSourAddr = pBufSour;

  }

  }

  

  上面的操作是經過裁減過後的顯示,裁減程式碼如下:

  

  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;//目標地址

  unsigned char*pBufSour = m_pData+m_nWidth*rtSour.top+rtSour.left;//源地址

  int nLine = rtSour.right-rtSour.left;

  int nRow = rtSour.bottom-rtSour.top;

  

  各種引數的含義都比較明顯,瞭解E文的並且寫過程式碼的應該都能看懂,看不懂的如果有興趣的話,自己去看完整原始碼,好了,如何才能在螢幕上正確的顯示呢? 這個問題就很簡單了,當然最最直接的方法就是:

  

  for(緩衝區上的每一個點)

  BackSurface上的每一個點 = 緩衝區上的每一個點所代表的調色盤的值

  

  嘿嘿,別忘記了,上面說過用到的調色盤是什麼來的?

  低8位和高8位相同!

  如果瞭解mmx的話,就應該知道這一條指令:punpcklbw

  哈哈!如何?知道最佳化的方法了吧?

  

  下面是我的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)//如果是採用了預設的調色盤(高8位==低8位)

  {

  //由於初學mmx,還不會作mmx指令的最佳化~程式碼見笑了~

  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個索引點

  {

  _asm

  {

  mov esi,pSourBuf;

  mov edi,pBuf;

  mov edx,dwHeight;

  rowLoop:

  cmp edx,0;

  je end;

  mov ecx,loopTime;

  mmxdraw:

  movq mm0,[esi]; //8個索引點

  movq mm2,[esi+8]; //後8個索引點

  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個索引的值

  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

  {

  unsign

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-951846/,如需轉載,請註明出處,否則將追究法律責任。

相關文章