绘制等距游戏世界


178

在2D游戏中绘制等距图块的正确方法是什么?

我已经阅读了一些参考资料(例如this),这些参考资料建议以某种方式渲染图块,这些图块将使地图的2D数组表示形式中的每一列都呈锯齿形。我想应该以菱形的方式绘制它们,在屏幕上绘制的内容与2D阵列的外观更紧密相关,只是稍微旋转一下即可。

两种方法都有优点还是缺点?

Answers:


505

更新:纠正了地图渲染算法,添加了更多插图,更改了格式。

也许可以说将瓦片映射到屏幕的“之字形”技术的优势在于,瓦片xy坐标位于垂直轴和水平轴上。

“画钻石”的方法:

通过使用“在菱形中绘制”绘制等距图,我相信这是指for通过在二维数组上使用嵌套-loop 来渲染地图,例如以下示例:

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循环,具有相当简单的逻辑,可在所有图块中一致地工作。

坏处:

这种方法的一个缺点是,地图上图块的xy坐标将以对角线增加,这可能使得将屏幕上的位置直观地映射到以数组表示的地图上更加困难:

瓷砖地图的图像

但是,实现上面的示例代码将有一个陷阱-渲染顺序将导致应该位于某些图块后面的图块被绘制在前面的图块之上:

错误渲染顺序导致的图像

为了解决此问题,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_widthtile_height是指表示图块的图像中图块的宽度和高度:

该图显示了瓷砖的宽度和高度

只要图像尺寸和图块尺寸匹配,就可以使用图像的尺寸。否则,可以使用图块之间的间隙来渲染图块地图。


136
你甚至画了照片。辛苦了
zaratustra,

干杯。对于正在开发的当前游戏,我正在使用菱形方法,这种方法很有效。再次感谢。
本尼·哈雷特

3
多个高度层呢?我可以从最低级别开始一直拖延直到达到最高级别吗?
NagyI 2011年

2
@DomenicDatti:谢谢您的客气话:)
coobird 2011年

2
这太棒了。我只是使用了菱形方法,而且,如果有人需要从屏幕位置获取网格坐标,我可以这样做:j = (2 * x - 4 * y) / tilewidth * 0.5; i = (p.x * 2 / tilewidth) - j;
Kyr Dunenkoff

10

无论哪种方式都可以完成工作。我假设通过之字形表示的意思是这样的:(数字是渲染的顺序)

..  ..  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的位置。最后,在一定效率下,两种方法在数学上大致相等。


14
实际上,我的意思是相反的。菱形(使地图的边缘保持平滑)和之字形方法,使边缘变得尖刺
Benny Hallett

1

如果您有一些超出钻石边界的瓷砖,我建议按深度顺序绘制:

...1...
..234..
.56789.
..abc..
...d...

1

Coobird的答案是正确,完整的答案。但是,我将他的提示与其他站点的提示相结合,以创建适用于我的应用程序(iOS / Objective-C)的代码,我想与任何来这里寻找此类东西的人分享。请,如果您喜欢/赞成这个答案,请对原件做同样的事情;我所做的只是“站在巨人的肩膀上”。

至于排序顺序,我的技术是修改画家的算法:每个对象都具有(a)基准的高度(我称为“水平”),以及(b)基准的“基准”或“英尺”的X / Y图像(例如:化身的底部在他的脚下;树的底部在它的根部;飞机的底部在中心图像等),然后我将最低到最高级别排序,然后从最低(屏幕上最高)排序到最高底部, Y,然后从最低(最左侧)到最高X。这样就可以像人们期望的那样渲染图块。

将屏幕(点)转换为图块(单元格)并返回的代码:

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:
//      http://stackoverflow.com/questions/892811/drawing-isometric-game-worlds/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;
}

1

您可以使用距观看者最高和最近的点的欧几里得距离,除非那不太正确。结果为球形排序。您可以从更远的地方看清楚。更远处的曲率变得平坦。因此,只需在x,y和z分量中分别添加1000,即可得出x',y'和z'。在x'* x'+ y'* y'+ z'* z'上排序。


0

真正的问题是当您需要绘制与两个或多个其他图块相交/跨越的图块/子图时。

经过2个月的艰苦的个人分析,我终于为我的新cocos2d-js游戏找到并实现了“正确的渲染图”。解决方案包括为每个图块(敏感图)映射哪些精灵为“正面,背面,顶部和背面”。完成后,您可以按照“递归逻辑”绘制它们。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.