绘制等距游戏世界

时间:2009-05-21 12:59:28

标签: 2d isometric

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

我已经读过引用(例如this one),这些引用建议以一种在地图的2D数组表示中对每列进行锯齿形的方式渲染切片。我想它们应该以钻石方式绘制得更多,其中被绘制到屏幕上的内容更接近于2D阵列的样子,只是旋转了一点。

这两种方法都有优缺点吗?

6 个答案:

答案 0 :(得分:490)

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

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

“绘制钻石”方法:

通过使用“在钻石中绘图”绘制等轴测图,我相信这只是通过在二维数组上使用嵌套的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 - 循环,具有相当直接的逻辑,可以在所有磁贴中一致地工作。

缺点:

该方法的一个缺点是地图上瓷砖的xy坐标会在对角线上增加,这可能会使在屏幕上的位置可视化地映射到更难地图表示为数组:

Image of tile map

然而,实现上面的示例代码会有一个陷阱 - 渲染顺序会导致应该在某些区块后面的区块被绘制在前面的区块之上:

Resulting image from incorrect rendering order

为了修正这个问题,必须颠倒内部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)
        )

通过上述修复,应更正地图的渲染:

Resulting image from correct rendering order

“Zig-zag”方法:

优势:

“zig-zag”方法的优势可能是渲染的地图可能看起来比“钻石”方法更紧凑:

Zig-zag approach to rendering seems compact

缺点:

从尝试实现Zig-zag技术,缺点可能是编写渲染代码有点困难,因为它不能像嵌套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
        )

此外,由于渲染顺序的交错特性,尝试找出图块的坐标可能有点困难:

Coordinates on a zig-zag order rendering

注意:本答案中包含的插图是使用呈现的平铺渲染代码的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指的是代表图块的图像中地面图块的宽度和高度:

Image showing the tile width and height

只要图像尺寸和图块尺寸匹配,使用图像的尺寸即可。否则,可以使用切片之间的间隙渲染切片贴图。

答案 1 :(得分:10)

无论哪种方式都可以完成工作。我假设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
..  ..  ..  ..  ..

第一种方法需要渲染更多的图块,以便绘制整个屏幕,但您可以轻松地进行边界检查并完全跳出任何图块。这两种方法都需要一些数字运算才能找出tile 01的位置。最后,两种方法在一定效率水平所需的数学方面大致相等。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

真正的问题是当你需要绘制一些相交/跨越两个或更多其他瓷砖的瓷砖/精灵时。

经过2个(硬)个月的问题解析后,我终于找到了并为我的新cocos2d-js游戏实现了“正确的渲染图”。 解决方案包括为每个瓷砖(易受影响)映射哪些精灵是“正面,背面,顶部和背面”。 一旦这样做,您可以按照“递归逻辑”绘制它们。

答案 5 :(得分:0)

Coobird的答案是正确的,完整的答案。但是,我将他的提示与来自其他网站的提示相结合,以创建可在我的应用程序中运行的代码(iOS / Objective-C),我想与来这里寻找此类事物的任何人分享。如果你喜欢/赞成这个答案,请为原件做同样的事情;我所做的就是站在巨人的肩膀上。&#34;

至于排序,我的技术是一个修改过的画家的算法:每个对象都有(a)基础的高度(我称之为#34;等级&#34;)和(b)X / Y为&#34; base&#34;或者&#34;脚&#34;图像的例子(例如:阿凡达的基地在他的脚下;树的基地在它的根部;飞机的基地是中心图像,等等)然后我只是排序从最低到最高水平,然后是最低(在屏幕上最高)到最高基础-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;
}