在2D游戏中绘制等距瓷砖的正确方法是什么?
我阅读了参考资料(如这篇文章),建议以一种方式渲染瓷砖,能够使地图的2D数组表示中的每列都呈现出锯齿状。我想他们应该更多地以菱形的方式绘制,使得屏幕上所绘制的内容更接近于2D数组的外观,只是稍微旋转了一下。
这两种方法有优缺点吗?
更新:修正了地图渲染算法,添加了更多插图,改变了格式。
也许“之字型”技术将瓦片映射到屏幕上的优点在于瓦片的 x
和 y
坐标分别位于垂直和水平轴上。
“以菱形绘制”方法:
通过使用“以菱形绘制”的等距地图绘制方法,我相信这指的是使用一个嵌套的for
循环遍历二维数组来渲染地图,例如下面的示例:
tile_map[][] = [[...],...]
for (cellY = 0; cellY < tile_map.size; cellY++):
for (cellX = 0; cellX < tile_map[cellY].size cellX++):
draw(
tile_map[cellX][cellY],
screenX = (cellX * tile_width / 2) + (cellY * tile_width / 2)
screenY = (cellY * tile_height / 2) - (cellX * tile_height / 2)
)
优点:
这种方法的优点在于它是一个简单的嵌套for
循环,具有相当直观的逻辑,并且在所有图块中始终工作。
缺点:
这种方法的一个缺点是地图上的x
和y
坐标会沿对角线增加,这可能会使得将屏幕上的位置映射到表示为数组的地图更加困难:
然而,实现上述示例代码存在一个问题——渲染顺序会导致本应该在某些图块后面的图块被绘制在前面的图块上:
为了解决这个问题,内部for
循环的顺序必须被颠倒——从最高值开始,向较低值渲染:
tile_map[][] = [[...],...]
for (i = 0; i < tile_map.size; i++):
for (j = tile_map[i].size; j >= 0; j--): // Changed loop condition here.
draw(
tile_map[i][j],
x = (j * tile_width / 2) + (i * tile_width / 2)
y = (i * tile_height / 2) - (j * tile_height / 2)
)
通过上述修复,地图的呈现应该已经被纠正:
“之”字形方法:
优点:
也许“之”字形方法的优点在于,呈现出来的地图可能比“菱形”方法更紧凑:
缺点:
尝试实现“之”字形技术时,缺点可能在于写渲染代码会有些困难,因为它不能像对数组中的每个元素进行嵌套的for
循环那样简单:
tile_map[][] = [[...],...]
for (i = 0; i < tile_map.size; i++):
if i is odd:
offset_x = tile_width / 2
else:
offset_x = 0
for (j = 0; j < tile_map[i].size; j++):
draw(
tile_map[i][j],
x = (j * tile_width) + offset_x,
y = i * tile_height / 2
)
此外,由于渲染顺序的错位性质,在尝试确定瓦片坐标时可能会有些困难:
注意:本答案中包含的插图是使用展示的瓦片渲染代码的Java实现创建的,使用以下 int
数组作为地图:
tileMap = new int[][] {
{0, 1, 2, 3},
{3, 2, 1, 0},
{0, 0, 1, 1},
{2, 2, 3, 3}
};
瓷砖图像如下:
tileImage[0] ->
一个盒子里面还有一个盒子。tileImage[1] ->
一个黑色的盒子。tileImage[2] ->
一个白色的盒子。tileImage[3] ->
一个盒子里面有一个高的灰色物体。关于瓷砖宽度和高度的说明
在上述代码示例中使用的变量tile_width
和tile_height
指的是表示瓷砖的图像中地面瓷砖的宽度和高度:
使用图像的尺寸将起作用,只要图像尺寸和瓷砖尺寸相匹配。否则,瓷砖地图可能会呈现出瓷砖之间的间隙。
无论哪种方法,都能完成任务。我猜你所说的"zigzag"是指像这样的东西:(数字表示渲染顺序)
.. .. 01 .. ..
.. 06 02 ..
.. 11 07 03 ..
16 12 08 04
21 17 13 09 05
22 18 14 10
.. 23 19 15 ..
.. 24 20 ..
.. .. 25 .. ..
而您所说的钻石是指:
.. .. .. .. ..
01 02 03 04
.. 05 06 07 ..
08 09 10 11
.. 12 13 14 ..
15 16 17 18
.. 19 20 21 ..
22 23 24 25
.. .. .. .. ..
第一种方法需要渲染更多的图块才能绘制全屏,但是你可以轻松进行边界检查并跳过任何完全不在屏幕上的图块。两种方法都需要一些数字计算来找出图块01的位置。最终,两种方法在达到一定的效率水平所需的数学计算方面大致相等。
...1...
..234..
.56789.
..abc..
...d...
typedef struct ASIntCell { // like CGPoint, but with int-s vice float-s
int x;
int y;
} ASIntCell;
// Cell-math helper here:
// http://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511
// Although we had to rotate the coordinates because...
// X increases NE (not SE)
// Y increases SE (not SW)
+ (ASIntCell) cellForPoint: (CGPoint) point
{
const float halfHeight = rfcRowHeight / 2.;
ASIntCell cell;
cell.x = ((point.x / rfcColWidth) - ((point.y - halfHeight) / rfcRowHeight));
cell.y = ((point.x / rfcColWidth) + ((point.y + halfHeight) / rfcRowHeight));
return cell;
}
// Cell-math helper here:
// https://dev59.com/OXNA5IYBdhLWcg3wn_Q1#893063
// X increases NE,
// Y increases SE
+ (CGPoint) centerForCell: (ASIntCell) cell
{
CGPoint result;
result.x = (cell.x * rfcColWidth / 2) + (cell.y * rfcColWidth / 2);
result.y = (cell.y * rfcRowHeight / 2) - (cell.x * rfcRowHeight / 2);
return result;
}
真正的问题是当你需要绘制一些与两个或多个其他图块相交/跨越的图块/精灵时。
经过2个(艰难)月的个人问题分析,我终于为我的新cocos2d-js游戏找到并实现了“正确的渲染绘制”。解决方案包括为每个图块(易感)映射哪些精灵是“前面、后面、顶部和后面”的。一旦这样做,您可以按照“递归逻辑”绘制它们。