斜視角遊戲地圖解析 |
|
axsoft
版主 ![]() ![]() ![]() ![]() ![]() ![]() 發表:681 回覆:1056 積分:969 註冊:2002-03-13 發送簡訊給我 |
Isometric Game Map作者:劉小軍 資料來源: http://www.cpp3d.com 關于斜視角方面的文章很多,之所以寫這組文章,一方面是對本站點的內容作一些補充,另一方面我們一直努力朝游戲這一方向靠近,能在同大家交流的同時,學到更多的東西才是最重要的,所以大家如有什麼高見,千萬千萬不要吝嗇批評指教。 圖塊的組織 大家都知道,游戲中的場景(map)可通過有限數量的一些圖塊(tile)拼接形成,就象磁磚畫一樣。游戲中採用的圖塊形狀有兩種,一種是矩形的,另一種是菱形的,由此就有了兩種不同的引擎,兩種引擎誰優誰劣這里就不做討論,但是有一點可以肯定,兩種方法暫時誰也無法絕對地否定誰。用少量的圖塊來構造一個較大的場景,這樣做的好處是顯然的,比如減少內存(顯存)的消耗、方便地計算從一處走到另一處所要消耗的時間或體力(通過率)、物體間的遮掩、動態場景的實現等等,你可以通過定義圖塊的屬性來方便地完成一些現實模擬計算。 斜視角所採用的圖塊是一些上、下、左、右都對稱的菱形,橫向寬度與縱向寬度之比為2:1(如圖一),實際中常用的是寬為32個象素點高為16個象素點的矩形圖塊,右圖中四周黑色區域是透明的,大家如果細心一點一定可以看到,底部一行象素點也是透明的,也就是說實際高度是15個象素點,為什麼呢?![]() ![]() ![]() ![]() ![]() const CHAR CDXIsoMap::m_CellFigue[16][32] = { {1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2}, {1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2}, {1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2}, {1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2}, {1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2}, {1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2}, {1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4}, {3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4}, {3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4}, {3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4}, {3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4}, {3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4}, {3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4}, {3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4} }; const int CDXIsoMap::m_nCellWidth = 32; const int CDXIsoMap::m_nCellHeight = 16; const int CDXIsoMap::m_nCellWidthShift = 5; const int CDXIsoMap::m_nCellHeightShift = 4; // 根據象素坐標計算出絕對圖塊坐標 // int nPixelX 橫向象素坐標 // int nPixelY 縱向象素坐標 // int nCellX 絕對橫向圖塊坐標 // int nCellY 絕對縱向圖塊坐標 // BOOL bAbsolutePixel 傳入的象素坐標是否是絕對坐標 void CDXIsoMap::AbsoluteCell(int nPixelX, int nPixelY, int &nCellX, int &nCellY, BOOL bAbsolutePixel/*=TRUE*/) { // 先計算出傳入象素點落在那個(偶數列)圖塊所在的矩形區域內 if (bAbsolutePixel) // 是絕對坐標 { nCellX = (nPixelX >> m_nCellWidthShift) << 1; nCellY = nPixelY >> m_nCellHeightShift; } else // 是屏幕坐標,則要先換算成絕對坐標后再計算 { nCellX = (((nPixelX-m_rcDest.left) m_nPosX) >> m_nCellWidthShift) << 1; nCellY = ((nPixelY-m_rcDest.top) m_nPosY) >> m_nCellHeightShift; } // 計算傳入點在小矩形區域內的相對位置 int nPx, nPy; if (bAbsolutePixel) // 絕對坐標 { nPx = nPixelX & (m_nCellWidth-1); nPy = nPixelY & (m_nCellHeight-1); } else // 相對坐標 { nPx = ((nPixelX-m_rcDest.left) m_nPosX) & (m_nCellWidth-1); nPy = ((nPixelY-m_rcDest.top) m_nPosY) & (m_nCellHeight-1); } switch( m_CellFigue[nPy][nPx] ) { case 1: nCellX--; nCellY--; break; // 左上角 case 2: nCellX ; nCellY--; break; // 右上角 case 3: nCellX--; break; // 左下角 case 4: nCellX ; break; // 右下角 } }注:上面的m_rcDest是相對于屏幕的一個視窗(view port),也就 是說地圖只在屏幕中的某個矩形區域內滾動。另外m_nPosX和 m_nPosY是該矩形視窗左上角的絕對象素坐標。 前半段我們解決了如何計算給定點所在圖塊的編號。這在接下來的圖塊拼接中起著非常重要的作用。在此之前還有一個小問題,就是如果給定一個圖塊編號,如何計算它的絕對象素坐標或相對象素坐標呢?又如何計算其它相鄰圖塊的編號呢?這在以后AI中路徑搜索等其它一些場合要用到,雖然暫時還不會用到,但因屬坐標計算範疇,所以提前討論了。 注:因為文章中很多還只是處于構思階段,大部分我自己也沒有來得及實現,因此如果前后文有所衝突,還請多多包涵。這里我沒有任何想誤人子弟的意思,如何取舍,請自行定奪。當然隨時不要忘記指出錯誤!! ![]() // 根據絕對圖塊坐標計算出象素坐標 // int nCellX 絕對橫向圖塊坐標 // int nCellY 絕對縱向圖塊坐標 // int nPixelX 橫向象素坐標 // int nPixelY 縱向象素坐標 // BOOL bAbsolute 計算絕對象素坐標還是相對(屏幕)象素坐標 void CDXIsoMap::Cell2Pixel(int nCellX, int nCellY, int &nPixelX, int &nPixelY, BOOL bAbsolute/*=TRUE*/) { // 先計算偶數列圖塊的絕對象素坐標 nPixelX = (nCellX >> 1) << m_nCellWidthShift; nPixelY = nCellY << m_nCellHeightShift; // 計算圖塊的絕對象素坐標 if (nCellX & 1) { nPixelX = (m_nCellWidth >> 1); nPixelY = (m_nCellHeight >> 1); } if (!bAbsolute) { // 計算相對(屏幕)坐標 nPixelX -= m_nPosX; nPixelY -= m_nPosY; } }然后再來看看如何計算相鄰圖塊的編號,圖塊的編號有兩種情況, ![]() ![]() // 計算相鄰圖塊編號 // 方向: // 3 4 5 // 2 6 // 1 0 7 void CDXIsoMap::NextCell(int &x, int &y, int nDirection) { if( x & 1) // 奇數列(如左上圖) { switch( nDirection ) { case 0 : y ; break; // 下 case 1 : x--; y ; break; // 左下 case 2 : x-=2; break; // 左 case 3 : x--; break; // 左上 case 4 : y--; break; // 上 case 5 : x ; break; // 右上 case 6 : x =2; break; // 右 case 7 : x ; y ; break; // 右下 } } else // 偶數列(如右上圖) { switch( nDirection ) { case 0 : y ; break; // 下 case 1 : x--; break; // 左下 case 2 : x-=2; break; // 左 case 3 : x--; y--; break; // 左上 case 4 : y--; break; // 上 case 5 : x ; y--; break; // 右上 case 6 : x =2; break; // 右 case 7 : x ; break; // 右下 } } }地圖數據的組織 現在我們要來討論一下如何組織圖塊單元的屬性。斜視角與直視角還有一個本質的區別就是用于拼接地圖的圖塊都是有高度的。通過這一屬性可以方便地實現遮掩算法。 這里我們先來看看一些傳統RPG(如仙劍)中地圖格式,一般它們的地圖都分成兩層或三層,第一層我們稱為基本圖塊層(base layer),如草地、路等,這一層是由一些完整的菱形圖塊組成。第二層為邊緣圖塊層(fringe layer),這一層中的圖塊主要都是一些不完整的菱形圖塊,如樹木、花草、石子等,透過這些圖塊縫隙可以看到第一層(base bayer)的圖形,這也就是實現遮掩效果的一大關鍵。如右圖 ![]() struct TILE { BYTE index; BYTE flag; }; struct CELL { TILE base; TILE fringe; }; class CDXIsoMap { . . . . . . Static const BYTE m_CellIdxFlg;// 圖塊索引高位 static const BYTE m_CellRmrFlg;// 障礙標志 static const BYTE m_CellNpcFlg;// 人物標志 static const BYTE m_CellLnkFlg;// 地圖鏈接標志 CELL* m_pMapData; // 地圖數據圖塊拼接 前面各文章其實都可以說是斜視角地圖實現的基礎,有了前面一些基礎知識之后,下面我們就可以開始做場景的渲染了。我們已經知道,各圖塊都是交錯排列的 ![]() // 畫地圖(“畫地圖”?) // IN: // pDestSurface: Destination surface // lprcDest: View port void CDXIsoMap::Draw(CDDSurface* pDestSurface, LPRECT lprcDest) { if (lprcDest != NULL) { m_rcDest.top = lprcDest->top; m_rcDest.left = lprcDest->left; m_rcDest.bottom = lprcDest->bottom; m_rcDest.right = lprcDest->right; } // 圖塊半寬、半高 int nHalfW = m_nCellWidth >> 1; int nHalfH = m_nCellHeight >> 1; int nOffsetX; int nOffsetY; // 計算相應偶數例圖塊與視窗的距離 // 視窗左上角處為奇數列 if (m_nPosTX & 1) { nOffsetX = (m_nPosX-nHalfW) & (m_nCellWidth-1); nOffsetY = (m_nPosY-nHalfH) & (m_nCellHeight-1); } else { nOffsetX = m_nPosX & (m_nCellWidth-1); nOffsetY = m_nPosY & (m_nCellHeight-1); } nOffsetX = -nOffsetX; nOffsetY = -nOffsetY; int nOffTx, nOffTy; // 左上方多畫一些圖塊 // 如果視窗左上角處是奇數列,則從相應偶數列畫起 if (m_nPosTX & 1) { nOffTx = m_nPosTX - 1; nOffTy = m_nPosTY; nOffsetX = nOffsetX - nHalfW; nOffsetY = nOffsetY - nHalfH; } // 否則起如列左移兩列,起始行上移一行 else { nOffTx = m_nPosTX - 2; nOffTy = m_nPosTY - 1; nOffsetX = nOffsetX - m_nCellWidth; nOffsetY = nOffsetY - m_nCellHeight; } int dy = nOffsetY m_rcDest.top; // 行循環 for (int I = nOffTy; ; I ) { if ((I>=0) && (I < m_nRows)) { int dx = nOffsetX m_rcDest.left; // 列循環 for (int j= nOffTx; ;) { // 畫偶列 if ((j>=0) && (j < m_nColumns)) BltTile(pDestSurface, dx, dy, &(m_pMapData[I*m_nColumns j])); j ; // 在右下方半個圖塊位置畫奇列 if ((j>=0) && (j < m_nColumns)) BltTile(pDestSurface, dx nHalfW, dy nHalfH, &(m_pMapData[I*m_nColumns j])); j ; dx = m_nCellWidth; if (dx >= m_rcDest.right) break; } } dy = m_nCellHeight; if (dy >= m_rcDest.bottom) break; } }人物遮掩 人物的遮掩分兩種情況, 一種是人物與人物之間的遮掩, 還有一種是人物與地圖中的建築、樹木等之間的遮掩。注:方便起見,所有精靈類這里統稱人物。第一種情況的解決辦法是通過構造一個具有位置屬性的基類,再在此基礎上引申出其它精靈類,這樣我們就可以在視覺方向對人物的位置進行排序,從遠到近畫出各人物就實現了人物的遮掩。當然排序算法看你自己喜歡了。 至于第二種情況,大家一定還記得我們前面說過圖塊是有高度的,而圖塊的高度又是如何來定義的呢?我們來看看右邊這幅圖, ![]() |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |