├── LICENSE
├── NavMeshAreas.cs
└── README.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
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 |
--------------------------------------------------------------------------------
/NavMeshAreas.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEngine.AI;
5 | using System;
6 | using System.Linq;
7 | using System.IO;
8 | using System.Text;
9 | using System.Runtime.CompilerServices;
10 |
11 | #if UNITY_EDITOR
12 | using UnityEditor;
13 | #endif
14 |
15 | namespace UnityEngine.AI
16 | {
17 | #region Auto-Generated Content
18 |
19 | // NavMeshAgent uses AreaMask.
20 | [Flags]
21 | public enum NavMeshAreas
22 | {
23 | None = 0,
24 | Walkable = 1, NotWalkable = 2, Jump = 4, Climb = 8, Blocked = 16, Hole = 32, Edge = 64, Fall = 128, New1 = 256, Stuff = 512,
25 | All = ~0,
26 | }
27 |
28 | // NavMeshSurface, NavMeshLink, NavMeshModifierVolume, etc. use indexes.
29 | public enum NavMeshAreaIndex
30 | {
31 | Walkable = 0, NotWalkable = 1, Jump = 2, Climb = 3, Blocked = 4, Hole = 5, Edge = 6, Fall = 7, New1 = 8, Stuff = 9,
32 | }
33 |
34 | #endregion
35 |
36 | #if UNITY_EDITOR
37 |
38 | ///
39 | /// Auto-updates the enum in this file if it has changed, when scripts are compiled or assets saved.
40 | ///
41 | public static class NavMeshAreasGenerator
42 | {
43 | private const string IndexValuesToken = "#IndexValues";
44 | private const string FlagValuesToken = "#FlagValues";
45 | private const string HashSettingsKey = "NavMeshAreasHash";
46 |
47 | private static void Update([CallerFilePath] string executingFilePath = "")
48 | {
49 | var areaNames = GameObjectUtility.GetNavMeshAreaNames();
50 | var lastHash = EditorPrefs.GetInt(HashSettingsKey);
51 | var newHash = GetAreaHash(areaNames);
52 |
53 | if (newHash != lastHash)
54 | {
55 | Debug.Log($"{nameof(NavMeshAreas)} have changed, updating enum: '{executingFilePath}'");
56 | GenerateFile(areaNames, newHash, executingFilePath);
57 | }
58 | }
59 |
60 | private static int GetAreaHash(string[] areaNames)
61 | {
62 | var input = areaNames.Aggregate((a, b) => a + b);
63 | var hash = 0;
64 | foreach (var t in input)
65 | hash = (hash << 5) + hash + t;
66 | return hash;
67 | }
68 |
69 | private static void GenerateFile(string[] areaNames = default, int hash = 0, string outputPath = null)
70 | {
71 | if (areaNames == null)
72 | areaNames = GameObjectUtility.GetNavMeshAreaNames();
73 |
74 | if (hash == 0)
75 | hash = GetAreaHash(areaNames);
76 |
77 | var text = GetAreaEnumValuesAsText(ref areaNames, as_flags: true);
78 | var newEnumText = FlagContentTemplate.Replace(FlagValuesToken, text);
79 | var output = ReplaceEnumInFile(nameof(NavMeshAreas), File.ReadAllLines(outputPath), newEnumText);
80 |
81 | text = GetAreaEnumValuesAsText(ref areaNames, as_flags: false);
82 | newEnumText = IndexContentTemplate.Replace(IndexValuesToken, text);
83 | output = ReplaceEnumInFile(nameof(NavMeshAreaIndex), output.Trim().Split(new[]{Environment.NewLine}, StringSplitOptions.None), newEnumText);
84 |
85 | CreateScriptAssetWithContent(outputPath, string.Concat(output));
86 | EditorPrefs.SetInt(HashSettingsKey, hash);
87 | AssetDatabase.Refresh();
88 | }
89 |
90 | private static string GetAreaEnumValuesAsText(ref string[] areaNames, bool as_flags)
91 | {
92 | var increment = 0;
93 | var output = new StringBuilder();
94 | var seenKeys = new HashSet();
95 |
96 | foreach (var name in areaNames)
97 | {
98 | var enumKey = string.Concat(name.Where(char.IsLetterOrDigit));
99 | var value = NavMesh.GetAreaFromName(name);
100 | if (as_flags)
101 | {
102 | value = 1 << value;
103 | }
104 |
105 | output.Append(seenKeys.Contains(name)
106 | ? $"{(enumKey + increment++)} = {value}, "
107 | : $"{enumKey} = {value}, ");
108 |
109 | seenKeys.Add(enumKey);
110 | }
111 | return output.ToString();
112 | }
113 |
114 | private static readonly string FlagContentTemplate =
115 | $@" public enum {nameof(NavMeshAreas)}
116 | {{
117 | None = 0,
118 | {FlagValuesToken}
119 | All = ~0,
120 | }}
121 | ";
122 | private static readonly string IndexContentTemplate =
123 | $@" public enum {nameof(NavMeshAreaIndex)}
124 | {{
125 | {IndexValuesToken}
126 | }}
127 | ";
128 |
129 | private static int SkipToLineStartingWith(string pattern, string[] fileLines, int start_index, StringBuilder accumulated)
130 | {
131 | for (int i = start_index; i < fileLines.Length; i++)
132 | {
133 | string line = fileLines[i];
134 | if (line.Trim().StartsWith(pattern))
135 | {
136 | return i;
137 | }
138 | else if (accumulated != null)
139 | {
140 | accumulated.AppendLine(line);
141 | }
142 | }
143 | return -1;
144 | }
145 |
146 | private static string ReplaceEnumInFile(string enumName, string[] fileLines, string newEnum)
147 | {
148 | int enumStartLine = 0, enumEndLine = 0;
149 | var result = new StringBuilder();
150 | enumStartLine = SkipToLineStartingWith("public enum " + enumName, fileLines, 0, result);
151 | if (enumStartLine > 0)
152 | {
153 | enumEndLine = SkipToLineStartingWith("}", fileLines, enumStartLine + 1, null);
154 | result.Append(newEnum);
155 | for (int i = enumEndLine + 1; i < fileLines.Length; i++)
156 | {
157 | result.AppendLine(fileLines[i]);
158 | }
159 | }
160 | return result.ToString();
161 | }
162 |
163 | ///
164 | /// Create a new script asset.
165 | /// UnityEditor.ProjectWindowUtil.CreateScriptAssetWithContent (2019.1)
166 | ///
167 | /// the path to where the new file should be created
168 | /// the text to put inside
169 | ///
170 | private static UnityEngine.Object CreateScriptAssetWithContent(string pathName, string templateContent)
171 | {
172 | templateContent = SetLineEndings(templateContent, EditorSettings.lineEndingsForNewScripts);
173 | string fullPath = Path.GetFullPath(pathName);
174 | System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(true);
175 | File.WriteAllText(fullPath, templateContent, encoding);
176 | string projectPath = GetPathRelativeToProject(fullPath);
177 | AssetDatabase.ImportAsset(projectPath);
178 | return AssetDatabase.LoadAssetAtPath(projectPath, typeof(UnityEngine.Object));
179 | }
180 |
181 | ///
182 | /// Get a path relative to the project folder suitable for passing to
183 | /// AssetDatabase.
184 | ///
185 | /// the path to make relative
186 | /// a path relative to the project (including "Assets" folder)
187 | private static string GetPathRelativeToProject(string pathName)
188 | {
189 | string path = Path.GetFullPath(pathName);
190 | string project = Path.GetFullPath(Application.dataPath);
191 | if (path.StartsWith(project))
192 | {
193 | path = "Assets" + path.Substring(project.Length);
194 | }
195 | return path;
196 | }
197 |
198 | ///
199 | /// Ensure correct OS specific line endings for saving file content.
200 | /// UnityEditor.ProjectWindowUtil.SetLineEndings (2019.1)
201 | ///
202 | /// a string to have line endings checked
203 | /// the type of line endings to use
204 | /// a cleaned string
205 | private static string SetLineEndings(string content, LineEndingsMode lineEndingsMode)
206 | {
207 | string replacement;
208 | switch (lineEndingsMode)
209 | {
210 | case LineEndingsMode.OSNative:
211 | replacement = Application.platform == RuntimePlatform.WindowsEditor ? "\r\n" : "\n";
212 | break;
213 | case LineEndingsMode.Unix:
214 | replacement = "\n";
215 | break;
216 | case LineEndingsMode.Windows:
217 | replacement = "\r\n";
218 | break;
219 | default:
220 | replacement = "\n";
221 | break;
222 | }
223 | content = System.Text.RegularExpressions.Regex.Replace(content, "\\r\\n?|\\n", replacement);
224 | return content;
225 | }
226 |
227 |
228 | ///
229 | /// Hook that runs the enum generator whenever assets are saved.
230 | ///
231 | private class UpdateOnAssetModification : UnityEditor.AssetModificationProcessor
232 | {
233 | public static string[] OnWillSaveAssets(string[] paths)
234 | {
235 | Update();
236 | return paths;
237 | }
238 | }
239 |
240 | ///
241 | /// Hook that runs the enum generator whenever scripts are compiled.
242 | ///
243 | [UnityEditor.Callbacks.DidReloadScripts]
244 | private static void UpdateOnScriptCompile()
245 | {
246 | Update();
247 | }
248 |
249 | ///
250 | /// Enables manually running the enum generator from the menus.
251 | ///
252 | [MenuItem("Tools/Update NavMeshAreas")]
253 | private static void UpdateOnMenuCommand()
254 | {
255 | UpdateOnScriptCompile();
256 | }
257 |
258 | }
259 |
260 | ///
261 | /// Flags enum dropdown GUI for selecting properties in the inspector
262 | ///
263 | [CustomPropertyDrawer(typeof(NavMeshAreas))]
264 | public class NavMeshAreasDrawer : PropertyDrawer
265 | {
266 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
267 | {
268 | label = EditorGUI.BeginProperty(position, label, property);
269 | var oldValue = (Enum)fieldInfo.GetValue(property.serializedObject.targetObject);
270 | var newValue = EditorGUI.EnumFlagsField(position, label, oldValue);
271 | if (!newValue.Equals(oldValue))
272 | {
273 | property.intValue = (int)Convert.ChangeType(newValue, fieldInfo.FieldType);
274 | }
275 | EditorGUI.EndProperty();
276 | }
277 | }
278 |
279 | #endif
280 |
281 | ///
282 | /// A helper for flag operations with NavMeshAreas
283 | ///
284 | public struct AreaMask
285 | {
286 | private readonly int _value;
287 |
288 | public int Value => _value;
289 | public NavMeshAreas Enum => (NavMeshAreas)_value;
290 |
291 | public AreaMask(int value)
292 | {
293 | _value = value;
294 | }
295 |
296 | public AreaMask(NavMeshAreas areas)
297 | {
298 | _value = (int)areas;
299 | }
300 |
301 | public static implicit operator AreaMask(int value) => new AreaMask(value);
302 | public static implicit operator AreaMask(string name) => new AreaMask(1 << NavMesh.GetAreaFromName(name));
303 | public static implicit operator AreaMask(NavMeshAreas areas) => new AreaMask((int)areas);
304 | public static implicit operator NavMeshAreas(AreaMask flag) => (NavMeshAreas)flag._value;
305 | public static implicit operator int(AreaMask flag) => flag._value;
306 |
307 | public static bool operator ==(AreaMask a, int b) => a._value.Equals(b);
308 | public static bool operator !=(AreaMask a, int b) => !a._value.Equals(b);
309 | public static int operator +(AreaMask a, AreaMask b) => a.Add(b._value);
310 | public static int operator -(AreaMask a, AreaMask b) => a.Remove(b._value);
311 | public static int operator |(AreaMask a, AreaMask b) => a.Add(b._value);
312 | public static int operator ~(AreaMask a) => ~a._value;
313 | public static int operator +(int a, AreaMask b) => a |= b._value;
314 | public static int operator -(int a, AreaMask b) => a &= ~b._value;
315 | public static int operator |(int a, AreaMask b) => a |= b._value;
316 | public static int operator +(AreaMask a, int b) => a.Add(b);
317 | public static int operator -(AreaMask a, int b) => a.Remove(b);
318 | public static int operator |(AreaMask a, int b) => a.Add(b);
319 |
320 | public bool HasFlag(AreaMask flag) => (_value & flag._value) == flag;
321 | public bool HasFlag(int value) => (_value & value) == value;
322 | public AreaMask Add(AreaMask flag) => _value | flag._value;
323 | public AreaMask Remove(AreaMask flag) => _value & ~flag._value;
324 | public AreaMask Add(NavMeshAreas flags) => _value | (int)flags;
325 | public AreaMask Remove(NavMeshAreas flags) => _value & ~(int)flags;
326 |
327 | public bool Equals(AreaMask other) => _value == other._value;
328 | public override string ToString() => ((NavMeshAreas)_value).ToString();
329 | public override int GetHashCode() => _value;
330 | public override bool Equals(object obj)
331 | => !ReferenceEquals(null, obj) && (obj is AreaMask other && Equals(other));
332 | }
333 |
334 | }
335 |
336 |
337 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UnityNavMeshAreas
2 | Self-updating Areas enum for NavMesh in Unity
3 |
4 | - Contains an **Enum called NavMeshAreas** so that you don't have to mess around with strings anymore.
5 | - The file **re-writes itself** whenever scripts compile or assets are saved in the editor.
6 | - Includes a **PropertyDrawer** so that the insepector will use a multiselect flags dropdown.
7 | - Contains a helper 'AreaMask' class to make working with flags easier.
8 |
9 | ###### Usage Examples
10 | ```
11 | Agent.areaMask = (int)NavMeshAreas.Walkable;
12 |
13 | var mask1 = (AreaMask)(NavMeshAreas.Walkable | NavMeshAreas.Climb);
14 | var mask2 = mask1.Add(NavMeshAreas.Edge);
15 | var mask3 = mask2.Remove(NavMeshAreas.Climb);
16 | ```
17 |
18 | Unity Version: 2018.3
19 |
--------------------------------------------------------------------------------