- 論壇徽章:
- 0
|
地圖是游戲中必不可少的一種預(yù)算元素,尤其是在RPG、ACT等類型的游戲中作用更為重要,一個漂亮的地圖效果和一個流暢的卷動速度會大大增加玩家的游戲體驗。而游戲中地圖滾動的重繪有多種算法,由于手機(jī)性能的限制和開發(fā)周期等其他非技術(shù)條件,需要根據(jù)情況靈活選擇所需的技術(shù)。本文將主要介紹如何使用OPhone API來繪制2D游戲中的場景,也即地圖的繪制方法。
![]()
地圖繪制及滾動的常用算法
無縫圖片滾動畫法
最簡單的一種畫地圖方法,無需使用數(shù)組,只需要使用一張無縫的背景圖片,在屏幕上繪制兩次,以此來實現(xiàn)最簡單的地圖滾動效果和圖片的重復(fù)使用以節(jié)約資源。
如下圖,紅色虛線部分為屏幕,使用一個偏移量在屏幕中錯開位置貼上兩次圖片,通過不斷改變偏移量的大小來實現(xiàn)動畫效果。
![]()
代碼舉例:
view plain
copy to clipboard
print
?
//imgBack圖片對象
//posX圖片在X軸方向上的偏移量
canvas.drawBitmap(imgBack, -posX, 0, paint);
canvas.drawBitmap(imgBack, imgBack.getHeight()+posX, 0, paint);
if(posX==-imgBack.getHeight())
posX=0; //imgBack圖片對象 //posX圖片在X軸方向上的偏移量 canvas.drawBitmap(imgBack, -posX, 0, paint); canvas.drawBitmap(imgBack, imgBack.getHeight()+posX, 0, paint); if(posX==-imgBack.getHeight()) posX=0;
優(yōu)點(diǎn)與局限:此算法非常簡單,由于是單張圖片反復(fù)滾動生成的背景圖片,所以對于美術(shù)人員的限制較少,利于發(fā)揮,而且外觀效果好。但因為不是地圖Tile組成的,資源復(fù)用率不高,只能用于生成不太復(fù)雜的地圖。而且由于沒有Tile的存在,無法針對不同的Tile計算碰撞。最終使得這種畫法只能用于繪制簡單屏幕背景圖片,而無法用在有復(fù)雜物理碰撞的地圖層。
裁剪區(qū)畫法
我們平時所玩的游戲一般場景都是大于屏幕的尺寸的,也就是說在游戲中的主角移動的時候,后面的地圖將會隨著主角的位置變化而發(fā)生移動,我們稱之為地圖的卷軸效果。而對諸如RPG,ACT這類地圖場景比較大的類型的游戲來說,地圖都不是一整張的背景圖直接使用,而是采用一種“拼接”的方式,這樣做既能節(jié)省內(nèi)存的占用,同時也能使圖片資源的利用率達(dá)到最大化。下圖就是2D游戲常用的圖片樣式:
![]()
從圖中我們能夠看出,我們可以把整張圖片進(jìn)行分割,并將分割后的圖片進(jìn)行編號,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
為每塊圖素編號之后,就可以設(shè)計自己的地圖了。這里需要使用一種叫做“地圖編輯器”的工具軟件。我們這里使用“mapwin”進(jìn)行地圖的設(shè)計,使用步驟如下圖所示:
![]()
上面的四個輸入框分別代表地圖小塊的寬度和高度,以及我們要創(chuàng)建的整個場景的水平和垂直的地圖塊數(shù),輸入后點(diǎn)擊“OK”如下圖所示:
![]()
下面需要引入一張圖片,引入方法為“File——Import”,選取一張圖片并點(diǎn)擊確定,隨后就能看到如下的圖片:
![]()
剩下的工作想必你就可以想到了,用鼠標(biāo)在右邊區(qū)域選取一個圖塊,然后將其放到左邊黑色區(qū)域中即可,拼接完的效果如下圖:
![]()
接下來要把地圖數(shù)據(jù)導(dǎo)出,導(dǎo)出放下如下圖:
![]()
最后我們需要的數(shù)據(jù)是這樣的:
const short ss_map0[10][10] = {
{ 1, 1, 1, 1, 1, 1, 1, 5, 1, 1 },
{ 10, 10, 10, 1, 1, 1, 1, 1, 1, 1 },
{ 8, 8, 8, 1, 1, 1, 1, 1, 1, 1 },
{ 9, 9, 9, 1, 1, 1, 1, 14, 15, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 16, 17, 1 },
{ 1, 1, 1, 6, 11, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 11, 1, 1, 1, 21, 1 },
{ 1, 4, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
};
實際上就是一個二維數(shù)組,數(shù)組中的數(shù)字即為地圖塊的索引號。
使用二維數(shù)組保存地圖信息,另外有一張圖片素材,根據(jù)地圖數(shù)組的不同下標(biāo),配合public boolean clipRect(float left, float top, float right, float bottom,Region.Op op) 裁剪區(qū)方法,將對應(yīng)的Tile顯示在正確的位置上。
如下圖所示,紅色虛線部分為屏幕,紅色實線為裁剪區(qū),通過讀取地圖數(shù)組,將相應(yīng)的位置設(shè)置為裁剪區(qū),并用將圖片素材相對于裁剪區(qū)偏移一定x,y位置的方法,使得要繪制的Tile正好對應(yīng)出現(xiàn)在裁剪區(qū)中。
![]()
代碼舉例:
view plain
copy to clipboard
print
?
// 繪制切割圖片
public void drawClipImg(int XDest, int YDest, int Width, int Height,
int XSrc, int YSrc, Bitmap img, Paint g,Canvas canvas)
{
canvas.clipRect(XDest, YDest, XDest + Width, YDest + Height,
Region.Op.REPLACE);
canvas.drawBitmap(img, XDest - XSrc, YDest - YSrc, g);
canvas.clipRect(0, 0, Const.SCREEN_WIDTH, Const.SCREEN_HEIGHT,
Region.Op.REPLACE);
} // 繪制切割圖片 public void drawClipImg(int XDest, int YDest, int Width, int Height, int XSrc, int YSrc, Bitmap img, Paint g,Canvas canvas) { canvas.clipRect(XDest, YDest, XDest + Width, YDest + Height, Region.Op.REPLACE); canvas.drawBitmap(img, XDest - XSrc, YDest - YSrc, g); canvas.clipRect(0, 0, Const.SCREEN_WIDTH, Const.SCREEN_HEIGHT, Region.Op.REPLACE); }
相對于前一種畫法,圖片資源的利用率提高了很多,可以繪制很復(fù)雜的地圖。由于Tile的存在,可以針對不同的Tile計算碰撞,可以用于地圖物理層的繪制。
最常見的地圖繪制優(yōu)化——只繪制當(dāng)前屏幕
上面的繪制方法都是將整個地圖的數(shù)據(jù)全部畫出來的,這樣做實際上也存在很大的浪費(fèi),因為玩家實際上只能看見屏幕中的一塊區(qū)域,其他大部分的地圖即使被繪制也不能反映到屏幕上,反而因為這個不必要的步驟大大增加了CPU的負(fù)擔(dān),從而影響了游戲的流暢程度。因此,在實際開發(fā)中,常用的優(yōu)化方法就是只繪制當(dāng)前屏幕的地圖塊。代碼如下:
view plain
copy to clipboard
print
?
//計算單元格起始位置下標(biāo)
int startIndexX =leftTopY/ MAP_TILE_SIZE;
int startIndexY =leftTopX/ MAP_TILE_SIZE;
//再使用上面得到的數(shù)據(jù)修改雙循環(huán)繪制的條件即可,
for (int i = startIndexX; i 1; i++)
for (int j = startIndexY; j 1; j++) //計算單元格起始位置下標(biāo) int startIndexX =leftTopY/ MAP_TILE_SIZE; int startIndexY =leftTopX/ MAP_TILE_SIZE; //再使用上面得到的數(shù)據(jù)修改雙循環(huán)繪制的條件即可, for (int i = startIndexX; i
卡馬克卷軸算法的引入
上面的算法雖然在一定程度上解決了地圖繪制的效率問題,但對于某些資源嚴(yán)重不足的手機(jī),或者由于地圖塊比較小、循環(huán)次數(shù)過多的情況,仍然會造成畫圖時屏幕閃爍。因此,在這種情況下,仍然需要對上述算法做進(jìn)一步的優(yōu)化。
不論采用哪種優(yōu)化算法,一個基本的思路就是盡量減少繪制的次數(shù),從而減少對系統(tǒng)資源的消耗。卡馬克卷軸算法就是這樣算法的一個經(jīng)典例子。
單方向卷軸
對于橫版游戲來說,如果角色向右側(cè)移動,則地圖向左側(cè)滾動。由于角色每次移動若干個步長,因此地圖中新畫出的區(qū)域?qū)挾纫矠槿舾蓚像素,那么如果讓系統(tǒng)重繪所有屏幕區(qū)域,很明顯,大部分區(qū)域都是和上一屏幕區(qū)域相同的,如此造成成了資源的浪費(fèi)。而卡馬克算法的思路就是——如果上一次繪制過的地圖也能夠部分重用到本次地圖繪制上來就好了。那么很容易想到在內(nèi)存中建立一個和屏幕一樣大或略大的緩沖區(qū)即可很好的完成這個設(shè)想。
![]()
由上圖可以看到,區(qū)域B為相同的地圖區(qū)域,這個區(qū)域在下一次屏幕重繪時,可以被重新利用。區(qū)域A是在下一次屏幕重繪中不被采用的區(qū)域,這區(qū)域應(yīng)當(dāng)被舍棄,但是如果稍微留意一下的話,不難發(fā)現(xiàn)區(qū)域A和區(qū)域C的面積大小其實居然是一樣的。
那么如果建立一個和屏幕大小相同的緩沖,在其被舍棄掉的繪制區(qū)域A中畫上新的區(qū)域C,再把區(qū)域B和區(qū)域C拼合到屏幕上,是不是就能達(dá)到減少系統(tǒng)資源消耗的目的了呢?卡馬克卷軸的基本原理正是如此。
![]()
圖顯示了卡馬克卷軸的最基本原理,首先在內(nèi)存中建立一塊和屏幕一樣大小(或略大)的緩沖區(qū)。然后在本應(yīng)由于地圖移動而被舍棄掉的區(qū)域1上面繪制,由于地圖滾動而出現(xiàn)的新地圖區(qū)域。最后把兩個區(qū)域按照地圖的實際位置拼合到屏幕上。
雙軸滾動的卡馬克卷軸
對于俯視游戲,或者有Y軸卷動的游戲來說,單單一個方向的地圖卷動并不夠用。那么如果是出現(xiàn)兩個方向的卷動會如何呢。不必?fù)?dān)心,上面的思路算法一樣能適應(yīng)這種情況。
![]()
由上圖可以看到,區(qū)域D為相同的地圖區(qū)域,這個區(qū)域在下一次屏幕重繪時,可以被重新利用。區(qū)域ABC是在下一次屏幕重繪中不被采用的區(qū)域,可以在這個3個區(qū)域上繪制上下一次需要重繪的區(qū)域A’B’C’。再將繪制好的四個區(qū)域拼合到屏幕的對應(yīng)位置。
![]()
上圖顯示了雙軸滾動的卡馬克卷軸的基本繪制原理,需要特別注意的是:在緩沖區(qū)的繪制順序和在屏幕上拼合的順序是完全相反的。
卡馬克算法的實現(xiàn)
卡馬克卷軸緩沖畫法的一般步驟如下:
1. 初始化所有地圖數(shù)據(jù),并且全屏繪制初始的地圖
2. 若人物移動,則調(diào)用攝像機(jī)算法,修正地圖偏移量
3. 地圖偏移量不滿足地圖的邊界條件,就重繪緩沖區(qū)
4. 重繪緩沖區(qū)
5. 后臺緩沖區(qū)的四個子區(qū)按照順序畫到屏幕上
地圖類——Map的設(shè)計
字段定義
view plain
copy to clipboard
print
?
//地圖數(shù)據(jù)
public byte mapData[][];
//移動緩沖區(qū)的當(dāng)前坐標(biāo)窗口
public int sx,sy;
//地圖圖片
private Bitmap imgMap;
public GameView m_View;
//常量
public final static int MAP_TILE_SIZE = 24;
/** 緩沖區(qū)寬高,命名方式為:Carmack width or height */
private int bufWidth, bufHeight;
/** 緩沖區(qū)寬的圖塊數(shù),與高的圖塊數(shù)*/
private int carTileWidth, carTileHeight;
/** 屏幕寬高命名方式為:screen width or height */
private int scrWidth, scrHeight;
/** 緩沖切割線,命名方式為:Carmack x or y */
private int carx, cary;
/** 地圖在緩沖區(qū)的X 、Y偏移量,命名方式為:map offset x or y */
private int mapOffx, mapOffy;
/** 緩沖區(qū),命名方式為:Carmack buffer */
public Bitmap carBuffer;
/** 緩沖區(qū)畫筆,命名方式為:Carmack Graphics */
private Canvas carGp;
/** 緩沖區(qū)增大的大。ㄉ舷麓笮∈且粯拥模 */
private int buffSize;
/** 圖片寬度的所切割的圖塊數(shù)量。 */
private int imageTileWidth;
/** 地圖圖片 */
private Bitmap mapImage;
Paint paint=new Paint();
/** 地圖數(shù)組 */
private byte mapArray[][];
/** 圖塊大小,寬高一致 */
private int tileSize;
/** 圖塊的寬度數(shù)量,與高度數(shù)量 */
private int tileW, tileH;
/** 地圖的寬高 */
private int mapLastx, mapLasty; //地圖數(shù)據(jù) public byte mapData[][]; //移動緩沖區(qū)的當(dāng)前坐標(biāo)窗口 public int sx,sy; //地圖圖片 private Bitmap imgMap; public GameView m_View; //常量 public final static int MAP_TILE_SIZE = 24; /** 緩沖區(qū)寬高,命名方式為:Carmack width or height */ private int bufWidth, bufHeight; /** 緩沖區(qū)寬的圖塊數(shù),與高的圖塊數(shù)*/ private int carTileWidth, carTileHeight; /** 屏幕寬高命名方式為:screen width or height */ private int scrWidth, scrHeight; /** 緩沖切割線,命名方式為:Carmack x or y */ private int carx, cary; /** 地圖在緩沖區(qū)的X 、Y偏移量,命名方式為:map offset x or y */ private int mapOffx, mapOffy; /** 緩沖區(qū),命名方式為:Carmack buffer */ public Bitmap carBuffer; /** 緩沖區(qū)畫筆,命名方式為:Carmack Graphics */ private Canvas carGp; /** 緩沖區(qū)增大的大。ㄉ舷麓笮∈且粯拥模 */ private int buffSize; /** 圖片寬度的所切割的圖塊數(shù)量。 */ private int imageTileWidth; /** 地圖圖片 */ private Bitmap mapImage; Paint paint=new Paint(); /** 地圖數(shù)組 */ private byte mapArray[][]; /** 圖塊大小,寬高一致 */ private int tileSize; /** 圖塊的寬度數(shù)量,與高度數(shù)量 */ private int tileW, tileH; /** 地圖的寬高 */ private int mapLastx, mapLasty;
方法定義
CarMapBuffer(int, int, int, int)構(gòu)造器
CarMapBuffer(int, int, int)構(gòu)造器的代理
setMap(Image, byte[][])設(shè)置地圖參數(shù)
initBuffer()初始化繪制地圖
scroll(int, int)卷動地圖算法
updateBuffer(int, int)繪制緩沖區(qū)
getIndexCarX()獲得切割線所在的圖塊索引X
getIndexCarY()獲得切割線所在的圖塊索引Y
getBufferCarX()獲得切割線在Buffer中的X位置
getBufferCarY()獲得切割線在Buffer中的Y位置
getIndexBuffLastX()獲得緩沖區(qū)后面的X索引
getIndexBuffLastY()獲得緩沖區(qū)后面的Y索引
getTitleHeight()獲得當(dāng)前要繪制的圖塊高度的數(shù)量
getTitelWidth()獲得當(dāng)前要繪制的圖塊寬度的數(shù)量
copyBufferX(int, int, int, int, int) 由于x方向卷動造成的重繪
copyBufferY(int, int, int, int, int) 由于y方向卷動造成的重繪
getMapX(int, int) 獲得地圖圖片的X坐標(biāo)偏移
getMapY(int, int) 獲得地圖圖片的Y坐標(biāo)偏移
paint(Graphics, int, int)將緩沖區(qū)的內(nèi)容分成4塊依次拼合到屏幕上
drawBuffer(Graphics, int, int)繪制緩沖區(qū)方法
drawRegion(Graphics, Image, int, int, int, int, int, int, int, int)封裝的drawRegion()方法
getGraphics()獲得緩沖區(qū)畫筆
getImage()獲得緩沖區(qū)Image對象
步驟一的實現(xiàn)
初始化所有地圖數(shù)據(jù),并且全屏繪制初始的地圖,代碼如下:
view plain
copy to clipboard
print
?
/**
* 初始化Buffer,全部地圖繪制在此方法中完成
*/
private void initBuffer()
{
int x, y, cx, cy;
for (int i = 0; i
{
for (int j = 0; j
{
x = getMapX(i, j);
y = getMapY(i, j);
cx = j * tileSize;
cy = i * tileSize;
m_View.drawClipImg(cx, cy, tileSize, tileSize, x, y, mapImage, paint, carGp);
}
}
} /** * 初始化Buffer,全部地圖繪制在此方法中完成 */ private void initBuffer() { int x, y, cx, cy; for (int i = 0; i
步驟二、三的實現(xiàn)
若人物移動,則調(diào)用攝像機(jī)算法,修正地圖偏移量,若偏移量在[0,maplast]移動范圍內(nèi)移動,則有可能發(fā)生重繪
view plain
copy to clipboard
print
?
/**
* 卷軸滾動 *
* @param x * X軸滾動
* @param y * Y軸滾動
*/
private void scroll(int x, int y)
{
try
{
x += mapOffx;
y += mapOffy;
// *************************************************
// 邊界檢測
if (x 0 || y 0)
{
return;
}
if (x > mapLastx)
{
mapOffx = mapLastx;
return;
}
if (y > mapLasty)
{
mapOffy = mapLasty;
return;
}
updateBuffer(x, y);
// *************************************************
}
catch (ArrayIndexOutOfBoundsException e)
{
}
}
/** * 卷軸滾動 * * @param x * X軸滾動 * @param y * Y軸滾動 */ private void scroll(int x, int y) { try { x += mapOffx; y += mapOffy; // ************************************************* // 邊界檢測 if (x mapLastx) { mapOffx = mapLastx; return; } if (y > mapLasty) { mapOffy = mapLasty; return; } updateBuffer(x, y); // ************************************************* } catch (ArrayIndexOutOfBoundsException e) { } }
步驟四的實現(xiàn)
重繪緩沖區(qū),地圖的x方向卷動會造成列方向上的重繪(調(diào)用copyBufferX()方法),地圖的y方向上的卷動會造成行方向上的重繪(調(diào)用copyBufferY()方法)。updateBuffer()方法用于針對不同的四個方向上的卷動進(jìn)行copyBuffer()參數(shù)的初始化。
view plain
copy to clipboard
print
?
/**
* 更新緩沖區(qū) *
* @param x * 緩沖區(qū)新的地圖X坐標(biāo)
* @param y * 緩沖區(qū)新的地圖Y坐標(biāo)
*/
private void updateBuffer(int x, int y)
{
mapOffx = x;
mapOffy = y;
// 右移
if (x > carx + buffSize)
{
// while (carx
int indexMapLastX = getIndexBuffLastX();
if (indexMapLastX
{
copyBufferX(indexMapLastX, getIndexCarY(), getTileHeight(),
getBufferCarX(), getBufferCarY());
carx += tileSize;
}
// }
}
// 左移
if (x
{
// do {
carx -= tileSize;
copyBufferX(getIndexCarX(), getIndexCarY(), getTileHeight(),
getBufferCarX(), getBufferCarY());
// } while (carx > mapOffx);
}
// 下移
if (y > cary + buffSize)
{
// while (cary
int indexMapLastY = getIndexBuffLastY();
if (indexMapLastY
{
copyBufferY(getIndexCarX(), indexMapLastY, getTitelWidth(),
getBufferCarX(), getBufferCarY());
cary += tileSize;
}
// }
}
// 上移
if (y
{
// do {
cary -= tileSize;
copyBufferY(getIndexCarX(), getIndexCarY(), getTitelWidth(),
getBufferCarX(), getBufferCarY());
// } while (cary > mapOffy);
}
} /** * 更新緩沖區(qū) * * @param x * 緩沖區(qū)新的地圖X坐標(biāo) * @param y * 緩沖區(qū)新的地圖Y坐標(biāo) */ private void updateBuffer(int x, int y) { mapOffx = x; mapOffy = y; // 右移 if (x > carx + buffSize) { // while (carx mapOffx); } // 下移 if (y > cary + buffSize) { // while (cary mapOffy); } }
重繪緩沖區(qū)的具體方法,該方法涉及到大量的坐標(biāo)運(yùn)算,而且由于卡馬克點(diǎn)的存在經(jīng)常會分成兩個區(qū)域分兩次進(jìn)行重繪。見下圖:
![]()
下面以x方向卷動為例舉例
view plain
copy to clipboard
print
?
private void copyBufferX(int indexMapx, int indexMapy, int tileHeight,
int destx, int desty)
{
int mapImagex, mapImagey, vy;
// 拷貝地圖上面到緩沖的下面
int timer=0;
for (int j = 0; j
{
mapImagex = getMapX(indexMapy + j, indexMapx);
mapImagey = getMapY(indexMapy + j, indexMapx);
vy = j * tileSize + desty;
m_View.drawClipImg(destx, vy, tileSize, tileSize, mapImagex, mapImagey, mapImage, paint, carGp);
timer++;
}
// 拷貝地圖下面到緩沖的上面
for (int k = tileHeight; k
{
mapImagex = getMapX(indexMapy + k, indexMapx);
mapImagey = getMapY(indexMapy + k, indexMapx);
vy = (k - tileHeight) * tileSize;
m_View.drawClipImg(destx, vy, tileSize, tileSize, mapImagex, mapImagey, mapImage, paint, carGp);
timer++;
}
System.out.println("x:"+timer);
} private void copyBufferX(int indexMapx, int indexMapy, int tileHeight, int destx, int desty) { int mapImagex, mapImagey, vy; // 拷貝地圖上面到緩沖的下面 int timer=0; for (int j = 0; j
步驟五的實現(xiàn)
將后臺緩沖區(qū)的四個子區(qū)按照順序畫到屏幕上:
view plain
copy to clipboard
print
?
public void paint(Canvas g, int x, int y)
{
// 地圖在緩沖中的坐標(biāo)
int tempx = mapOffx % bufWidth;
int tempy = mapOffy % bufHeight;
// 切割線右下角的寬與高
int rightWidth = bufWidth - tempx;
int rightHeight = bufHeight - tempy;
// 畫左上
drawRegion(g, carBuffer, tempx, tempy, rightWidth, rightHeight, 0, x, y);
// 畫右上
drawRegion(g, carBuffer, 0, tempy, scrWidth - rightWidth, rightHeight, 0, x + rightWidth, y);
// 畫左下
drawRegion(g, carBuffer, tempx, 0, rightWidth, scrHeight - rightHeight, 0, x, y + rightHeight);
// 畫右下
drawRegion(g, carBuffer, 0, 0, scrWidth - rightWidth, scrHeight
- rightHeight, 0, x + rightWidth, y + rightHeight);
}
/**
* 畫圖 *
* @param g * 目標(biāo)屏幕的畫筆
* @param img * 原圖片
* @param x_src * 原圖片X坐標(biāo)
* @param y_src * 原圖片Y坐標(biāo)
* @param width * 原圖片寬度
* @param height * 原圖片高度
* @param transform * 旋轉(zhuǎn)角度
* @param x_dest * 目標(biāo)屏幕的X坐標(biāo)
* @param y_dest * 目標(biāo)屏幕的Y坐標(biāo)
* @param anchor * 畫筆的錨點(diǎn)
*/
private void drawRegion(Canvas g, Bitmap img, int x_src, int y_src,
int width, int height, int transform, int x_dest,
int y_dest)
{
// 作寬度檢測
if (width 0 || height 0)
{
return;
}
// 作超屏幕寬度檢測
if (width > scrWidth)
{
width = scrWidth;
// 作超屏幕高度檢測
}
if (height > scrHeight)
{
height = scrHeight;
}
m_View.drawClipImg(x_dest, y_dest, width, height, x_src, y_src, img, paint, g);
} public void paint(Canvas g, int x, int y) { // 地圖在緩沖中的坐標(biāo) int tempx = mapOffx % bufWidth; int tempy = mapOffy % bufHeight; // 切割線右下角的寬與高 int rightWidth = bufWidth - tempx; int rightHeight = bufHeight - tempy; // 畫左上 drawRegion(g, carBuffer, tempx, tempy, rightWidth, rightHeight, 0, x, y); // 畫右上 drawRegion(g, carBuffer, 0, tempy, scrWidth - rightWidth, rightHeight, 0, x + rightWidth, y); // 畫左下 drawRegion(g, carBuffer, tempx, 0, rightWidth, scrHeight - rightHeight, 0, x, y + rightHeight); // 畫右下 drawRegion(g, carBuffer, 0, 0, scrWidth - rightWidth, scrHeight - rightHeight, 0, x + rightWidth, y + rightHeight); } /** * 畫圖 * * @param g * 目標(biāo)屏幕的畫筆 * @param img * 原圖片 * @param x_src * 原圖片X坐標(biāo) * @param y_src * 原圖片Y坐標(biāo) * @param width * 原圖片寬度 * @param height * 原圖片高度 * @param transform * 旋轉(zhuǎn)角度 * @param x_dest * 目標(biāo)屏幕的X坐標(biāo) * @param y_dest * 目標(biāo)屏幕的Y坐標(biāo) * @param anchor * 畫筆的錨點(diǎn) */ private void drawRegion(Canvas g, Bitmap img, int x_src, int y_src, int width, int height, int transform, int x_dest, int y_dest) { // 作寬度檢測 if (width scrWidth) { width = scrWidth; // 作超屏幕高度檢測 } if (height > scrHeight) { height = scrHeight; } m_View.drawClipImg(x_dest, y_dest, width, height, x_src, y_src, img, paint, g); }
當(dāng)然,地圖的卷動和精靈的移動是分不開的,在本文中我們只闡述了游戲的地圖繪制方法,關(guān)于精靈的繪制以及地圖隨精靈的位移而卷動,我們會在另一篇文章中做以介紹。
總結(jié)
卡馬克算法是在進(jìn)行2D游戲地圖卷動的算法中內(nèi)存痕跡最小、效率適中的算法之一。其核心的思想就是把地圖卷動過程中移出屏幕(不需要在顯示的部分)所占用的buffer區(qū)域,繪制上新的需要圖塊,在往屏幕上繪制的時候,通過四次繪制buffer把完整的地圖重現(xiàn)。
我們在實際的代碼編寫中按以下的方式進(jìn)行。根據(jù)上面的基本思想,把地圖分為四個塊(十字形的將buffer劃分為四塊),用carx和cary來記錄十字分區(qū)的中心坐標(biāo)(相對于buffer的坐標(biāo),我把這個點(diǎn)叫卡馬克分區(qū)點(diǎn))。
當(dāng)?shù)貓D向右移動的時候這時把卡馬克分區(qū)點(diǎn)的坐標(biāo)X方向加上一個tile的width,然后在從現(xiàn)在的卡馬克分區(qū)點(diǎn)的坐標(biāo)Y開始繪制提取出來的tileID對應(yīng)的圖象,注意是從當(dāng)前的卡馬克分區(qū)點(diǎn)的坐標(biāo)Y開始繪制,當(dāng)超出carHeight時在從0開始繪制直到結(jié)束,這樣就完成了在水平方向上的更新。
還有就是在水平移動卡馬克分區(qū)點(diǎn)的時候是在buffer中循環(huán)的,也就是從0到carWidth的一個循環(huán)過程,Y方向上完全一致。最后是繪制過程,也就是將四個分區(qū)繪制出來,口訣就是左變右上變下,掌握好卡馬克算法對手游開發(fā)很有幫助的。
注:本文參考了網(wǎng)上關(guān)于卡馬克算法的一些介紹并引用了其中的部分文字和圖片。
本文來自ChinaUnix博客,如果查看原文請點(diǎn):http://blog.chinaunix.net/u1/49717/showart_2186130.html |
|