Unity3D A* Routing Algorithms

Keywords: Unity Mobile

The author introduces: Jiang Xuewei Technological Partner of IT Company, Senior IT Lecturer, CSDN Community Expert, Invited Editor, Best-selling Book Author, National Patent Inventor; Published Book: Hand-to-Hand Teaching Framework 3D Game Engine. Electronic Industry Press and< Unity3D Detailed Explanation of Actual Core Technologies, Electronic Industry Press, etc.

CSDN Video Website: http://edu.csdn.net/lecturer/144

A* routing algorithm is usually used in large-scale online games, A* algorithm is usually used in servers, in mobile game development, A* algorithm can also be implemented in Unity. Here we introduce a plug-in A* PathFinding Project, its download address: https://arongranberg.com/astar/download# It can be directly imported into Unity Project, which currently supports direct path finding and grid path finding. There are free versions and paid versions on the official website. As learners, the principle of downloading free versions is the same. Following is the Demo they provide to introduce the reader:

After importing the Unity project, it is preferable to create an empty GameObject and then hook up the AstarPath script. As shown in the following figure:


This AstarPath is the core component of the whole A* routing. It calculates the whole ground routing and saves information. It saves the binary file format. The operation is as follows:

This class provides functions such as save, load and so on. For more details, you can see the class AstarPathEditor. The function interface is as follows:

void DrawSerializationSettings () {
			serializationSettingsArea.Begin();
			GUILayout.BeginHorizontal();

			if (GUILayout.Button("Save & Load", level0LabelStyle)) {
				serializationSettingsArea.open = !serializationSettingsArea.open;
			}

			if (script.data.cacheStartup && script.data.file_cachedStartup != null) {
				GUIUtilityx.PushTint(Color.yellow);
				GUILayout.Label("Startup cached", thinHelpBox, GUILayout.Height(15));
				GUILayout.Space(20);
				GUIUtilityx.PopTint();
			}

			GUILayout.EndHorizontal();

			// This displays the serialization settings
			if (serializationSettingsArea.BeginFade()) {
				script.data.cacheStartup = EditorGUILayout.Toggle(new GUIContent("Cache startup", "If enabled, will cache the graphs so they don't have to be scanned at startup"), script.data.cacheStartup);

				script.data.file_cachedStartup = EditorGUILayout.ObjectField(script.data.file_cachedStartup, typeof(TextAsset), false) as TextAsset;

				if (script.data.cacheStartup && script.data.file_cachedStartup == null) {
					EditorGUILayout.HelpBox("No cache has been generated", MessageType.Error);
				}

				if (script.data.cacheStartup && script.data.file_cachedStartup != null) {
					EditorGUILayout.HelpBox("All graph settings will be replaced with the ones from the cache when the game starts", MessageType.Info);
				}

				GUILayout.BeginHorizontal();

				if (GUILayout.Button("Generate cache")) {
					var serializationSettings = new Pathfinding.Serialization.SerializeSettings();
					serializationSettings.nodes = true;

					if (EditorUtility.DisplayDialog("Scan before generating cache?", "Do you want to scan the graphs before saving the cache.\n" +
							"If the graphs have not been scanned then the cache may not contain node data and then the graphs will have to be scanned at startup anyway.", "Scan", "Don't scan")) {
						MenuScan();
					}

					// Save graphs
					var bytes = script.data.SerializeGraphs(serializationSettings);

					// Store it in a file
					script.data.file_cachedStartup = SaveGraphData(bytes, script.data.file_cachedStartup);
					script.data.cacheStartup = true;
				}

				if (GUILayout.Button("Load from cache")) {
					if (EditorUtility.DisplayDialog("Are you sure you want to load from cache?", "Are you sure you want to load graphs from the cache, this will replace your current graphs?", "Yes", "Cancel")) {
						script.data.LoadFromCache();
					}
				}

				GUILayout.EndHorizontal();

				if (script.data.data_cachedStartup != null && script.data.data_cachedStartup.Length > 0) {
					EditorGUILayout.HelpBox("Storing the cached starup data on the AstarPath object has been deprecated. It is now stored " +
						"in a separate file.", MessageType.Error);

					if (GUILayout.Button("Transfer cache data to separate file")) {
						script.data.file_cachedStartup = SaveGraphData(script.data.data_cachedStartup);
						script.data.data_cachedStartup = null;
					}
				}

				GUILayout.Space(5);

				GUILayout.BeginHorizontal();
				if (GUILayout.Button("Save to file")) {
					string path = EditorUtility.SaveFilePanel("Save Graphs", "", "graph.bytes", "bytes");

					if (path != "") {
						var serializationSettings = Pathfinding.Serialization.SerializeSettings.Settings;
						if (EditorUtility.DisplayDialog("Include node data?", "Do you want to include node data in the save file. " +
								"If node data is included the graph can be restored completely without having to scan it first.", "Include node data", "Only settings")) {
							serializationSettings.nodes = true;
						}

						if (serializationSettings.nodes && EditorUtility.DisplayDialog("Scan before saving?", "Do you want to scan the graphs before saving? " +
								"\nNot scanning can cause node data to be omitted from the file if the graph is not yet scanned.", "Scan", "Don't scan")) {
							MenuScan();
						}

						uint checksum;
						var bytes = SerializeGraphs(serializationSettings, out checksum);
						Pathfinding.Serialization.AstarSerializer.SaveToFile(path, bytes);

						EditorUtility.DisplayDialog("Done Saving", "Done saving graph data.", "Ok");
					}
				}

				if (GUILayout.Button("Load from file")) {
					string path = EditorUtility.OpenFilePanel("Load Graphs", "", "");

					if (path != "") {
						byte[] bytes;
						try {
							bytes = Pathfinding.Serialization.AstarSerializer.LoadFromFile(path);
						} catch (System.Exception e) {
							Debug.LogError("Could not load from file at '"+path+"'\n"+e);
							bytes = null;
						}

						if (bytes != null) DeserializeGraphs(bytes);
					}
				}

				GUILayout.EndHorizontal();
			}

			serializationSettingsArea.End();
		}

This function calls two functions in the JsonSerializer class as follows:

		public static void SaveToFile (string path, byte[] data) {
#if NETFX_CORE
			throw new System.NotSupportedException("Cannot save to file on this platform");
#else
			using (var stream = new FileStream(path, FileMode.Create)) {
				stream.Write(data, 0, data.Length);
			}
#endif
		}

		/** Load the specified data from the specified path */
		public static byte[] LoadFromFile (string path) {
#if NETFX_CORE
			throw new System.NotSupportedException("Cannot load from file on this platform");
#else
			using (var stream = new FileStream(path, FileMode.Open)) {
				var bytes = new byte[(int)stream.Length];
				stream.Read(bytes, 0, (int)stream.Length);
				return bytes;
			}
#endif
		}

After the scenario is laid out through the AstarPath script, the following script needs to be hooked up:


The AI script is used to query objects. It calls the functions in the Seeker script. At the same time, the Seeker will do some identification operations on the scene. So the AI path-finding is basically completed. The AI script inherits the AIPath class, which is very convenient for us to write and expand the function. It is a recursive way to query the path and lock the target. The effect of the AI script is as follows:






Posted by DocSeuss on Sun, 30 Jun 2019 13:38:43 -0700