绘制等距游戏世界

发布于 2024-07-21 09:18:59 字数 319 浏览 8 评论 0原文

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

我已阅读参考文献(例如 这个)建议以锯齿形方式呈现图块,使地图的 2D 数组表示中的每一列呈锯齿状。 我想它们应该更多地以菱形方式绘制,其中绘制到屏幕上的内容与 2D 阵列的外观更密切相关,只是旋转了一点。

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

What is the correct way to draw isometric tiles in a 2D game?

I've read references (such as this one) that suggest the tiles be rendered in a way that will zig-zag each column in the 2D array representation of the map. I imagine that they should be drawn more in a diamond fashion, where what gets drawn to the screen relates more closely to what the 2D array would look like, just rotated a little.

Are there advantages or disadvantages to either method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

甜柠檬 2024-07-28 09:18:59

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

也许将图块映射到屏幕的“zig-zag”技术的优势可以说是图块的 x< /code> 和 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 循环,具有相当直接的逻辑:在所有瓷砖上一致地工作。

缺点

该方法的一个缺点是地图上图块的 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)
        )

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

正确渲染顺序生成的图像

“之字形”方法:

优点:

也许“zig-zag”方法的优点是渲染的地图可能看起来比“菱形”方法在垂直方向上更紧凑:

Zig-zag 渲染方法看起来很紧凑

缺点:

从尝试实现 zig-zag 技术来看,缺点可能是编写渲染代码有点困难,因为它不能像嵌套 < 那样简单地编写code>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 指的是地面的宽度和高度代表图块的图像中的图块:

显示图块宽度和高度的图像

只要图像尺寸和图块尺寸匹配,就可以使用图像的尺寸。 否则,瓦片地图可能会在瓦片之间呈现间隙。

Update: Corrected map rendering algorithm, added more illustrations, changed formating.

Perhaps the advantage for the "zig-zag" technique for mapping the tiles to the screen can be said that the tile's x and y coordinates are on the vertical and horizontal axes.

"Drawing in a diamond" approach:

By drawing an isometric map using "drawing in a diamond", which I believe refers to just rendering the map by using a nested for-loop over the two-dimensional array, such as this example:

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

Advantage:

The advantage to the approach is that it is a simple nested for-loop with fairly straight forward logic that works consistently throughout all tiles.

Disadvantage:

One downside to that approach is that the x and y coordinates of the tiles on the map will increase in diagonal lines, which might make it more difficult to visually map the location on the screen to the map represented as an array:

Image of tile map

However, there is going to be a pitfall to implementing the above example code -- the rendering order will cause tiles that are supposed to be behind certain tiles to be drawn on top of the tiles in front:

Resulting image from incorrect rendering order

In order to amend this problem, the inner for-loop's order must be reversed -- starting from the highest value, and rendering toward the lower value:

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

With the above fix, the rendering of the map should be corrected:

Resulting image from correct rendering order

"Zig-zag" approach:

Advantage:

Perhaps the advantage of the "zig-zag" approach is that the rendered map may appear to be a little more vertically compact than the "diamond" approach:

Zig-zag approach to rendering seems compact

Disadvantage:

From trying to implement the zig-zag technique, the disadvantage may be that it is a little bit harder to write the rendering code because it cannot be written as simple as a nested for-loop over each element in an array:

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
        )

Also, it may be a little bit difficult to try to figure out the coordinate of a tile due to the staggered nature of the rendering order:

Coordinates on a zig-zag order rendering

Note: The illustrations included in this answer were created with a Java implementation of the tile rendering code presented, with the following int array as the map:

tileMap = new int[][] {
    {0, 1, 2, 3},
    {3, 2, 1, 0},
    {0, 0, 1, 1},
    {2, 2, 3, 3}
};

The tile images are:

  • tileImage[0] -> A box with a box inside.
  • tileImage[1] -> A black box.
  • tileImage[2] -> A white box.
  • tileImage[3] -> A box with a tall gray object in it.

A Note on Tile Widths and Heights

The variables tile_width and tile_height which are used in the above code examples refer to the width and height of the ground tile in the image representing the tile:

Image showing the tile width and height

Using the dimensions of the image will work, as long as the image dimensions and the tile dimensions match. Otherwise, the tile map could be rendered with gaps between the tiles.

等待我真够勒 2024-07-28 09:18:59

无论哪种方式都能完成工作。 我假设锯齿形你的意思是这样的:(数字是渲染的顺序)

..  ..  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 的位置。最终,两种方法在一定效率水平所需的数学方面大致相同。

Either way gets the job done. I assume that by zigzag you mean something like this: (numbers are order of rendering)

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

And by diamond you mean:

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

The first method needs more tiles rendered so that the full screen is drawn, but you can easily make a boundary check and skip any tiles fully off-screen. Both methods will require some number crunching to find out what is the location of tile 01. In the end, both methods are roughly equal in terms of math required for a certain level of efficiency.

回忆追雨的时光 2024-07-28 09:18:59

如果您有一些超出菱形边界的图块,我建议按深度顺序绘制:

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

If you have some tiles that exceed the bounds of your diamond, I recommend drawing in depth order:

...1...
..234..
.56789.
..abc..
...d...
执着的年纪 2024-07-28 09:18:59

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;
}

Coobird's answer is the correct, complete one. However, I combined his hints with those from another site to create code that works in my app (iOS/Objective-C), which I wanted to share with anyone who comes here looking for such a thing. Please, if you like/up-vote this answer, do the same for the originals; all I did was "stand on the shoulders of giants."

As for sort-order, my technique is a modified painter's algorithm: each object has (a) an altitude of the base (I call "level") and (b) an X/Y for the "base" or "foot" of the image (examples: avatar's base is at his feet; tree's base is at it's roots; airplane's base is center-image, etc.) Then I just sort lowest to highest level, then lowest (highest on-screen) to highest base-Y, then lowest (left-most) to highest base-X. This renders the tiles the way one would expect.

Code to convert screen (point) to tile (cell) and back:

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;
}
ま昔日黯然 2024-07-28 09:18:59

您可以使用距观察者最近的最高点的欧几里得距离,但这不太正确。 它会产生球形排序顺序。 您可以通过从更远的地方观察来理清这一点。 距离更远,曲率变得平坦。 因此,只需将 1000 添加到每个 x、y 和 z 分量即可得到 x'、y' 和 z'。 对 x'*x'+y'*y'+z'*z' 进行排序。

You could use euclidean distance from the point highest and nearest the viewer, except that is not quite right. It results in spherical sort order. You can straighten that out by looking from further away. Further away the curvature becomes flattened out. So just add say 1000 to each of the x,y and z components to give x',y' and z'. The sort on x'*x'+y'*y'+z'*z'.

别把无礼当个性 2024-07-28 09:18:59

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

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

Real problem is when you need draw some tile/sprites intersecting/spanning two or more other tiles.

After 2 (hard) months of personal analisys of problem I finally found and implemented a "correct render drawing" for my new cocos2d-js game.
Solution consists in mapping, for each tile (susceptible), which sprites are "front, back, top and behind".
Once doing that you can draw them following a "recursive logic".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文