web-dev-qa-db-ja.com

等尺性ゲームの世界を描く

2Dゲームで等尺性タイルを描画する正しい方法は何ですか?

マップの2D配列表現の各列をジグザグする方法でタイルをレンダリングすることを示唆する参照( this one など)を読みました。画面に描画されるものが2D配列の外観とより密接に関連し、少し回転するだけで、ダイヤモンド風にもっと描画されるべきだと思います。

どちらの方法にも長所と短所はありますか?

176
Benny Hallett

更新:マップレンダリングアルゴリズムが修正され、イラストが追加され、フォーマットが変更されました。

おそらく、タイルを画面にマッピングする「ジグザグ」技術の利点は、タイルのxおよびy座標が垂直および水平軸上にあると言えるでしょう。

「ひし形で描く」アプローチ:

「菱形で描画」を使用して等尺性マップを描画することにより、この例のように、2次元配列でネストされた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ループであり、すべてのタイルで一貫して動作するかなり単純なロジックを備えていることです。

欠点:

そのアプローチの欠点の1つは、マップ上のタイルのxおよびy座標が対角線で増加することです。これにより、画面上の場所を表示されるマップに視覚的にマッピングすることがより難しくなる配列として:

Image of tile map

ただし、上記のサンプルコードの実装には落とし穴があります。レンダリングの順序により、特定のタイルの後ろにあるはずのタイルが前のタイルの上に描画されます。

Resulting image from incorrect rendering order

この問題を修正するには、内側のfor- loopの順序を逆にする必要があります。最高値から始まり、低い値に向かってレンダリングします。

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 approach to rendering seems compact

欠点:

ジグザグ技術を実装しようとすると、配列内の各要素にネストされたfor- loopほど簡単に記述できないため、レンダリングコードを書くのが少し難しくなるという欠点があります。

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

注:この回答に含まれるイラストは、マップとして次のint配列を使用して、タイルレンダリングコードのJava実装を使用して作成されました。

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は、タイルを表す画像内の地面タイルの幅と高さを参照します。

Image showing the tile width and height

画像の寸法とタイルの寸法が一致している限り、画像の寸法を使用しても機能します。それ以外の場合、タイルマップは、タイル間のギャップでレンダリングできます。

493
coobird

どちらの方法でも仕事は完了です。ジグザグとは、次のようなことを意味すると思います:(数字はレンダリングの順序です)

..  ..  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の位置を特定するには、いくつかの計算が必要になります。最終的に、両方の方法は、特定の効率レベルに必要な数学の点ではほぼ同じです。

10
zaratustra

ダイヤモンドの境界を超えるタイルがある場合は、深さ順に描画することをお勧めします。

...1...
..234..
.56789.
..abc..
...d...
1
Wouter Lievens

実際の問題は、2つ以上の他のタイルと交差/スパンするタイル/スプライトを描画する必要がある場合です。

問題の個人的な分析の2(ハード)ヶ月後、私は最終的に私の新しいcocos2d-jsゲームの「正しいレンダリング描画」を見つけて実装しました。解決策は、各タイル(感受性)に対して、どのスプライトが「前、後ろ、上、後ろ」であるかをマッピングすることです。それを行うと、「再帰的ロジック」に従ってそれらを描くことができます。

0
Carlos Lopez

Coobirdの答えは正しい、完全なものです。ただし、彼のヒントを別のサイトのヒントと組み合わせて、アプリ(iOS/Objective-C)で動作するコードを作成しました。このコードを探している人と共有したいと思います。この回答に賛成または賛成票を投じる場合は、オリジナルについても同じようにしてください。私がしたことは「巨人の肩の上に立つ」ことだけでした。

ソート順については、私のテクニックはPainterのアルゴリズムを修正したものです。各オブジェクトは、(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;
}
0
Olie