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

來源:互聯網
上載者:User

原始連結:http://dev.gameres.com/Program/Visual/2D/對2D遊戲引擎設計的一些思考.htm  

 

     前不久用模擬器玩了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<nRow;i++)
        {
            for(register int j=0;j<nLine;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
    {
        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);

嘿嘿,最後最最重要的一點就是:效果如何呢?
這一點我無權評論,大家可以看看demo再說在我的機器( CII 950 + 256M SDR)上FPS最高能到200左右。

  附帶一點,這個Demo中,我運用了類似模擬器上的圖層管理的方法,其思想就是分n個layer,每個layer上的圖元都有一個高度,高度範圍為m,然後每一個layer上的圖元全部由m個鏈表串連起來,畫圖順序為:最先畫的圖層是0號圖層,最先畫的是0號鏈表,直到n個layer和m個高度(這樣就可以隨意關閉或者開啟第幾個layer,就像模擬器一樣,並且很容易的實現流水線渲染具體情況看我的demo代碼)。

  最後說明,現在的遊戲都使用的是即時計算來進行渲染(包括我正在寫的一個engine),並且使用3d加速來做特效上面這種方式雖然簡單高效,但是只是在TILE方式下~~有興趣研究Tile方式的遊戲的朋友們不妨try一下~

  好了,就到這裡,浪費大家的寶貴時間了,多有得罪~

Demo download

有興趣的朋友歡迎和我探討:
game-diy@163.com
OICQ:30784290(難得糊塗)
http://www.gamepp.org/

2003/12/4 night

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.