斜45度地圖簡介、座標系轉換以及資料碰撞
手機平臺上開發斜45度地圖系統的遊戲,相信做慣了正面俯視的開發者剛接觸總很不習慣。所謂斜45度遊戲,也就是常說的2.5D遊戲,用斜方向俯視的角度來增強立體感的一種技術。這種技術在PC平臺上早就流行了,手機平臺由於螢幕表現力的限制,大部分使用正面視角。但隨著手機螢幕解析度不斷增大,斜45度視角的遊戲出現得越來越多。
斜45度地圖系統分Staggered、Slide、Diamond等幾種,除了起始位置的區別,與正視地圖系統的主要區別在於使用菱形的圖塊。關於什麼是45度地圖系統以及其原理,我不想再多說,網上有很多的資料,下面主要講一講座標系的轉換。
圖1
如圖1,雖然圖塊是菱形拼接,但圖片還是矩形,繪製地圖就是要矩形的圖片對映到正確的螢幕座標系上,使圖片無縫地拼接起來。如圖2,以Diamond地圖系統為例,為了簡化,掠過滾屏等問題,假設始終從螢幕左上角開始繪製0,0的資料,圖中所標的數字是指圖塊資料下標(data[j][i]中的(j,i))。通過觀察你會發現:圖片的X位置為資料j,i之差乘以圖片寬度的一半,Y位置為資料j,i之和乘以高度的一半,即公式1:
px = (CHIP_W >> 1) * (i - j);
py = (CHIP_H >> 1) * (i + j);
通過公式1,我們就能根據資料陣列的下標值獲得對應圖塊在螢幕座標系中的位置。逆推,得到公式2:
i = 0.5f * (y / (CHIP_H >> 1) + x / (CHIP_W >> 1));
j = 0.5f * (y / (CHIP_H >> 1) - x / (CHIP_W >> 1));
通過公式2,可以把圖塊位置對映到對應的資料座標。
下面開始繪製地圖內容。傳統的俯視角度遊戲地圖繪製,都是記錄螢幕(或攝像機)所在的資料j,i位置,計算螢幕能容納多少個圖塊,用一個巢狀迴圈完成地圖的繪製。如下所示:
for(int j=0;j<SCREEN_HEIGHT/CHIP_H;j++)
{
for(int i=0;i<SCREEN_WIDTH/CHIP_W;i++)
{
......//do draw
}
}
圖3
但是如圖3所示,斜45度地圖是斜的,無法用陣列的迴圈遍歷來繪製。不過既然有了轉換公式,我們可以把螢幕分割成CHIP_W/2*CHIP_H/2的若干個區域,通過“遍歷”這些區域的座標,可以用公式知道,要在這個座標上畫哪張圖片。演算法如下:
Java程式碼- //當paintY為CHIP_H / 2的奇數倍時,paintX需要偏移CHIP_W / 2
- int offset = 0;
- for (int paintY = 0; paintY <= SCREEN_H + CHIP_H; paintY += CHIP_H / 2)
- for (int paintX = 0; paintX <= SCREEN_W + CHIP_W; paintX += CHIP_W)
- {
- int gx = getGx(paintX + offset, paintY) + startCol;
- int gy = getGy(paintX + offset, paintY) + startRow;
- if (gy < 0 || gx < 0 || gy > 10 || gx > 10)
- {
- continue;
- }
- drawTile(g, data[gy][gx], paintX + offset, paintY);
- }
- offset = offset == 0 ? CHIP_W / 2 : 0;
- }
- //螢幕座標轉換成遊戲座標
- int getGx(int x, int y)
- {
- return (int) (0.5f * (y / (CHIP_H >> 1) + x / (CHIP_W >> 1)));
- }
- int getGy(int x, int y)
- {
- return (int) (0.5f * (y / (CHIP_H >> 1) - x / (CHIP_W >> 1)));
- }
關於地圖上的碰撞與拾取。當你需要判斷精靈所處地圖資料位置的時候用公式2,會發現判斷誤差很嚴重,仔細想一下不難解釋:逆推過來的公式是根據圖片之間的座標系來定義的,而菱形切片之間透明的部分都是重疊的,所以當你判斷重疊部分的碰撞時就無法預計判斷是那一塊資料了,所以用公式2進行地圖資料的碰撞判斷不可行。
圖4
圖5 圖6
介紹一下我的碰撞方法。如圖4,要判斷螢幕中任意一點X,Y於資料中的位置。首先按圖塊的圖片尺寸將螢幕分割,計算X,Y位於圖中綠色矩形框選的哪個圖片中,初步得到j,i。知道了位於哪張圖塊圖片中,如圖5,再判斷X,Y位於圖5中四個角落哪一個區域。結合圖6,如果都不位於這四個角落,那X,Y就屬於j,i。位於左上角的紅色區域,對應i-1,其它三個角落同理。這裡的難點在於如何判斷紅色區域,建議用三角形與點碰撞的演算法。這種碰撞拾取演算法的優點是精確無誤差,而且無論菱形圖塊比例是32:15 、2:1還是其他比例都可以檢測。
資料碰撞檢測的主要程式碼:
Java程式碼- int[] checkInDataIJ(int x, int y)
- {
- final int I = 0;
- final int J = 1;
- int[] data = new int[] { 0, 0 };
- Log.e("", "click:" + x + "," + y);
- int xd = x / CHIP_W;
- int yd = y / CHIP_H;
- if (x < 0)
- {
- xd -= 1;
- }
- if (y < 0)
- {
- yd -= 1;
- }
- Log.e("", "xd:" + xd + " yd:" + yd);
- data[I] = yd + xd;
- data[J] = yd - xd;
- //計算觸控點位於矩形中,與菱形的位置
- int cx = x % CHIP_W;
- if (cx < 0)
- {
- cx += CHIP_W;
- }
- int cy = y % CHIP_H;
- if (cy < 0)
- {
- cy += CHIP_H;
- } //是否位於左上角的三角形
- if (MyMath.isInsideTriangle(cx,
- cy,
- new int[] { 0, CHIP_W / 2, 0 },
- new int[] { 0, 0, CHIP_H / 2 }))
- {
- data[I] -= 1;
- }
- //是否位於右上角的三角形
- else if (MyMath.isInsideTriangle(cx, cy, new int[] { CHIP_W / 2,
- CHIP_W, CHIP_W }, new int[] { 0, 0, CHIP_H / 2 }))
- {
- data[J] -= 1;
- }
- //是否位於右下角的三角形
- else if (MyMath.isInsideTriangle(cx, cy, new int[] { CHIP_W, CHIP_W,
- CHIP_W / 2 }, new int[] { CHIP_H / 2, CHIP_H, CHIP_H }))
- {
- data[I] += 1;
- }
- //是否位於左下角的三角形
- else if (MyMath.isInsideTriangle(cx,
- cy,
- new int[] { 0, CHIP_W / 2, 0 },
- new int[] { CHIP_H / 2, CHIP_H, CHIP_H }))
- {
- data[J] += 1;
- }
- Log.e("debug", "get(:" + data[J] + "," + data[I] + ")");
- return data;
- }
三角形檢測演算法:
Java程式碼- public static boolean isInsideTriangle(int cx, int cy, int[] x, int[] y)
- {
- float vx2 = cx - x[0];
- float vy2 = cy - y[0];
- float vx1 = x[1] - x[0];
- float vy1 = y[1] - y[0];
- float vx0 = x[2] - x[0];
- float vy0 = y[2] - y[0];
- float dot00 = vx0 * vx0 + vy0 * vy0;
- float dot01 = vx0 * vx1 + vy0 * vy1;
- float dot02 = vx0 * vx2 + vy0 * vy2;
- float dot11 = vx1 * vx1 + vy1 * vy1;
- float dot12 = vx1 * vx2 + vy1 * vy2;
- float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
- float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
- float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
- return ((u > 0) && (v > 0) && (u + v < 1));
- }