Unity3D A* 尋路演算法
阿新 • • 發佈:2019-02-12
筆者介紹:姜雪偉,IT公司技術合夥人,IT高階講師,CSDN社群專家,特邀編輯,暢銷書作者,國家專利發明人;已出版書籍:《手把手教你架構3D遊戲引擎》電子工業出版社和《Unity3D實戰核心技術詳解》電子工業出版社等。
A*尋路演算法通常是應用於大型網路遊戲中,A*演算法通常應用在伺服器中,在移動端遊戲開發中,A*演算法也可以在Unity端實現,下面給讀者介紹一個外掛A* PathFindingProject,它的下載地址:https://arongranberg.com/astar/download#,可以直接匯入到Unity工程中,它目前支援直接尋路和網格尋路。在官網上有免費的版本和收費的版本,作為學習者可以下載免費的版本原理是一樣的。下面通過它們提供的Demo,給讀者介紹一下:
在匯入到Unity工程後,使用時首選建立一個空的GameObject,然後將AstarPath指令碼掛接上去。如下圖所示:
這個AstarPath是整個A*尋路的核心元件,它計算了整個地面的尋路路徑以及儲存資訊,它儲存的是二進位制檔案格式,操作如下所示:
該類提供了儲存,載入等函式,詳情可以檢視類AstarPathEditor,函式介面如下所示:
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(); }
該函式呼叫了JsonSerializer類中的兩個函式如下所示:
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 }
場景通過AstarPath指令碼佈置完後,接下來開始尋路了,需要掛接如下的指令碼:
其中AI指令碼是用於查詢物體的,它會呼叫Seeker指令碼中的函式,同時Seeker會對場景做一些標識操作,這樣AI尋路基本完成,其中AI指令碼是繼承AIPath類,非常方便我們自己編寫,擴充套件功能,它是通過遞迴的方式進行路徑查詢,鎖定目標的,它執行的效果如下所示: