├── 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 | --------------------------------------------------------------------------------