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


178

2Dゲームでアイソメトリックタイルを描画する正しい方法は何ですか?

Iは、(例えば、参考文献読んだこれタイルがジグザグれるマップの2次元配列表現の各列の方法でレンダリングすることを示唆しています)。画面に描画されるものは、少し回転しただけで2D配列がどのように表示されるかにより密接に関連しているため、ダイヤモンドのように描画する必要があると思います。

どちらの方法にも利点または欠点はありますか?

回答:


505

更新:マップレンダリングアルゴリズムの修正、イラストの追加、書式の変更。

おそらく、タイルを画面にマッピングする「ジグザグ」手法の利点は、タイルの座標xy座標が垂直軸と水平軸上にあると言えます。

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

「ひし形で描画」を使用してアイソメマップを描画することによりfor、次の例のように、2次元配列上でネストされたループを使用してマップをレンダリングするだけだと思います。

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座標が対角線で増加するため、画面上の場所を配列として表されたマップに視覚的にマッピングすることがより困難になる可能性があることです。

タイルマップの画像

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

正しくないレンダリング順序の結果の画像

この問題を修正するには、内部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、2009年

乾杯クーバード。私が開発している現在のゲームにダイヤモンドアプローチを使用しています。再度、感謝します。
ベニーハレット

3
複数の高さのレイヤーはどうですか?最低レベルから始めて最高レベルに到達するまで続けることはできますか?
NagyI 2011年

2
@DomenicDatti:親切な言葉をありがとう:)
coobird '

2
これは素晴らしいです。私はダイアモンドアプローチを使用しました。また、誰かが画面の位置からグリッド座標を取得する必要がある場合に備えて、これを行いましたj = (2 * x - 4 * y) / tilewidth * 0.5; i = (p.x * 2 / tilewidth) - j;
Kyr Dunenkoff 2013

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
私は実際にはその逆を意味しました。(マップのエッジが滑らか葉)ダイヤモンド形状及びジグザグ方式、エッジフック側を残し
ベニーハレット

1

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

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

1

クーバードの答えは正しい、完全なものです。しかし、私は彼のヒントを別のサイトのヒントと組み合わせて、私のアプリ(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つ以上の他のタイルと交差/スパンするいくつかのタイル/スプライトを描画する必要がある場合です。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.