オートタイル:自動タイル遷移

文字通り、グリッドタイルに関するサンドボックスからの記事を偶然見つけて、自分のアナログを書くことにしました。

私の変換割り当て方法は、その記事で言及されているものとは少し異なります。

このシステムの始まりは、悪名高いゲームWarCraft IIIにあります。



画像



WC3マップエディターのこのスクリーンショットでは、縫い目を見ることができます。縫い目は赤い矢印で示されています。 どうやら、このゲームのタイルのロジックは、ほとんどのゲームとは多少異なります。 ここの1つのタイルはセル全体を占有しません 。 あたかもその角が既に描かれているポイントのようです。



これは、グリッドがオンの場合に特に便利です。



画像



通常、この状況では、タイルを4つの小さなタイルに分割することをお勧めします。 しかし、一つのことがあります:そのような場合に何をすべきか?



画像



同じクワッドタイルを囲む4つすべてが異なるのはいつですか? ここでは、最下部のタイルがそのほとんどを占めていることがはっきりとわかります。

長所と短所を比較検討した後、私は独自の、かなり具体的なシステムになりました。 新しいメッシュ、遷移メッシュを追加します。 その中に、たとえばint型を保存できます。 この場合、16個の遷移オプションを使用して、4つのタイルを囲むタイルの各クワッドに16個のIDを書き込むことができます。 これで十分です。 いいえ、誰かがさらにIDを必要とする場合-長く使用してください。 ゲームの場所の16個の自動テールで十分だと判断しましたが、残りは自動遷移なしです。



次に、タイルのセットが必要です。 もちろん、マスクを使用することもできますが、タイルのセットを使用すると、優れたスキル(私のものではなく)を使用して、非常に優れた画像を実現できます。



画像



私自身、このようなタイルのテストセットを作成しました。 タイルごとに12の遷移オプションがあり、独自の4を追加できます。WC3のように、将来のタイルのバリエーション用にスロットを予約しましたが、この部分は非常に簡単なので、ここでは説明しません。



プログラミングの部分に進みます。 まず、適切なテクスチャインデックスを選択するために必要なビットマスクを決定する関数について説明します。 すぐに予約しますが、標準的ではないソリューションを選択する傾向があります。 ここでは、Java + LWJGLが使用されます。



この関数は、このクワッドのビットマスクを作成します。 ビット1は、タイルのこのコーナーに隣接するタイルがあることを意味します(したがって、同じ高さの異なるタイルセットを組み合わせることができます)。 そうそう。 高さ、私はそれを忘れていました。 もちろん、上に何を描画し、下に何を描画するかを知るために、各タイルの高さを決定する必要があります。 これは、明らかな変数を追加するだけで解決されます。



public int getTransitionCornerFor(World world, int x, int y, int height) { int corner = 0; if (world.getTile(x-1, y).zOrder == height) corner |= 0b0001; if (world.getTile(x, y).zOrder == height) corner |= 0b0010; if (world.getTile(x, y-1).zOrder == height) corner |= 0b0100; if (world.getTile(x-1, y-1).zOrder == height) corner |= 0b1000; return corner; }
      
      







各ビットは、独自の角度を意味します。 1ビットは左上隅、2は左下隅、3は右下隅、4は右上隅です。



次に、遷移に必要なテクスチャインデックスを決定するまさにその方法に関して。 この方法は、すべて私のスキルのために、面倒でいことが判明しました。 この記事のために特別に作成しましたが、大量のインデントが作成されないように、いくつかの方法に分けました。



 public void updateTransitionMap(World world, int x, int y) { int w = 16, h = 16; int[] temp = new int[4]; // ,     4   4   ID  4    (.. 32      ) for (int i = 0; i < 4; i++) //            temp[i] = 0; if (tileID > 0) { for (int i = 1; i <= tilesNum; i++) { int corner = getTransitionCornerFor(world, x, y, i); int c = 0; if (corner > 0) { c = setPointTransition(world, temp, x, y, corner, c, i); //      if (c == 3) c = setCornerTransition(world, temp, x, y, corner, c, i); //,   3 (!) ,      if (c == 2) c = setEdgeTransition(world, temp, x, y, corner, c, i); //  2 (!) ,     } } } }
      
      







そして、メソッド自体は次のとおりです。



 public int setPointTransition(World world, int[] temp, int x, int y, int corner, int c, int i) { for (int k = 0; k < 4; k++) if ((corner >> k & 1) == 1) { int idx = 8+k; int storage = 0; storage = (idx & 0xF) << 4 | (i & 0xF); temp[k] = storage; int t = 0; for (int l = 0; l < 4; l++) { t = (t << 8) | temp[l] & 0xFF; } world.setTransition(x, y, t); c++; } return c; }
      
      







ここではすべてが簡単です。 私たちは隅々まで行き渡り、ビットをチェックします。 1の場合-インデックス8 + k 、つまり 角度(上記で各側の数値(NE、SE、SW、SE)を説明しました)。 次に、松葉杖の方法を使用して、サイクルを通じて遷移マップを更新します。



最後に更新された数値sを忘れずに与えてください。 Javaのおかげで、単純型を参照たり、渡したりすることができません。



ポイントをコーナーとサイドに接続する方法:

  public int setEdgeTransition(World world, int[] temp, int x, int y, int corner, int c, int i) { for (int offset = 0; offset < 4; offset++) { boolean isSide = true; for (int k = 0; k < 2; k++) { //    if ((corner >> ((k + offset) % 4) & 1) != 1) isSide = false; else if (k == 1 && isSide) { int idx = (offset+1)%4; int storage = 0; storage = (idx & 0xF) << 4 | (i & 0xF); temp[offset] = storage; int t = 0; for (int l = 0; l < 4; l++) { t = (t << 8) | temp[l] & 0xFF; } world.setTransition(x, y, t); } } } return c; } public int setCornerTransition(World world, int[] temp, int x, int y, int corner, int c, int i) { for (int offset = 0; offset < 4; offset++) { boolean isCorner = true; for (int k = 0; k < 3; k++) { //    if ((corner >> ((k + offset) % 4) & 1) != 1) isCorner = false; else if (k == 2 && isCorner) { int idx = 4+offset; int storage = 0; storage = (idx & 0xF) << 4 | (i & 0xF); temp[offset] = storage; int t = 0; for (int l = 0; l < 4; l++) { t = (t << 8) | temp[l] & 0xFF; } world.setTransition(x, y, t); } } } return c; }
      
      







これはまったく同じ原理です。 唯一の違いは、テクスチャインデックスの開始番号です。したがって、オフセットを設定する目的のサイクルと別のサイクルを取ることができます。つまり、どのポイントから角度が始まるかを意味します。 そのポイントから開始して、反時計回りに隣接する角度(または側面)をチェックします。 少なくとも1つのポイントが隣接するタイルではない場合、角度も側面も取得されません。

それだけです、移行マップを作成しました! タイルごとに5ビットがあります。 タイル(256種類のバリエーション)を保存するための1つと、メタデータを保存するための各コーナーのビット



このビジネスをレンダリングするだけです。 イミディエイトモードで古い非推奨のメソッドを検討します(VBOに向けて出発する予定です。VBOの構造と動的更新、およびその可視部分のみのレンダリングについて少し理解する必要があります)。



複雑なことは何もありません:



 public void renderTile(World world, int x, int y) { int w = 16, h = 16; int s = 0; if (tileID > 0) { for (int i = 0; i < 4; i++) { int t = world.getTransition(x, y); int src = ((t >> (3-i)*8) & 0xFF); int idx = src >> 4 & 0xF; int id = src & 0xF; int u = (idx%8)*16, v = 16 + 16*(idx/8) + (id-1)*48, u1 = u + w, v1 = v + h; if (id != 0) { GRenderEngine.drawTextureQuad(x*16, y*16, 128, 144, u, v, u1, v1); //    ,      VBO } } } }
      
      







ここで何をしているの? ええ、各8ビットを調べて、IDとトランジションの最初の4と最後の4を取得します。 次に、OpenGLパラメーターを渡します。既にレンダリングが配布されています。



結果:



画像

(はい、Swingに組み込まれたLWJGLキャンバス)。



私たちは何かを忘れたようですか? 周囲の4つのポイントの高さが似ている場合、タイル全体を描画します。



 public void renderTile(World world, int x, int y) { int w = 16, h = 16; int s = 0; if (tileID > 0) { int c = 0; for (int i = 0; i < 4; i++) { int t = world.getTransition(x, y); int src = ((t >> (3-i)*8) & 0xFF); int idx = src >> 4 & 0xF; int id = src & 0xF; int u = (idx%8)*16, v = 16 + 16*(idx/8) + (id-1)*48, u1 = u + w, v1 = v + h; if (id != 0) { if (id == tileID) c++; GRenderEngine.drawTextureQuad(x*16, y*16, 128, 144, u, v, u1, v1); } } if (c == 4) { GRenderEngine.drawTextureQuad(x*16, y*16, 128, 144, 0, 48*(tileID-1), 16, (tileID-1)*48+16); } } }
      
      







画像



何か不足していますか? 確かに、下のタイルの描画方法を決める必要があります。 正直に言うと、私はこれをほぼ偶然に解決することができましたが、改善する必要があるのはこの瞬間です。 これはねじ込み式松葉杖と見なすことができますが、結果には影響しません。



メソッドを少し変更してみましょう。



  public void renderTile(World world, int x, int y) { int w = 16, h = 16; int s = 0; if (tileID > 0) { for (int i = 1; i <= tilesNum; i++) { int corner = getTransitionCornerFor(world, x, y, i); int c = 0; if (corner > 0) { for (int k = 0; k < 4; k++) if ((corner >> k & 1) == 1) { c++; } } boolean flag = false; int fill = getFillCornerFor(world, x, y, i); if (fill > 0) for (int k = 0; k < 4; k++) if ((fill >> k & 1) == 1) { c++; if (k == 4 && c == 4) flag = true; } if (c == 4) { GRenderEngine.drawTextureQuad(x*16, y*16, 128, 144, 0, 48*(i-1), 16, (i-1)*48+16); if (flag) break; } } for (int i = 0; i < 4; i++) { int t = world.getTransition(x, y); int src = ((t >> (3-i)*8) & 0xFF); int idx = src >> 4 & 0xF; int id = src & 0xF; int u = (idx%8)*16, v = 16 + 16*(idx/8) + (id-1)*48, u1 = u + w, v1 = v + h; if (id != 0) { GRenderEngine.drawTextureQuad(x*16, y*16, 128, 144, u, v, u1, v1); } } } }
      
      







別の方法が追加されました。 これは、隣接するタイルのビットを書き込む方法とほぼ同等です。 ここにあります:



  public int getFillCornerFor(World world, int x, int y, int height) { int corner = 0; if (world.getTile(x-1, y).zOrder > height) corner |= 0b0001; if (world.getTile(x, y).zOrder > height) corner |= 0b0010; if (world.getTile(x, y-1).zOrder > height) corner |= 0b0100; if (world.getTile(x-1, y-1).zOrder > height) corner |= 0b1000; return corner; }
      
      







高さが転送されたタイルの高さよりも大きい地区内のすべてのタイルを決定します。



つまり 特定のセルのすべてのタイルを繰り返し処理し(当然、ソートする必要があるのはオートタイルのみです)、このセルの上にいくつのタイルがあるかを確認します。 その前に、このタイルで覆われているポイントの数を数えることを忘れないでください。 指定されたタイルのポイント数+指定された== 4に重なる他のタイルのポイントの合計の場合、このテクスチャで完全な四角形を描画し、ループを中断します。 これらは松葉杖です。



結果は素晴らしいです:



画像



おそらくそれだけです。



PSなぜこの方法はそれより優れているのですか WC3は、このようなシステムを使用すると、想像を絶する美しさの景観を実現できることを明確に示しています。 個人的には、より柔軟であるように思えますが、その実装にはいくつかの困難が生じます。 そして、はい、まだ上で述べたように、いくつかの改良が必要です。



All Articles