├── Example.cs ├── LICENSE ├── PathFinding2D.cs └── README.md /Example.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | 7 | /** 8 | * 1.create a unity 2D project 9 | * 2.copy scripts to you project folder 10 | * 3.create a gameobject,and attach example.cs to it 11 | * 4.play 12 | */ 13 | public class Example : MonoBehaviour 14 | { 15 | enum TileType 16 | { 17 | none, 18 | wall, 19 | } 20 | 21 | public int width = 15; //tile map width 22 | public int height = 12; //tile map height 23 | public int obstacleFillPercent = 30; //tile map obstacle fill percent 24 | public float scale = 32f; 25 | 26 | Sprite tilePrefab; 27 | string message = ""; 28 | 29 | List passableValues; 30 | 31 | GameObject allMapTiles; //the map and tiles 32 | GameObject player; //the player 33 | GameObject goal; //the goal 34 | 35 | 36 | 37 | void Start() 38 | { 39 | Camera.main.orthographicSize = scale * 10; 40 | Camera.main.gameObject.transform.position = new Vector3(width * scale / 2, height * scale / 2, -10); 41 | tilePrefab = Sprite.Create(new Texture2D((int)scale, (int)scale), new Rect(0, 0, scale, scale), new Vector2(0.5f, 0.5f), 1f); 42 | 43 | goal = new GameObject("goal"); 44 | goal.AddComponent(); 45 | goal.GetComponent().sprite = tilePrefab; 46 | goal.GetComponent().color = Color.yellow; 47 | goal.GetComponent().sortingOrder = 1; 48 | 49 | player = new GameObject("player"); 50 | player.AddComponent(); 51 | player.GetComponent().sprite = tilePrefab; 52 | player.GetComponent().color = Color.red; 53 | player.GetComponent().sortingOrder = 2; 54 | 55 | 56 | passableValues = new List(); 57 | passableValues.Add((int)TileType.none); 58 | } 59 | 60 | void OnGUI() 61 | { 62 | if (GUI.Button(new Rect(10, 10, 150, 30), "pathfinding 4")) 63 | { 64 | message = "finding..."; 65 | simPathFinding4(); 66 | } 67 | if (GUI.Button(new Rect(10, 50, 150, 30), "pathfinding 6X")) 68 | { 69 | message = "finding..."; 70 | simPathFinding6(); 71 | } 72 | if (GUI.Button(new Rect(10, 90, 150, 30), "pathfinding 6Y")) 73 | { 74 | message = "finding..."; 75 | simPathFinding6(false); 76 | } 77 | 78 | GUI.Label(new Rect(180, 20, 300, 30), message); 79 | } 80 | 81 | /** 82 | * simulate path finding in grid tilemaps 83 | */ 84 | public void simPathFinding4() 85 | { 86 | StopAllCoroutines(); 87 | 88 | //init map 89 | var map = mapToDict4(generateMapArray(width, height)); 90 | float xScale = scale; 91 | float yScale = scale; 92 | renderMap(map, xScale, yScale); 93 | 94 | //init player and goal 95 | var playerPos = new Vector2Int(0, 0); 96 | map[playerPos] = (int)TileType.none; 97 | setTransformPosition(player.transform, playerPos, xScale, yScale); 98 | var goalPos = new Vector2Int(width - 1, height - 1); 99 | map[goalPos] = (int)TileType.none; 100 | setTransformPosition(goal.transform, goalPos, xScale, yScale); 101 | 102 | //finding 103 | var path = PathFinding2D.find4(playerPos, goalPos, map, passableValues); 104 | if (path.Count == 0) 105 | { 106 | message = "oops! cant find goal"; 107 | } 108 | else 109 | { 110 | StartCoroutine(movePlayer(path, xScale, yScale, .2f)); 111 | } 112 | } 113 | 114 | /** 115 | * simulate path finding in hexagonal grid tilemaps 116 | */ 117 | public void simPathFinding6(bool staggerByRow = true) 118 | { 119 | StopAllCoroutines(); 120 | 121 | //init map 122 | var map = mapToDict6(generateMapArray(width, height), staggerByRow); 123 | var hexScale = scale + 4f; //addtional 4f makes tiles seperated 124 | float xScale = staggerByRow ? hexScale / 2 : hexScale; 125 | float yScale = staggerByRow ? hexScale : hexScale/2; 126 | renderMap(map, xScale, yScale); 127 | 128 | //init player and goal 129 | var mapPoses = map.Keys.ToList(); 130 | mapPoses.Sort((a, b) => a.x + a.y - b.x - b.y); 131 | var playerPos = mapPoses.First(); 132 | map[playerPos] = (int)TileType.none; 133 | setTransformPosition(player.transform, playerPos, xScale, yScale); 134 | var goalPos = mapPoses.Last(); 135 | map[goalPos] = (int)TileType.none; 136 | setTransformPosition(goal.transform, goalPos, xScale, yScale); 137 | 138 | //find 139 | List path; 140 | if (staggerByRow) { 141 | path = PathFinding2D.find6X(playerPos, goalPos, map, passableValues); 142 | } else { 143 | path = PathFinding2D.find6Y(playerPos, goalPos, map, passableValues); 144 | } 145 | if (path.Count == 0) 146 | { 147 | message = "oops! cant find goal"; 148 | } 149 | else 150 | { 151 | StartCoroutine(movePlayer(path, xScale, yScale, .2f)); 152 | } 153 | } 154 | 155 | 156 | void setTransformPosition(Transform trans, Vector2Int pos, float xScale, float yScale) 157 | { 158 | trans.position = new Vector3(pos.x * xScale, pos.y * yScale, 0); 159 | } 160 | 161 | void renderMap(Dictionary map, float xScale, float yScale) 162 | { 163 | Destroy(allMapTiles); 164 | allMapTiles = new GameObject("allMapTiles"); 165 | foreach (var item in map) 166 | { 167 | GameObject temp = new GameObject(); 168 | temp.transform.position = new Vector3(item.Key.x * xScale, item.Key.y * yScale, 0); 169 | SpriteRenderer spr = temp.AddComponent(); 170 | spr.sprite = tilePrefab; 171 | switch (item.Value) 172 | { 173 | case (int)TileType.none: 174 | spr.color = Color.white; 175 | break; 176 | case (int)TileType.wall: 177 | spr.color = Color.black; 178 | break; 179 | } 180 | temp.transform.parent = allMapTiles.transform; 181 | } 182 | } 183 | 184 | IEnumerator movePlayer(List path, float xScale, float yScale, float interval = 0.1f) 185 | { 186 | foreach(var item in path) { 187 | setTransformPosition(player.transform, item, xScale, yScale); 188 | yield return new WaitForSeconds(interval); 189 | } 190 | 191 | message = "reach goal !"; 192 | } 193 | 194 | int[,] generateMapArray(int pwidth, int pheight) 195 | { 196 | var mapArray = new int[pwidth, pheight]; 197 | for (int x = 0; x < pwidth; x++) 198 | { 199 | for (int y = 0; y < pheight; y++) 200 | { 201 | mapArray[x, y] = Random.Range(0, 100) < obstacleFillPercent ? (int)TileType.wall : (int)TileType.none; 202 | } 203 | } 204 | return mapArray; 205 | } 206 | 207 | Dictionary mapToDict4(int[,] mapArray) 208 | { 209 | Dictionary mapDict = new Dictionary(); 210 | for (int x = 0; x < mapArray.GetLength(0); x++) 211 | { 212 | for (int y = 0; y < mapArray.GetLength(1); y++) 213 | { 214 | mapDict.Add(new Vector2Int(x, y), mapArray[x, y]); 215 | } 216 | } 217 | return mapDict; 218 | } 219 | 220 | Dictionary mapToDict6(int[,] mapArray, bool stretchRow) 221 | { 222 | Dictionary mapDict = new Dictionary(); 223 | for (int x = 0; x < mapArray.GetLength(0); x++) 224 | { 225 | for (int y = 0; y < mapArray.GetLength(1); y++) 226 | { 227 | if (stretchRow) 228 | { 229 | if (y % 2 == 0) 230 | { 231 | mapDict.Add(new Vector2Int(2 * x, y), mapArray[x, y]); 232 | } 233 | else 234 | { 235 | mapDict.Add(new Vector2Int(2 * x + 1, y), mapArray[x, y]); 236 | } 237 | } 238 | else 239 | { 240 | if (x % 2 == 0) 241 | { 242 | mapDict.Add(new Vector2Int(x, 2 * y), mapArray[x, y]); 243 | } 244 | else 245 | { 246 | mapDict.Add(new Vector2Int(x, 2 * y + 1), mapArray[x, y]); 247 | } 248 | } 249 | 250 | } 251 | } 252 | return mapDict; 253 | } 254 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 magacy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PathFinding2D.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | using System; 4 | using System.Linq; 5 | 6 | public static class PathFinding2D 7 | { 8 | /** 9 | * find a path in grid tilemaps 10 | */ 11 | public static List find4(Vector2Int from, Vector2Int to, Dictionary map, List passableValues) 12 | { 13 | Func getDistance = delegate (Vector2Int a, Vector2Int b) 14 | { 15 | float xDistance = Mathf.Abs(a.x - b.x); 16 | float yDistance = Mathf.Abs(a.y - b.y); 17 | return xDistance * xDistance + yDistance * yDistance; 18 | }; 19 | Func> getNeighbors = delegate (Vector2Int pos) 20 | { 21 | var neighbors = new List(); 22 | neighbors.Add(new Vector2Int(pos.x, pos.y + 1)); 23 | neighbors.Add(new Vector2Int(pos.x, pos.y - 1)); 24 | neighbors.Add(new Vector2Int(pos.x + 1, pos.y)); 25 | neighbors.Add(new Vector2Int(pos.x - 1, pos.y)); 26 | return neighbors; 27 | }; 28 | 29 | return astar(from, to, map, passableValues, getDistance, getNeighbors); 30 | } 31 | 32 | /** 33 | * find a path in hexagonal grid tilemaps (when grid rows are staggered with each other) 34 | */ 35 | public static List find6X(Vector2Int from, Vector2Int to, Dictionary map, List passableValues) 36 | { 37 | Func getDistance = delegate (Vector2Int a, Vector2Int b) 38 | { 39 | float xDistance = Mathf.Abs(a.x - b.x); 40 | float yDistance = Mathf.Abs(a.y - b.y) * Mathf.Sqrt(3); 41 | return xDistance * xDistance + yDistance * yDistance; 42 | }; 43 | Func> getNeighbors = delegate (Vector2Int pos) 44 | { 45 | var neighbors = new List(); 46 | neighbors.Add(new Vector2Int(pos.x + 1, pos.y + 1)); 47 | neighbors.Add(new Vector2Int(pos.x - 1, pos.y + 1)); 48 | neighbors.Add(new Vector2Int(pos.x + 1, pos.y - 1)); 49 | neighbors.Add(new Vector2Int(pos.x - 1, pos.y - 1)); 50 | neighbors.Add(new Vector2Int(pos.x - 2, pos.y)); 51 | neighbors.Add(new Vector2Int(pos.x + 2, pos.y)); 52 | return neighbors; 53 | }; 54 | return astar(from, to, map, passableValues, getDistance, getNeighbors); 55 | } 56 | 57 | /** 58 | * find a path in hexagonal grid tilemaps (when grid columns are staggered with each other) 59 | */ 60 | public static List find6Y(Vector2Int from, Vector2Int to, Dictionary map, List passableValues) 61 | { 62 | Func getDistance = delegate (Vector2Int a, Vector2Int b) 63 | { 64 | float xDistance = Mathf.Abs(a.x - b.x) * Mathf.Sqrt(3); 65 | float yDistance = Mathf.Abs(a.y - b.y); 66 | return xDistance * xDistance + yDistance * yDistance; 67 | }; 68 | Func> getNeighbors = delegate (Vector2Int pos) 69 | { 70 | var neighbors = new List(); 71 | neighbors.Add(new Vector2Int(pos.x + 1, pos.y + 1)); 72 | neighbors.Add(new Vector2Int(pos.x - 1, pos.y + 1)); 73 | neighbors.Add(new Vector2Int(pos.x + 1, pos.y - 1)); 74 | neighbors.Add(new Vector2Int(pos.x - 1, pos.y - 1)); 75 | neighbors.Add(new Vector2Int(pos.x, pos.y - 2)); 76 | neighbors.Add(new Vector2Int(pos.x, pos.y + 2)); 77 | return neighbors; 78 | }; 79 | return astar(from, to, map, passableValues, getDistance, getNeighbors); 80 | } 81 | 82 | 83 | static List astar(Vector2Int from, Vector2Int to, Dictionary map, List passableValues, 84 | Func getDistance, Func> getNeighbors) 85 | { 86 | var result = new List(); 87 | if (from == to) 88 | { 89 | result.Add(from); 90 | return result; 91 | } 92 | Node finalNode; 93 | List open = new List(); 94 | if (findDest(new Node(null, from, getDistance(from, to), 0), open, map, to, out finalNode, passableValues, getDistance, getNeighbors)) 95 | { 96 | while (finalNode != null) 97 | { 98 | result.Add(finalNode.pos); 99 | finalNode = finalNode.preNode; 100 | } 101 | } 102 | result.Reverse(); 103 | return result; 104 | } 105 | 106 | static bool findDest(Node currentNode, List openList, 107 | Dictionary map, Vector2Int to, out Node finalNode, List passableValues, 108 | Func getDistance, Func> getNeighbors) 109 | { 110 | if (currentNode == null) { 111 | finalNode = null; 112 | return false; 113 | } 114 | else if (currentNode.pos == to) 115 | { 116 | finalNode = currentNode; 117 | return true; 118 | } 119 | currentNode.open = false; 120 | openList.Add(currentNode); 121 | 122 | foreach (var item in getNeighbors(currentNode.pos)) 123 | { 124 | if (map.ContainsKey(item) && passableValues.Contains(map[item])) 125 | { 126 | findTemp(openList, currentNode, item, to, getDistance); 127 | } 128 | } 129 | var next = openList.FindAll(obj => obj.open).Min(); 130 | return findDest(next, openList, map, to, out finalNode, passableValues, getDistance, getNeighbors); 131 | } 132 | 133 | static void findTemp(List openList, Node currentNode, Vector2Int from, Vector2Int to, Func getDistance) 134 | { 135 | 136 | Node temp = openList.Find(obj => obj.pos == (from)); 137 | if (temp == null) 138 | { 139 | temp = new Node(currentNode, from, getDistance(from, to), currentNode.gScore + 1); 140 | openList.Add(temp); 141 | } 142 | else if (temp.open && temp.gScore > currentNode.gScore + 1) 143 | { 144 | temp.gScore = currentNode.gScore + 1; 145 | temp.fScore = temp.hScore + temp.gScore; 146 | temp.preNode = currentNode; 147 | } 148 | } 149 | 150 | class Node:IComparable 151 | { 152 | public Node preNode; 153 | public Vector2Int pos; 154 | public float fScore; 155 | public float hScore; 156 | public float gScore; 157 | public bool open = true; 158 | 159 | public Node(Node prePos, Vector2Int pos, float hScore, float gScore) 160 | { 161 | this.preNode = prePos; 162 | this.pos = pos; 163 | this.hScore = hScore; 164 | this.gScore = gScore; 165 | this.fScore = hScore + gScore; 166 | } 167 | 168 | public int CompareTo(object obj) 169 | { 170 | Node temp = obj as Node; 171 | 172 | if (temp == null) return 1; 173 | 174 | if (Mathf.Abs(this.fScore - temp.fScore) < 0.01f) { 175 | return this.fScore > temp.fScore ? 1 : -1; 176 | } 177 | 178 | if (Mathf.Abs(this.hScore - temp.hScore) < 0.01f) 179 | { 180 | return this.hScore > temp.hScore ? 1 : -1; 181 | } 182 | return 0; 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityPathFinding2D 2 | A simple c# path finding script for 2D, hexagonal grid supported. 3 | 4 | ## example 5 | ![sns5cr2hve](https://user-images.githubusercontent.com/19882542/44308746-cdcad980-a3ed-11e8-8c0f-d522076f2529.gif) 6 | 7 | ## hexagonal grid X (grid staggered by row) 8 | ![image](https://user-images.githubusercontent.com/19882542/44308633-f81b9780-a3eb-11e8-8342-fac0977792f4.png) 9 | 10 | ## hexagonal grid Y (grid staggered by column) 11 | ![image](https://user-images.githubusercontent.com/19882542/44308656-55afe400-a3ec-11e8-8186-c6760fb5d8cd.png) 12 | --------------------------------------------------------------------------------