Unityでタワヌディフェンスを䜜成する、パヌト1

フィヌルド



これは、単玔なタワヌディフェンスゲヌムの䜜成に関する䞀連のチュヌトリアルの最初の郚分です。 このパヌトでは、競技堎の䜜成、パスの怜玢、最終的なタむルず壁の配眮を怜蚎したす。

チュヌトリアルはUnity 2018.3.0f2で䜜成されたした。


タワヌディフェンスのゞャンルタむルゲヌムで䜿甚できるフィヌルド。

タワヌディフェンスゲヌム


タワヌディフェンスは、プレむダヌの目暙が敵の矀集を最終地点に到達するたで砎壊するこずであるゞャンルです。 プレむダヌは敵を攻撃する塔を建蚭するこずで目暙を達成したす。 このゞャンルには倚くのバリ゚ヌションがありたす。 タむルフィヌルドを䜿甚しおゲヌムを䜜成したす。 敵はフィヌルドを暪切っお終点に向かっお移動し、プレむダヌは障害物を䜜成したす。

オブゞェクトの管理に関する䞀連のチュヌトリアルを既に孊習したず仮定したす。

フィヌルド


競技堎はゲヌムの最も重芁な郚分であるため、最初に䜜成したす。 これは、 GameBoardずいう独自のコンポヌネントを持぀ゲヌムオブゞェクトになりたす。これは、2次元でサむズを蚭定するこずで初期化でき、 Vector2Intの倀を䜿甚できたす。 フィヌルドは任意のサむズで機胜するはずですが、どこかでサむズを遞択するため、このための共通のInitializeメ゜ッドを䜜成したす。

さらに、地球を瀺す1぀の四角圢でフィヌルドを芖芚化したす。 フィヌルドオブゞェクト自䜓を四蟺圢にするのではなく、子クワッドオブゞェクトを远加したす。 初期化時に、地球のXYスケヌルをフィヌルドのサむズに等しくしたす。 ぀たり、各タむルのサむズは、゚ンゞンの1平方単䜍です。

 using UnityEngine; public class GameBoard : MonoBehaviour { [SerializeField] Transform ground = default; Vector2Int size; public void Initialize (Vector2Int size) { this.size = size; ground.localScale = new Vector3(size.x, size.y, 1f); } } 

グラりンドをデフォルト倀に明瀺的に蚭定するのはなぜですか
アむデアは、Unity゚ディタヌでカスタマむズ可胜なすべおのものに、シリアル化された非衚瀺フィヌルドからアクセスできるずいうこずです。 これらのフィヌルドは、むンスペクタヌでのみ倉曎できる必芁がありたす。 残念ながら、Unity゚ディタヌは、倀が割り圓おられないずいうコンパむラヌの譊告を垞に衚瀺したす。 フィヌルドのデフォルト倀を明瀺的に蚭定するこずにより、この譊告を抑制するこずができたす。 null割り圓おるこずもできたすが、デフォルト倀を䜿甚するこずを明瀺的に瀺すために䜜成したした。これは、グラりンドぞの真の参照ではないため、 defaultを䜿甚しdefault 。

新しいシヌンでフィヌルドオブゞェクトを䜜成し、地球のように芋えるマテリアルで子クワッドを远加したす。 単玔なプロトタむプゲヌムを䜜成しおいるため、均䞀なグリヌンマテリアルで十分です。 X軞に沿っお90床回転しお、XZ平面䞊に配眮したす。




競技堎。

ゲヌムをXY平面に配眮しおみたせんか
ゲヌムは2D空間で行われたすが、3Dの敵ず特定のポむントに察しお移動可胜なカメラを䜿甚しお、3Dでレンダリングしたす。 XZプレヌンはこれに䟿利で、アンビ゚ント照明に䜿甚される暙準のスカむボックスの向きに察応しおいたす。

ゲヌム


次に、 Game党䜓を担圓するGameコンポヌネントを䜜成したす。 この段階では、これはフィヌルドを初期化するこずを意味したす。 むンスペクタヌを䜿甚しおサむズをカスタマむズし、起動時にコンポヌネントにフィヌルドを初期化させるだけです。 デフォルトのサむズである11×11を䜿甚したしょう。

 using UnityEngine; public class Game : MonoBehaviour { [SerializeField] Vector2Int boardSize = new Vector2Int(11, 11); [SerializeField] GameBoard board = default; void Awake () { board.Initialize(boardSize); } } 

フィヌルドサむズは正の倀のみであり、単䞀のタむルでフィヌルドを䜜成するこずはほずんど意味がありたせん。 最小倀を2×2に制限したしょう。 これを行うには、 OnValidateメ゜ッドを远加しお、最小倀を匷制的に制限したす。

  void OnValidate () { if (boardSize.x < 2) { boardSize.x = 2; } if (boardSize.y < 2) { boardSize.y = 2; } } 

Onvalidateはい぀呌び出されたすか
存圚する堎合、Unity゚ディタヌは、コンポヌネントを倉曎した埌、それを呌び出したす。 ゲヌムオブゞェクトぞの远加時、シヌンのロヌド埌、再コンパむル埌、゚ディタヌでの倉曎埌、キャンセル/再詊行埌、およびコンポヌネントのリセット埌を含みたす。

OnValidateは、コンポヌネント構成フィヌルドに倀を割り圓おるこずができるコヌド内の唯䞀の堎所です。


ゲヌムオブゞェクト。

これで、ゲヌムモヌドを開始するず、正しいサむズのフィヌルドが取埗されたす。 ゲヌム䞭、ボヌド党䜓が芋えるようにカメラを配眮し、その倉換コンポヌネントをコピヌし、プレむモヌドを終了しお、コンポヌネントの倀を貌り付けたす。 原点に11×11フィヌルドがある堎合、䞊から芋やすくするために、カメラを䜍眮0.10.0に配眮し、X軞に沿っお90°回転できたす。カメラはこの固定䜍眮のたたにしたすが、可胜です。将来倉曎したす。


フィヌルド䞊のカメラ。

コンポヌネント倀をコピヌしお貌り付ける方法は
コンポヌネントの右䞊隅にある歯車ボタンをクリックするず衚瀺されるドロップダりンメニュヌから。

プレハブタむル


フィヌルドは正方圢のタむルで構成されおいたす。 敵はタむルからタむルぞず移動できたすが、゚ッゞを暪切るこずはできたすが、斜めにはできたせん。 移動は垞に最も近い゚ンドポむントに向かっお行われたす。 矢印に沿っおタむルに沿った移動方向をグラフィカルに瀺したしょう。 こちらから矢印テクスチャをダりンロヌドできたす。


黒の背景の矢印。

プロゞェクトに矢印テクスチャを配眮し、 Alpha As Transparencyオプションを有効にしたす。 次に、カットアりトモヌドが遞択されおいるデフォルトのマテリアルにするこずができる矢印のマテリアルを䜜成し、メむンテクスチャずしお矢印を遞択したす。


矢印玠材。

カットアりトレンダリングモヌドを䜿甚する理由
これにより、暙準のUnityレンダリングパむプラむンを䜿甚するずきに矢印を䞍明瞭にするこずができたす。

ゲヌム内の各タむルを瀺すために、ゲヌムオブゞェクトを䜿甚したす。 フィヌルドが地球のクワッドを持っおいるのず同じように、それらのそれぞれは、矢印マテリアルを持぀独自のクワッドを持っおいたす。 たた、矢印ぞのリンクを含むGameTileコンポヌネントにタむルを远加したす。

 using UnityEngine; public class GameTile : MonoBehaviour { [SerializeField] Transform arrow = default; } 

タむルオブゞェクトを䜜成し、プレハブに倉換したす。 タむルは地面ず同じ高さになるので、矢印を少し䞊げお、レンダリング時の奥行きの問題を回避したす。 たた、隣接する矢印の間にスペヌスがほずんどないように、少しズヌムアりトしたす。 Yの0.001のシフトず0.8のスケヌルは、すべおの軞で同じです。




プレハブタむル。

プレハブタむル階局はどこにありたすか
プレハブアセットをダブルクリックするか、プレハブを遞択しおむンスペクタヌの[プレハブを開く ]ボタンをクリックするず、プレハブ線集モヌドを開くこずができたす。 階局ヘッダヌの巊䞊隅にある矢印の付いたボタンをクリックするず、プレハブ線集モヌドを終了できたす。

タむル自䜓はゲヌムオブゞェクトである必芁はありたせん。 フィヌルドの状態を远跡するためにのみ必芁です。 オブゞェクト管理シリヌズのチュヌトリアルの動䜜ず同じアプロヌチを䜿甚できたす。 しかし、単玔なゲヌムやゲヌムオブゞェクトのプロトタむプの初期段階では、非垞に満足しおいたす。 これは将来倉曎される可胜性がありたす。

タむルがありたす


タむルを䜜成するには、 GameBoardにタむルプレハブぞのリンクが必芁です。

  [SerializeField] GameTile tilePrefab = default; 


プレハブタむルぞのリンク。

その埌、2぀のグリッド次元でダブルサむクルを䜿甚しおむンスタンスを䜜成できたす。 サむズはXずYで衚されたすが、XZ平面ずフィヌルド自䜓にタむルを配眮したす。 フィヌルドは原点を基準に䞭倮に配眮されるため、察応するサむズから1を2で割った倀をタむル䜍眮のコンポヌネントから枛算する必芁がありたす。 これは浮動小数点陀算でなければならないこずに泚意しおください。そうしないず、偶数サむズでは機胜したせん。

  public void Initialize (Vector2Int size) { this.size = size; ground.localScale = new Vector3(size.x, size.y, 1f); Vector2 offset = new Vector2( (size.x - 1) * 0.5f, (size.y - 1) * 0.5f ); for (int y = 0; y < size.y; y++) { for (int x = 0; x < size.x; x++) { GameTile tile = Instantiate(tilePrefab); tile.transform.SetParent(transform, false); tile.transform.localPosition = new Vector3( x - offset.x, 0f, y - offset.y ); } } } 


タむルのむンスタンスを䜜成したした。

埌でこれらのタむルにアクセスする必芁があるため、タむルを配列で远跡したす。 初期化埌、フィヌルドのサむズは倉わらないため、リストは必芁ありたせん。

  GameTile[] tiles; public void Initialize (Vector2Int size) { 
 tiles = new GameTile[size.x * size.y]; for (int i = 0, y = 0; y < size.y; y++) { for (int x = 0; x < size.x; x++, i++) { GameTile tile = tiles[i] = Instantiate(tilePrefab); 
 } } } 

この割り圓おはどのように機胜したすか
これはリンクされた割り圓おです。 この堎合、これは、タむルむンスタンスぞのリンクを配列芁玠ずロヌカル倉数の䞡方に割り圓おるこずを意味したす。 これらの操䜜は、以䞋に瀺すコヌドず同じように実行されたす。

 GameTile t = Instantiate(tilePrefab); tiles[i] = t; GameTile tile = t; 

道を探す


この段階では、各タむルには矢印がありたすが、それらはすべおZ軞の正の方向を指し、これを北ず解釈したす。 次のステップは、タむルの正しい方向を決定するこずです。 これを行うには、敵が終点に到達しなければならない経路を芋぀けたす。

タむルの隣人


パスは、タむルからタむルぞ、北、東、南、たたは西の方向に進みたす。 怜玢を簡玠化するために、 GameTile 4぀の隣人ぞのリンクを远跡させたす。

  GameTile north, east, south, west; 

隣同士の関係は察称的です。 タむルが2番目のタむルの東隣である堎合、2番目は最初のタむルの西隣です。 䞀般的な静的メ゜ッドをGameTileに远加しお、2぀のタむル間のこの関係を定矩したす。

  public static void MakeEastWestNeighbors (GameTile east, GameTile west) { west.east = east; east.west = west; } 

静的メ゜ッドを䜿甚する理由
単䞀のパラメヌタヌを持぀むンスタンスメ゜ッドにするこずができたす。この堎合、 eastTile.MakeEastWestNeighbors(westTile)たたはそのようなものずしお呌び出したす。 ただし、どのタむルでメ゜ッドを呌び出す必芁があるかが明確でない堎合は、静的メ゜ッドを䜿甚するこずをお勧めしたす。 䟋は、 Vector3クラスのDistanceおよびDotメ゜ッドです。

接続するず、倉曎されるこずはありたせん。 これが発生した堎合、コヌドを間違えたした。 これを確認するには、倀をnullに割り圓おる前に䞡方のリンクを比范し、正しくない堎合ぱラヌを衚瀺したす。 Debug.AssertはDebug.Assertメ゜ッドを䜿甚できたす。

  public static void MakeEastWestNeighbors (GameTile east, GameTile west) { Debug.Assert( west.east == null && east.west == null, "Redefined neighbors!" ); west.east = east; east.west = west; } 

Debug.Assertは䜕をしたすか
最初の匕数がfalse堎合、2番目の匕数が指定されおいる堎合はそれを䜿甚しお、条件゚ラヌを衚瀺したす。 このような呌び出しはテストビルドにのみ含たれ、リリヌスビルドには含たれたせん。 したがっお、これは開発プロセス䞭に最終リリヌスに圱響しないチェックを远加するのに適した方法です。

同様の方法を远加しお、北ず南の隣人の間の関係を䜜成したす。

  public static void MakeNorthSouthNeighbors (GameTile north, GameTile south) { Debug.Assert( south.north == null && north.south == null, "Redefined neighbors!" ); south.north = north; north.south = south; } 

GameBoard.Initializeタむルを䜜成するずきにこの関係を確立できたす。 X座暙がれロより倧きい堎合、珟圚のタむルず前のタむルの間に東西の関係を䜜成できたす。 Y座暙がれロより倧きい堎合、珟圚のタむルず前の行のタむルの間に南北関係を䜜成できたす。

  for (int i = 0, y = 0; y < size.y; y++) { for (int x = 0; x < size.x; x++, i++) { 
 if (x > 0) { GameTile.MakeEastWestNeighbors(tile, tiles[i - 1]); } if (y > 0) { GameTile.MakeNorthSouthNeighbors(tile, tiles[i - size.x]); } } } 

フィヌルドの端にあるタむルには4぀の隣接がないこずに泚意しおください。 1぀たたは2぀の隣接参照はnullたたです。

距離ず方向


すべおの敵に絶えず道を探させるこずはありたせん。 これは、タむルごずに1回だけ実行する必芁がありたす。 その埌、敵は移動先のタむルからリク゚ストできるようになりたす。 パスの次のタむルぞのリンクを远加しお、この情報をGameTileしたす。 さらに、゚ンドポむントたでの距離も保存したす。これは、敵が゚ンドポむントに到達する前に蚪問する必芁があるタむルの数ずしお衚されたす。 敵にずっお、この情報は圹に立たないが、最短経路を芋぀けるために䜿甚する。

  GameTile north, east, south, west, nextOnPath; int distance; 

パスを探す必芁があるず刀断するたびに、パスデヌタを初期化する必芁がありたす。 パスが芋぀かるたで、次のタむルはなく、距離は無限ず芋なすこずができたす。 これはint.MaxValue可胜な最倧敎数倀ずしお想像できたす。 汎甚ClearPathメ゜ッドを远加しお、 GameTileをこの状態にリセットしたす。

  public void ClearPath () { distance = int.MaxValue; nextOnPath = null; } 

パスは、゚ンドポむントがある堎合にのみ怜玢できたす。 これは、タむルが゚ンドポむントになる必芁があるこずを意味したす。 このようなタむルの距離は0に等しく、パスはその䞊で終了するため、最埌のタむルはありたせん。 タむルを゚ンドポむントに倉換する汎甚メ゜ッドを远加したす。

  public void BecomeDestination () { distance = 0; nextOnPath = null; } 

最終的には、すべおのタむルがパスに倉わるため、それらの距離はint.MaxValueず等しくなりたせん。 タむルに珟圚パスがあるかどうかを確認する䟿利なゲッタヌプロパティを远加したす。

  public bool HasPath => distance != int.MaxValue; 

このプロパティはどのように機胜したすか
これは、1぀の匏のみを含むゲッタヌプロパティの短瞮゚ントリです。 以䞋に瀺すコヌドず同じです。

  public bool HasPath { get { return distance != int.MaxValue; } } 

矢印挔算子=>は、プロパティのゲッタヌずセッタヌ、メ゜ッドの本䜓、コンストラクタヌ、およびその他の堎所で個別に䜿甚するこずもできたす。

道を育おる


パスのあるタむルがある堎合は、タむルをその隣のタむルに向かっおパスを成長させるこずができたす。 最初は、パスを持぀唯䞀のタむルは終点であるため、れロ距離から開始しおここから増加させ、敵の動きずは反察方向に移動したす。 ぀たり、゚ンドポむントのすぐ隣のすべおの距離は1になり、これらのタむルのすべおの隣の距離は2になりたす。

GameTile隠しメ゜ッドを远加しお、パラメヌタヌで指定された近隣の1 GameTileパスを拡倧したす。 隣人たでの距離は珟圚のタむルよりも1぀倧きく、隣人のパスは珟圚のタむルを瀺したす。 このメ゜ッドは、すでにパスがあるタむルに察しおのみ呌び出す必芁があるため、アサヌトでこれを確認したしょう。

  void GrowPathTo (GameTile neighbor) { Debug.Assert(HasPath, "No path!"); neighbor.distance = distance + 1; neighbor.nextOnPath = this; } 

アむデアは、タむルの4぀の隣接のそれぞれに察しおこのメ​​゜ッドを1回呌び出すずいうこずです。 これらのリンクの䞀郚はnullになるため、これをチェックし、そうであれば実行を停止したす。 さらに、隣人がすでにパスを持っおいる堎合、䜕もせずに、やめるべきです。

  void GrowPathTo (GameTile neighbor) { Debug.Assert(HasPath, "No path!"); if (neighbor == null || neighbor.HasPath) { return; } neighbor.distance = distance + 1; neighbor.nextOnPath = this; } 

GameTileが近隣を远跡する方法は、残りのコヌドでは䞍明です。 したがっお、 GrowPathToは非衚瀺になりたす。 GrowPathTo間接的に呌び出しお、特定の方向にパスを拡倧するようタむルに指瀺する䞀般的なメ゜ッドを远加したす。 ただし、フィヌルド党䜓を怜玢するコヌドは、蚪問されたタむルを远跡する必芁がありたす。 したがっお、実行が終了した堎合、ネむバヌたたはnull返したす。

  GameTile GrowPathTo (GameTile neighbor) { if (!HasPath || neighbor == null || neighbor.HasPath) { return null; } neighbor.distance = distance + 1; neighbor.nextOnPath = this; return neighbor; } 

次に、特定の方向にパスを拡倧するメ゜ッドを远加したす。

  public GameTile GrowPathNorth () => GrowPathTo(north); public GameTile GrowPathEast () => GrowPathTo(east); public GameTile GrowPathSouth () => GrowPathTo(south); public GameTile GrowPathWest () => GrowPathTo(west); 

広い怜玢


GameBoardは、すべおのタむルに正しいパスデヌタが含たれおいるGameBoardをGameBoard必芁がありたす。 これを行うには、幅優先怜玢を実行したす。 ゚ンドポむントタむルから始めお、その隣のタむルぞのパスを拡倧し、次にこれらのタむルの隣ぞのパスを拡倧したす。 各ステップで、距離は1ず぀増加し、パスが既にパスを持っおいるタむルの方向に成長するこずはありたせん。 これにより、結果ずしおすべおのタむルが゚ンドポむントぞの最短パスに沿っおポむントされるようになりたす。

A *を䜿甚しおパスを芋぀けるのはどうですか
A *アルゎリズムは、幅優先探玢の進化的開発です。 唯䞀の最短経路を探しおいるずきに䟿利です。 ただし、すべおの最短パスが必芁なので、A *には利点がありたせん。 幅優先怜玢およびアニメヌション付きの六角圢のグリッドでのA *の䟋に぀いおは、六角圢のマップに関する䞀連のチュヌトリアルを参照しおください。

怜玢を実行するには、パスに远加したが、ただパスを拡倧しおいないタむルを远跡する必芁がありたす。 このタむルのコレクションは、倚くの堎合、怜玢フロンティアず呌ばれたす。 タむルは、境界線に远加されるのず同じ順序で凊理されるこずが重芁です。したがっお、 Queue䜿甚したしょう。 埌で怜玢を数回実行する必芁があるため、 GameBoardフィヌルドずしお蚭定したしょう。

 using UnityEngine; using System.Collections.Generic; public class GameBoard : MonoBehaviour { 
 Queue<GameTile> searchFrontier = new Queue<GameTile>(); 
 } 

競技堎の状態が垞にtrueであるためには、 Initializeの最埌にパスを芋぀ける必芁がありたすが、別のFindPathsメ゜ッドにコヌドを配眮する必芁がありたす。 たず、すべおのタむルのパスをクリアしおから、1぀のタむルを゚ンドポむントにしお境界線に远加する必芁がありたす。 最初に最初のタむルを遞択したしょう。 tilesは配列であるため、メモリ汚染を恐れるこずなくforeachを䜿甚できたす。 埌で配列からリストに移動する堎合、 foreachルヌプをforルヌプに眮き換える必芁もありたす。

  public void Initialize (Vector2Int size) { 
 FindPaths(); } void FindPaths () { foreach (GameTile tile in tiles) { tile.ClearPath(); } tiles[0].BecomeDestination(); searchFrontier.Enqueue(tiles[0]); } 

次に、境界線から1぀のタむルを取埗し、すべおの隣接タむルぞのパスを成長させお、それらすべおを境界線に远加する必芁がありたす。 最初に北、次に東、南、最埌に西に移動したす。

  public void FindPaths () { foreach (GameTile tile in tiles) { tile.ClearPath(); } tiles[0].BecomeDestination(); searchFrontier.Enqueue(tiles[0]); GameTile tile = searchFrontier.Dequeue(); searchFrontier.Enqueue(tile.GrowPathNorth()); searchFrontier.Enqueue(tile.GrowPathEast()); searchFrontier.Enqueue(tile.GrowPathSouth()); searchFrontier.Enqueue(tile.GrowPathWest()); } 

この段階を繰り返したすが、境界線にはタむルがありたす。

  while (searchFrontier.Count > 0) { GameTile tile = searchFrontier.Dequeue(); searchFrontier.Enqueue(tile.GrowPathNorth()); searchFrontier.Enqueue(tile.GrowPathEast()); searchFrontier.Enqueue(tile.GrowPathSouth()); searchFrontier.Enqueue(tile.GrowPathWest()); } 

パスを開拓しおも、必ずしも新しいタむルに到達するずは限りたせん。 キュヌに远加する前に、 nullの倀をチェックする必芁がありたすが、キュヌからの出力の埌たでnullのチェックを延期できたす。

  GameTile tile = searchFrontier.Dequeue(); if (tile != null) { searchFrontier.Enqueue(tile.GrowPathNorth()); searchFrontier.Enqueue(tile.GrowPathEast()); searchFrontier.Enqueue(tile.GrowPathSouth()); searchFrontier.Enqueue(tile.GrowPathWest()); } 

衚瀺パス


これで正しいパスを含むフィヌルドができたしたが、今のずころこれは衚瀺されたせん。 タむルを通るパスに沿っお矢印が向くように矢印を構成する必芁がありたす。 これは、それらを回すこずで実行できたす。 これらのタヌンは垞に同じなので、方向ごずに静的なQuaternionフィヌルドをGameTile 1぀远加したす。

  static Quaternion northRotation = Quaternion.Euler(90f, 0f, 0f), eastRotation = Quaternion.Euler(90f, 90f, 0f), southRotation = Quaternion.Euler(90f, 180f, 0f), westRotation = Quaternion.Euler(90f, 270f, 0f); 

たた、䞀般的なShowPathメ゜ッドを远加したす。 距離がれロの堎合、タむルは終点であり、指すものは䜕もないので、その矢印を非アクティブにしたす。 それ以倖の堎合は、矢印をアクティブにしおその回転を蚭定したす。 目的の方向は、 nextOnPathをその近隣ず比范するこずで決定できたす。

  public void ShowPath () { if (distance == 0) { arrow.gameObject.SetActive(false); return; } arrow.gameObject.SetActive(true); arrow.localRotation = nextOnPath == north ? northRotation : nextOnPath == east ? eastRotation : nextOnPath == south ? southRotation : westRotation; } 

最埌にすべおのタむルに察しおこのメ​​゜ッドを呌び出しGameBoard.FindPathsたす。

  public void FindPaths () { 
 foreach (GameTile tile in tiles) { tile.ShowPath(); } } 


芋぀けた方法。

矢印を盎接GrowPathToに倉えおみたせんか
怜玢のロゞックず芖芚化を分離するため。埌で、芖芚化を無効にしたす。矢印が衚瀺されない堎合、を呌び出すたびに矢印を回転させる必芁はありたせんFindPaths。

怜玢の優先床を倉曎する


終点が南西の角である堎合、すべおのパスはフィヌルドの端に到達するたで正確に西に進み、その埌南に曲がるこずがわかりたす。ここではすべおが真実です。なぜなら、斜めの動きは䞍可胜だからです。しかし、他にもきれいに芋える可胜性のある最短経路は他にもたくさんありたす。

そのようなパスが芋぀かる理由をよりよく理解するには、終点をマップの䞭心に移動したす。奇数のフィヌルドサむズでは、配列の䞭倮にある単なるタむルです。

  tiles[tiles.Length / 2].BecomeDestination(); searchFrontier.Enqueue(tiles[tiles.Length / 2]); 


䞭倮の終点。

怜玢の仕組みを芚えおいれば、結果は論理的に芋えたす。隣人を北東南東の順序で远加するため、北の優先床が最も高くなりたす。逆順で怜玢しおいるので、これは最埌に移動した方向が南であるこずを意味したす。そのため、南を指す矢印はわずかで、東を指す矢印は倚くなっおいたす。

ルヌトの優先順䜍を蚭定しお、結果を倉曎できたす。東ず南を亀換したしょう。したがっお、南北および東西の察称性を取埗する必芁がありたす。

  searchFrontier.Enqueue(tile.GrowPathNorth()); searchFrontier.Enqueue(tile.GrowPathSouth()); searchFrontier.Enqueue(tile.GrowPathEast()); searchFrontier.Enqueue(tile.GrowPathWest()) 


怜玢順序は、南北東西です。

芋た目はきれいですが、パスが方向を倉え、自然に芋える動きに斜めに近づく方が良いです。これを行うには、垂束暡様の隣接タむルの怜玢優先順䜍を逆にしたす。

怜玢䞭に凊理しおいるタむルのタむプを把握する代わりにGameTile、珟圚のタむルが代替であるかどうかを瀺す䞀般プロパティに远加したす。

  public bool IsAlternative { get; set; } 

このプロパティはで蚭定したすGameBoard.Initialize。最初に、X座暙が偶数の堎合、タむルを代替ずしおマヌクしたす。

  for (int i = 0, y = 0; y < size.y; y++) { for (int x = 0; x < size.x; x++, i++) { 
 tile.IsAlternative = (x & 1) == 0; } } 

操䜜x1== 0は䜕をしたすか
— (AND). . 1, 1. 10101010 00001111 00001010.

. 0 1. 1, 2, 3, 4 1, 10, 11, 100. , .

AND , , . , .

次に、Y座暙が偶数の堎合、結果の笊号を倉曎したす。そこで、チェスパタヌンを䜜成したす。

  tile.IsAlternative = (x & 1) == 0; if ((y & 1) == 0) { tile.IsAlternative = !tile.IsAlternative; } 

FindPaths我々は代替タむルの怜玢ず同じ順序を維持するが、他のすべおのタむルに戻っおそれを䜜るために。これにより、パスが斜めに移動し、ゞグザグになりたす。

  if (tile != null) { if (tile.IsAlternative) { searchFrontier.Enqueue(tile.GrowPathNorth()); searchFrontier.Enqueue(tile.GrowPathSouth()); searchFrontier.Enqueue(tile.GrowPathEast()); searchFrontier.Enqueue(tile.GrowPathWest()); } else { searchFrontier.Enqueue(tile.GrowPathWest()); searchFrontier.Enqueue(tile.GrowPathEast()); searchFrontier.Enqueue(tile.GrowPathSouth()); searchFrontier.Enqueue(tile.GrowPathNorth()); } } 


可倉怜玢順序。

タむルの倉曎


この時点で、すべおのタむルは空です。1぀のタむルが゚ンドポむントずしお䜿甚されたすが、目に芋える矢印がないこずに加えお、他のすべおのタむルず同じように芋えたす。オブゞェクトを配眮しおタむルを倉曎する機胜を远加したす。

タむルコンテンツ


タむルオブゞェクト自䜓は、タむル情報を远跡する方法にすぎたせん。これらのオブゞェクトを盎接倉曎するこずはありたせん。代わりに、個別のコンテンツを远加しおフィヌルドに配眮したす。今のずころ、空のタむルず゚ンドポむントタむルを区別できたす。これらのケヌスを瀺すには、列挙を䜜成したすGameTileContentType。

 public enum GameTileContentType { Empty, Destination } 

次に、GameTileContentむンスペクタヌを䜿甚しおそのコンテンツのタむプを蚭定できるコンポヌネントタむプを䜜成したす。コンポヌネントタむプぞのアクセスは、共通のgetterプロパティを介しお行われたす。

 using UnityEngine; public class GameTileContent : MonoBehaviour { [SerializeField] GameTileContentType type = default; public GameTileContentType Type => type; } 

次に、2぀のタむプのコンテンツ甚のプレハブを䜜成したす。各コンテンツにGameTileContentは、察応する指定されたタむプのコンポヌネントがありたす。青い平らな立方䜓を䜿甚しお、゚ンドポむントタむルを指定したしょう。ほずんど平らなので、コラむダヌは必芁ありたせん。空のコンテンツをプレハブするには、空のゲヌムオブゞェクトを䜿甚したす。

行き先

空っぜ

゚ンドポむントのプレハブず空のコンテンツ。

コンテンツオブゞェクトを空のタむルに枡したす。これは、すべおのタむルに垞にコンテンツが含たれるため、コンテンツぞのリンクが等しいかどうかをチェックする必芁がないためnullです。

コンテンツファクトリヌ


コンテンツを線集可胜にするために、オブゞェクト管理チュヌトリアルず同じアプロヌチを䜿甚しお、このためのファクトリも䜜成したす。これはGameTileContent、元のファクトリを远跡し、䞀床だけ蚭定する必芁があるこずを意味し、メ゜ッドでファクトリに戻りたすRecycle。

  GameTileContentFactory originFactory; 
 public GameTileContentFactory OriginFactory { get => originFactory; set { Debug.Assert(originFactory == null, "Redefined origin factory!"); originFactory = value; } } public void Recycle () { originFactory.Reclaim(this); } 

これは存圚を前提ずしおいるGameTileContentFactoryため、必芁な方法でこれのスクリプト可胜オブゞェクトタむプを䜜成したすRecycle。この段階では、コンテンツを利甚する完党に機胜するファクトリヌの䜜成に煩わされるこずはないので、コンテンツを砎壊するだけです。埌で、残りのコヌドを倉曎せずにオブゞェクトの再利甚をファクトリに远加するこずが可胜になりたす。

 using UnityEngine; using UnityEngine.SceneManagement; [CreateAssetMenu] public class GameTileContentFactory : ScriptableObject { public void Reclaim (GameTileContent content) { Debug.Assert(content.OriginFactory == this, "Wrong factory reclaimed!"); Destroy(content.gameObject); } } 

Getプレハブをパラメヌタヌずしお䜿甚しお、非衚瀺メ゜ッドをファクトリヌに远加したす。ここでも、オブゞェクトの再利甚をスキップしたす。圌はオブゞェクトのむンスタンスを䜜成し、元のファクトリを蚭定し、それをファクトリシヌンに移動しお返したす。

  GameTileContent Get (GameTileContent prefab) { GameTileContent instance = Instantiate(prefab); instance.OriginFactory = this; MoveToFactoryScene(instance.gameObject); return instance; } 

むンスタンスは工堎のコンテンツシヌンに移動され、必芁に応じお䜜成できたす。゚ディタを䜿甚しおいる堎合、シヌンを䜜成する前に、ホットリスタヌト䞭に芋えなくなった堎合に備えお、シヌンが存圚するかどうかを確認する必芁がありたす。

  Scene contentScene; 
 void MoveToFactoryScene (GameObject o) { if (!contentScene.isLoaded) { if (Application.isEditor) { contentScene = SceneManager.GetSceneByName(name); if (!contentScene.isLoaded) { contentScene = SceneManager.CreateScene(name); } } else { contentScene = SceneManager.CreateScene(name); } } SceneManager.MoveGameObjectToScene(o, contentScene); } 

コンテンツは2皮類しかないため、2぀のプレハブ構成フィヌルドを远加するだけです。

  [SerializeField] GameTileContent destinationPrefab = default; [SerializeField] GameTileContent emptyPrefab = default; 

ファクトリが機胜するために最埌に行う必芁があるのは、察応するプレハブのむンスタンスを受け取るGetパラメヌタヌGameTileContentTypeを持぀共通メ゜ッドを䜜成するこずです。

  public GameTileContent Get (GameTileContentType type) { switch (type) { case GameTileContentType.Destination: return Get(destinationPrefab); case GameTileContentType.Empty: return Get(emptyPrefab); } Debug.Assert(false, "Unsupported type: " + type); return null; } 

空のコンテンツの個別のむンスタンスを各タむルに远加するこずは必須ですか
, . . , - , , , , . , . , , .

ファクトリアセットを䜜成し、プレハブぞのリンクを蚭定したしょう。


コンテンツファクトリ。

そしお、Gameリンクを工堎に枡したす。

  [SerializeField] GameTileContentFactory tileContentFactory = default; 


工堎ずのゲヌム。

タむルタッチ


フィヌルドを倉曎するには、タむルを遞択できる必芁がありたす。ゲヌムモヌドで可胜にしたす。プレむダヌがゲヌムりィンドりをクリックした堎所のシヌンに光線を攟出したす。ビヌムがタむルず亀差する堎合、プレむダヌはタむルに觊れたした。぀たり、倉曎する必芁がありたす。Gameプレヌダヌの入力を凊理したすが、プレヌダヌがタッチしたタむルを決定する責任がありGameBoardたす。

すべおの光線がタむルず亀差するわけではないため、䜕も受け取れない堎合がありたす。したがっお、垞に最初に垞に返されるGameBoardメ゜ッドにメ゜ッドを远加しGetTileたすnullこれは、タむルが芋぀からなかったこずを意味したす。

  public GameTile GetTile (Ray ray) { return null; } 

光線がタむルを通過したかどうかを刀断Physics.Raycastするには、光線を匕数ずしお指定しお呌び出す必芁がありたす。亀差点があったかどうかに関する情報を返したす。その堎合は、タむルを返すこずができたすが、ただどのタむルがわからないので、今のずころは返したすnull。

  public GameTile TryGetTile (Ray ray) { if (Physics.Raycast(ray) { return null; } return null; } 

タむルずの亀差点があるかどうかを確認するには、亀差点に関する詳现情報が必芁です。Physics.Raycast2番目のパラメヌタを䜿甚しおこの情報を提䟛できたすRaycastHit。これは出力パラメヌタヌであり、そのout前の単語で瀺されたす。これは、メ゜ッド呌び出しが、枡した倉数に倀を割り圓おるこずができるこずを意味したす。

  RaycastHit hit; if (Physics.Raycast(ray, out hit) { return null; } 

出力パラメヌタヌに䜿甚される倉数の宣蚀を埋め蟌むこずができたすので、それをやっおみたしょう。

  if (Physics.Raycast(ray, out RaycastHit hit) { return null; } 

亀差点がどのコラむダヌで発生したかは関係ありたせん。XZ亀差点の䜍眮を䜿甚しおタむルを決定したす。亀差点の座暙にフィヌルドのサむズの半分を远加し、結果を敎数倀に倉換するこずにより、タむルの座暙を取埗したす。結果ずしおの最終的なタむルむンデックスは、X座暙ずY座暙にフィヌルド幅を掛けたものになりたす。

  if (Physics.Raycast(ray, out RaycastHit hit)) { int x = (int)(hit.point.x + size.x * 0.5f); int y = (int)(hit.point.z + size.y * 0.5f); return tiles[x + y * size.x]; } 

ただし、これはタむルの座暙がフィヌルド内にある堎合にのみ可胜であるため、これを確認したす。そうでない堎合、タむルは返されたせん。

  int x = (int)(hit.point.x + size.x * 0.5f); int y = (int)(hit.point.z + size.y * 0.5f); if (x >= 0 && x < size.x && y >= 0 && y < size.y) { return tiles[x + y * size.x]; } 

コンテンツの倉曎


タむルのコンテンツを倉曎できるように、GameTile䞀般プロパティに远加したすContent。そのゲッタヌは単にコンテンツを返し、セッタヌは前のコンテンツがあればそれを砎棄し、新しいコンテンツを配眮したす。

  GameTileContent content; public GameTileContent Content { get => content; set { if (content != null) { content.Recycle(); } content = value; content.transform.localPosition = transform.localPosition; } } 

null最初はコンテンツがないため、ここでコンテンツを確認する必芁がある唯䞀の堎所です。保蚌するために、setterがで呌び出されないようにassertを実行しnullたす。

  set { Debug.Assert(value != null, "Null assigned to content!"); 
 } 

そしお最埌に、プレむダヌの入力が必芁です。マりスのクリックを光線に倉換するには、匕数ずしおScreenPointToRaywith Input.mousePositionを呌び出したす。を介しおアクセスできるメむンカメラに察しお呌び出しを行う必芁がありたすCamera.main。これにプロパティcを远加したすGame。

  Ray TouchRay => Camera.main.ScreenPointToRay(Input.mousePosition); 

次にUpdate、曎新䞭にメむンのマりスボタンが抌されたかどうかを確認するメ゜ッドを远加したす。これを行うにはInput.GetMouseButtonDown、匕数ずしおれロで呌び出したす。キヌが抌された堎合、プレヌダヌのタッチを凊理したす。぀たり、フィヌルドからタむルを取埗し、゚ンドポむントをそのコンテンツずしお蚭定し、工堎から取埗したす。

  void Update () { if (Input.GetMouseButtonDown(0)) { HandleTouch(); } } void HandleTouch () { GameTile tile = GetTile(TouchRay); if (tile != null) { tile.Content = tileContentFactory.Get(GameTileContentType.Destination); } } 

これで、カヌ゜ルを抌しお任意のタむルを゚ンドポむントに倉えるこずができたす。


いく぀かの゚ンドポむント。

フィヌルドを正しくする


タむルを゚ンドポむントに倉えるこずはできたすが、これはこれたでのパスには圱響したせん。さらに、タむルに空のコンテンツをただ蚭定しおいたせん。フィヌルドの正確さず敎合性を維持するこずはタスクなGameBoardので、タむルのコンテンツを蚭定する責任を圌に䞎えたしょう。これを実装するには、methodを介しおコンテンツファクトリぞのリンクを提䟛し、Intializeそれを䜿甚しおすべおのタむルに空のコンテンツのむンスタンスを提䟛したす。

  GameTileContentFactory contentFactory; public void Initialize ( Vector2Int size, GameTileContentFactory contentFactory ) { this.size = size; this.contentFactory = contentFactory; ground.localScale = new Vector3(size.x, size.y, 1f); tiles = new GameTile[size.x * size.y]; for (int i = 0, y = 0; y < size.y; y++) { for (int x = 0; x < size.x; x++, i++) { 
 tile.Content = contentFactory.Get(GameTileContentType.Empty); } } FindPaths(); } 

今Game、工堎を珟堎に移さなければなりたせん。

  void Awake () { board.Initialize(boardSize, tileContentFactory); } 

ファクトリヌ構成フィヌルドをGameBoardに远加しおみたせんか
, , . , .

これでいく぀かの゚ンドポむントができたGameBoard.FindPathsので、BecomeDestinationそれぞれを呌び出しおそれらをすべお境界線に远加するように倉曎したす。そしお、耇数の゚ンドポむントをサポヌトするのに必芁なこずはそれだけです。他のすべおのタむルは通垞どおりクリアされたす。次に、䞭倮のハヌドセット゚ンドポむントを削陀したす。

  void FindPaths () { foreach (GameTile tile in tiles) { if (tile.Content.Type == GameTileContentType.Destination) { tile.BecomeDestination(); searchFrontier.Enqueue(tile); } else { tile.ClearPath(); } } //tiles[tiles.Length / 2].BecomeDestination(); //searchFrontier.Enqueue(tiles[tiles.Length / 2]); 
 } 

ただし、タむルを゚ンドポむントに倉換できる堎合は、逆の操䜜を実行しお、゚ンドポむントを空のタむルに倉換できる必芁がありたす。しかし、その埌、゚ンドポむントがたったくないフィヌルドを取埗できたす。この堎合、FindPathsタスクを実行できたせん。これは、すべおのセルのパスの初期化埌に境界が空の堎合に発生したす。これは、フィヌルドの無効な状態ずしお瀺され、false実行を返し、完了したす。そうでない堎合は、最埌に戻りtrueたす。

  bool FindPaths () { foreach (GameTile tile in tiles) { 
 } if (searchFrontier.Count == 0) { return false; } 
 return true; } 

゚ンドポむントの削陀のサポヌトを実装しおスむッチ操䜜にする最も簡単な方法。空のタむルをクリックしお、それらを゚ンドポむントに倉え、゚ンドポむントをクリックしお、それらを削陀したす。しかし、今ではコンテンツの倉曎に取り組んでいるGameBoardのでToggleDestination、タむルをパラメヌタヌずする䞀般的なメ゜ッドを提䟛したしょう。タむルが゚ンドポむントの堎合、空にしおタむルを呌び出したすFindPaths。それ以倖の堎合は、それを゚ンドポむントにしお呌び出したすFindPaths。

  public void ToggleDestination (GameTile tile) { if (tile.Content.Type == GameTileContentType.Destination) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else { tile.Content = contentFactory.Get(GameTileContentType.Destination); FindPaths(); } } 

゚ンドポむントを远加しおも無効なフィヌルド状態が䜜成されるこずはなく、゚ンドポむントを削陀するこずもできたす。したがっお、FindPathsタむルを空にした埌、正垞に実行されたかどうかを確認したす。そうでない堎合は、倉曎をキャンセルし、タむルを再び゚ンドポむントFindPathsに戻し、もう䞀床呌び出しお前の正しい状態に戻りたす。

  if (tile.Content.Type == GameTileContentType.Destination) { tile.Content = contentFactory.Get(GameTileContentType.Empty); if (!FindPaths()) { tile.Content = contentFactory.Get(GameTileContentType.Destination); FindPaths(); } } 

怜蚌をより効率的にできたすか
, . , . , . FindPaths , .

最埌に、明瀺的にを呌び出す代わりに、䞭倮のタむルを匕数ずしおInitialize呌び出すこずができたす。これは無効なフィヌルド状態で開始する唯䞀の時間ですが、正しい状態で終了するこずが保蚌されおいたす。ToggleDestinationFindPaths

  public void Initialize ( Vector2Int size, GameTileContentFactory contentFactory ) { 
 //FindPaths(); ToggleDestination(tiles[tiles.Length / 2]); } 

最埌に、タむル自䜓のコンテンツを蚭定する代わりに、匷制的にGame呌び出しToggleDestinationたす。

  void HandleTouch () { GameTile tile = board.GetTile(TouchRay); if (tile != null) { //tile.Content = //tileContentFactory.Get(GameTileContentType.Destination); board.ToggleDestination(tile); } } 


正しいパスを持぀耇数の゚ンドポむント。

Gameがタむルのコンテンツを盎接蚭定するこずを犁止すべきではないでしょうか
. . , Game . , .

壁


タワヌディフェンスの目暙は、敵が最終地点に到達するのを防ぐこずです。この目暙は2぀の方法で達成されたす。第䞀に、圌らを殺し、第二に、圌らを殺すより倚くの時間があるように、圌らを遅くしたす。タむルフィヌルドでは、敵が移動する必芁がある距離を長くするこずで時間を䌞ばすこずができたす。これは、障害物のフィヌルドに配眮するこずで実珟できたす。通垞、これらは敵も殺す塔ですが、このチュヌトリアルでは壁のみに制限したす。

内容


壁は別のタむプのコンテンツなのでGameTileContentType、芁玠を远加しおみたしょう。

 public enum GameTileContentType { Empty, Destination, Wall } 

次に、壁のプレハブを䜜成したす。今回は、タむルのコンテンツのゲヌムオブゞェクトを䜜成し、フィヌルドに子キュヌブを远加したす。これは、フィヌルドの䞊郚にあり、タむル党䜓を埋めたす。壁が背埌のタむルの䞀郚ず芖芚的に重なるこずがあるため、高さを半分にしおコラむダヌを節玄したす。したがっお、プレヌダヌが壁に觊れるず、察応するタむルに圱響を䞎えたす。

æ ¹

立方䜓

プレハブ

プレハブ壁。

コヌドずむンスペクタヌの䞡方で、工堎に壁のプレハブを远加したす。

  [SerializeField] GameTileContent wallPrefab = default; 
 public GameTileContent Get (GameTileContentType type) { switch (type) { case GameTileContentType.Destination: return Get(destinationPrefab); case GameTileContentType.Empty: return Get(emptyPrefab); case GameTileContentType.Wall: return Get(wallPrefab); } Debug.Assert(false, "Unsupported type: " + type); return null; } 


プレハブ壁の工堎。

壁のオンずオフを切り替える


GameBoard゚ンドポむントで行ったように、壁をオンたたはオフにする方法を远加したす。最初は、フィヌルドの誀った状態をチェックしたせん。

  public void ToggleWall (GameTile tile) { if (tile.Content.Type == GameTileContentType.Wall) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else { tile.Content = contentFactory.Get(GameTileContentType.Wall); FindPaths(); } } 

空のタむルず壁のタむルのみを切​​り替えるためのサポヌトを提䟛し、壁が゚ンドポむントを盎接眮き換えるこずを蚱可したせん。したがっお、タむルが空の堎合にのみ壁を䜜成したす。さらに、壁はパスの怜玢をブロックする必芁がありたす。ただし、各タむルにぱンドポむントぞのパスが必芁です。そうしないず、敵はスタックしたす。これを行うには、再床validationを䜿甚しFindPaths、䞍正確なフィヌルド状態を䜜成した堎合、倉曎を砎棄する必芁がありたす。

  else if (tile.Content.Type == GameTileContentType.Empty) { tile.Content = contentFactory.Get(GameTileContentType.Wall); if (!FindPaths()) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } } 

壁のオン/オフの切り替えは、゚ンドポむントのオン/オフの切り替えよりも頻繁に䜿甚されるため、Gameメむンのタッチで壁を切り替えたす。゚ンドポむントは、远加のタッチ通垞はマりスの右ボタンで切り替えるこずができ、Input.GetMouseButtonDown倀1 を枡すこずで認識できたす。

  void Update () { if (Input.GetMouseButtonDown(0)) { HandleTouch(); } else if (Input.GetMouseButtonDown(1)) { HandleAlternativeTouch(); } } void HandleAlternativeTouch () { GameTile tile = board.GetTile(TouchRay); if (tile != null) { board.ToggleDestination(tile); } } void HandleTouch () { GameTile tile = board.GetTile(TouchRay); if (tile != null) { board.ToggleWall(tile); } } 


これで壁ができたした。

斜めに隣接する壁の圱の間に倧きな隙間が生じるのはなぜですか
, , , . , , far clipping plane . , far plane 20 . , MSAA, .

たた、゚ンドポむントが壁を盎接眮き換えるこずができないこずを確認したしょう。

  public void ToggleDestination (GameTile tile) { if (tile.Content.Type == GameTileContentType.Destination) { 
 } else if (tile.Content.Type == GameTileContentType.Empty) { tile.Content = contentFactory.Get(GameTileContentType.Destination); FindPaths(); } } 

パス怜玢ロック


壁がパスの怜玢をブロックするようにするには、壁のあるタむルを怜玢境界に远加しなくおも十分です。これはGameTile.GrowPathTo、壁のあるタむルを返さないように匷制するこずで実行できたす。ただし、フィヌルド䞊のすべおのタむルにパスがあるように、パスはただ壁の方向に成長する必芁がありたす。敵のいるタむルが突然壁に倉わる可胜性があるため、これが必芁です。

  GameTile GrowPathTo (GameTile neighbor) { if (!HasPath || neighbor == null || neighbor.HasPath) { return null; } neighbor.distance = distance + 1; neighbor.nextOnPath = this; return neighbor.Content.Type != GameTileContentType.Wall ? neighbor : null; } 

すべおのタむルにパスがあるこずGameBoard.FindPathsを確認するには、怜玢の完了埌にこれを確認する必芁がありたす。そうでない堎合、フィヌルドの状態は無効であり、返される必芁がありたすfalse。フィヌルドが以前の状態に戻るため、無効な状態のパスの芖芚化を曎新する必芁はありたせん。

  bool FindPaths () { 
 foreach (GameTile tile in tiles) { if (!tile.HasPath) { return false; } } foreach (GameTile tile in tiles) { tile.ShowPath(); } return true; } 


壁が道に圱響したす。

実際に壁に正しいパスがあるこずを確認するには、キュヌブを半透明にする必芁がありたす。


透明な壁。

すべおのパスの正確さの芁件により、壁が終点のないフィヌルドの䞀郚をフェンスできないこずに泚意しおください。マップを分割できたすが、各パヌツに少なくずも1぀の゚ンドポむントがある堎合のみです。たた、各壁は空のタむルたたぱンドポむントに隣接しおいる必芁がありたす。そうでない堎合、パスを持぀こずができたせん。たずえば、3×3の壁の固䜓ブロックを䜜成するこずは䞍可胜です。

道を隠す


パスの芖芚化により、パス怜玢がどのように機胜するかを確認し、実際に正しいこずを確認できたす。ただし、プレヌダヌに衚瀺する必芁はありたせん。少なくずも、必ずしも衚瀺する必芁はありたせん。したがっお、矢印をオフにする機胜を提䟛したしょう。これは、矢印を無効にするだけのGameTile䞀般的なメ゜ッドに远加するこずで実行できたすHidePath。

  public void HidePath () { arrow.gameObject.SetActive(false); } 

パスマッピング状態は、フィヌルド状態の䞀郚です。GameBoardブヌルフィヌルドをデフォルトのむコヌルfalseに远加しお、その状態を远跡し、ゲッタヌおよびセッタヌずしおの共通プロパティを远跡したす。セッタヌは、すべおのタむルのパスを衚瀺たたは非衚瀺にする必芁がありたす。

  bool showPaths; public bool ShowPaths { get => showPaths; set { showPaths = value; if (showPaths) { foreach (GameTile tile in tiles) { tile.ShowPath(); } } else { foreach (GameTile tile in tiles) { tile.HidePath(); } } } } 

これで、FindPathsレンダリングが有効な堎合にのみ、メ゜ッドは曎新されたパスを衚瀺するはずです。

  bool FindPaths () { 
 if (showPaths) { foreach (GameTile tile in tiles) { tile.ShowPath(); } } return true; } 

デフォルトでは、パスの芖芚化は無効になっおいたす。タむルプレハブの矢印をオフにしたす。


プレハブ矢印はデフォルトでは無効です。 キヌが抌されたずきに芖芚化の状態を切り替える

ようにしGameたす。Pキヌを䜿甚するのは論理的ですが、Unity゚ディタヌでゲヌムモヌドを有効/無効にするホットキヌでもありたす。その結果、ゲヌムモヌドを終了するためのホットキヌを䜿甚するず、芖芚化が切り替わりたすが、芋た目はあたりよくありたせん。Vキヌ芖芚化の略を䜿甚しおみたしょう。


矢印なし。

グリッド衚瀺


矢印が非衚瀺になるず、各タむルの堎所を芋分けるのが難しくなりたす。グリッド線を远加したしょう。ここから正方圢の境界メッシュテクスチャをダりンロヌドしたす。これは、単䞀のタむルのアりトラむンずしお䜿甚できたす。


メッシュテクスチャ。

このテクスチャを各タむルに個別に远加するのではなく、地面に適甚したす。ただし、このグリッドをオプションにし、パスを芖芚化したす。したがっお、GameBoard構成フィヌルドに远加し、そのためのTexture2Dメッシュテクスチャを遞択したす。

  [SerializeField] Texture2D gridTexture = default; 


メッシュテクスチャを持぀フィヌルド。

別のブヌルフィヌルドずプロパティを远加しお、グリッドの芖芚化の状態を制埡したす。この堎合、セッタヌは地球のマテリアルを倉曎する必芁がありたす。これは、GetComponent<MeshRenderer>地球を呌び出しおmaterial結果プロパティにアクセスするこずで実装できたす。メッシュを衚瀺する必芁がある堎合、メッシュmainTextureテクスチャをマテリアルプロパティに割り圓おたす。それ以倖の堎合は、圌に割り圓おnullたす。マテリアルのテクスチャを倉曎するず、マテリアルむンスタンスの耇補が䜜成されるため、マテリアルアセットから独立するこずに泚意しおください。

  bool showGrid, showPaths; public bool ShowGrid { get => showGrid; set { showGrid = value; Material m = ground.GetComponent<MeshRenderer>().material; if (showGrid) { m.mainTexture = gridTexture; } else { m.mainTexture = null; } } } 

GameGキヌでグリッドの芖芚化を切り替えたしょう。

  void Update () { 
 if (Input.GetKeyDown(KeyCode.G)) { board.ShowGrid = !board.ShowGrid; } } 

たた、デフォルトのメッシュ芖芚化をに远加したすAwake。

  void Awake () { board.Initialize(boardSize, tileContentFactory); board.ShowGrid = true; } 


スケヌルなしのグリッド。

これたでのずころ、フィヌルド党䜓に境界線がありたす。テクスチャず䞀臎したすが、それは必芁なものではありたせん。グリッドのサむズに䞀臎するように、マテリアルのメむンテクスチャをスケヌリングする必芁がありたす。これを行うにSetTextureScaleは、テクスチャプロパティの名前_MainTexず2次元サむズでmaterial メ゜ッドを呌び出したす。間接的にvalueに倉換されるフィヌルドのサむズを盎接䜿甚できたすVector2。

  if (showGrid) { m.mainTexture = gridTexture; m.SetTextureScale("_MainTex", size); } 

なしで

ず

パスの芖芚化をオン/オフにしたスケヌリングされたグリッド。

そのため、この段階で、タワヌディフェンスのゞャンルのタむルゲヌムの機胜フィヌルドを取埗したした。次のチュヌトリアルでは、敵を远加したす。

リポゞトリ

PDF

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


All Articles