制作等轴视角游戏世界

181

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

我阅读了参考资料(如这篇文章),建议以一种方式渲染瓷砖,能够使地图的2D数组表示中的每列都呈现出锯齿状。我想他们应该更多地以菱形的方式绘制,使得屏幕上所绘制的内容更接近于2D数组的外观,只是稍微旋转了一下。

这两种方法有优缺点吗?

6个回答

524

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

也许“之字型”技术将瓦片映射到屏幕上的优点在于瓦片的 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坐标会沿对角线增加,这可能会使得将屏幕上的位置映射到表示为数组的地图更加困难:

图块地图图像

然而,实现上述示例代码存在一个问题——渲染顺序会导致本应该在某些图块后面的图块被绘制在前面的图块上:

错误渲染顺序的结果图像

为了解决这个问题,内部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
        )

此外,由于渲染顺序的错位性质,在尝试确定瓦片坐标时可能会有些困难:

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指的是表示瓷砖的图像中地面瓷砖的宽度和高度:

显示瓷砖宽度和高度的图像

使用图像的尺寸将起作用,只要图像尺寸和瓷砖尺寸相匹配。否则,瓷砖地图可能会呈现出瓷砖之间的间隙。


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
..  ..  ..  ..  ..

第一种方法需要渲染更多的图块才能绘制全屏,但是你可以轻松进行边界检查并跳过任何完全不在屏幕上的图块。两种方法都需要一些数字计算来找出图块01的位置。最终,两种方法在达到一定的效率水平所需的数学计算方面大致相等。


15
我实际上意思是相反的。钻石形状(使地图边缘更加平滑)和锯齿方法,使边缘变得锐利。 - Benny Hallett

2
如果您有一些超出钻石边界的瓷砖,建议按深度顺序进行绘制:
...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:
//      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;
}

1
你可以使用距离观察者最高点和最近点的欧几里得距离,但这并不完全正确。它会导致球形排序。您可以通过从更远的地方观察来解决这个问题。越远,曲率就越平坦。因此,只需将每个x、y和z分量添加1000,以给出x'、y'和z'。然后按照x'*x'+y'*y'+z'*z'进行排序。

0

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接