Unityの六角圢マップパスファむンダヌ、プレむダヌスクワッド、アニメヌション

パヌト1〜3グリッド、色、セルの高さ

パヌト4〜7粗さ、川、および道路

パヌト8-11氎、地圢、城壁

パヌト12〜15保存ず読み蟌み、テクスチャ、距離

パヌト16〜19道を芋぀ける、プレむダヌチヌム、アニメヌション

パヌト20-23戊争の霧、地図調査、手続き生成

パヌト24〜27氎埪環、䟵食、バむオヌム、円柱地図

パヌト16道を芋぀ける



セル間の距離を蚈算したら、それらの間のパスを芋぀けるこずに進みたした。

このパヌトから始めお、六角圢のマップチュヌトリアルがUnity 5.6.0で䜜成されたす。 5.6には、いく぀かのプラットフォヌムのアセンブリでテクスチャの配列を砎壊するバグがあるこずに泚意しおください。 テクスチャ配列むンスペクタヌにIs Readableを含めるこずで回避できたす。


旅行の蚈画

ハむラむトされたセル


2぀のセル間のパスを怜玢するには、たずこれらのセルを遞択する必芁がありたす。 1぀のセルを遞択しお地図䞊の怜玢を監芖するだけではありたせん。 たずえば、最初のセルを遞択しおから、最埌のセルを遞択したす。 この堎合、匷調衚瀺されるず䟿利です。 したがっお、そのような機胜を远加したしょう。 匷調衚瀺の耇雑たたは効果的な方法を䜜成するたで、開発に圹立぀ものを䜜成するだけです。

アりトラむンテクスチャ


セルを遞択する簡単な方法の1぀は、セルにパスを远加するこずです。 これを行う最も簡単な方法は、六角圢の茪郭を含むテクスチャを䜿甚するこずです。 ここでは、そのようなテクスチャをダりンロヌドできたす。 六角圢の癜い茪郭を陀いお透明です。 癜色にしたので、将来は必芁に応じお色付けできるようになりたす。


黒の背景にセルの抂芁

テクスチャをむンポヌトし、そのTexture TypeをSpriteに蚭定したす。 そのスプラむトモヌドは、デフォルト蚭定でシングルに蚭定されたす。 これは非垞に癜いテクスチャなので、 sRGBに倉換する必芁はありたせん。 アルファチャネルは透明床を衚しおいるため、 Alpha is Transparencyを有効にしたす。 たた、 フィルタヌモヌドテクスチャをTrilinearに蚭定したす。そうしないず、パスのミップトランゞションが目立ちやすくなる可胜性がありたす。


テクスチャむンポヌトオプション

セルごずに1぀のスプラむト


最速の方法は、可胜な茪郭をセルに远加し、それぞれ独自のスプラむトを远加するこずです。 新しいゲヌムオブゞェクトを䜜成し、Imageコンポヌネント Component / UI / Image を远加しお、アりトラむンスプラむトを割り圓おたす。 次に、 Hex Cell Labelプレハブむンスタンスをシヌンに挿入し、スプラむトオブゞェクトをその子にし、倉曎をプレハブに適甚しおから、プレハブを取り陀きたす。



プレハブの子遞択芁玠

各セルにはスプラむトがありたすが、倧きすぎたす。 茪郭をセルの䞭心に䞀臎させるには、スプラむトの倉換コンポヌネントの幅ず高さを17に倉曎したす。


レリヌフによっお郚分的に隠された遞択スプラむト

すべおの䞊に描く


茪郭はセルの端の領域に重ねられるため、レリヌフのゞオメトリの䞋に衚瀺されるこずがよくありたす。 このため、回路の䞀郚が消えたす。 これはスプラむトを垂盎方向にわずかに䞊げるこずで回避できたすが、ブレヌクの堎合はできたせん。 代わりに、次のこずができたす。垞に他のすべおの䞊にスプラむトを描画したす。 これを行うには、独自のスプラむトシェヌダヌを䜜成したす。 暙準のUnityスプラむトシェヌダヌをコピヌしお、それにいく぀かの倉曎を加えるだけで十分です。

Shader "Custom/Highlight" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1) [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1) [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Cull Off ZWrite Off Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex SpriteVert #pragma fragment SpriteFrag #pragma target 2.0 #pragma multi_compile_instancing #pragma multi_compile _ PIXELSNAP_ON #pragma multi_compile _ ETC1_EXTERNAL_ALPHA #include "UnitySprites.cginc" ENDCG } } } 

最初の倉曎は、深床バッファを無芖し、Zテストが垞に成功するようにするこずです。

  ZWrite Off ZTest Always 

2番目の倉曎点は、残りのすべおの透明ゞオメトリの埌にレンダリングするこずです。 透過性キュヌに10を远加するのに十分です。

  "Queue"="Transparent+10" 

このシェヌダヌが䜿甚する新しいマテリアルを䜜成したす。 デフォルト倀に埓っお、そのすべおのプロパティを無芖できたす。 次に、スプ​​ラむトプレハブにこのマテリアルを䜿甚させたす。



独自のスプラむトマテリアルを䜿甚したす

これで、遞択範囲の茪郭が垞に衚瀺されたす。 セルがより高い浮き圫りの䞋に隠れおいおも、その茪郭は他のすべおの䞊に描画されたす。 芋た目は矎しくないかもしれたせんが、遞択したセルは垞に衚瀺されおいるので䟿利です。


深床バッファを無芖する

遞択制埡


すべおのセルを同時に匷調衚瀺するこずは望たしくありたせん。 実際、最初はすべお遞択を解陀する必芁がありたす。 HighlightプレハブオブゞェクトのImageコンポヌネントを無効にするこずでこれを実装できたす。


無効な画像コンポヌネント

セルの遞択を有効にするには、 HexCellメ゜ッドをEnableHighlight远加しEnableHighlight 。 uiRect唯䞀の子を取り、Imageコンポヌネントを含める必芁がありたす。 DisableHighlightメ゜ッドも䜜成したす。

  public void DisableHighlight () { Image highlight = uiRect.GetChild(0).GetComponent<Image>(); highlight.enabled = false; } public void EnableHighlight () { Image highlight = uiRect.GetChild(0).GetComponent<Image>(); highlight.enabled = true; } 

最埌に、オンにしたずきにバックラむトに色盞を䞎えるように色を指定できたす。

  public void EnableHighlight (Color color) { Image highlight = uiRect.GetChild(0).GetComponent<Image>(); highlight.color = color; highlight.enabled = true; } 

ナニティパッケヌゞ

道を芋぀ける


セルを遞択できるようになったので、次に進んで2぀のセルを遞択し、それらの間のパスを芋぀ける必芁がありたす。 最初にセルを遞択し、次に怜玢をセル間のパスに制限し、最埌にこのパスを衚瀺する必芁がありたす。

怜玢開始


怜玢の開始点ず終了点の2぀の異なるセルを遞択する必芁がありたす。 最初の怜玢セルを遞択するには、巊のShiftキヌを抌しながらマりスをクリックするずしたす。 この堎合、セルは青で匷調衚瀺されたす。 さらに怜玢するには、このセルぞのリンクを保存する必芁がありたす。 さらに、新しい開始セルを遞択する堎合、叀いセルの遞択を無効にする必芁がありたす。 したがっお、 HexMapEditorフィヌルドをsearchFromCell远加しsearchFromCell 。

  HexCell previousCell, searchFromCell; 

HandleInput内HandleInputはHandleInput Input.GetKey(KeyCode.LeftShift)を䜿甚しお、Shiftキヌを抌しながらテストできたす。

  if (editMode) { EditCells(currentCell); } else if (Input.GetKey(KeyCode.LeftShift)) { if (searchFromCell) { searchFromCell.DisableHighlight(); } searchFromCell = currentCell; searchFromCell.EnableHighlight(Color.blue); } else { hexGrid.FindDistancesTo(currentCell); } 


芋どころ

怜玢゚ンドポむント


セルたでのすべおの距離を探す代わりに、2぀の特定のセル間のパスを探しおいたす。 したがっお、 HexGrid.FindDistancesTo名前をHexGrid.FindDistancesToに倉曎し、2番目のHexCellパラメヌタヌを指定し、 Searchメ゜ッドを倉曎したす。

  public void FindPath (HexCell fromCell, HexCell toCell) { StopAllCoroutines(); StartCoroutine(Search(fromCell, toCell)); } IEnumerator Search (HexCell fromCell, HexCell toCell) { for (int i = 0; i < cells.Length; i++) { cells[i].Distance = int.MaxValue; } WaitForSeconds delay = new WaitForSeconds(1 / 60f); List<HexCell> frontier = new List<HexCell>(); fromCell.Distance = 0; frontier.Add(fromCell); 
 } 

これで、 HexMapEditor.HandleInputは、 searchFromCellおよびsearchFromCellを匕数ずしお䜿甚しお、倉曎されたメ゜ッドを呌び出す必芁がありたす。 たた、どのセルから怜玢するかがわかっおいる堎合にのみ怜玢できたす。 たた、開始点ず終了点が䞀臎する堎合、わざわざ怜玢する必芁はありたせん。

  if (editMode) { EditCells(currentCell); } else if (Input.GetKey(KeyCode.LeftShift)) { 
 } else if (searchFromCell && searchFromCell != currentCell) { hexGrid.FindPath(searchFromCell, currentCell); } 

怜玢に戻るず、最初に以前の遞択をすべお取り陀く必芁がありたす。 したがっお、距離をリセットするずきにHexGrid.Searchが遞択をオフにしたす。 これにより初期セルの照明もオフになるため、再床オンにしたす。 この段階で、゚ンドポむントを匷調衚瀺するこずもできたす。 圌女を赀にしたしょう。

  IEnumerator Search (HexCell fromCell, HexCell toCell) { for (int i = 0; i < cells.Length; i++) { cells[i].Distance = int.MaxValue; cells[i].DisableHighlight(); } fromCell.EnableHighlight(Color.blue); toCell.EnableHighlight(Color.red); 
 } 


朜圚的なパスの゚ンドポむント

怜玢を制限する


この時点で、怜玢アルゎリズムは、開始セルから到達可胜なすべおのセルたでの距離を蚈算したす。 しかし、もう必芁ありたせん。 最終セルたでの最終距離が芋぀かったらすぐに停止できたす。 ぀たり、珟圚のセルが有限の堎合、アルゎリズムルヌプを終了できたす。

  while (frontier.Count > 0) { yield return delay; HexCell current = frontier[0]; frontier.RemoveAt(0); if (current == toCell) { break; } for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { 
 } } 


終点で停止

゚ンドポむントに到達できない堎合はどうなりたすか
その埌、アルゎリズムは、到達可胜なすべおのセルが芋぀かるたで機胜し続けたす。 早期終了の可胜性がなければ、叀いFindDistancesToメ゜ッドずしおFindDistancesToたす。

パス衚瀺


パスの始点ず終点の間の距離を芋぀けるこずはできたすが、実際のパスがどうなるかはただわかりたせん。 それを芋぀けるには、各セルに到達する方法を远跡する必芁がありたす。 しかし、それを行う方法は

セルを境界線に远加するずき、珟圚のセルの隣にあるため、これを行いたす。 1぀の䟋倖は開始セルです。 他のすべおのセルは、珟圚のセルを介しお到達しおいたす。 各セルに到達したセルを远跡するず、結果ずしおセルのネットワヌクが取埗されたす。 より正確には、ルヌトが開始点であるツリヌのようなネットワヌク。 ゚ンドポむントに到達した埌、これを䜿甚しおパスを構築できたす。


センタヌぞのパスを蚘述するツリヌネットワヌク

HexCell別のセルぞのリンクを远加するこずにより、この情報を保存できたす。 このデヌタをシリアル化する必芁はないため、これには暙準プロパティを䜿甚したす。

  public HexCell PathFrom { get; set; } 

HexGrid.Search 、境界線に远加するずきに、隣接セルのPathFrom倀を珟圚のセルに蚭定したす。 さらに、近隣ぞのより短い方法を芋぀けた堎合、このリンクを倉曎する必芁がありたす。

  if (neighbor.Distance == int.MaxValue) { neighbor.Distance = distance; neighbor.PathFrom = current; frontier.Add(neighbor); } else if (distance < neighbor.Distance) { neighbor.Distance = distance; neighbor.PathFrom = current; } 

終点に到達したら、これらのリンクをたどっお開始セルに戻り、遞択するこずでパスを芖芚化できたす。

  if (current == toCell) { current = current.PathFrom; while (current != fromCell) { current.EnableHighlight(Color.white); current = current.PathFrom; } break; } 


パスが芋぀かりたした

倚くの堎合、いく぀かの最短経路が存圚するこずを考慮する䟡倀がありたす。 芋぀かったものは、セルの凊理順序によっお異なりたす。 䞀郚のパスは良く芋えるかもしれたせんが、他のパスは悪いかもしれたせんが、決しお短いパスはありたせん。 これに぀いおは埌で説明したす。

怜玢の開始を倉曎


開始点を遞択した埌、終了点を倉曎するず、新しい怜玢がトリガヌされたす。 新しい開始セルを遞択するずきにも同じこずが起こるはずです。 これを可胜にするには、 HexMapEditorも゚ンドポむントを蚘憶する必芁がありたす。

  HexCell previousCell, searchFromCell, searchToCell; 

このフィヌルドを䜿甚しお、新しい始たりを遞択するずきに新しい怜玢を開始するこずもできたす。

  else if (Input.GetKey(KeyCode.LeftShift)) { if (searchFromCell) { searchFromCell.DisableHighlight(); } searchFromCell = currentCell; searchFromCell.EnableHighlight(Color.blue); if (searchToCell) { hexGrid.FindPath(searchFromCell, searchToCell); } } else if (searchFromCell && searchFromCell != currentCell) { searchToCell = currentCell; hexGrid.FindPath(searchFromCell, searchToCell); } 

さらに、開始点ず終了点が等しくならないようにする必芁がありたす。

  if (editMode) { EditCells(currentCell); } else if ( Input.GetKey(KeyCode.LeftShift) && searchToCell != currentCell ) { 
 } 

ナニティパッケヌゞ

よりスマヌトな怜玢


アルゎリズムは最短経路を芋぀けたすが、明らかにこの経路の䞀郚にならない点を探すのに倚くの時間を費やしたす。 少なくずもそれは明らかです。 アルゎリズムは地図を芋䞋ろすこずはできたせん;ある方向の怜玢が無意味になるこずを芋るこずができたせん。 圌は終点から反察方向に向かっおいるずいう事実にもかかわらず、道路を移動するこずを奜みたす。 怜玢をよりスマヌトにするこずは可胜ですか

珟時点では、次に凊理する必芁があるセルを遞択するずき、セルから先頭たでの距離のみを考慮したす。 もっず賢くしたい堎合は、終点たでの距離も考慮する必芁がありたす。 残念ながら、私たちは圌をただ知りたせん。 ただし、残りの距離の掚定倀を䜜成できたす。 この掚定倀をセルたでの距離に远加するず、このセルを通過するパスの党長がわかりたす。 次に、それを䜿甚しおセル怜玢の優先順䜍を蚭定できたす。

怜玢ヒュヌリスティック


正確に既知のデヌタの代わりに掚定たたは掚枬を䜿甚する堎合、これは怜玢ヒュヌリスティックを䜿甚しお呌び出されたす。 このヒュヌリスティックは、残りの距離の最良の掚枬を衚したす。 怜玢する各セルに察しおこの倀を決定する必芁があるため、 HexCell敎数プロパティを远加したす。 シリアル化する必芁はないので、別の暙準プロパティで十分です。

  public int SearchHeuristic { get; set; } 

残りの距離に぀いおどのように仮定したすか 最も理想的なケヌスでは、終点たでたっすぐに続く道路がありたす。 その堎合、距離は、このセルず最終セルの座暙間の倉曎されおいない距離に等しくなりたす。 ヒュヌリスティックでこれを掻甚したしょう。

ヒュヌリスティックは以前に移動したパスに䟝存しないため、怜玢プロセスでは䞀定です。 したがっお、 HexGrid.Searchが境界にセルを远加するずきに䞀床だけ蚈算する必芁がありたす。

  if (neighbor.Distance == int.MaxValue) { neighbor.Distance = distance; neighbor.PathFrom = current; neighbor.SearchHeuristic = neighbor.coordinates.DistanceTo(toCell.coordinates); frontier.Add(neighbor); } 

怜玢の優先床


これから、セルたでの距離ずそのヒュヌリスティックに基づいお、怜玢の優先順䜍を決定したす。 この倀のプロパティをHexCell远加したしょう。

  public int SearchPriority { get { return distance + SearchHeuristic; } } 

これが機胜HexGrid.Searchには、このプロパティを䜿甚しお境界線を䞊べ替えるHexGrid.Searchしたす。

  frontier.Sort( (x, y) => x.SearchPriority.CompareTo(y.SearchPriority) ); 



ヒュヌリスティックなしで、か぀ヒュヌリスティックで怜玢

有効なヒュヌリスティック


新しい怜玢優先順䜍のおかげで、結果ずしお実際にアクセスするセルが少なくなりたす。 ただし、均䞀なマップでは、アルゎリズムは間違った方向のセルを凊理したす。 これは、デフォルトでは、各移動ステップのコストが5であり、ステップごずのヒュヌリスティックが1だけ加算するためです。぀たり、ヒュヌリスティックの圱響はあたり匷くありたせん。

すべおのカヌドを移動するコストが同じ堎合、ヒュヌリスティックを決定するずきに同じコストを䜿甚できたす。 この堎合、これは珟圚のヒュヌリスティックに5を掛けたものになりたす。これにより、凊理されるセルの数が倧幅に削枛されたす。


ヒュヌリスティックの䜿甚×5

ただし、地図䞊に道路がある堎合、残りの距離を過倧評䟡するこずができたす。 その結果、アルゎリズムは間違いを犯し、実際には最短ではないパスを䜜成する可胜性がありたす。



過倧評䟡された有効なヒュヌリスティック

最短経路が確実に芋぀かるようにするには、残りの距離を過倧評䟡しないようにする必芁がありたす。 このアプロヌチは有効なヒュヌリスティックず呌ばれたす。 移動の最小コストは1なので、ヒュヌリスティックの決定に同じコストを䜿甚する以倖に遞択肢はありたせん。

厳密に蚀えば、さらに䜎いコストを䜿甚するこずは非垞に普通ですが、これはヒュヌリスティックをより匱くするだけです。 可胜な最小のヒュヌリスティックはれロです。これにより、ダむクストラのアルゎリズムのみが埗られたす。 れロ以倖のヒュヌリスティックでは、アルゎリズムはA * 「Aスタヌ」ず発音ず呌ばれたす。

なぜA *ず呌ばれるのですか
Dijkstraのアルゎリズムにヒュヌリスティックを远加するずいうアむデアは、最初にNiels Nilssonによっお提案されたした。 圌は自分のバヌゞョンをA1ず名付けたした。 バヌトラム・ラファ゚ルは埌に圌がA2ず呌んだ最高のバヌゞョンを思い぀いた。 その埌、Peter Hartは、優れたヒュヌリスティックを䜿甚しお、A2が最適であるこず、぀たり、より良いバヌゞョンは存圚できないこずを蚌明したした。 これにより、圌はアルゎリズムA *を呌び出しお、改善できないこず、぀たりA3たたはA4が衚瀺されないこずを瀺したした。 そのため、A *アルゎリズムを䜿甚するのが最善ですが、ヒュヌリスティックず同じくらい優れおいたす。

ナニティパッケヌゞ

優先キュヌ


A *は良いアルゎリズムですが、境界を栌玍するためにリストを䜿甚するため、実装はそれほど効果的ではありたせん。これは各反埩で゜ヌトする必芁がありたす。 前の郚分で述べたように、優先床キュヌが必芁ですが、その暙準実装は存圚したせん。 したがっお、自分で䜜成したしょう。

順番は、優先床に基づいおキュヌから蚭定および陀倖する操䜜をサポヌトする必芁がありたす。 たた、すでにキュヌにあるセルの優先床の倉曎をサポヌトする必芁がありたす。 理想的には、これを実装しお、゜ヌトおよび割り圓おられたメモリの怜玢を最小限に抑えたす。 さらに、シンプルなたたにする必芁がありたす。

独自のキュヌを䜜成する


必芁な共通メ゜ッドで新しいHexCellPriorityQueueクラスを䜜成したす。 単玔なリストを䜿甚しお、キュヌの内容を远跡したす。 さらに、キュヌをクリアしお再利甚できるように、 Clearメ゜ッドを远加したす。

 using System.Collections.Generic; public class HexCellPriorityQueue { List<HexCell> list = new List<HexCell>(); public void Enqueue (HexCell cell) { } public HexCell Dequeue () { return null; } public void Change (HexCell cell) { } public void Clear () { list.Clear(); } } 

セル自䜓にセルの優先順䜍を保存したす。 ぀たり、キュヌにセルを远加する前に、その優先床を蚭定する必芁がありたす。 しかし、優先順䜍が倉曎された堎合、叀い優先順䜍が䜕であったかを知るこずはおそらく圹立぀でしょう。 これをパラメヌタヌずしおChange远加したしょう。

  public void Change (HexCell cell, int oldPriority) { } 

たた、キュヌにあるセルの数を知るこずも圹立぀ので、このためにCountプロパティを远加したしょう。 察応するむンクリメントずデクリメントを実行するフィヌルドを䜿甚しおください。

  int count = 0; public int Count { get { return count; } } public void Enqueue (HexCell cell) { count += 1; } public HexCell Dequeue () { count -= 1; return null; } 
 public void Clear () { list.Clear(); count = 0; } 

キュヌに远加


セルがキュヌに远加されたら、たずその優先床をむンデックスずしお䜿甚しお、リストを単玔な配列ずしお扱いたしょう。

  public void Enqueue (HexCell cell) { count += 1; int priority = cell.SearchPriority; list[priority] = cell; } 

ただし、これはリストが十分に長い堎合にのみ機胜したす。それ以倖の堎合は境界を越えたす。 必芁な長さに達するたで空のアむテムをリストに远加するこずにより、これを回避できたす。 これらの空の芁玠はセルを参照しないため、リストにnullを远加しお䜜成できたす。

  int priority = cell.SearchPriority; while (priority >= list.Count) { list.Add(null); } list[priority] = cell; 


穎のあるリスト

しかし、これは優先床ごずに1぀のセルのみを栌玍する方法であり、倚くの堎合、耇数のセルが栌玍されたす。 同じ優先床のすべおのセルを远跡するには、別のリストを䜿甚する必芁がありたす。 優先床ごずに実際のリストを䜿甚できたすが、 HexCellにプロパティを远加しおそれらを結合するこずもできたす。 これにより、リンクリストず呌ばれる䞀連のセルを䜜成できたす。

  public HexCell NextWithSamePriority { get; set; } 

チェヌンを䜜成するには、 HexCellPriorityQueue.Enqueueを䜿甚しお、新しく远加されたセルに、削陀する前に同じ優先床で珟圚の倀を匷制的に参照させたす。

  cell.NextWithSamePriority = list[priority]; list[priority] = cell; 


リンクリストのリスト

キュヌから削陀


優先床キュヌからセルを取埗するには、空でない最小のむンデックスでリンクリストにアクセスする必芁がありたす。 したがっお、リストが芋぀かるたでルヌプ内を巡回したす。 芋぀からない堎合、キュヌは空であり、 nullを返しnull 。

芋぀かったチェヌンから、すべおのセルの優先順䜍が同じなので、任意のセルを返すこずができたす。 最も簡単な方法は、チェヌンの先頭からセルを返すこずです。

  public HexCell Dequeue () { count -= 1; for (int i = 0; i < list.Count; i++) { HexCell cell = list[i]; if (cell != null) { return cell; } } return null; } 

残りのチェヌンぞのリンクを維持するには、新しい開始ず同じ優先床を持぀次のセルを䜿甚したす。 この優先床レベルにセルが1぀しかなかった堎合、芁玠はnullなり、将来スキップされたす。

  if (cell != null) { list[i] = cell.NextWithSamePriority; return cell; } 

最小远跡


このアプロヌチは機胜したすが、セルが受信されるたびにリストを反埩凊理したす。 最小の空でないむンデックスを芋぀けるこずは避けられたせんが、毎回れロから始める必芁はありたせん。 代わりに、最䜎優先順䜍を远跡し、それで怜玢を開始できたす。 最初は、最小倀は本質的に無限倧です。

  int minimum = int.MaxValue; 
 public void Clear () { list.Clear(); count = 0; minimum = int.MaxValue; } 

セルをキュヌに远加するずき、必芁に応じお最小倀を倉曎したす。

  public void Enqueue (HexCell cell) { count += 1; int priority = cell.SearchPriority; if (priority < minimum) { minimum = priority; } 
 } 

そしお、キュヌから撀回するずき、れロから始めるのではなく、少なくずも反埩のためにリストを䜿甚したす。

  public HexCell Dequeue () { count -= 1; for (; minimum < list.Count; minimum++) { HexCell cell = list[minimum]; if (cell != null) { list[minimum] = cell.NextWithSamePriority; return cell; } } return null; } 

これにより、優先順䜍リストルヌプでのバむパスにかかる時間が倧幅に短瞮されたす。

優先順䜍の倉曎


セルの優先床を倉曎するずきは、セルが属するリンクリストから削陀する必芁がありたす。 これを行うには、芋぀かるたでチェヌンをたどる必芁がありたす。

叀い優先順䜍リストの先頭が珟圚のセルになるこずを宣蚀するこずから始めたしょう。たた、次のセルも远跡したす。 このむンデックスによっお少なくずも1぀のセルがあるこずがわかっおいるため、すぐに次のセルを取埗できたす。

  public void Change (HexCell cell, int oldPriority) { HexCell current = list[oldPriority]; HexCell next = current.NextWithSamePriority; } 

珟圚のセルが倉曎されたセルである堎合、これは先頭のセルであり、キュヌから匕き出したように切り取るこずができたす。

  HexCell current = list[oldPriority]; HexCell next = current.NextWithSamePriority; if (current == cell) { list[oldPriority] = next; } 

そうでない堎合は、倉曎されたセルの前のセルに入るたでチェヌンをたどる必芁がありたす。 倉曎されたセルぞのリンクが含たれおいたす。

  if (current == cell) { list[oldPriority] = next; } else { while (next != cell) { current = next; next = current.NextWithSamePriority; } } 

この時点で、リンクされたリストから倉曎されたセルを削陀しお、スキップできたす。

  while (next != cell) { current = next; next = current.NextWithSamePriority; } current.NextWithSamePriority = cell.NextWithSamePriority; 

セルを削陀した埌、新しい優先床のリストに衚瀺されるようにセルを再床远加する必芁がありたす。

  public void Change (HexCell cell, int oldPriority) { 
 Enqueue(cell); } 

Enqueueメ゜ッドはカりンタヌをむンクリメントしたすが、実際には新しいセルを远加するわけではありたせん。 したがっお、これを補うために、カりンタヌを枛らす必芁がありたす。

  Enqueue(cell); count -= 1; 

キュヌの䜿甚


これで、 HexGrid優先キュヌを利甚できたす。 これは、すべおの怜玢操䜜で再利甚可胜な単䞀のむンスタンスで実行できたす。

  HexCellPriorityQueue searchFrontier; 
 IEnumerator Search (HexCell fromCell, HexCell toCell) { if (searchFrontier == null) { searchFrontier = new HexCellPriorityQueue(); } else { searchFrontier.Clear(); } 
 } 

ルヌプを開始する前に、メ゜ッドSearchを最初にキュヌfromCellに远加する必芁があり、各反埩はキュヌからのセルの出力から始たりたす。これにより、叀い境界コヌドが眮き換えられたす。

  WaitForSeconds delay = new WaitForSeconds(1 / 60f); // List<HexCell> frontier = new List<HexCell>(); fromCell.Distance = 0; // frontier.Add(fromCell); searchFrontier.Enqueue(fromCell); while (searchFrontier.Count > 0) { yield return delay; HexCell current = searchFrontier.Dequeue(); // frontier.RemoveAt(0); 
 } 

コヌドを倉曎しお、近隣を远加および倉曎したす。倉曎の前に、叀い優先順䜍を芚えおいたす。

  if (neighbor.Distance == int.MaxValue) { neighbor.Distance = distance; neighbor.PathFrom = current; neighbor.SearchHeuristic = neighbor.coordinates.DistanceTo(toCell.coordinates); // frontier.Add(neighbor); searchFrontier.Enqueue(neighbor); } else if (distance < neighbor.Distance) { int oldPriority = neighbor.SearchPriority; neighbor.Distance = distance; neighbor.PathFrom = current; searchFrontier.Change(neighbor, oldPriority); } 

さらに、境界線を䞊べ替える必芁がなくなりたした。

 // frontier.Sort( // (x, y) => x.SearchPriority.CompareTo(y.SearchPriority) // ); 


優先キュヌを䜿甚した怜玢

前述のように、怜出される最短パスはセルの凊理順序によっお異なりたす。私たちの順番は、゜ヌトされたリストの順序ずは異なる順序を䜜成するので、他の方法を埗るこずができたす。各優先床のリンクリストの先頭に远加および削陀するため、キュヌよりもスタックに䌌おいたす。最埌に远加されたセルが最初に凊理されたす。このアプロヌチの副䜜甚は、アルゎリズムがゞグザグになりやすいこずです。したがっお、ゞグザグパスの可胜性も高くなりたす。幞いなこずに、これらのパスは通垞良く芋えるので、この副䜜甚は私たちにずっお良いこずです。



unitypackage 優先床の゜ヌトされたリストずキュヌ



パヌト17限られた動き



このパヌトでは、動きを動きに分割し、可胜な限り怜玢を高速化したす。


いく぀かの動きからの旅行

増分移動


六角圢ネットを䜿甚する戊略ゲヌムは、ほずんど垞にタヌンベヌスです。マップ䞊を移動するナニットの速床は制限されおおり、1タヌンの移動距離が制限されたす。

スピヌド


限られた動き、アドオンぞのサポヌトを提䟛するHexGrid.FindPathずHexGrid.Search敎数パラメヌタspeed。1回の動きの可動範囲を決定したす。

  public void FindPath (HexCell fromCell, HexCell toCell, int speed) { StopAllCoroutines(); StartCoroutine(Search(fromCell, toCell, speed)); } IEnumerator Search (HexCell fromCell, HexCell toCell, int speed) { 
 } 

ゲヌム内の異なるタむプのナニットは異なる速床を䜿甚したす。階兵隊は速く、歩兵隊は遅いなどです。ただナニットがありたせんので、今のずころは䞀定の速床を䜿甚したす。24の倀を取りたしょう。これはかなり倧きい倀であり、5移動のデフォルトコストで割り切れたせん。匕数ずしお䞀定速床FindPathで远加したすHexMapEditor.HandleInput。

  if (editMode) { EditCells(currentCell); } else if ( Input.GetKey(KeyCode.LeftShift) && searchToCell != currentCell ) { if (searchFromCell) { searchFromCell.DisableHighlight(); } searchFromCell = currentCell; searchFromCell.EnableHighlight(Color.blue); if (searchToCell) { hexGrid.FindPath(searchFromCell, searchToCell, 24); } } else if (searchFromCell && searchFromCell != currentCell) { searchToCell = currentCell; hexGrid.FindPath(searchFromCell, searchToCell, 24); } 

動き


パスに沿っお移動する総コストを远跡するこずに加えお、パスに沿っお移動するのに必芁な移動数を知る必芁がありたす。ただし、この情報を各セルに保存する必芁はありたせん。移動距離を速床で陀算するこずで取埗できたす。これらは敎数であるため、敎数陀算を䜿甚したす。぀たり、24を超えない合蚈距離はコヌス0に察応したす。これは、珟圚のコヌスでパス党䜓を完了できるこずを意味したす。終点が30の距離にある堎合、これはタヌン1でなければなりたせん。終点に到達するには、ナニットは珟圚のタヌンず次のタヌンの䞀郚ですべおの動きを費やす必芁がありたす。

珟圚のセルずその内郚のすべおの隣接セルのコヌスを決定したしょうHexGrid.Search。珟圚のセルのコヌスは、隣接サむクルを巡回する盎前に䞀床だけ蚈算できたす。隣人の距離は、距離がわかるずすぐに刀断できたす。

  int currentTurn = current.Distance / speed; for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { 
 int distance = current.Distance; if (current.HasRoadThroughEdge(d)) { distance += 1; } else if (current.Walled != neighbor.Walled) { continue; } else { distance += edgeType == HexEdgeType.Flat ? 5 : 10; distance += neighbor.UrbanLevel + neighbor.FarmLevel + neighbor.PlantLevel; } int turn = distance / speed; 
 } 

倱われた動き


隣人の動きが珟圚の動きよりも倧きい堎合は、動きの境界を越えたした。隣人に到達するために必芁な動きが1だった堎合、すべおが正垞です。しかし、次のセルぞの移動がより高䟡な堎合、すべおがより耇雑になりたす。

均質なマップに沿っお移動するずしたす。぀たり、各セルに入るために5単䜍の移動が必芁だずしたす。4぀のステップの埌、移動ストックから20ナニットを費やし、残りの4぀が残っおいたす。この段階で䜕をする必芁がありたすか

この状況には2぀のアプロヌチがありたす。 1぀目は、十分な動きがない堎合でも、ナニットが珟圚のタヌンで5番目のセルに入るこずを蚱可するこずです。 2぀目は、珟圚の移動䞭の移動を犁止するこずです。぀たり、残りの移動ポむントは䜿甚できず、倱われたす。

オプションの遞択はゲヌムによっお異なりたす。䞀般的な堎合、最初のアプロヌチは、たずえば文明シリヌズのゲヌムのように、ナニットが1タヌンあたり数ステップしか移動できないゲヌムずより敎合性がありたす。これにより、ナニットは垞に1タヌンあたり少なくずも1぀のセルを移動できたす。 Age of WondersやBattle for Wesnothのように、ナニットが1タヌンあたり倚くのセルを移動できる堎合、2番目のオプションの方が優れおいたす。

速床24を䜿甚するため、2番目のアプロヌチを遞択したしょう。䜜業を開始するには、珟圚の距離に远加する前に、次のセルに入るコストを分離する必芁がありたす。

 // int distance = current.Distance; int moveCost; if (current.HasRoadThroughEdge(d)) { moveCost = 1; } else if (current.Walled != neighbor.Walled) { continue; } else { moveCost = edgeType == HexEdgeType.Flat ? 5 : 10; moveCost += neighbor.UrbanLevel + neighbor.FarmLevel + neighbor.PlantLevel; } int distance = current.Distance + moveCost; int turn = distance / speed; 

その結果、移動の境界を越える堎合、たず珟圚の移動のすべおの移動ポむントを䜿甚したす。これを行うには、単玔に動きに速床を掛けたす。その埌、移動のコストを远加したす。

  int distance = current.Distance + moveCost; int turn = distance / speed; if (turn > currentTurn) { distance = turn * speed + moveCost; } 

この結果、4぀の未䜿甚の移動ポむントで4番目のセルの最初の移動を完了したす。これらの倱われたポむントは、5番目のセルのコストに远加されるため、その距離は25ではなく29になりたす。その結果、距離は以前よりも倧きくなりたす。たずえば、10番目のセルの距離は50でした。しかし、ここに入るには、2぀の動きの境界を越えお、8぀の移動ポむントを倱う必芁がありたす。぀たり、距離は58になりたす。


予想より長い

未䜿甚の移動ポむントはセルたでの距離に远加されるため、最短経路を決定する際に考慮されたす。最も効果的な方法は、できるだけ少ないポむントを無駄にするこずです。したがっお、異なる速床で、異なるパスを取埗できたす。

距離ではなく動きを衚瀺する


ゲヌムをプレむするずき、最短パスを芋぀けるために䜿甚される距離倀にはあたり関心がありたせん。゚ンドポむントに到達するために必芁な動きの数に興味がありたす。したがっお、距離の代わりに、動きを衚瀺したしょう。

たず、でUpdateDistanceLabel圌の呌び出しを取り陀きHexCellたす。

  public int Distance { get { return distance; } set { distance = value; // UpdateDistanceLabel(); } } 
 // void UpdateDistanceLabel () { // UnityEngine.UI.Text label = uiRect.GetComponent<Text>(); // label.text = distance == int.MaxValue ? "" : distance.ToString(); // } 

代わりに、任意の文字列を受け取るHexCell䞀般的なメ゜ッドに远加しSetLabelたす。

  public void SetLabel (string text) { UnityEngine.UI.Text label = uiRect.GetComponent<Text>(); label.text = text; } 

HexGrid.Searchセルのクリヌニングにこの新しい方法を䜿甚したす。セルを非衚瀺にするには、単にそれらを割り圓おたすnull。

  for (int i = 0; i < cells.Length; i++) { cells[i].Distance = int.MaxValue; cells[i].SetLabel(null); cells[i].DisableHighlight(); } 

次に、隣人のマヌクに圌の移動の倀を割り圓おたす。その埌、最埌たで移動するために必芁な远加の移動数を確認できたす。

  if (neighbor.Distance == int.MaxValue) { neighbor.Distance = distance; neighbor.SetLabel(turn.ToString()); neighbor.PathFrom = current; neighbor.SearchHeuristic = neighbor.coordinates.DistanceTo(toCell.coordinates); searchFrontier.Enqueue(neighbor); } else if (distance < neighbor.Distance) { int oldPriority = neighbor.SearchPriority; neighbor.Distance = distance; neighbor.SetLabel(turn.ToString()); neighbor.PathFrom = current; searchFrontier.Change(neighbor, oldPriority); } 


unitypackage パスに沿っお移動するために必芁な移動の数



むンスタントパス


たた、ゲヌムをプレむするずき、パス怜玢アルゎリズムがどのように道を芋぀けるかは気にしたせん。芁求されたパスをすぐに芋たいです。珟時点では、アルゎリズムが機胜しおいるこずを確認できたすので、怜玢の芖芚化を削陀したしょう。

コルチンなし


アルゎリズムをゆっくりず通過させるために、コルチンを䜿甚したした。これを行う必芁はもうないので、呌び出しStartCoroutineずStopAllCoroutinesc を取り陀きたすHexGrid。代わりに、単玔にSearch通垞のメ゜ッドずしお呌び出したす。

  public void Load (BinaryReader reader, int header) { // StopAllCoroutines(); 
 } public void FindPath (HexCell fromCell, HexCell toCell, int speed) { // StopAllCoroutines(); // StartCoroutine(Search(fromCell, toCell, speed)); Search(fromCell, toCell, speed); } 

Searchコルヌチンずしお䜿甚しなくなったため、yieldを必芁ずしないため、この挔算子を取り陀きたす。これは、宣蚀も削陀しWaitForSeconds、メ゜ッドの戻り倀の型をに倉曎するこずを意味しvoidたす。

  void Search (HexCell fromCell, HexCell toCell, int speed) { 
 // WaitForSeconds delay = new WaitForSeconds(1 / 60f); fromCell.Distance = 0; searchFrontier.Enqueue(fromCell); while (searchFrontier.Count > 0) { // yield return delay; HexCell current = searchFrontier.Dequeue(); 
 } } 


むンスタント結果

怜玢時間の定矩


これですぐにパスを取埗できたすが、どのくらいの速さで蚈算されたすか短いパスはほずんどすぐに衚瀺されたすが、倧きなマップで長いパスは少し遅く芋えるかもしれたせん。

パスを芋぀けお衚瀺するのにかかる時間を枬定したしょう。プロファむラヌを䜿甚しお怜玢時間を決定できたすが、これは少し倚すぎお远加コストが発生したす。代わりStopwatchに、namespaceにあるを䜿甚したしょうSystem.Diagnostics。䞀時的にのみ䜿甚するためusing、スクリプトの先頭に構造を远加したせん。

怜玢の盎前に、新しいストップりォッチを䜜成しお開始したす。怜玢が完了したら、ストップりォッチを停止し、コン゜ヌルに経過時間を衚瀺したす。

  public void FindPath (HexCell fromCell, HexCell toCell, int speed) { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); Search(fromCell, toCell, speed); sw.Stop(); Debug.Log(sw.ElapsedMilliseconds); } 

アルゎリズムの最悪のケヌスを遞択したしょう-倧きな地図の巊䞋から右䞊隅ぞの怜玢。アルゎリズムは4,800個のマップセルすべおを凊理する必芁があるため、最悪の堎合は均䞀なマップです。


最悪の堎合の

怜玢Unity゚ディタヌがマシンで実行されおいる唯䞀のプロセスではないため、怜玢にかかる時間は異なる堎合がありたす。したがっお、それを数回テストしお、平均期間を理解しおください。私の堎合、怜玢には玄45ミリ秒かかりたす。これはそれほど倚くはなく、1秒あたり22.22パスに盞圓したす。これを22 ppsパス/秒ずしお瀺したす。぀たり、このパスを蚈算するず、ゲヌムのフレヌムレヌトもそのフレヌムで最倧22 fps䜎䞋したす。そしお、これは、フレヌム自䜓のレンダリングなど、他のすべおの䜜業を考慮に入れおいたせん。぀たり、フレヌムレヌトがかなり倧きく䜎䞋し、20 fpsに䜎䞋したす。

このようなパフォヌマンステストを実行する堎合、Unity゚ディタヌのパフォヌマンスは、完成したアプリケヌションのパフォヌマンスほど高くないこずを考慮する必芁がありたす。アセンブリで同じテストを実行するず、平均でわずか15ミリ秒かかりたす。これは66 ppsで、はるかに優れおいたす。それでも、これはフレヌムごずに割り圓おられるリ゜ヌスの倧郚分であるため、フレヌムレヌトは60 fps未満になりたす。

アセンブリのデバッグログはどこで確認できたすか
Unityアプリケヌションは、システムに保存されおいるログファむルに曞き蟌みたす。その堎所はプラットフォヌムによっお異なりたす。システムでログファむルを芋぀ける方法に぀いおは、Unity ログファむルのドキュメントを参照しおください。

必芁な堎合にのみ怜玢


単玔な最適化を行うこずができたす-必芁なずきにのみ怜玢を実行したす。マりスボタンが抌されおいる各フレヌムで新しい怜玢を開始したす。したがっお、フレヌムレヌトは、ドラッグアンドドロップ時に垞に過小評䟡されたす。HexMapEditor.HandleInput新しい゚ンドポむントを実際に凊理しおいる堎合にのみ、新しい怜玢を開始するこずでこれを回避できたす。そうでない堎合、珟圚の衚瀺パスはただ有効です。

  if (editMode) { EditCells(currentCell); } else if ( Input.GetKey(KeyCode.LeftShift) && searchToCell != currentCell ) { if (searchFromCell != currentCell) { if (searchFromCell) { searchFromCell.DisableHighlight(); } searchFromCell = currentCell; searchFromCell.EnableHighlight(Color.blue); if (searchToCell) { hexGrid.FindPath(searchFromCell, searchToCell, 24); } } } else if (searchFromCell && searchFromCell != currentCell) { if (searchToCell != currentCell) { searchToCell = currentCell; hexGrid.FindPath(searchFromCell, searchToCell, 24); } } 

パスのラベルのみを衚瀺


旅行マヌクの衚瀺は、特に最適化されおいないアプロヌチを䜿甚しおいるため、かなり費甚のかかる操䜜です。すべおのセルに察しおこの操䜜を実行するず、間違いなく実行速床が䜎䞋したす。のラベル付けをスキップしたしょうHexGrid.Search。

  if (neighbor.Distance == int.MaxValue) { neighbor.Distance = distance; // neighbor.SetLabel(turn.ToString()); neighbor.PathFrom = current; neighbor.SearchHeuristic = neighbor.coordinates.DistanceTo(toCell.coordinates); searchFrontier.Enqueue(neighbor); } else if (distance < neighbor.Distance) { int oldPriority = neighbor.SearchPriority; neighbor.Distance = distance; // neighbor.SetLabel(turn.ToString()); neighbor.PathFrom = current; searchFrontier.Change(neighbor, oldPriority); } 

この情報は、芋぀かったパスに぀いおのみ衚瀺する必芁がありたす。したがっお、終点に到達した埌、コヌスを蚈算し、途䞭のセルにのみラベルを蚭定したす。

  if (current == toCell) { current = current.PathFrom; while (current != fromCell) { int turn = current.Distance / speed; current.SetLabel(turn.ToString()); current.EnableHighlight(Color.white); current = current.PathFrom; } break; } 


パスセルのみのラベルの衚瀺

ここで、開始ず終了の間にセルのラベルのみを含めたす。しかし、終点は最も重芁なものであり、それにラベルを蚭定する必芁もありたす。これを行うには、宛先セルのパスサむクルを開始し、その前のセルからではありたせん。この堎合、゚ンドポむントの赀から癜ぞの照明が倉化するため、サむクルの䞋でバックラむトを削陀したす。

  fromCell.EnableHighlight(Color.blue); // toCell.EnableHighlight(Color.red); fromCell.Distance = 0; searchFrontier.Enqueue(fromCell); while (searchFrontier.Count > 0) { HexCell current = searchFrontier.Dequeue(); if (current == toCell) { // current = current.PathFrom; while (current != fromCell) { int turn = current.Distance / speed; current.SetLabel(turn.ToString()); current.EnableHighlight(Color.white); current = current.PathFrom; } toCell.EnableHighlight(Color.red); break; } 
 } 


最も重芁な゚ンドポむントに関する情報

これらの倉曎埌、最悪の堎合の時間は、゚ディタで、完成したアセンブリで6ミリ秒たでの23ミリ秒に枛少したした。これらは43 ppsず166 ppsです。

ナニティパッケヌゞ

最も賢い怜玢


前のパヌトでは、A *アルゎリズムを実装するこずにより、怜玢手順をよりスマヌトにしたした。ただし、実際にはただ最適な方法で怜玢を実行しおいたせん。各反埩で、珟圚のセルからそのすべおの隣接セルたでの距離を蚈算したす。これは、ただ怜玢枠に含たれおいないか、珟圚怜玢枠の䞀郚になっおいるセルに圓おはたりたす。しかし、すでに境界から削陀されたセルは、これらのセルぞの最短経路をすでに芋぀けおいるため、考慮する必芁はありたせん。A *の正しい実装はこれらのセルをスキップするので、同じこずができたす。

セル怜玢フェヌズ


セルがすでに境界線を離れおいるかどうかをどのようにしお知るのでしょうかこれを刀断するこずはできたせんが。そのため、セルが怜玢のどの段階にあるかを远跡する必芁がありたす。圌女はただ囜境にいなかったか、今囜境にいおいるか、海倖にいたす。これを远跡するには、HexCell単玔な敎数プロパティに远加したす。

  public int SearchPhase { get; set; } 

たずえば、0はセルがただ到達しおいないこず、1-セルは珟圚境界内にあるこず、2-既に境界から削陀されおいるこずを意味したす。

ボヌダヌを打぀


ここでHexGrid.Searchは、すべおのセルを0にリセットし、境界に垞に1を䜿甚できたす。たたは、新しい怜玢ごずに境界線の数を増やすこずができたす。このおかげで、境界線の数を毎回2ず぀増やしおも、セルのダンプを凊理する必芁はありたせん。

  int searchFrontierPhase; 
 void Search (HexCell fromCell, HexCell toCell, int speed) { searchFrontierPhase += 2; 
 } 

次に、境界にセルを远加するずきにセル怜玢のフェヌズを蚭定する必芁がありたす。プロセスは、境界に远加される初期セルから始たりたす。

  fromCell.SearchPhase = searchFrontierPhase; fromCell.Distance = 0; searchFrontier.Enqueue(fromCell); 

たた、囜境に隣人を远加するたびに。

  if (neighbor.Distance == int.MaxValue) { neighbor.SearchPhase = searchFrontierPhase; neighbor.Distance = distance; neighbor.PathFrom = current; neighbor.SearchHeuristic = neighbor.coordinates.DistanceTo(toCell.coordinates); searchFrontier.Enqueue(neighbor); } 

ボヌダヌチェック


これたで、セルがただ境界線に远加されおいないこずを確認するために、に等しい距離を䜿甚したしたint.MaxValue。これで、セル怜玢のフェヌズず珟圚の境界線を比范できたす。

 // if (neighbor.Distance == int.MaxValue) { if (neighbor.SearchPhase < searchFrontierPhase) { neighbor.SearchPhase = searchFrontierPhase; neighbor.Distance = distance; neighbor.PathFrom = current; neighbor.SearchHeuristic = neighbor.coordinates.DistanceTo(toCell.coordinates); searchFrontier.Enqueue(neighbor); } 

これは、怜玢する前にセルの距離をリセットする必芁がなくなったこずを意味したす。぀たり、䜜業が少なくおすみたす。これは良いこずです。

  for (int i = 0; i < cells.Length; i++) { // cells[i].Distance = int.MaxValue; cells[i].SetLabel(null); cells[i].DisableHighlight(); } 

囜境を離れる


境界からセルが削陀されるず、怜玢フェヌズの増加によっおこれを瀺したす。これにより、圌女は珟圚の境界を越えお次の境界の前に配眮されたす。

  while (searchFrontier.Count > 0) { HexCell current = searchFrontier.Dequeue(); current.SearchPhase += 1; 
 } 

これで、境界から削陀されたセルをスキップしお、無意味な蚈算ず距離の比范を回避できたす。

  for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { HexCell neighbor = current.GetNeighbor(d); if ( neighbor == null || neighbor.SearchPhase > searchFrontierPhase ) { continue; } 
 } 

この時点で、アルゎリズムは同じ結果を生成したすが、より効率的です。私のマシンでは、最悪の堎合の怜玢にぱディタヌで20ミリ秒、アセンブリで5ミリ秒かかりたす。

アルゎリズムによっおセルが凊理された回数を蚈算するこずもできたす。これにより、セルたでの距離を蚈算するずきにカりンタヌが増えたす。以前は、最悪の堎合のアルゎリズムは28,239の距離を蚈算しおいたした。既補のA *アルゎリズムでは、14,120の距離を蚈算したす。量は50枛少したした。これらの指暙が生産性に䞎える圱響の皋床は、移動コストを蚈算するコストに䟝存したす。私たちの堎合、ここでの䜜業はあたりないので、アセンブリの改善はそれほど倧きくありたせんが、゚ディタヌでは非垞に顕著です。

ナニティパッケヌゞ

道を開く


新しい怜玢を開始するずき、最初に以前のパスの芖芚化をクリアする必芁がありたす。この間、遞択をオフにしお、各グリッドセルからラベルを削陀したす。これは非垞に難しいアプロヌチです。理想的には、前のパスの䞀郚であったセルのみをリセットする必芁がありたす。

怜玢のみ


から芖芚化コヌドを完党に削陀するこずから始めたしょうSearch。圌はパス怜玢を実行するだけでよく、この情報で䜕をするかを知る必芁はありたせん。

  void Search (HexCell fromCell, HexCell toCell, int speed) { searchFrontierPhase += 2; if (searchFrontier == null) { searchFrontier = new HexCellPriorityQueue(); } else { searchFrontier.Clear(); } // for (int i = 0; i < cells.Length; i++) { // cells[i].SetLabel(null); // cells[i].DisableHighlight(); // } // fromCell.EnableHighlight(Color.blue); fromCell.SearchPhase = searchFrontierPhase; fromCell.Distance = 0; searchFrontier.Enqueue(fromCell); while (searchFrontier.Count > 0) { HexCell current = searchFrontier.Dequeue(); current.SearchPhase += 1; if (current == toCell) { // while (current != fromCell) { // int turn = current.Distance / speed; // current.SetLabel(turn.ToString()); // current.EnableHighlight(Color.white); // current = current.PathFrom; // } // toCell.EnableHighlight(Color.red); // break; } 
 } } 

Search方法が芋぀かったこずを報告するには、ブヌル倀を返したす。

  bool Search (HexCell fromCell, HexCell toCell, int speed) { searchFrontierPhase += 2; if (searchFrontier == null) { searchFrontier = new HexCellPriorityQueue(); } else { searchFrontier.Clear(); } fromCell.SearchPhase = searchFrontierPhase; fromCell.Distance = 0; searchFrontier.Enqueue(fromCell); while (searchFrontier.Count > 0) { HexCell current = searchFrontier.Dequeue(); current.SearchPhase += 1; if (current == toCell) { return true; } 
 } return false; } 

方法を芚えおいる


パスが芋぀かったら、それを芚えおおく必芁がありたす。これにより、将来的にはクリヌニングできるようになりたす。したがっお、゚ンドポむントず、それらの間にパスがあるかどうかを远跡したす。

  HexCell currentPathFrom, currentPathTo; bool currentPathExists; 
 public void FindPath (HexCell fromCell, HexCell toCell, int speed) { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); currentPathFrom = fromCell; currentPathTo = toCell; currentPathExists = Search(fromCell, toCell, speed); sw.Stop(); Debug.Log(sw.ElapsedMilliseconds); } 

パスをもう䞀床衚瀺する


蚘録した怜玢デヌタを䜿甚しお、パスを再床芖芚化できたす。このための新しいメ゜ッドを䜜成したしょうShowPath。パスの終わりから始たりたでのサむクルで行われ、セルを匷調衚瀺し、ラベルにストロヌク倀を割り圓おたす。これを行うには、速床を知る必芁があるため、速床をパラメヌタにしたす。パスがない堎合、メ゜ッドは単に゚ンドポむントを遞択したす。

  void ShowPath (int speed) { if (currentPathExists) { HexCell current = currentPathTo; while (current != currentPathFrom) { int turn = current.Distance / speed; current.SetLabel(turn.ToString()); current.EnableHighlight(Color.white); current = current.PathFrom; } } currentPathFrom.EnableHighlight(Color.blue); currentPathTo.EnableHighlight(Color.red); } 

FindPath怜玢埌にこのメ゜ッドを呌び出したす。

  currentPathExists = Search(fromCell, toCell, speed); ShowPath(speed); 

スむヌプ


私たちは再び道を芋るが、今ではそれは離れおいない。クリアするには、メ゜ッドを䜜成したすClearPath。実際、これはコピヌですShowPath。ただし、遞択ずラベルは無効になりたすが、それらは含たれたせん。これを行った埌、圌は無効になった蚘録されたパスデヌタを消去する必芁がありたす。

  void ClearPath () { if (currentPathExists) { HexCell current = currentPathTo; while (current != currentPathFrom) { current.SetLabel(null); current.DisableHighlight(); current = current.PathFrom; } current.DisableHighlight(); currentPathExists = false; } currentPathFrom = currentPathTo = null; } 

この方法を䜿甚するず、必芁なセルのみにアクセスするこずで叀いパスの芖芚化をクリアできたす。マップのサむズは重芁ではなくなりたした。FindPath新しい怜玢を開始する前に呌び出しおください。

  sw.Start(); ClearPath(); currentPathFrom = fromCell; currentPathTo = toCell; currentPathExists = Search(fromCell, toCell, speed); if (currentPathExists) { ShowPath(speed); } sw.Stop(); 

さらに、新しいマップを䜜成するずきにパスをクリアしたす。

  public bool CreateMap (int x, int z) { 
 ClearPath(); if (chunks != null) { for (int i = 0; i < chunks.Length; i++) { Destroy(chunks[i].gameObject); } } 
 } 

たた、別のカヌドをロヌドする前に。

  public void Load (BinaryReader reader, int header) { ClearPath(); 
 } 

この倉曎前ず同様に、パスの芖芚化は再びクリアされたす。しかし、今ではより効率的なアプロヌチを䜿甚しおおり、最悪の怜玢の堎合、時間は14ミリ秒に短瞮されおいたす。よりむンテリゞェントなクリヌニングのみによる十分な深刻な改善。アセンブリ時間は3ミリ秒に枛少したした。これは333 ppsです。これにより、パスの怜玢はリアルタむムで正確に適甚できたす。

パスをすばやく怜玢したので、䞀時的なデバッグコヌドを削陀できたす。

  public void FindPath (HexCell fromCell, HexCell toCell, int speed) { // System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); // sw.Start(); ClearPath(); currentPathFrom = fromCell; currentPathTo = toCell; currentPathExists = Search(fromCell, toCell, speed); ShowPath(speed); // sw.Stop(); // Debug.Log(sw.ElapsedMilliseconds); } 

ナニティパッケヌゞ

パヌト18ナニット



パスを怜玢する方法がわかったので、マップにチヌムを配眮したしょう。


増揎が到着したした

チヌムを䜜成する


ここたでは、セルずその固定オブゞェクトのみを扱っおきたした。ナニットはモバむルであるずいう点でそれらず異なりたす。分離ずは、1人の人間や車䞡から軍党䜓に至るたで、あらゆる芏暡のあらゆるものを意味したす。このチュヌトリアルでは、単玔な汎甚タむプのナニットに限定したす。その埌、いく぀かのタむプのナニットの組み合わせのサポヌトに進みたす。

プレハブ分隊


分隊で䜜業するには、新しいタむプのコンポヌネントを䜜成したすHexUnit。ずりあえず、空のものから始めお、MonoBehaviour埌で機胜を远加したしょう。

 using UnityEngine; public class HexUnit : MonoBehaviour { } 

このコンポヌネントで空のゲヌムオブゞェクトを䜜成したす。これはプレハブになりたす。これがチヌムのルヌトオブゞェクトになりたす。


プレハブ郚隊。

分離を象城する3Dモデルを子オブゞェクトずしお远加したす。青色のマテリアルを䜜成したシンプルなスケヌルキュヌブを䜿甚したした。ルヌトオブゞェクトがデタッチメントのグラりンドレベルを決定するため、それに応じお子芁玠を移動したす。



子芁玠キュヌブ

将来的に遞択しやすくするために、チヌムにコラむダヌを远加したす。暙準キュヌブのコラむダヌは私たちに非垞に適しおいたす。コラむダヌを1぀のセルに収めるだけです。

分隊むンスタンスの䜜成


ただゲヌムプレむがないため、ナニットの䜜成は線集モヌドで行われたす。したがっお、これに察凊する必芁がありたすHexMapEditor。これを行うには、プレハブが必芁なので、フィヌルドHexUnit unitPrefabを远加しお接続したす。

  public HexUnit unitPrefab; 


プレハブの接続

ナニットを䜜成するずき、それらをカヌ゜ルの䞋のセルに配眮したす。HandleInput地圢を線集するずきにこのセルを芋぀けるためのコヌドがありたす。今床はチヌムにも必芁なので、察応するコヌドを別のメ゜ッドに移動したす。

  HexCell GetCellUnderCursor () { Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(inputRay, out hit)) { return hexGrid.GetCell(hit.point); } return null; } 

これで、このメ゜ッドを䜿甚しおHandleInput単玔化できたす。

  void HandleInput () { // Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition); // RaycastHit hit; // if (Physics.Raycast(inputRay, out hit)) { // HexCell currentCell = hexGrid.GetCell(hit.point); HexCell currentCell = GetCellUnderCursor(); if (currentCell) { 
 } else { previousCell = null; } } 

次に、を䜿甚する新しいメ゜ッドCreateUnitを远加したすGetCellUnderCursor。セルがあれば、新しいチヌムを䜜成したす。

  void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { Instantiate(unitPrefab); } } 

階局をきれいに保぀ために、グリッドをチヌムのすべおのゲヌムオブゞェクトの芪ずしお䜿甚しおみたしょう。

  void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { HexUnit unit = Instantiate(unitPrefab); unit.transform.SetParent(hexGrid.transform, false); } } 

HexMapEditorナニット䜜成のサポヌトを远加する最も簡単な方法は、キヌを抌すこずです。Uキヌが抌されたずきUpdateに呌び出さCreateUnitれるようにメ゜ッドを倉曎したすc HandleInputず同様に、これはカヌ゜ルがGUI芁玠の䞊にない堎合に発生したす。最初に、マップを線集する必芁があるかどうかを確認し、線集しない堎合は、チヌムを远加する必芁があるかどうかを確認したす。その堎合は、を呌び出したすCreateUnit。

  void Update () { // if ( // Input.GetMouseButton(0) && // !EventSystem.current.IsPointerOverGameObject() // ) { // HandleInput(); // } // else { // previousCell = null; // } if (!EventSystem.current.IsPointerOverGameObject()) { if (Input.GetMouseButton(0)) { HandleInput(); return; } if (Input.GetKeyDown(KeyCode.U)) { CreateUnit(); return; } } previousCell = null; } 


分隊のむンスタンスを䜜成したした

郚隊配眮


これでナニットを䜜成できたすが、それらはマップの原点に衚瀺されたす。適切な堎所に配眮する必芁がありたす。このためには、軍隊が自分の立堎を認識する必芁がありたす。したがっお、それらが占めるセルを瀺すHexUnitプロパティに远加したすLocation。プロパティを蚭定するずき、セルの䜍眮に䞀臎するように分隊の䜍眮を倉曎したす。

  public HexCell Location { get { return location; } set { location = value; transform.localPosition = value.Position; } } HexCell location; 

次にHexMapEditor.CreateUnit、カヌ゜ルの䞋の分隊セルの䜍眮を割り圓おる必芁がありたす。その埌、ナニットは必芁な堎所に配眮されたす。

  void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { HexUnit unit = Instantiate(unitPrefab); unit.transform.SetParent(hexGrid.transform, false); unit.Location = cell; } } 


マップ䞊の分隊

ナニットの向き


これたでのずころ、すべおのナニットの向きは同じで、かなり䞍自然に芋えたす。それらを埩掻させるには、HexUnitプロパティに远加したすOrientation。これは、Y軞に沿った分隊の回転を床単䜍で瀺すフロヌト倀です。それを蚭定するずき、それに応じおゲヌムオブゞェクト自䜓の回転を倉曎したす。

  public float Orientation { get { return orientation; } set { orientation = value; transform.localRotation = Quaternion.Euler(0f, value, 0f); } } float orientation; 

HexMapEditor.CreateUnit0〜360床から割り圓おランダム回転。

  void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell) { HexUnit unit = Instantiate(unitPrefab); unit.transform.SetParent(hexGrid.transform, false); unit.Location = cell; unit.Orientation = Random.Range(0f, 360f); } } 


異なるナニットの向き

セルごずに1぀のチヌム


ナニットは、1぀のセルで䜜成されおいない堎合に衚瀺されたす。この堎合、奇劙に芋えるキュヌブのクラスタヌを取埗したす。


オヌバヌレむされたナニット

ゲヌムによっおは、耇数のナニットを1か所に配眮できるものずそうでないものがありたす。セルごずに1぀のチヌムで䜜業する方が簡単なので、このオプションを遞択したす。これは、珟圚のセルが占有されおいない堎合にのみ新しいチヌムを䜜成する必芁があるこずを意味したす。芋぀けられるように、HexCell暙準プロパティに远加したすUnit。

  public HexUnit Unit { get; set; } 

このプロパティを䜿甚しおHexUnit.Location、ナニットがその䞊にあるかどうかをセルに知らせたす。

  public HexCell Location { get { return location; } set { location = value; value.Unit = this; transform.localPosition = value.Position; } } 

珟圚HexMapEditor.CreateUnit、珟圚のセルが空いおいるかどうかを確認できたす。

  void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell && !cell.Unit) { HexUnit unit = Instantiate(unitPrefab); unit.Location = cell; unit.Orientation = Random.Range(0f, 360f); } } 

ビゞヌセルの線集


最初は、ナニットは正しく配眮されおいたすが、将来セルが線集される堎合、すべおが倉曎される可胜性がありたす。セルの高さが倉わるず、セルを占有しおいるナニットがセルの䞊にぶら䞋がるか、セルに突入したす。


ぶら䞋げおandれた分隊

解決策は、倉曎を行った埌、分隊の䜍眮を確認するこずです。これを行うには、メ゜ッドをに远加しHexUnitたす。これたでのずころ、デタッチメントの䜍眮にのみ関心があるため、単玔に再蚭定したす。

  public void ValidateLocation () { transform.localPosition = location.Position; } 

セルを曎新するずき、メ゜ッドRefreshたたはRefreshSelfOnlyオブゞェクトがHexCell呌び出されたずきに䜕が起こるか、デタッチメントの䜍眮を調敎する必芁がありたす。もちろん、これはセルに実際に分離がある堎合にのみ必芁です。

  void Refresh () { if (chunk) { chunk.Refresh(); 
 if (Unit) { Unit.ValidateLocation(); } } } void RefreshSelfOnly () { chunk.Refresh(); if (Unit) { Unit.ValidateLocation(); } } 

チヌムを削陀する


ナニットを䜜成するこずに加えお、ナニットを砎壊するこずは有甚です。したがっお、HexMapEditorメ゜ッドに远加しDestroyUnitたす。圌は、カヌ゜ルの䞋のセルにデタッチメントがあるかどうかを確認する必芁がありたす。ある堎合は、デタッチメントのゲヌムオブゞェクトを砎壊したす。

  void DestroyUnit () { HexCell cell = GetCellUnderCursor(); if (cell && cell.Unit) { Destroy(cell.Unit.gameObject); } } 

分隊に着くために、私たちはセルを通過するこずに泚意しおください。分隊ずやり取りするには、マりスをそのセルの䞊に移動するだけです。したがっお、これが機胜するためには、チヌムにはコラむダヌが必芁ありたせん。ただし、コラむダヌを远加するず、分隊の背埌のセルに衝突する光線をブロックするため、それらを簡単に分離できたす。巊Shift + Uの組み合わせを䜿甚

しお、分隊を砎壊したしょうUpdate。

  if (Input.GetKeyDown(KeyCode.U)) { if (Input.GetKey(KeyCode.LeftShift)) { DestroyUnit(); } else { CreateUnit(); } return; } 

耇数のナニットを䜜成および砎棄する堎合、ナニットを削陀する際に泚意しおプロパティをクリアしたしょう。぀たり、分隊ぞのセルリンクを明瀺的にクリアしたす。これに察凊するHexUnitメ゜ッドに加えお、Die独自のゲヌムオブゞェクトを砎壊したす。

  public void Die () { location.Unit = null; Destroy(gameObject); } 

このメ゜ッドはで呌び出しHexMapEditor.DestroyUnit、分隊を盎接砎壊したせん。

  void DestroyUnit () { HexCell cell = GetCellUnderCursor(); if (cell && cell.Unit) { // Destroy(cell.Unit.gameObject); cell.Unit.Die(); } } 

ナニティパッケヌゞ

チヌムの保存ず読み蟌み


マップ䞊にナニットを配眮できるようになったので、保存および読み蟌みプロセスにナニットを含める必芁がありたす。このタスクには2぀の方法でアプロヌチできたす。1぀は、セルず分隊デヌタが混合されるように、セルを蚘録するずきに分隊デヌタを蚘録するこずです。2番目の方法は、セルず分隊のデヌタを別々に保存するこずです。最初のアプロヌチの方が実装が簡単に思えるかもしれたせんが、2番目のアプロヌチはより構造化されたデヌタを提䟛したす。デヌタを共有すれば、将来それらずの䜜業が容易になりたす。

ナニット远跡


すべおのナニットをたずめるために、それらを远跡する必芁がありたす。これを行うにはHexGrid、ナニットのリストに远加したす。このリストには、マップ䞊のすべおのナニットが含たれおいる必芁がありたす。

  List<HexUnit> units = new List<HexUnit>(); 

新しいマップを䜜成たたはロヌドするずき、マップ䞊のすべおのナニットを取り陀く必芁がありたす。このプロセスを簡玠化するにClearUnitsは、リスト内の党員を殺しおクリアするメ゜ッドを䜜成したす。

  void ClearUnits () { for (int i = 0; i < units.Count; i++) { units[i].Die(); } units.Clear(); } 

このメ゜ッドをCreateMapずで呌び出したすLoad。道を掃陀しおからやろう。

  public bool CreateMap (int x, int z) { 
 ClearPath(); ClearUnits(); 
 } 
 public void Load (BinaryReader reader, int header) { ClearPath(); ClearUnits(); 
 } 

グリッドぞのチヌムの远加


ここで、新しいナニットを䜜成するずきに、それらをリストに远加する必芁がありたす。これのメ゜ッドを蚭定しおみたしょう。これAddUnitは、分隊の䜍眮ずその芪オブゞェクトのパラメヌタヌも凊理したす。

  public void AddUnit (HexUnit unit, HexCell location, float orientation) { units.Add(unit); unit.transform.SetParent(transform, false); unit.Location = location; unit.Orientation = orientation; } 

これで、デタッチメントの新しいむンスタンス、その堎所、ランダムな方向でHexMapEditor.CreatUnit呌び出すだけで十分AddUnitです。

  void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell && !cell.Unit) { // HexUnit unit = Instantiate(unitPrefab); // unit.transform.SetParent(hexGrid.transform, false); // unit.Location = cell; // unit.Orientation = Random.Range(0f, 360f); hexGrid.AddUnit( Instantiate(unitPrefab), cell, Random.Range(0f, 360f) ); } } 

グリッドからチヌムを削陀する


分隊ずcを削陀するメ゜ッドを远加したすHexGrid。リストからチヌムを削陀しお、死ぬように呜じおください。

  public void RemoveUnit (HexUnit unit) { units.Remove(unit); unit.Die(); } 

HexMapEditor.DestroyUnitチヌムを盎接砎壊する代わりに、このメ゜ッドを呌び出したす。

  void DestroyUnit () { HexCell cell = GetCellUnderCursor(); if (cell && cell.Unit) { // cell.Unit.Die(); hexGrid.RemoveUnit(cell.Unit); } } 

ナニットの保存


すべおのナニットをたずめおおくので、ナニットが占めるセルを芚えおおく必芁がありたす。最も信頌できる方法は、その堎所の座暙を保存するこずです。これを可胜にするために、フィヌルドXずZ をそれを曞き蟌むHexCoordinatesメ゜ッドに远加しSaveたす。

 using UnityEngine; using System.IO; [System.Serializable] public struct HexCoordinates { 
 public void Save (BinaryWriter writer) { writer.Write(x); writer.Write(z); } } 

この方法SaveのためのHexUnit猶は今ナニットの座暙ず向きを蚘録したす。これは、珟圚持っおいるナニットのすべおのデヌタです。

 using UnityEngine; using System.IO; public class HexUnit : MonoBehaviour { 
 public void Save (BinaryWriter writer) { location.coordinates.Save(writer); writer.Write(orientation); } } 

HexGridナニットを远跡するため、そのメ゜ッドSaveはナニットのデヌタを蚘録したす。最初に、ナニットの総数を曞き留めおから、ルヌプ内でそれらをすべお回っおください。

  public void Save (BinaryWriter writer) { writer.Write(cellCountX); writer.Write(cellCountZ); for (int i = 0; i < cells.Length; i++) { cells[i].Save(writer); } writer.Write(units.Count); for (int i = 0; i < units.Count; i++) { units[i].Save(writer); } } 

保存されおいるデヌタを倉曎したため、バヌゞョン番号SaveLoadMenu.Saveを2に増やしたす。叀いブヌトコヌドは、分隊デヌタを読み取れないため、匕き続き機胜したす。ただし、ファむルにナニットデヌタがあるこずを瀺すには、バヌゞョン番号を増やす必芁がありたす。

  void Save (string path) { using ( BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)) ) { writer.Write(2); hexGrid.Save(writer); } } 

郚隊の読み蟌み


これHexCoordinatesは構造䜓であるため、通垞のメ゜ッドを远加するこずはあたり意味がありたせんLoad。保存された座暙を読み蟌んで返す静的メ゜ッドにしたす。

  public static HexCoordinates Load (BinaryReader reader) { HexCoordinates c; cx = reader.ReadInt32(); cz = reader.ReadInt32(); return c; } 

ナニットの数は可倉であるため、デヌタをロヌドできる既存のナニットはありたせん。デヌタを読み蟌む前にナニットの新しいむンスタンスを䜜成できたすが、これにはHexGridブヌト時に新しいナニットのむンスタンスを䜜成する必芁がありたす。そのたたにしおおく方が良いHexUnitです。たた、静的メ゜ッドも䜿甚したすHexUnit.Load。これらのチヌムを単に読むこずから始めたしょう。方向フロヌトの倀を読み取るには、メ゜ッドを䜿甚しBinaryReader.ReadSingleたす。

なぜ独身
float , . , double , . Unity .

  public static void Load (BinaryReader reader) { HexCoordinates coordinates = HexCoordinates.Load(reader); float orientation = reader.ReadSingle(); } 

次のステップは、新しいチヌムのむンスタンスを䜜成するこずです。ただし、このためには、ナニットのプレハブぞのリンクが必芁です。ただ耇雑にならないように、これにHexUnit静的メ゜ッドを远加したしょう。

  public static HexUnit unitPrefab; 

このリンクを蚭定HexGridするには、ノむズテクスチャで行ったように、再床䜿甚したす。倚くの皮類のナニットをサポヌトする必芁がある堎合、より良い゜リュヌションに進みたす。

  public HexUnit unitPrefab; 
 void Awake () { HexMetrics.noiseSource = noiseSource; HexMetrics.InitializeHashGrid(seed); HexUnit.unitPrefab = unitPrefab; CreateMap(cellCountX, cellCountZ); } 
 void OnEnable () { if (!HexMetrics.noiseSource) { HexMetrics.noiseSource = noiseSource; HexMetrics.InitializeHashGrid(seed); HexUnit.unitPrefab = unitPrefab; } } 


ナニットのプレハブを枡したす。

フィヌルドを接続した埌、に盎接リンクする必芁はなくなりたしたHexMapEditor。代わりに、圌はを䜿甚できたすHexUnit.unitPrefab。

 // public HexUnit unitPrefab; 
 void CreateUnit () { HexCell cell = GetCellUnderCursor(); if (cell && !cell.Unit) { hexGrid.AddUnit( Instantiate(HexUnit.unitPrefab), cell, Random.Range(0f, 360f) ); } } 

これで、新しいチヌムのむンスタンスを䜜成できたすHexUnit.Load。返す代わりに、ロヌドされた座暙ず方向を䜿甚しおグリッドに远加できたす。これを可胜にするには、パラメヌタヌを远加したすHexGrid。

  public static void Load (BinaryReader reader, HexGrid grid) { HexCoordinates coordinates = HexCoordinates.Load(reader); float orientation = reader.ReadSingle(); grid.AddUnit( Instantiate(unitPrefab), grid.GetCell(coordinates), orientation ); } 

最埌に、HexGrid.Loadナニットの数をカりントし、それを䜿甚しお、栌玍されおいるすべおのナニットをロヌドし、远加の匕数ずしお自分自身を枡したす。

  public void Load (BinaryReader reader, int header) { 
 int unitCount = reader.ReadInt32(); for (int i = 0; i < unitCount; i++) { HexUnit.Load(reader, this); } } 

もちろん、これは、バヌゞョンが2以䞊の保存ファむルに察しおのみ機胜したす。若いバヌゞョンでは、ロヌドするナニットがありたせん。

  if (header >= 2) { int unitCount = reader.ReadInt32(); for (int i = 0; i < unitCount; i++) { HexUnit.Load(reader, this); } } 

バヌゞョン2のファむルを正しくアップロヌドできるようにSaveLoadMenu.Loadなったため、サポヌトされるバヌゞョンの数を2 に増やしたす。

  void Load (string path) { if (!File.Exists(path)) { Debug.LogError("File does not exist " + path); return; } using (BinaryReader reader = new BinaryReader(File.OpenRead(path))) { int header = reader.ReadInt32(); if (header <= 2) { hexGrid.Load(reader, header); HexMapCamera.ValidatePosition(); } else { Debug.LogWarning("Unknown map format " + header); } } } 

ナニティパッケヌゞ

軍隊の動き


分隊はモバむルであるため、マップ䞊で移動できる必芁がありたす。既にパス怜玢コヌドがありたすが、これたでのずころ、任意の堎所に぀いおのみテストしたした。次に、叀いテストUIを削陀し、チヌム管理甚の新しいUIを䜜成する必芁がありたす。

マップ゚ディタヌのクリヌンアップ


パスに沿っおナニットを移動するこずはゲヌムプレむの䞀郚であり、マップ゚ディタヌには適甚されたせん。したがっお、HexMapEditorパスの怜玢に関連するすべおのコヌドを取り陀きたす。

 // HexCell previousCell, searchFromCell, searchToCell; HexCell previousCell; 
 void HandleInput () { HexCell currentCell = GetCellUnderCursor(); if (currentCell) { if (previousCell && previousCell != currentCell) { ValidateDrag(currentCell); } else { isDrag = false; } if (editMode) { EditCells(currentCell); } // else if ( // Input.GetKey(KeyCode.LeftShift) && searchToCell != currentCell // ) { // if (searchFromCell != currentCell) { // if (searchFromCell) { // searchFromCell.DisableHighlight(); // } // searchFromCell = currentCell; // searchFromCell.EnableHighlight(Color.blue); // if (searchToCell) { // hexGrid.FindPath(searchFromCell, searchToCell, 24); // } // } // } // else if (searchFromCell && searchFromCell != currentCell) { // if (searchToCell != currentCell) { // searchToCell = currentCell; // hexGrid.FindPath(searchFromCell, searchToCell, 24); // } // } previousCell = currentCell; } else { previousCell = null; } } 

このコヌドを削陀した埌、線集モヌドではないずきに゚ディタヌをアクティブのたたにしおおくこずは意味がありたせん。したがっお、モヌド远跡フィヌルドの代わりに、コンポヌネントを有効たたは無効にするこずができたすHexMapEditor。さらに、゚ディタヌはUIラベルを凊理する必芁がなくなりたした。

 // bool editMode; 
 public void SetEditMode (bool toggle) { // editMode = toggle; // hexGrid.ShowUI(!toggle); enabled = toggle; } 
 void HandleInput () { HexCell currentCell = GetCellUnderCursor(); if (currentCell) { if (previousCell && previousCell != currentCell) { ValidateDrag(currentCell); } else { isDrag = false; } // if (editMode) { EditCells(currentCell); // } previousCell = currentCell; } else { previousCell = null; } } 

デフォルトではマップ線集モヌドではないため、アりェむクでぱディタヌを無効にしたす。

  void Awake () { terrainMaterial.DisableKeyword("GRID_ON"); SetEditMode(false); } 

レむキャストを䜿甚しお、マップを線集するずきにカヌ゜ルの䞋の珟圚のセルを怜玢し、ナニットを制埡する必芁がありたす。おそらく将来的には、他の䜕かのために私たちに圹立぀でしょう。レむキャスティングロゞックを、ビヌムパラメヌタヌを䜿甚HexGridした新しいメ゜ッドに移動したしょうGetCell。

  public HexCell GetCell (Ray ray) { RaycastHit hit; if (Physics.Raycast(ray, out hit)) { return GetCell(hit.point); } return null; } 

HexMapEditor.GetCellUniderCursor カヌ゜ルビヌムでこのメ゜ッドを呌び出すだけです。

  HexCell GetCellUnderCursor () { return hexGrid.GetCell(Camera.main.ScreenPointToRay(Input.mousePosition)); } 

ゲヌムUI


ゲヌムモヌドUIを制埡するには、新しいコンポヌネントを䜿甚したす。圌はナニットの遞択ず移動のみを扱いたす。新しいコンポヌネントタむプを䜜成したすHexGameUI。圌の仕事をするには、グリッドぞのリンクで十分です。

 using UnityEngine; using UnityEngine.EventSystems; public class HexGameUI : MonoBehaviour { public HexGrid grid; } 

このコンポヌネントをUI階局の新しいゲヌムオブゞェクトに远加したす。圌は自分のオブゞェクトを持っおいる必芁はありたせんが、ゲヌムには別のUIがあるこずは明らかです。



ゲヌムUIオブゞェクトのよう

にHexGameUIメ゜ッドを远加SetEditModeしHexMapEditorたす。線集モヌドでないずきは、ゲヌムUIをオンにする必芁がありたす。たた、ゲヌムUIはパスで機胜するため、ここにラベルを含める必芁がありたす。

  public void SetEditMode (bool toggle) { enabled = !toggle; grid.ShowUI(!toggle); } 

線集モヌドスむッチのむベントリストにゲヌムUIメ゜ッドを远加したす。これは、プレヌダヌがモヌドを倉曎するず、䞡方のメ゜ッドが呌び出されるこずを意味したす。


いく぀かのむベントメ゜ッド。

珟圚のセルを远跡


状況に応じお、HexGameUI珟圚カヌ゜ルの䞋にあるセルを知る必芁がありたす。したがっお、フィヌルドを远加したすcurrentCell。

  HexCell currentCell; 

カヌ゜ルビヌムをUpdateCurrentCell䜿甚しおHexGrid.GetCellこのフィヌルドを曎新するメ゜ッドを䜜成したす。

  void UpdateCurrentCell () { currentCell = grid.GetCell(Camera.main.ScreenPointToRay(Input.mousePosition)); } 

珟圚のセルを曎新するずき、倉曎されおいるかどうかを確認する必芁がある堎合がありたす。UpdateCurrentCellこの情報を匷制的に返したす。

  bool UpdateCurrentCell () { HexCell cell = grid.GetCell(Camera.main.ScreenPointToRay(Input.mousePosition)); if (cell != currentCell) { currentCell = cell; return true; } return false; } 

ナニット遞択


分隊を移動する前に、それを遞択しお远跡する必芁がありたす。したがっお、フィヌルドを远加したすselectedUnit。

  HexUnit selectedUnit; 

遞択を詊みるずき、珟圚のセルを曎新するこずから始める必芁がありたす。珟圚のセルがこのセルを占めるナニットである堎合、遞択されたナニットになりたす。セルにナニットがない堎合、ナニットは遞択されたせん。このためのメ゜ッドを䜜成したしょうDoSelection。

  void DoSelection () { UpdateCurrentCell(); if (currentCell) { selectedUnit = currentCell.Unit; } } 

ナニットの遞択は、マりスを1回クリックするだけで実珟したす。したがっお、Updateマりスボタンがアクティブになったずきに遞択を実行するメ゜ッドを远加したすが、もちろん、カヌ゜ルがGUI芁玠の䞊にないずきにのみ実行する必芁がありたす。

  void Update () { if (!EventSystem.current.IsPointerOverGameObject()) { if (Input.GetMouseButtonDown(0)) { DoSelection(); } } } 

この段階で、マりスをクリックしお䞀床に1぀のナニットを遞択するこずを孊びたした。空のセルをクリックするず、ナニットの遞択が削陀されたす。しかし、これに぀いおは芖芚的な確認は受けおいたせん。

分隊で道を探す


ナニットを遞択するず、その堎所をパスを芋぀けるための開始点ずしお䜿甚できたす。これをアクティブにするために、マりスボタンをもう䞀床クリックする必芁はありたせん。代わりに、分隊の䜍眮ず珟圚のセルの間のパスを自動的に芋぀けお衚瀺したす。Update遞択が行われた堎合を陀き、これは垞にで行いたす。これを行うには、デタッチメントがあるずきに、メ゜ッドを呌び出したすDoPathfinding。

  void Update () { if (!EventSystem.current.IsPointerOverGameObject()) { if (Input.GetMouseButtonDown(0)) { DoSelection(); } else if (selectedUnit) { DoPathfinding(); } } } 

DoPathfinding珟圚のセルを曎新しHexGrid.FindPath、゚ンドポむントがある堎合に呌び出したす。再び24の䞀定速床を䜿甚したす。

  void DoPathfinding () { UpdateCurrentCell(); grid.FindPath(selectedUnit.Location, currentCell, 24); } 

曎新ごずに新しいパスを芋぀ける必芁はありたせんが、珟圚のセルが倉曎されたずきにのみ芋぀ける必芁があるこずに泚意しおください。

  void DoPathfinding () { if (UpdateCurrentCell()) { grid.FindPath(selectedUnit.Location, currentCell, 24); } } 


デタッチメントのパスを芋぀けるこれで、デタッチメント

を遞択した埌にカヌ゜ルが移動したずきに衚瀺されるパスが衚瀺されたす。これにより、どのナニットが遞択されおいるかが明らかです。ただし、パスは垞に正しくクリアされるずは限りたせん。たず、カヌ゜ルがマップ倖にある堎合、叀いパスをクリアしたしょう。

  void DoPathfinding () { if (UpdateCurrentCell()) { if (currentCell) { grid.FindPath(selectedUnit.Location, currentCell, 24); } else { grid.ClearPath(); } } } 

もちろん、これにはHexGrid.ClearPath共通性が必芁なので、このような倉曎を行いたす。

  public void ClearPath () { 
 } 

次に、デタッチメントを遞択するずきに叀いパスをクリアしたす。

  void DoSelection () { grid.ClearPath(); UpdateCurrentCell(); if (currentCell) { selectedUnit = currentCell.Unit; } } 

最埌に、線集モヌドを倉曎するずきにパスをクリアしたす。

  public void SetEditMode (bool toggle) { enabled = !toggle; grid.ShowUI(!toggle); grid.ClearPath(); } 

有効な゚ンドポむントのみを怜玢


最終的なセルに到達できない堎合があるため、垞に道を芋぀けるこずはできたせん。 これは正垞です。しかし、最終的なセル自䜓が受け入れられない堎合がありたす。たずえば、パスには氎䞭セルを含めるこずはできないず刀断したした。ただし、ナニットに䟝存する堎合がありたす。HexUnitセルが有効な゚ンドポむントであるかどうかを通知するメ゜ッドに远加したしょう。氎䞭の现胞はそうではありたせん。

  public bool IsValidDestination (HexCell cell) { return !cell.IsUnderwater; } 

さらに、セル内に立぀こずができるナニットは1぀だけです。したがっお、最終セルはビゞヌの堎合は無効になりたす。

  public bool IsValidDestination (HexCell cell) { return !cell.IsUnderwater && !cell.Unit; } 

このメ゜ッドを䜿甚しお、HexGameUI.DoPathfinding無効な゚ンドポむントを無芖したす。

  void DoPathfinding () { if (UpdateCurrentCell()) { if (currentCell && selectedUnit.IsValidDestination(currentCell)) { grid.FindPath(selectedUnit.Location, currentCell, 24); } else { grid.ClearPath(); } } } 

終点に移動


有効なパスがあれば、分隊を終点に移動できたす。HexGridこれがい぀できるかを知っおいたす。この情報を新しい読み取り専甚プロパティに枡したすHasPath。

  public bool HasPath { get { return currentPathExists; } } 

分隊を移動するには、HexGameUIメ゜ッドに远加しDoMoveたす。このメ゜ッドは、コマンドが発行されたずき、およびナニットが遞択されたずきに呌び出されたす。したがっお、圌は方法があるかどうかを確認する必芁がありたす。ある堎合は、切り離しの堎所を倉曎したす。すぐにチヌムを終点にテレポヌトしたす。次のチュヌトリアルのいずれかで、チヌムを実際に最埌たで進めたす。

  void DoMove () { if (grid.HasPath) { selectedUnit.Location = currentCell; grid.ClearPath(); } } 

マりスボタン1右クリックを䜿甚しおコマンドを送信したしょう。デタッチメントが遞択されおいる堎合、これをチェックしたす。ボタンが抌されおいない堎合、パスを怜玢したす。

  void Update () { if (!EventSystem.current.IsPointerOverGameObject()) { if (Input.GetMouseButtonDown(0)) { DoSelection(); } else if (selectedUnit) { if (Input.GetMouseButtonDown(1)) { DoMove(); } else { DoPathfinding(); } } } } 

これでナニットを移動できたすしかし、時には圌らはいく぀かの现胞ぞの道を芋぀けるこずを拒吊したす。特に、剥離がか぀おあった现胞。これはHexUnit、新しい堎所を蚭定するずきに叀い堎所を曎新しないために発生したす。これを修正するために、叀い堎所のチヌムぞのリンクをクリアしたす。

  public HexCell Location { get { return location; } set { if (location) { location.Unit = null; } location = value; value.Unit = this; transform.localPosition = value.Position; } } 

分隊を避ける


道を芋぀けるこずが正しく機胜するようになり、ナニットはマップ䞊でテレポヌトできたす。すでに分隊を持っおいるセルに移動するこずはできたせんが、邪魔になっおいる分遣隊は無芖されたす。


途䞭で軍無芖

1぀の掟閥の分隊は、通垞、互いに間を移動するこずができたすが、これたでのずころ、我々は掟閥を持っおいたせん。したがっお、すべおのナニットが互いに切断され、パスをブロックしおいるず考えおみたしょう。これは、のビゞヌセルをスキップするこずで実珟できたすHexGrid.Search。

  if ( neighbor == null || neighbor.SearchPhase > searchFrontierPhase ) { continue; } if (neighbor.IsUnderwater || neighbor.Unit) { continue; } 


避け分遣隊の

unitypackage

パヌト19モヌションアニメヌション



このパヌトでは、テレポヌテヌションの代わりにナニットをトラックに沿っお移動させたす。


途䞭の分隊

道に沿った動き


前のパヌトでは、ナニットずそれらを移動する機胜を远加したした。有効な゚ンドポむントを決定するためにパスの怜玢を䜿甚したしたが、コマンドを䞎えた埌、軍隊は最終的なセルにテレポヌトしたした。圌らが実際に芋぀かった経路をたどるように、この経路を远跡し、分隊をセルからセルに匷制的に移動させるアニメヌションプロセスを䜜成する必芁がありたす。アニメヌションを芋るず、分隊がどのように動いたかに気付くのが難しいため、ギズモを䜿甚しお移動したパスも芖芚化したす。しかし、先に進む前に、゚ラヌを修正する必芁がありたす。

タヌン゚ラヌ


監芖のため、セルに到達するコヌスを誀っお蚈算したす。ここで、合蚈距離を分隊速床で割るこずにより、コヌスを決定したすで、残りを砎棄したす。この゚ラヌは、セルに移動するずきに、移動ごずに残りのすべおの移動ポむントを正確に費やす必芁があるずきに発生したす。たずえば、各ステップのコストが1で、速床が3の堎合、1タヌンあたり3぀のセルを移動できたす。ただし、既存の蚈算では、最初の移動では2぀のステップしか実行できたせん。t=d/s

t=d/s=3/3=1。


誀っお定矩された移動、速床3で移動する総コスト。移動を正しく

蚈算するには、境界を最初のセルから1ステップ移動する必芁がありたす。これを行うには、移動を蚈算する前に距離を1枛らすこずにより、3番目のステップの移動は次のようになりたす。t=2/3=0


正しい動き

蚈算匏をt=(d−1)/s 。この倉曎をに行いHexGrid.Searchたす。

  bool Search (HexCell fromCell, HexCell toCell, int speed) { 
 while (searchFrontier.Count > 0) { 
 int currentTurn = (current.Distance - 1) / speed; for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { 
 int distance = current.Distance + moveCost; int turn = (distance - 1) / speed; if (turn > currentTurn) { distance = turn * speed + moveCost; } 
 } } return false; } 

たた、動きのマヌクを倉曎したす。

  void ShowPath (int speed) { if (currentPathExists) { HexCell current = currentPathTo; while (current != currentPathFrom) { int turn = (current.Distance - 1) / speed; 
 } } 
 } 

このアプロヌチでは、初期セルパスが-1であるこずに泚意しおください。これは衚瀺されないため正垞であり、怜玢アルゎリズムは匕き続き動䜜したす。

方法を取埗


パスに沿っお移動するこずはチヌムのタスクです。圌がこれを行うためには、圌は方法を知る必芁がありたす。この情報HexGridがあるので、セルのリストの圢匏で珟圚のパスを取埗するメ゜ッドを远加したしょう。圌はリストのプヌルからそれを取埗し、本圓にパスがあれば戻りたす。

  public List<HexCell> GetPath () { if (!currentPathExists) { return null; } List<HexCell> path = ListPool<HexCell>.Get(); return path; } 

リストは、パスを芖芚化するずきに行われるように、最埌のセルから最初のセルぞのリンクパスをたどるこずで埋められたす。

  List<HexCell> path = ListPool<HexCell>.Get(); for (HexCell c = currentPathTo; c != currentPathFrom; c = c.PathFrom) { path.Add(c); } return path; 

この堎合、初期セルを含むパス党䜓が必芁です。

  for (HexCell c = currentPathTo; c != currentPathFrom; c = c.PathFrom) { path.Add(c); } path.Add(currentPathFrom); return path; 

これで、パスは逆の順序になりたした。圌ず仕事をするこずはできたすが、それはあたり盎感的ではありたせん。リストを反転させお、最初から最埌たで行きたしょう。

  path.Add(currentPathFrom); path.Reverse(); return path; 

モヌションリク゚スト


これでHexUnitメ゜ッドに远加しお、パスに埓うように圌に呜什するこずができたす。最初は、圌に最埌のセルにテレポヌトさせるだけです。リストはしばらく圹立぀ので、すぐにリストをプヌルに返したせん。

 using UnityEngine; using System.Collections.Generic; using System.IO; public class HexUnit : MonoBehaviour { 
 public void Travel (List<HexCell> path) { Location = path[path.Count - 1]; } 
 } 

移動を芁求するにHexGameUI.DoMoveは、ナニットの䜍眮を蚭定するだけでなく、珟圚のパスで新しいメ゜ッドを呌び出すように倉曎したす。

  void DoMove () { if (grid.HasPath) { // selectedUnit.Location = currentCell; selectedUnit.Travel(grid.GetPath()); grid.ClearPath(); } } 

パスの可芖化


分隊のアニメヌションを開始する前に、パスが正しいこずを確認したしょう。HexUnitギズモを䜿甚しお芖芚化できるように、移動する必芁があるパスを蚘憶するように呜什するこずでこれを行いたす。

  List<HexCell> pathToTravel; 
 public void Travel (List<HexCell> path) { Location = path[path.Count - 1]; pathToTravel = path; } 

OnDrawGizmos最埌のパスを衚瀺するメ゜ッドを远加したす存圚する堎合。ナニットがただ移動しおいない堎合、パスは等しくなりnullたす。ただし、プレむモヌドでの再コンパむル埌の線集䞭のUnityのシリアル化により、空のリストになるこずもありたす。

  void OnDrawGizmos () { if (pathToTravel == null || pathToTravel.Count == 0) { return; } } 

パスを衚瀺する最も簡単な方法は、パスの各セルにギズモ球を描くこずです。半埄が2単䜍の球䜓が適しおいたす。

  void OnDrawGizmos () { if (pathToTravel == null || pathToTravel.Count == 0) { return; } for (int i = 0; i < pathToTravel.Count; i++) { Gizmos.DrawSphere(pathToTravel[i].Position, 2f); } } 

デタッチメントのパスを衚瀺するので、最埌のパスをすべお同時に芋るこずができたす。


ギズモは、最埌に移動したパスを衚瀺したす

セルの接続をよりよく衚瀺するために、前のセルず珟圚のセルの間の線にルヌプでいく぀かの球䜓を描画したす。これを行うには、2番目のセルからプロセスを開始する必芁がありたす。球は、0.1単䜍の増分で線圢補間を䜿甚しお配眮できるため、セグメントごずに10個の球が埗られたす。

  for (int i = 1; i < pathToTravel.Count; i++) { Vector3 a = pathToTravel[i - 1].Position; Vector3 b = pathToTravel[i].Position; for (float t = 0f; t < 1f; t += 0.1f) { Gizmos.DrawSphere(Vector3.Lerp(a, b, t), 2f); } } 


より明癜な方法

道に沿っお滑る


同じ方法を䜿甚しおナニットを移動できたす。このためのコルヌチンを䜜成したしょう。ギズモを描画する代わりに、分隊の䜍眮を蚭定したす。増分する代わりに、0.1時間のデルタを䜿甚し、各反埩でyieldを実行したす。この堎合、分隊は1秒で1぀のセルから次のセルに移動したす。

 using UnityEngine; using System.Collections; using System.Collections.Generic; using System.IO; public class HexUnit : MonoBehaviour { 
 IEnumerator TravelPath () { for (int i = 1; i < pathToTravel.Count; i++) { Vector3 a = pathToTravel[i - 1].Position; Vector3 b = pathToTravel[i].Position; for (float t = 0f; t < 1f; t += Time.deltaTime) { transform.localPosition = Vector3.Lerp(a, b, t); yield return null; } } } 
 } 

メ゜ッドの最埌にコルヌチンを開始しTravelたす。ただし、最初に、既存のコルヌチンをすべお停止したす。そのため、2぀のコルヌチンが同時に開始しないこずを保蚌したす。そうしないず、非垞に奇劙な結果になりたす。

  public void Travel (List<HexCell> path) { Location = path[path.Count - 1]; pathToTravel = path; StopAllCoroutines(); StartCoroutine(TravelPath()); } 

1秒間に1぀のセルを移動するのはかなり遅いです。ゲヌム䞭のプレむダヌはそんなに長く埅ちたくないでしょう。分隊の移動速床を構成オプションにするこずもできたすが、ここでは定数を䜿甚したす。私は圌女に毎秒4セルの倀を割り圓おたした。それは非垞に高速ですが、䜕が起こっおいるかを確認できたす。

  const float travelSpeed = 4f; 
 IEnumerator TravelPath () { for (int i = 1; i < pathToTravel.Count; i++) { Vector3 a = pathToTravel[i - 1].Position; Vector3 b = pathToTravel[i].Position; for (float t = 0f; t < 1f; t += Time.deltaTime * travelSpeed) { transform.localPosition = Vector3.Lerp(a, b, t); yield return null; } } } 

耇数の経路を同時に芖芚化できるように、耇数のナニットを同時に移動させるこずができたす。ゲヌムの状態の芳点から芋るず、動きは䟝然ずしおテレポヌテヌションであり、アニメヌションは芖芚的にのみです。ナニットは即座に最終セルを占有したす。方法を芋぀けお、到着する前に新しい動きを開始するこずもできたす。この堎合、それらは芖芚的に新しいパスの始たりにテレポヌトされたす。これは、移動䞭にナニットたたはUI党䜓をブロックするこずで回避できたすが、このような迅速な反応は、移動を蚭蚈およびテストするずきに非垞に䟿利です。


移動ナニット。

高さの違いはどうですか
, . , . , . , . , Endless Legend, , . , .

コンパむル埌の䜍眮


コルチンの欠点の1぀は、プレむモヌドで再コンパむルしおも「生き残らない」こずです。ゲヌムの状態は垞に真ですが、これは、圌らがただ動いおいる間に再コンパむルが開始されるず、分隊が最埌のパスのどこかで立ち埀生するこずに぀ながる可胜性がありたす。結果を緩和するために、再コンパむル埌、ナニットが垞に正しい䜍眮にあるこずを確認したしょう。これは、の䜍眮を曎新するこずで実行できたすOnEnable。

  void OnEnable () { if (location) { transform.localPosition = location.Position; } } 

ナニティパッケヌゞ

スムヌズな動き


セルの䞭心から䞭心ぞの動きは機械的すぎるように芋え、方向が急激に倉化したす。倚くのゲヌムでは、これは正垞ですが、少なくずも少し珟実的な動きが必芁な堎合は受け入れられたせん。それでは、ムヌブメントを倉曎しお、少し有機的に芋えるようにしたす。

端から端ぞ移動したす


チヌムはセルの䞭心から旅を始めたす。セルの端の䞭倮に移動し、その埌次のセルに入りたす。圌は䞭倮に向かっお移動する代わりに、圌が亀差しなければならない次の゚ッゞに向かっおたっすぐ進むこずができたす。実際、ナニットは方向を倉える必芁があるずきにパスをカットしたす。これは、パスの終点を陀くすべおのセルで可胜です。


端から端ぞ移動する3぀の方法 この方法で生成されたパスの衚瀺に

適応OnDrawGizmosしたしょう。セルの゚ッゞ間を補間する必芁がありたす。これは、隣接するセルの䜍眮を平均化するこずで芋぀けるこずができたす。反埩ごずに1぀の゚ッゞを蚈算し、前の反埩からの倀を再利甚するだけで十分です。したがっお、メ゜ッドを初期セルに察しお機胜させるこずができたすが、゚ッゞの代わりにその䜍眮を取りたす。

  void OnDrawGizmos () { if (pathToTravel == null || pathToTravel.Count == 0) { return; } Vector3 a, b = pathToTravel[0].Position; for (int i = 1; i < pathToTravel.Count; i++) { // Vector3 a = pathToTravel[i - 1].Position; // Vector3 b = pathToTravel[i].Position; a = b; b = (pathToTravel[i - 1].Position + pathToTravel[i].Position) * 0.5f; for (float t = 0f; t < 1f; t += 0.1f) { Gizmos.DrawSphere(Vector3.Lerp(a, b, t), 2f); } } } 

終了セルの䞭心に到達するには、セルの䜍眮を゚ッゞではなく最埌のポむントずしお䜿甚する必芁がありたす。このケヌスのチェックをルヌプに远加できたすが、単玔なコヌドなので、コヌドを耇補しお少し倉曎するだけの方が明らかになりたす。

  void OnDrawGizmos () { 
 for (int i = 1; i < pathToTravel.Count; i++) { 
 } a = b; b = pathToTravel[pathToTravel.Count - 1].Position; for (float t = 0f; t < 1f; t += 0.1f) { Gizmos.DrawSphere(Vector3.Lerp(a, b, t), 2f); } } 


リブベヌスの

パス結果ずしお生じるパスはゞグザグのように芋えず、最倧回転角床は120°から90°に枛少したす。これは改善ず芋なすこずができるため、同じ倉曎をコルヌチンTravelPathに適甚しお、アニメヌションでどのように芋えるかを確認したす。

  IEnumerator TravelPath () { Vector3 a, b = pathToTravel[0].Position; for (int i = 1; i < pathToTravel.Count; i++) { // Vector3 a = pathToTravel[i - 1].Position; // Vector3 b = pathToTravel[i].Position; a = b; b = (pathToTravel[i - 1].Position + pathToTravel[i].Position) * 0.5f; for (float t = 0f; t < 1f; t += Time.deltaTime * travelSpeed) { transform.localPosition = Vector3.Lerp(a, b, t); yield return null; } } a = b; b = pathToTravel[pathToTravel.Count - 1].Position; for (float t = 0f; t < 1f; t += Time.deltaTime * travelSpeed) { transform.localPosition = Vector3.Lerp(a, b, t); yield return null; } } 


倉化する速床での移動

角床をカットした埌、パスセグメントの長さは方向の倉化に䟝存するようになりたした。ただし、1秒あたりのセル数で速床を蚭定したす。その結果、分隊の速床はランダムに倉化したす。

次の曲線


セルの境界を越えるずきの方向ず速床の瞬間的な倉化は芋苊しい。方向の段階的な倉曎を䜿甚するこずをお勧めしたす。これをサポヌトするには、郚隊を匷制的に盎線ではなく曲線に沿っお远埓させたす。これにはベゞェ曲線を䜿甚できたす。特に、セルの䞭心が䞭倮の制埡点になる2次ベゞ゚曲線を䜿甚できたす。この堎合、隣接する曲線の接線は互いに鏡像になりたす。぀たり、パス党䜓が連続した滑らかな曲線になりたす。


゚ッゞから゚ッゞぞの 曲線二次ベゞェ曲線䞊の点を取埗する方法で

補助クラスBezierを䜜成したす。曲線ずスプラむンのチュヌトリアルで説明されおいるように、匏はこのために䜿甚されたす、ここで(1−t)2A+2(1−t)tB+t2CA 、 Bそしお は制埡点で、tは補間噚です。C

 using UnityEngine; public static class Bezier { public static Vector3 GetPoint (Vector3 a, Vector3 b, Vector3 c, float t) { float r = 1f - t; return r * r * a + 2f * r * t * b + t * t * c; } } 

GetPointは0-1に制限されるべきではありたせんか
0-1, . . , GetPointClamped , t . , GetPointUnclamped .

で曲線のパスを衚瀺するにはOnDrawGizmos、2぀ではなく3぀のポむントを远跡する必芁がありたす。远加のポむントは、珟圚の反埩で䜜業しおいるセルの䞭心ですi - 1。サむクルは1から始たるため、むンデックスがありたす。すべおのポむントを受け取ったらVector3.Lerp、に眮き換えるこずができBezier.GetPointたす。

開始点ず終了点では、終了点ず䞭間点の代わりに、単玔にセルの䞭心を䜿甚できたす。

  void OnDrawGizmos () { if (pathToTravel == null || pathToTravel.Count == 0) { return; } Vector3 a, b, c = pathToTravel[0].Position; for (int i = 1; i < pathToTravel.Count; i++) { a = c; b = pathToTravel[i - 1].Position; c = (b + pathToTravel[i].Position) * 0.5f; for (float t = 0f; t < 1f; t += Time.deltaTime * travelSpeed) { Gizmos.DrawSphere(Bezier.GetPoint(a, b, c, t), 2f); } } a = c; b = pathToTravel[pathToTravel.Count - 1].Position; c = b; for (float t = 0f; t < 1f; t += 0.1f) { Gizmos.DrawSphere(Bezier.GetPoint(a, b, c, t), 2f); } } 


ベゞェで䜜成したパスの

曲線パスが非垞に良く芋えたす。同じ倉曎を適甚し、TravelPathこのアプロヌチでナニットがどのようにアニメヌション化されるかを確認したす。

  IEnumerator TravelPath () { Vector3 a, b, c = pathToTravel[0].Position; for (int i = 1; i < pathToTravel.Count; i++) { a = c; b = pathToTravel[i - 1].Position; c = (b + pathToTravel[i].Position) * 0.5f; for (float t = 0f; t < 1f; t += Time.deltaTime * travelSpeed) { transform.localPosition = Bezier.GetPoint(a, b, c, t); yield return null; } } a = c; b = pathToTravel[pathToTravel.Count - 1].Position; c = b; for (float t = 0f; t < 1f; t += Time.deltaTime * travelSpeed) { transform.localPosition = Bezier.GetPoint(a, b, c, t); yield return null; } } 


カヌブに沿っお移動する

ず、デタッチの速床が䞍安定な堎合でも、アニメヌションもスムヌズになりたした。隣接するセグメントの曲線の接線が䞀臎するため、速床は連続的です。速床の倉化は埐々に起こり、剥離がセルを通過するずきに起こり、方向が倉わるず枛速したす。圌が盎進した堎合、速床は䞀定のたたです。さらに、分隊はれロ速床で旅を開始および終了したす。これは自然な動きを暡倣しおいるので、そのたたにしおおきたす。

時間の远跡


この時点たで、各セグメントの0から反埩を開始し、1に達するたで継続したした。これは䞀定の倀で増加する堎合は正垞に機胜したすが、反埩はデルタ時間に䟝存したす。 1぀のセグメントでの反埩が完了するず、時間差に応じお、1をある皋床超える可胜性がありたす。これは、高フレヌムレヌトでは芋えたせんが、䜎フレヌムレヌトではぎくしゃくするこずがありたす。

時間のロスを避けるため、残りの時間をあるセグメントから次のセグメントに転送する必芁がありたす。これはt、各セグメントだけでなく、パス党䜓に沿っお远跡するこずで実行できたす。次に、各セグメントの終わりに、1を枛算したす。

  IEnumerator TravelPath () { Vector3 a, b, c = pathToTravel[0].Position; float t = 0f; for (int i = 1; i < pathToTravel.Count; i++) { a = c; b = pathToTravel[i - 1].Position; c = (b + pathToTravel[i].Position) * 0.5f; for (; t < 1f; t += Time.deltaTime * travelSpeed) { transform.localPosition = Bezier.GetPoint(a, b, c, t); yield return null; } t -= 1f; } a = c; b = pathToTravel[pathToTravel.Count - 1].Position; c = b; for (; t < 1f; t += Time.deltaTime * traveSpeed) { transform.localPosition = Bezier.GetPoint(a, b, c, t); yield return null; } } 

すでにこれを行っおいる堎合は、パスの開始時に時間の差分が考慮されるようにしたす。぀たり、私たちはすぐに動き始め、1぀のフレヌムの間アむドル状態になりたせん。

  float t = Time.deltaTime * travelSpeed; 

さらに、パスが終了する時点で正確に終了するのではなく、少し前に終了したす。ここで、違いはフレヌムレヌトにも䟝存したす。したがっお、分隊に正確に終点でパスを完成させたしょう。

  IEnumerator TravelPath () { 
 transform.localPosition = location.Position; } 

ナニティパッケヌゞ

オリ゚ンテヌションアニメヌション


ナニットは滑らかな曲線に沿っお動き始めたしたが、動きの方向に応じお方向を倉えたせんでした。その結果、圌らは滑るように芋えたす。動きを実際の動きのように芋せるために、それらを回転させる必芁がありたす。

楜しみにしお


曲線ずスプラむンのチュヌトリアルのように、曲線の導関数を䜿甚しおナニットの方向を決定できたす。二次ベゞェ曲線の導関数の匏2((1−t)(B−A)+t(C−B)) 。Bezierメ゜ッドに远加しお蚈算したす。

  public static Vector3 GetDerivative ( Vector3 a, Vector3 b, Vector3 c, float t ) { return 2f * ((1f - t) * (b - a) + t * (c - b)); } 

埮分ベクトルは、動きの方向ず1本の盎線䞊にありたす。このメ゜ッドQuaternion.LookRotationを䜿甚しお、分隊タヌンに倉換できたす。のすべおのステップで実行しHexUnit.TravelPathたす。

  transform.localPosition = Bezier.GetPoint(a, b, c, t); Vector3 d = Bezier.GetDerivative(a, b, c, t); transform.localRotation = Quaternion.LookRotation(d); yield return null; 
 transform.localPosition = Bezier.GetPoint(a, b, c, t); Vector3 d = Bezier.GetDerivative(a, b, c, t); transform.localRotation = Quaternion.LookRotation(d); yield return null; 

パスの始めに間違いはありたせんか
, . Aそしお B, . , t=0, , Quaternion.LookRotation . , , t=0. . , t>0.
, t<1。

デタッチメントの䜍眮ずは察照的に、パスの終点での方向の非理想性は重芁ではありたせん。ただし、その向きが最終回転に察応するこずを確認する必芁がありたす。これを行うには、完了埌、その向きをYの回転ず同等にしたす。

  transform.localPosition = location.Position; orientation = transform.localRotation.eulerAngles.y; 

珟圚、ナニットは氎平方向ず垂盎方向の䞡方で移動方向を正確に芋おいたす。これは、圌らが前埌に傟き、斜面から降りお登るこずを意味したす。それらが垞に真っ盎ぐになるように、方向ベクトルの成分Yを匷制的にれロにしおから、ナニットの回転を決定したす。

  Vector3 d = Bezier.GetDerivative(a, b, c, t); dy = 0f; transform.localRotation = Quaternion.LookRotation(d); 
 Vector3 d = Bezier.GetDerivative(a, b, c, t); dy = 0f; transform.localRotation = Quaternion.LookRotation(d); 


移動しながら楜しみ

ポむントを芋る


パス党䜓を通しお、ナニットは前方を芋たすが、移動を開始する前に、ナニットは他の方向を芋るこずができたす。この堎合、圌らは即座に向きを倉えたす。移動を開始する前にパスの方向に曲がる方が良いでしょう。

他の状況では正しい方向を芋るのが䟿利な堎合があるのでLookAt、特定のポむントを芋るためにチヌムの向きを匷制的に倉曎するメ゜ッドを䜜成したしょう。メ゜ッドを䜿甚しお、必芁な回転を蚭定するにはTransform.LookAt、たず、デタッチメントず同じ垂盎䜍眮にポむントを䜜成したす。その埌、チヌムの方向を抜出できたす。

  void LookAt (Vector3 point) { point.y = transform.localPosition.y; transform.LookAt(point); orientation = transform.localRotation.eulerAngles.y; } 

デタッチメントを実際に回転させるために、メ゜ッドを別のコルチンに倉え、䞀定の速床で回転させたす。回転速床も調敎できたすが、ここでも定数を䜿甚したす。回転は毎秒玄180°の速さでなければなりたせん。

  const float rotationSpeed = 180f; 
 IEnumerator LookAt (Vector3 point) { 
 } 

感知できないので、タヌンの加速をいじる必芁はありたせん。 2぀の方向の間を単玔に補間するだけで十分です。残念ながら、角床は円圢であるため、これは2぀の数倀の堎合ほど簡単ではありたせん。たずえば、350°から10°ぞの移行では、時蚈回りに20°回転したすが、単玔な補間では、反時蚈回りに340°回転したす。

正しい回転を䜜成する最も簡単な方法は、球面補間を䜿甚しお2぀のクォヌタニオン間を補間するこずです。これは最短のタヌンに぀ながりたす。これを行うには、最初ず最埌のクォヌタニオンを取埗し、を䜿甚しおそれらの間のトランゞションを䜜成しQuaternion.Slerpたす。

  IEnumerator LookAt (Vector3 point) { point.y = transform.localPosition.y; Quaternion fromRotation = transform.localRotation; Quaternion toRotation = Quaternion.LookRotation(point - transform.localPosition); for (float t = Time.deltaTime; t < 1f; t += Time.deltaTime) { transform.localRotation = Quaternion.Slerp(fromRotation, toRotation, t); yield return null; } transform.LookAt(point); orientation = transform.localRotation.eulerAngles.y; } 

これは機胜したすが、回転角床に関係なく、補間は垞に0から1になりたす。角速床を均䞀にするには、回転角が倧きくなるに぀れお補間を遅くする必芁がありたす。

  Quaternion fromRotation = transform.localRotation; Quaternion toRotation = Quaternion.LookRotation(point - transform.localPosition); float angle = Quaternion.Angle(fromRotation, toRotation); float speed = rotationSpeed / angle; for ( float t = Time.deltaTime * speed; t < 1f; t += Time.deltaTime * speed ) { transform.localRotation = Quaternion.Slerp(fromRotation, toRotation, t); yield return null; } 

角床がわかっおいるので、タヌンがれロになった堎合、タヌンを完党にスキップできたす。

  float angle = Quaternion.Angle(fromRotation, toRotation); if (angle > 0f) { float speed = rotationSpeed / angle; for ( 
 ) { 
 } } 

これで、ナニットの回転をに远加できたす。2番目のセルの䜍眮でTravelPath歩留たりLookAtを移動する前に実行するだけです。Unityは自動的にkorutinu実行されLookAt、そしおTravelPathその完了を埅ちたす。

  IEnumerator TravelPath () { Vector3 a, b, c = pathToTravel[0].Position; yield return LookAt(pathToTravel[1].Position); float t = Time.deltaTime * travelSpeed; 
 } 

コヌドを確認するず、分隊は最終セルにテレポヌトし、そこでタヌンし、次にパスの先頭にテレポヌトし、そこから移動を開始したす。これはLocation、coroutineの開始前にプロパティに倀を割り圓おるために発生したすTravelPath。テレポヌテヌションを取り陀くTravelPathために、最初にデタッチメントの䜍眮を初期セルに戻すこずができたす。

  Vector3 a, b, c = pathToTravel[0].Position; transform.localPosition = c; yield return LookAt(pathToTravel[1].Position); 


移動する前に回す

スむヌプ


必芁な動きを受け取ったら、メ゜ッドを取り陀くこずができたすOnDrawGizmos。将来パスを確認する必芁がある堎合に備えお、削陀するかコメントアりトしおください。

 // void OnDrawGizmos () { // 
 // } 

移動した方向を芚える必芁がなくなったため、最終的TravelPathにセルのリストを解攟できたす。

  IEnumerator TravelPath () { 
 ListPool<HexCell>.Add(pathToTravel); pathToTravel = null; } 

本物のチヌムのアニメヌションはどうですか
, . 3D- . . , . Mecanim, TravelPath .

ナニティパッケヌゞ

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


All Articles