移動するプラットフォームの等尺性深度ソート


作成するもの

簡単に言えば、深度によるソートは、カメラに近い要素と遠い要素を識別する方法として説明できます。 したがって、シーン内の正しい深度に一致するために配置する必要がある順序を決定します。

このチュートリアルでは、移動プラットフォームも追加するため、等尺性レベルの深度ソートについて詳しく見ていきます。 このチュートリアルは、アイソメトリの理論の紹介ではなく、コード専用ではありません。 その中で、コードを分析するのではなく、ロジックと理論を理解します。 Unityはツールとして使用されるため、深度によるソートはsortingOrderスプライトの変更にsortingOrderます。 他のフレームワークでは、Z軸の順序の変更または描画シーケンスが可能です。

アイソメ理論の基礎を学ぶには、 このチュートリアルをお読みください 。 コードとコード構造は、 以前の等尺性チュートリアルと一致してます。 このチュートリアルが複雑に思える場合は、ここで学習してください。なぜなら、このチュートリアルではロジックのみに焦点を当てるからです。

1.動きのないレベル


ゲームの等尺性レベルで動く要素が存在しないか、レベルに沿って走っている少数のキャラクターのみが存在する場合、深度によるソートは非常に簡単です。 そのような場合、等尺性タイルを占有する文字はタイル自体よりも小さいため、占有するタイルと同じ描画順序/深さを単純に使用できます。

このような固定レベルを静的と呼びましょう。 正しい深さを維持するために、さまざまな方法で描画できます。 通常、レベルデータは、行と列がレベルの行と列に対応する2次元配列です。

2行7列だけの次の等尺性レベルを考えてみましょう。


タイル上の数字は、 sortingOrder 、またはZの深さまたは順序に対応していsortingOrder 。 描画する必要がある順序。 この場合、最初にsortingOrder = 1の最初の列から始めて、最初の行のすべての列を描画します。

最初の行のすべての列を描画した後、カメラに最も近い列のsortingOrder = 7になり、次の行にsortingOrderます。 つまり、2行目の各要素には、1行目の要素よりもsortingOrder値が高くsortingOrderます。

sortingOrder値がsortingOrderスプライトは、 sortingOrder値が低い他のすべてのスプライトと重なるため、正しい順序を維持するためにタイルを並べる必要があります。

コードに関しては、単純にレベル配列の行と列を循環し、 sortingOrdersortingOrderを順番にsortingOrderます。 下の図に見られるように、行と列を入れ替えても結果は悪くなりません。


ここでは、次の行に進む前に、最初に列を完全に描画します。 奥行きの認識は同じままです。 つまり、静的レベルのロジックでは、行全体または列全体を描画し、 sortingOrder次の連続した割り当てにsortingOrderで移動します。

高さを追加する


レベルを建物と見なす場合、今のところは1階のみを描画します。 建物に新しいフロアを追加する必要がある場合は、1階全体が描画されるのを待ってから、2階に対して同じアルゴリズムを繰り返します。

正しい深さの順序については、ラインが完了するのを待ってから、次のラインに移動しました。 同様に、すべての行の完了を待ってから、次のフロアに移動します。 つまり、1つのラインと2つのフロアを持つレベルの場合、これは次の図に示すように機能します。


当然、高層階のタイルには、低層階のタイルよりもsortingOrderが大きくsortingOrderます。 コードに関しては、上階を追加するには、占有する階に応じてタイルの画面座標のy値をシフトするだけです。

 float floorHeight=tileSize/2.2f; float currentFloorHeight=floorHeight*floorLevel; // tmpPos=GetScreenPointFromLevelIndices(i,j); tmpPos.y+=currentFloorHeight; tile.transform.position=tmpPos; 

floorHeightの値は、タイル等尺性ブロックの画像の知覚される高さを示し、 floorHeightはタイルが属するフロアを決定します。

2. X軸に沿ったタイルの動き


静的等尺性レベルでの深さのソートはそれほど複雑ではありませんか? 先にsortingOrderましょう-「最初の行」メソッドを使用します。つまり、最初にsortingOrderを最初の行に完全に割り当ててから、次の行に進みます。 単一のX軸に沿って移動する最初の移動タイルまたはプラットフォームを見てみましょう。

運動がX軸に沿って発生すると言うとき、私は等角座標系ではなくデカルト座標を意味します。 3つの行と7つの列で構成される、下の階のみのレベルを見てみましょう。 2行目には、移動するタイルが1つだけあると想定しています。 レベルは下の画像のようになります。


暗いタイルは移動可能なタイルで、最初の行には7つのタイルがあるため、 sortingOrderは8になります。 タイルがデカルト軸Xに沿って移動する場合、2本の線の間の「トラック」に沿って移動します。 そのパスで占めることができるすべての位置について、行1のタイルはより小さいsortingOrderます。

同様に、2行目のすべてのタイルは、パス内の暗いタイルの位置に関係なく、 sortingOrdersortingOrderます。 sortingOrderの目的のために「行優先」メソッドを選択したため、X軸に沿って移動するために特別なことをする必要はありません。 このケースは非常に簡単です。

3. Y軸に沿ったタイルの動き


下の図に示すように、Y軸をとると問題が発生し始めますので、暗いタイルが長方形のトラックに沿って移動するレベルを想像してみましょう。 ソースの Unity MovingSortingProblemでも同じ状況を見ることができます。


「行優先」アプローチを使用して、現在占有している行に基づいて移動可能なタイルにsortingOrder割り当てることができます。 タイルが2行の間にある場合、 sortingOrder元の行に基づいてsortingOrderが割り当てられます。 この場合、移動先の行でsortingOrderられたsortingOrderを追跡できません。 これにより、深度ソートアルゴリズムが破壊されます。

ブロックソート


この問題を解決するには、レベルをさまざまなブロックに分割する必要があります。そのうちの1つは「最初の行」アプローチを破壊する問題ブロックで、残りは「最初の行」アプローチを中断せずに使用できるブロックです。 これをよりよく理解するには、下の画像をご覧ください。


青い領域で示されている2x2タイルブロックが問題のブロックです。 他のすべてのブロックは、「最初の行」アプローチを使用できます。 写真に煩わされないようにしてください-ブロックアルゴリズムを使用して既に正しくソートされたレベルを示しています。 青いブロックは、行の2つの列タイルで構成されています。これらのタイルの間では、暗いタイルが現在移動しており、タイルのすぐ左にあります。

問題ブロックの深さの問題を解決するには、このブロックに対してのみ「最初の列」アプローチを使用できます。 つまり、緑、ピンク、黄色のブロックには「行優先」、青には「列優先」を使用します。

なお、 sortingOrderを順番に割り当てる必要があることに注意してください。 最初に緑色のブロック、次に左側のピンクのブロック、次に青色のブロック、次に右側のピンクのブロック、最後に黄色のブロック。 青いブロックに移動するとき、「最初の列」モードに切り替えるためにのみ順序を変更します。

代替ソリューションとして、移動するタイル列の右側にある2x2ブロックを検討することもできます。 (興味深いのは、この場合BlockSortで問題自体が解決されるため、アプローチを変更する必要さえないことです。)実際の解決策はBlockSortシーンに示されてBlockSortます。


このアルゴリズムは、次のコードで実装されます。

 private void DepthSort(){ Vector2 movingTilePos=GetLevelIndicesFromScreenPoint(movingGO.transform.position); int blockColStart=(int)movingTilePos.y; int blockRowStart=(int)movingTilePos.x; int depth=1; //    for (int i = 0; i < blockRowStart; i++) { for (int j = 0; j < cols; j++) { depth=AssignDepth(i,j,depth); } } //        for (int i = blockRowStart; i < blockRowStart+2; i++) { for (int j = 0; j < blockColStart; j++) { depth=AssignDepth(i,j,depth); } } //  for (int i = blockRowStart; i < blockRowStart+2; i++) { for (int j = blockColStart; j < blockColStart+2; j++) { if(movingTilePos.x==i&&movingTilePos.y==j){ SpriteRenderer sr=movingGO.GetComponent<SpriteRenderer>(); sr.sortingOrder=depth;//assign new depth depth++;//increment depth }else{ depth=AssignDepth(i,j,depth); } } } //        for (int i = blockRowStart; i < blockRowStart+2; i++) { for (int j = blockColStart+2; j < cols; j++) { depth=AssignDepth(i,j,depth); } } //    for (int i = blockRowStart+2; i < rows; i++) { for (int j = 0; j < cols; j++) { depth=AssignDepth(i,j,depth); } } } 

4. Z軸に沿ったタイルの動き


Z軸の動きは、等尺性レベルに沿ってシミュレートされた動きです。 本質的に、これは画面軸Yに沿った単なる動きです。1フロアの等尺性レベルで、Z軸に沿った動きを追加するために、上記のブロックによる並べ替えの方法を既に実装している場合は、順番に何もする必要はありません。 この状況は、Unity SingleLayerWaveSingleLayerWaveできます。ここでは、「トラック」に沿った横方向の動きに、Z軸に沿った波動を追加しました。

複数のフロアがあるレベルでZを運転する


上記のように、レベルに新しいフロアを追加するのは、画面座標Yをシフトするだけです。タイルがZ軸に沿って移動しない場合、深度による並べ替えで特別な操作を行う必要はありません。 ムーブメントを使用して1階のブロックを並べ替え、その後、すべての後続の階に「行優先」の並べ替えを適用できます。 実際には、この状況はUnity BlockSortWithHeightで確認できます。


タイルがフロア間を移動し始めると、非常によく似た奥行きの問題が発生します。 このアプローチを使用すると、1フロアのみの順序を満たし、別のフロアの深さによるソートを破棄できます。 この床の深さの問題に対処するには、ブロックによる並べ替えを3次元に拡張または変更する必要があります。

本質的に、問題は2フロアのみで、その間でタイルが現在移動しています。 他のすべてのフロアの場合、既存のソートアルゴリズムを引き続き使用できます。 特別な要件は、これら2つのフロアに対してのみ発生します。これらのうち、以下のように下のフロアを設定できます。ここで、 tileZOffsetは、移動するタイルのZ軸に沿った移動量です。

 float whichFloor=(tileZOffset/floorHeight); float lower=Mathf.Floor(whichFloor); 

これは、 lowerおよびlower+1が特別なアプローチを必要とするフロアであることを意味します。 次のコードに示すように、これらのフロアの両方にsortingOrder一緒に割り当てるのがコツです。 これにより順序が修正され、深さによるソートの問題が解決されます。

 if(floor==lower){ //           ,    depth=(floor*(rows*cols))+1; int nextFloor=floor+1; if(nextFloor>=totalFloors)nextFloor=floor; //    for (int i = 0; i < blockRowStart; i++) { for (int j = 0; j < cols; j++) { depth=AssignDepth(i,j,depth,floor); depth=AssignDepth(i,j,depth,nextFloor); } } //        for (int i = blockRowStart; i < blockRowStart+2; i++) { for (int j = 0; j < blockColStart; j++) { depth=AssignDepth(i,j,depth,floor); depth=AssignDepth(i,j,depth,nextFloor); } } //  for (int i = blockRowStart; i < blockRowStart+2; i++) { for (int j = blockColStart; j < blockColStart+2; j++) { if(movingTilePos.x==i&&movingTilePos.y==j){ SpriteRenderer sr=movingGO.GetComponent<SpriteRenderer>(); sr.sortingOrder=depth;//assign new depth depth++;//increment depth }else{ depth=AssignDepth(i,j,depth,floor); depth=AssignDepth(i,j,depth,nextFloor); } } } //        for (int i = blockRowStart; i < blockRowStart+2; i++) { for (int j = blockColStart+2; j < cols; j++) { depth=AssignDepth(i,j,depth,floor); depth=AssignDepth(i,j,depth,nextFloor); } } //    for (int i = blockRowStart+2; i < rows; i++) { for (int j = 0; j < cols; j++) { depth=AssignDepth(i,j,depth,floor); depth=AssignDepth(i,j,depth,nextFloor); } } } 

本質的に、2つのフロアを1つと見なし、この1つのフロアのブロックで並べ替えます。 BlockSortWithHeightMovementシーンで実行中のコードを参照してください。 このアプローチのおかげで、下図に示すように、タイルは2つの軸のどちらかに沿って自由に移動できるようになりました。


おわりに


このチュートリアルは、深度ソートアルゴリズムのロジックを説明することを目的としており、理解していただければ幸いです。 明らかに、移動するタイルが1つだけの比較的単純なレベルを検討しています。

また、傾斜に対処しません。そうしないと、チュートリアルが長くなりすぎるためです。 ただし、ソートロジックを理解したら、2次元の傾斜ロジックを等角図に適合させることができます。

チュートリアルのすべての例のソースコードはGithubにアップロードされます。

Source: https://habr.com/ru/post/J348138/


All Articles