質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

3回答

3396閲覧

Unity A*(A-Star)アルゴリズム 挙動がおかしくなる

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2020/08/10 07:43

編集2020/08/10 07:56

前提・実現したいこと

経路探索を正常に動作させたい

該当のソースコード

C#

1using System.Collections; 2using System.Collections.Generic; 3using System.Linq; 4using System.Runtime.ConstrainedExecution; 5using UnityEngine; 6 7public class Astar : MonoBehaviour 8{ 9 public int map_Width = 10; 10 public int map_Height = 10; 11 public GameObject[] walls; 12 public GameObject currenCell; 13 public class Node 14 { 15 public Vector2 pos; 16 //スタートからゴールまでの最短距離。gCost + hCost。 17 public float fCost; 18 //スタートからnノードまでの最短距離 19 public float gCost; 20 //nノードからゴールまでの最短距離 21 public float hCost; 22 public bool walkable; 23 public Node parent; 24 } 25 /// <summary> 26 /// スタートからnノードまでの最短距離 27 /// </summary> 28 public float GetgCost(Node startNode,Node nNode) 29 { 30 float distance = Vector3.Distance(startNode.pos, nNode.pos); 31 return distance; 32 } 33 /// <summary> 34 /// nノードからゴールまでの最短距離 35 /// </summary> 36 public float GethCost(Node nNode,Node goalNode) 37 { 38 float distance = Vector3.Distance(nNode.pos, goalNode.pos); 39 return distance; 40 } 41 public void SetCost(List<Node> nodeList,Node startNode,Node goalNode) 42 { 43 foreach(Node item in nodeList) 44 { 45 item.gCost = GetgCost(startNode, item); 46 item.hCost = GethCost(item, goalNode); 47 item.fCost = item.gCost + item.hCost; 48 } 49 } 50 public Node GetMinNode(List<Node> nodeList) 51 { 52 List<float> floatList = new List<float>(); 53 foreach(Node item in nodeList) 54 { 55 floatList.Add(item.fCost); 56 } 57 float min = floatList.Min(); 58 List<Node> minNodeList = new List<Node>(); 59 foreach(Node item in nodeList) 60 { 61 if(item.fCost == min) 62 { 63 minNodeList.Add(item); 64 } 65 } 66 if(minNodeList.Count == 0) 67 { 68 return minNodeList[0]; 69 } 70 else 71 { 72 List<float> hCostList = new List<float>(); 73 foreach(Node item in minNodeList) 74 { 75 hCostList.Add(item.hCost); 76 } 77 float minhCost = hCostList.Min(); 78 foreach(Node item in minNodeList) 79 { 80 if(item.hCost == minhCost) 81 { 82 return item; 83 } 84 } 85 } 86 return null; 87 } 88 public Node startNode; 89 public Node goalNode; 90 public List<Node> wallNodeList = new List<Node>(); 91 public List<Node> allNodeList = new List<Node>(); 92 public void SetNode() 93 { 94 //すべてのノードをメモリに格納 95 for (int x = 0; x < map_Width; x++) 96 { 97 for (int y = 0; y < map_Height; y++) 98 { 99 Node node = new Node(); 100 node.pos = new Vector2(x, y); 101 node.walkable = true; 102 allNodeList.Add(node); 103 } 104 } 105 } 106 public Node GetNode(int x,int y) 107 { 108 foreach(Node item in allNodeList) 109 { 110 if(item.pos.x == x && item.pos.y == y) 111 { 112 return item; 113 } 114 } 115 return null; 116 } 117 public void SetWall() 118 { 119 List<Vector2> list = new List<Vector2>(); 120 for(int i = 0; i < walls.Length; i++) 121 { 122 list.Add(walls[i].transform.position); 123 } 124 foreach(Vector2 item in list) 125 { 126 Node node = GetNode((int)item.x, (int)item.y); 127 if(node == null) 128 { 129 Debug.Log("OK"); 130 } 131 node.walkable = false; 132 wallNodeList.Add(node); 133 } 134 } 135 136 //OpenリストとCloseリストを設定 137 List<Node> openList = new List<Node>(); 138 List<Node> closeList = new List<Node>(); 139 140 List<Node> parentList = new List<Node>(); 141 142 public void Pathfinding() 143 { 144 SetNode(); 145 SetWall(); 146 //スタート位置とゴール位置を設定 147 startNode = GetNode(0,0); 148 goalNode = GetNode(2,5); 149 openList.Add(startNode); 150 while(openList.Count != 0) 151 { 152 //OpenListが空になったら解なし 153 if(openList == null) 154 { 155 break; 156 } 157 //コストを設定 158 SetCost(openList, startNode, goalNode); 159 //OpenListからfCostが最小のノードnを取得 160 Node n = GetMinNode(openList); 161 openList.Remove(n); 162 closeList.Add(n); 163 if (n == goalNode) 164 { 165 break; 166 } 167 //ノードnの移動可能な方向を調べる 168 foreach(Node neighbour in GetNeighbours(n)) 169 { 170 //隣接するNodeのコストを計算する 171 SetCost(GetNeighbours(n), startNode, goalNode); 172 //検索したNodeをすべてOpenListに追加する 173 openList.Add(neighbour); 174 //すべての隣接するセルの親Nodeを設定する 175 neighbour.parent = n; 176 if (!parentList.Contains(neighbour.parent)) 177 { 178 parentList.Add(neighbour.parent); 179 } 180 } 181 } 182 } 183 public List<Node> GetNeighbours(Node node) 184 { 185 List<Node> neighbours = new List<Node>(); 186 for(int i = (int)node.pos.x - 1;i <= (int)node.pos.x + 1; i++) 187 { 188 for (int j = (int)node.pos.y - 1; j <= (int)node.pos.y + 1; j++) 189 { 190 Node nei = GetNode(i, j); 191 if(nei != null && nei.walkable == true && !openList.Contains(nei) && !closeList.Contains(nei)) 192 { 193 neighbours.Add(nei); 194 } 195 } 196 } 197 return neighbours; 198 } 199 private void Start() 200 { 201 Pathfinding(); 202 parentList.Add(goalNode); 203 StartCoroutine(Move()); 204 } 205 206 IEnumerator Move() 207 { 208 foreach(Node item in parentList) 209 { 210 currenCell.transform.position = item.pos; 211 yield return new WaitForSeconds(0.5f); 212 } 213 } 214} 215

試したこと

Unityで、色々なサイトを参考にしながら、A*アルゴリズムを実装しようとしたのですが、なぜか複雑な形状になると、挙動がおかしくなってしまいます。
どこにおかしくなる原因があるのか、分からなかったため、質問しました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

高速化について

GetNodeで一つ一つ座標を比較してノードを探したり、先の回答のコメントで申し上げたようにGetMinNodeで複雑そうな処理を行っていたりするのを何とかしようと思い、下記のようにしてみました。

C#

1using System; 2using System.Collections.Generic; 3using System.Collections.ObjectModel; 4using UnityEngine; 5 6public class ChessboardPathFinder 7{ 8 private static readonly Vector2Int[] Directions = 9 { 10 new Vector2Int(-1, -1), // 南西 11 new Vector2Int(-1, 0), // 西 12 new Vector2Int(-1, 1), // 北西 13 new Vector2Int(0, 1), // 北 14 new Vector2Int(1, 1), // 北東 15 new Vector2Int(1, 0), // 東 16 new Vector2Int(1, -1), // 南東 17 new Vector2Int(0, -1) // 南 18 }; 19 20 private readonly Node[] world; 21 private readonly List<Vector2Int> path = new List<Vector2Int>(); 22 23 public ChessboardPathFinder(int width, int height) 24 { 25 this.Path = this.path.AsReadOnly(); 26 this.Width = Mathf.Max(width, 1); 27 this.Height = Mathf.Max(height, 1); 28 this.world = new Node[this.Width * this.Height]; 29 } 30 31 public int Width { get; } 32 public int Height { get; } 33 public ReadOnlyCollection<Vector2Int> Path { get; } 34 35 public void SetWallPositions(IEnumerable<Vector2Int> positions) 36 { 37 this.RemoveAllWalls(); 38 if (positions == null) 39 { 40 return; 41 } 42 43 foreach (var position in positions) 44 { 45 if (this.IsInbound(position)) 46 { 47 this.SetWall(position); 48 } 49 } 50 } 51 52 public bool FindPath(Vector2Int from, Vector2Int to) 53 { 54 // スタート位置・ゴール位置をワールド内にクランプする 55 var minPosition = Vector2Int.zero; 56 var maxPosition = new Vector2Int(this.Width - 1, this.Height - 1); 57 var startPosition = Vector2Int.Min(Vector2Int.Max(from, minPosition), maxPosition); 58 var goalPosition = Vector2Int.Min(Vector2Int.Max(to, minPosition), maxPosition); 59 60 // 経路探索を実行する 61 var pathFound = this.FindPathInternal(startPosition, goalPosition); 62 63 // 経路が見つからなければpathには手を付けずfalseを返し... 64 if (!pathFound) 65 { 66 return false; 67 } 68 69 // 見つかればpathを更新してtrueを返す 70 this.path.Clear(); 71 var position = goalPosition; 72 var directionIndex = this.GetParent(position); 73 this.path.Add(position); 74 while (directionIndex >= 0) 75 { 76 position += Directions[directionIndex]; 77 directionIndex = this.GetParent(position); 78 this.path.Add(position); 79 } 80 81 this.path.Reverse(); 82 return true; 83 } 84 85 private bool FindPathInternal(Vector2Int startPosition, Vector2Int goalPosition) 86 { 87 // 経路が見つかったらこのフラグを立てる 88 var pathFound = false; 89 90 // ワールド全域を最大コストで埋める 91 this.CleanWorld(); 92 93 // スタート地点のコストを設定し、オープンリストを作ってスタート地点を追加する 94 this.SetGScore(startPosition, 0); 95 this.SetState(startPosition, NodeState.Open); 96 var openSet = new SortedSet<NodeAnchor> {new NodeAnchor(Distance(startPosition, goalPosition), startPosition)}; 97 98 // オープンリストが空になるまでループ 99 while (openSet.Count > 0) 100 { 101 // オープンリストの中でFスコアが最小のノードを取得 102 var current = openSet.Min; 103 104 // そこがゴールなら経路発見フラグを立ててループを終了 105 if (current.Position == goalPosition) 106 { 107 pathFound = true; 108 break; 109 } 110 111 // オープンリストからcurrentを除去、クローズ状態に変える 112 openSet.Remove(current); 113 this.SetState(current.Position, NodeState.Closed); 114 115 // 隣接ノードの評価はGスコアをもとに行うことにする 116 // 隣接ノードへの移動コストは常に1と決め打ちし、そのため隣接ノード走査中に 117 // いちいち計算する必要はなくなるので、ループ前に計算してしまう 118 var tentativeGScore = this.GetGScore(current.Position) + 1; 119 120 // 隣接8方向を調べる 121 for (var i = 0; i < 8; i++) 122 { 123 var neighborPosition = current.Position - Directions[i]; 124 125 // ワールド範囲外だったり、壁だった場合はスキップ 126 if (!this.IsInbound(neighborPosition) || this.GetWall(neighborPosition)) 127 { 128 continue; 129 } 130 131 // クローズされていればスキップ 132 // https://en.wikipedia.org/wiki/A*_search_algorithm によると、ヒューリスティック関数が無矛盾である場合は 133 // いったんクローズされたノードまでの経路は最適であることが保証され、より小さいコストの経路が以降の探索で 134 // 見つかることはないらしい...ということはスキップしても問題ないということか? 135 var neighborState = this.GetState(neighborPosition); 136 if (neighborState == NodeState.Closed) 137 { 138 continue; 139 } 140 141 // もし計算したGスコアがそれまでのGスコアより小さいなら... 142 var previousGScore = this.GetGScore(neighborPosition); 143 if (tentativeGScore < previousGScore) 144 { 145 // スコアを記録、親を設定する 146 var neighborHScore = Distance(neighborPosition, goalPosition); 147 this.SetGScore(neighborPosition, tentativeGScore); 148 this.SetParent(neighborPosition, i); 149 150 // もしこの隣接ノードがオープン状態なら、古いデータをオープンリストから削除する 151 if (neighborState == NodeState.Open) 152 { 153 openSet.Remove(new NodeAnchor(previousGScore + neighborHScore, neighborPosition)); 154 } 155 156 // この隣接ノードをオープン状態にして、オープンリストに追加する 157 this.SetState(neighborPosition, NodeState.Open); 158 openSet.Add(new NodeAnchor(tentativeGScore + neighborHScore, neighborPosition)); 159 } 160 } 161 } 162 163 return pathFound; 164 } 165 166 private bool IsInbound(in Vector2Int position) 167 { 168 return (position.x >= 0) && (position.x < this.Width) && (position.y >= 0) && (position.y < this.Height); 169 } 170 171 private void SetGScore(in Vector2Int position, in int gScore) 172 { 173 this.world[(position.y * this.Width) + position.x].GScore = gScore; 174 } 175 176 private void SetParent(in Vector2Int position, in int parentDirectionIndex) 177 { 178 this.world[(position.y * this.Width) + position.x].ParentDirectionIndex = parentDirectionIndex; 179 } 180 181 private void SetState(in Vector2Int position, in NodeState state) 182 { 183 this.world[(position.y * this.Width) + position.x].State = state; 184 } 185 186 private void SetWall(in Vector2Int position, in bool wall = true) 187 { 188 this.world[(position.y * this.Width) + position.x].IsWall = wall; 189 } 190 191 private int GetGScore(in Vector2Int position) 192 { 193 return this.world[(position.y * this.Width) + position.x].GScore; 194 } 195 196 private int GetParent(in Vector2Int position) 197 { 198 return this.world[(position.y * this.Width) + position.x].ParentDirectionIndex; 199 } 200 201 private NodeState GetState(in Vector2Int position) 202 { 203 return this.world[(position.y * this.Width) + position.x].State; 204 } 205 206 private bool GetWall(in Vector2Int position) 207 { 208 return this.world[(position.y * this.Width) + position.x].IsWall; 209 } 210 211 // 2点間の距離としてチェビシェフ距離を使うことにする 212 private static int Distance(Vector2Int a, Vector2Int b) 213 { 214 return Mathf.Max(Mathf.Abs(a.x - b.x), Mathf.Abs(a.y - b.y)); 215 } 216 217 private void CleanWorld() 218 { 219 for (var i = 0; i < this.world.Length; i++) 220 { 221 this.world[i].GScore = int.MaxValue; 222 this.world[i].ParentDirectionIndex = -1; 223 this.world[i].State = NodeState.None; 224 } 225 } 226 227 private void RemoveAllWalls() 228 { 229 for (var i = 0; i < this.world.Length; i++) 230 { 231 this.world[i].IsWall = false; 232 } 233 } 234 235 private struct Node 236 { 237 public int GScore; 238 public int ParentDirectionIndex; 239 public NodeState State; 240 public bool IsWall; 241 } 242 243 private enum NodeState 244 { 245 None, 246 Open, 247 Closed 248 } 249 250 private readonly struct NodeAnchor : IComparable<NodeAnchor>, IEquatable<NodeAnchor> 251 { 252 public readonly int FScore; 253 public readonly Vector2Int Position; 254 255 public NodeAnchor(int fScore, Vector2Int position) 256 { 257 this.FScore = fScore; 258 this.Position = position; 259 } 260 261 public int CompareTo(NodeAnchor other) 262 { 263 var fResult = this.FScore.CompareTo(other.FScore); 264 if (fResult != 0) 265 { 266 return fResult; 267 } 268 269 var yResult = this.Position.y.CompareTo(other.Position.y); 270 if (yResult != 0) 271 { 272 return yResult; 273 } 274 275 return this.Position.x.CompareTo(other.Position.x); 276 } 277 278 public bool Equals(NodeAnchor other) 279 { 280 return (this.FScore == other.FScore) && this.Position.Equals(other.Position); 281 } 282 283 public override bool Equals(object obj) 284 { 285 return obj is NodeAnchor other && this.Equals(other); 286 } 287 288 public override int GetHashCode() 289 { 290 unchecked 291 { 292 return (this.FScore * 397) ^ this.Position.GetHashCode(); 293 } 294 } 295 } 296}

任意のグラフ構造に対してA*経路探索を行えるような汎用的なものではなく、チェス盤のような縦横のマス目の上を8方向に移動させるということに特化した作りのためChessboardPathFinderというネーミングにしました。思考の整理のため経路探索機能だけを抜き出した形にしましたので、オブジェクトにアタッチするためのスクリプトは別途用意することになります。
ご質問者さんのAsterに沿った使用例としては、たとえば下記のようになるでしょう。

C#

1using System.Collections; 2using System.Linq; 3using UnityEngine; 4 5public class Astar : MonoBehaviour 6{ 7 public int map_Width = 10; 8 public int map_Height = 10; 9 public GameObject[] walls; 10 public GameObject currenCell; 11 12 ChessboardPathFinder pathFinder; 13 14 void Start() 15 { 16 // スタート位置とゴール位置 17 Vector2Int startPosition = new Vector2Int(0, 0); 18 Vector2Int goalPosition = new Vector2Int(2, 5); 19 20 // まずマップのサイズを決めてパスファインダーを生成する 21 // マップのサイズが変わらない限り、経路探索のたびにパスファインダーを再生成する必要はない 22 pathFinder = new ChessboardPathFinder(map_Width, map_Height); 23 24 // 壁を設置して... 25 pathFinder.SetWallPositions(walls.Select( 26 g => 27 { 28 Vector3 p = g.transform.position; 29 return new Vector2Int(Mathf.RoundToInt(p.x), Mathf.RoundToInt(p.y)); 30 })); 31 32 // どの点からどの点までの経路を探すかを引数で与えてFindPathを実行し、経路が見つかったかどうかを受け取る 33 bool pathFound = pathFinder.FindPath(startPosition, goalPosition); 34 35 // 経路が見つかったなら移動を行う 36 if (pathFound) 37 { 38 StartCoroutine(Move()); 39 } 40 else 41 { 42 Debug.LogError("Path not found."); 43 } 44 } 45 46 IEnumerator Move() 47 { 48 // 経路が見つかった場合、パスファインダーのPathが更新されているので 49 // そこから経路を取得できる 50 foreach (Vector2Int position in pathFinder.Path) 51 { 52 currenCell.transform.position = new Vector3(position.x, position.y, 0.0f); 53 yield return new WaitForSeconds(0.5f); 54 } 55 } 56}

ChessboardPathFinderの中で核となる経路探索作業を担当する部分はFindPathInternalと名前を付けてFindPathメソッドから分離していますが、これはさらなる改造を加えることになった場合に備えての布石です。
おそらく現状でそれなりにスピードアップしているとは思うのですが、もしまだ遅くて支障が出るようでしたら「FindPathInternalを別スレッドで実行してメインスレッドを止めないようにする」といった手が使えるんじゃないかと思うのですが...

投稿2020/08/13 17:14

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/08/14 05:13

回答していただき、ありがとうございました。
guest

0

ベストアンサー

ご提示のコードをWikipediaの記事と見比べてみたのですが、ご質問者さんの隣接ノードのコスト計算やオープンリストへの追加ルールに疑問を感じ、PathfindingGetNeighboursを下記のように改変してみました。これならどうでしょうか?

C#

1using System.Collections; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5 6public class Astar : MonoBehaviour 7{ 8 public int map_Width = 10; 9 public int map_Height = 10; 10 public GameObject[] walls; 11 public GameObject currenCell; 12 public class Node 13 { 14 public Vector2 pos; 15 //スタートからゴールまでの最短距離。gCost + hCost。 16 public float fCost; 17 //スタートからnノードまでの最短距離 18 public float gCost; 19 //nノードからゴールまでの最短距離 20 public float hCost; 21 public bool walkable; 22 public Node parent; 23 } 24 /// <summary> 25 /// スタートからnノードまでの最短距離 26 /// </summary> 27 public float GetgCost(Node startNode, Node nNode) 28 { 29 float distance = Vector3.Distance(startNode.pos, nNode.pos); 30 return distance; 31 } 32 /// <summary> 33 /// nノードからゴールまでの最短距離 34 /// </summary> 35 public float GethCost(Node nNode, Node goalNode) 36 { 37 float distance = Vector3.Distance(nNode.pos, goalNode.pos); 38 return distance; 39 } 40 public void SetCost(List<Node> nodeList, Node startNode, Node goalNode) 41 { 42 foreach (Node item in nodeList) 43 { 44 item.gCost = GetgCost(startNode, item); 45 item.hCost = GethCost(item, goalNode); 46 item.fCost = item.gCost + item.hCost; 47 } 48 } 49 public Node GetMinNode(List<Node> nodeList) 50 { 51 List<float> floatList = new List<float>(); 52 foreach (Node item in nodeList) 53 { 54 floatList.Add(item.fCost); 55 } 56 float min = floatList.Min(); 57 List<Node> minNodeList = new List<Node>(); 58 foreach (Node item in nodeList) 59 { 60 if (item.fCost == min) 61 { 62 minNodeList.Add(item); 63 } 64 } 65 if (minNodeList.Count == 0) 66 { 67 return minNodeList[0]; 68 } 69 else 70 { 71 List<float> hCostList = new List<float>(); 72 foreach (Node item in minNodeList) 73 { 74 hCostList.Add(item.hCost); 75 } 76 float minhCost = hCostList.Min(); 77 foreach (Node item in minNodeList) 78 { 79 if (item.hCost == minhCost) 80 { 81 return item; 82 } 83 } 84 } 85 return null; 86 } 87 public Node startNode; 88 public Node goalNode; 89 public List<Node> wallNodeList = new List<Node>(); 90 public List<Node> allNodeList = new List<Node>(); 91 public void SetNode() 92 { 93 //すべてのノードをメモリに格納 94 for (int x = 0; x < map_Width; x++) 95 { 96 for (int y = 0; y < map_Height; y++) 97 { 98 Node node = new Node(); 99 node.pos = new Vector2(x, y); 100 node.walkable = true; 101 allNodeList.Add(node); 102 } 103 } 104 } 105 public Node GetNode(int x, int y) 106 { 107 foreach (Node item in allNodeList) 108 { 109 if (item.pos.x == x && item.pos.y == y) 110 { 111 return item; 112 } 113 } 114 return null; 115 } 116 public void SetWall() 117 { 118 List<Vector2> list = new List<Vector2>(); 119 for (int i = 0; i < walls.Length; i++) 120 { 121 list.Add(walls[i].transform.position); 122 } 123 foreach (Vector2 item in list) 124 { 125 Node node = GetNode((int)item.x, (int)item.y); 126 if (node == null) 127 { 128 Debug.Log("OK"); 129 } 130 node.walkable = false; 131 wallNodeList.Add(node); 132 } 133 } 134 135 //OpenリストとCloseリストを設定 136 List<Node> openList = new List<Node>(); 137 List<Node> closeList = new List<Node>(); 138 139 List<Node> parentList = new List<Node>(); 140 141 public void Pathfinding() 142 { 143 SetNode(); 144 SetWall(); 145 //スタート位置とゴール位置を設定 146 startNode = GetNode(0, 0); 147 goalNode = GetNode(2, 5); 148 149 // スタートノードのコストを設定 150 startNode.gCost = 0; 151 startNode.hCost = GethCost(startNode, goalNode); 152 startNode.fCost = startNode.gCost + startNode.hCost; 153 startNode.parent = null; 154 155 // ゴールが見つかったらこのフラグを立てることにする 156 bool goalFound = false; 157 158 openList.Clear(); 159 closeList.Clear(); 160 openList.Add(startNode); 161 while (openList.Count != 0) 162 { 163 // openListがnullかどうか判定する意義はなさそうだったので削除 164 /* 165 //OpenListが空になったら解なし 166 if (openList == null) 167 { 168 break; 169 } 170 */ 171 //コストを設定 172 //SetCost(openList, startNode, goalNode); // ここのSetCostは意図不明だったため削除 173 //OpenListからfCostが最小のノードnを取得 174 Node n = GetMinNode(openList); 175 openList.Remove(n); 176 closeList.Add(n); 177 if (n == goalNode) 178 { 179 // ゴールが見つかったので、フラグを立ててループを終える 180 goalFound = true; 181 break; 182 } 183 //ノードnの移動可能な方向を調べる 184 foreach (Node neighbour in GetNeighbours(n)) 185 { 186 //隣接するNodeのコストを計算する 187 //SetCost(GetNeighbours(n), startNode, goalNode); // ここのSetCostは意図不明だったため削除 188 189 // neighbourのHコスト、Fコストを求め... 190 float neighbourH = GethCost(neighbour, goalNode); 191 float neighbourF = n.gCost + Vector2.Distance(n.pos, neighbour.pos) + neighbourH; 192 193 bool openListContainsNeighbour = openList.Contains(neighbour); 194 bool closeListContainsNeighbour = closeList.Contains(neighbour); 195 if ((!openListContainsNeighbour && !closeListContainsNeighbour) || (neighbourF < neighbour.fCost)) 196 { 197 // neighbourがオープン・クローズリストのいずれにも存在しない新しいノードの場合、または 198 // オープンまたはクローズリストにあり、かつ以前の探索で得られたものよりも小さいFコストが得られた場合、 199 // こちらの経路の方が優れていると考えられるため経路を更新する(つまりneighbourの親をnとする) 200 neighbour.fCost = neighbourF; 201 neighbour.hCost = neighbourH; 202 neighbour.gCost = neighbour.fCost - neighbour.hCost; 203 neighbour.parent = n; 204 205 // クローズリストにあったノードなら、リストから削除することでクローズを取り消して... 206 if (closeListContainsNeighbour) 207 { 208 closeList.Remove(neighbour); 209 } 210 211 // オープンリストに未追加のノードなら、リストに追加して処理対象とする 212 if (!openListContainsNeighbour) 213 { 214 openList.Add(neighbour); 215 } 216 } 217 218 // 以降の部分は削除 219 /* 220 //検索したNodeをすべてOpenListに追加する 221 openList.Add(neighbour); 222 //すべての隣接するセルの親Nodeを設定する 223 neighbour.parent = n; 224 if (!parentList.Contains(neighbour.parent)) 225 { 226 parentList.Add(neighbour.parent); 227 } 228 */ 229 } 230 } 231 232 parentList.Clear(); 233 if (goalFound) 234 { 235 // ゴールが見つかったなら、ゴールから親を辿ってparentListを作る 236 Node n = goalNode; 237 while (n != null) 238 { 239 parentList.Add(n); 240 n = n.parent; 241 } 242 243 parentList.Reverse(); 244 } 245 } 246 public List<Node> GetNeighbours(Node node) 247 { 248 List<Node> neighbours = new List<Node>(); 249 int nx = (int)node.pos.x; 250 int ny = (int)node.pos.y; 251 for (int i = nx - 1; i <= nx + 1; i++) 252 { 253 for (int j = ny - 1; j <= ny + 1; j++) 254 { 255 if (i == nx && j == ny) 256 { 257 // 自分自身は近傍ノードから除外する 258 continue; 259 } 260 Node nei = GetNode(i, j); 261 // オープン・クローズリストに入っているかどうかはここでは判定しない 262 if (nei != null && nei.walkable == true)// && !openList.Contains(nei) && !closeList.Contains(nei)) 263 { 264 neighbours.Add(nei); 265 } 266 } 267 } 268 return neighbours; 269 } 270 private void Start() 271 { 272 Pathfinding(); 273 parentList.Add(goalNode); 274 StartCoroutine(Move()); 275 } 276 277 IEnumerator Move() 278 { 279 foreach (Node item in parentList) 280 { 281 currenCell.transform.position = item.pos; 282 yield return new WaitForSeconds(0.5f); 283 } 284 } 285}

結局SetCostメソッドを使っていた部分は全部削除されてしまったため、SetCostメソッド自体を削除してしまってもいいでしょう。

投稿2020/08/11 22:38

編集2020/08/12 08:17
Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/08/12 05:24

とても分かりやすいです。いつも回答していただき、ありがとうございます。
Bongo

2020/08/12 05:56

お役に立てたようで幸いです。うまく動きましたら、それをさらに効率的になるよう改修していくのも面白いかと思います。 たとえば現状のGetMinNodeではコストが最小のノードを探すのにだいぶ手間のかかる方法をとっているようですが、回答中で言及しましたWikipediaの記事に出てくる「優先度付きキュー」(https://ja.wikipedia.org/wiki/%E5%84%AA%E5%85%88%E5%BA%A6%E4%BB%98%E3%81%8D%E3%82%AD%E3%83%A5%E3%83%BC )を使ったり、その次の節で紹介されている「ノードにオープン/クローズ状態を表す情報を持たせる」といった手を使えばスピードアップが期待できそうです。 その他気になった点として、現状ではノード間の最短距離としてユークリッド距離(https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%BC%E3%82%AF%E3%83%AA%E3%83%83%E3%83%89%E8%B7%9D%E9%9B%A2 )を使っている様子ですが、もしキャラクターの移動がチェスのキングのように8方向に制限されるのならチェビシェフ距離(https://ja.wikipedia.org/wiki/%E3%83%81%E3%82%A7%E3%83%93%E3%82%B7%E3%82%A7%E3%83%95%E8%B7%9D%E9%9B%A2 )を使った方が自然かもしれません。とはいえ、ユークリッド距離でもWikipediaの記事で言及されている「真のゴール距離 h*(n) を上回らない」という条件は満たしていると思いますので、得られる結果は適切な経路になってくれそうです。
Bongo

2020/08/12 08:18

すみません!GetNeighboursの「自分自身は近傍ノードから除外する」とコメントを入れた部分ですが、あの条件では自分自身を除外できませんね。コードを修正版に差し替えます。うっかりしていました... それと距離としてユークリッド距離を使うのならば、neighbourFを求める際に増加させるコストは1固定とするよりも、nとneighbourのユークリッド距離を使う方が妥当だろうと思い直しまして、併せて変更しました。 もし他にも妙な動きをしていたり、「ここのコードはおかしいんじゃないか」といったところを見つけましたらコメントいただければ修正してみます。
退会済みユーザー

退会済みユーザー

2020/08/12 23:19

経路探索をするとき、FPSがガクッと落ちて、ゲームが10秒くらいフリーズしてしまうのですが、どのようにすればよいでしょうか? マップのサイズが200×200で、複雑な地形になっているため、計算に時間がかかっているのだと思います。マップをいくつかのチャンク(塊)に分割して、計算はチャンクごとに行う、という方法を考えましたが、他にも良い方法があれば、教えていただきたいです。
Bongo

2020/08/13 17:14

スピードアップ案を検討してみました。字数制限のため別回答になってしまいますがご容赦ください... さらに高速化するとなると、高速化というよりもむしろゲームをフリーズさせないことが主眼になりますが、回答中の末尾で申し上げたように非同期化してメインの処理を妨害しないようにする手があるかと思います。 やはり経路探索は高コストな処理ですから、たとえばNavMeshAgentだとかも目的地を設定した直後に経路を利用可能になるとは限らず、pathPendingを見て探索が完了したか確認するような方式になっていますね。ですが経路探索中でもフリーズせず、他のスクリプトがちゃんと動くのでストレスを感じにくいと思います。 ご質問者さんのおっしゃるような分割案が有効な場面もあると思います。たとえばローグ(https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%82%B0 )みたいに小部屋が通路で接続されたようなマップを持つゲームでしたら、マップ全体をタイル単位で経路探索しなくても、まず通路の交差点や小部屋をノードとするグラフ構造について探索を行うことで目的オブジェクトのある部屋までの経路を求め、部屋に着いたら目的オブジェクトへ向かって詳細な経路探索を行う...といった多段階の探索方法が使えるかもしれません。
guest

0

最短経路を求めたいなら Start から Goal に Start -> a -> b -> c -> Goal というパスがあったときに、Start -> a のコスト、 a -> b のコスト、 b -> c のコスト...とすべて求めていく必要があるわけです。しかし今のコードを見る限り、それぞれのノードについてスタートからのコストとゴールまでのコストを求めてるだけです。これでは無理です。

投稿2020/08/10 10:44

yudedako67

総合スコア2047

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問