├── .gitignore ├── FSM.meta ├── FSM ├── Scripts.meta ├── Scripts │ ├── Editor.meta │ ├── Editor │ │ ├── EditorCategory.cs │ │ ├── EditorCategory.cs.meta │ │ ├── EditorUtil.cs │ │ ├── EditorUtil.cs.meta │ │ ├── StateMachineEditor.cs │ │ ├── StateMachineEditor.cs.meta │ │ ├── StateManagerEditor.cs │ │ └── StateManagerEditor.cs.meta │ ├── State Machine.meta │ ├── State Machine │ │ ├── SerializableState.cs │ │ ├── SerializableState.cs.meta │ │ ├── State.cs │ │ ├── State.cs.meta │ │ ├── StateMachine.cs │ │ ├── StateMachine.cs.meta │ │ ├── StateManager.cs │ │ ├── StateManager.cs.meta │ │ ├── StateManagerComponent.cs │ │ └── StateManagerComponent.cs.meta │ ├── Utility.meta │ └── Utility │ │ ├── UnityReflectionUtil.cs │ │ └── UnityReflectionUtil.cs.meta ├── State Manager.asset └── State Manager.asset.meta ├── LICENSE ├── LICENSE.meta ├── README.md └── README.md.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /FSM.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cc7269b2724b82f48bfc7f008e92fe2a 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /FSM/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6ff7a4c40073a0746b540fe44b2f7e69 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /FSM/Scripts/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f2ea448d4df2094e811673bc1a684c7 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /FSM/Scripts/Editor/EditorCategory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace FSM.Editors 5 | { 6 | public class EditorCategory 7 | { 8 | public string catergoryName; 9 | 10 | public bool isFocused = true; 11 | public int selected; 12 | 13 | public List elements; 14 | 15 | public EditorCategory(string name) 16 | { 17 | catergoryName = name; 18 | elements = new List(); 19 | } 20 | 21 | public void Add(string element) 22 | { 23 | if (string.IsNullOrEmpty(element)) 24 | { 25 | Debug.Log("Cannot add element of null to catergory " + catergoryName); 26 | return; 27 | } 28 | 29 | elements.Add(element); 30 | } 31 | 32 | public void Add(string[] elements) 33 | { 34 | if (elements == null) 35 | { 36 | Debug.Log("Cannot add null array elements to " + catergoryName); 37 | return; 38 | } 39 | 40 | if (elements.Length == 0) 41 | return; 42 | 43 | for (int i = 0; i < elements.Length; i++) 44 | Add(elements[i]); 45 | } 46 | 47 | public string[] LookupNames(string name) 48 | { 49 | if (string.IsNullOrEmpty(name)) 50 | return null; 51 | 52 | List names = new List(); 53 | 54 | foreach (var item in elements) 55 | { 56 | if (item.Contains(name)) 57 | names.Add(item); 58 | } 59 | 60 | return names.ToArray(); 61 | } 62 | 63 | public string LookUpName(string name) 64 | { 65 | if (string.IsNullOrEmpty(name)) 66 | return null; 67 | 68 | foreach (string element in elements) 69 | { 70 | if (element == name) 71 | return element; 72 | } 73 | 74 | return null; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /FSM/Scripts/Editor/EditorCategory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab4d9fdf73a0b54489687e464266a4c6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/Editor/EditorUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using Object = UnityEngine.Object; 6 | 7 | namespace FSM.Editors 8 | { 9 | /// 10 | /// Utility methods for the editor 11 | /// 12 | public static class EditorUtil 13 | { 14 | public static Object GetObjectFromGUID(string guid) 15 | { 16 | string path = AssetDatabase.GUIDToAssetPath(guid); 17 | return AssetDatabase.LoadAssetAtPath(path, typeof(Object)); 18 | } 19 | 20 | public static Object GetAssetFromName(string name) 21 | { 22 | //Get file/class, gotta make sure that file is named the same 23 | name = name.Split('.').Last(); 24 | 25 | //Get GUID for path 26 | var a = AssetDatabase.FindAssets(name); 27 | 28 | if (a.Length == 0) 29 | { 30 | return null; 31 | } 32 | 33 | string b = AssetDatabase.GUIDToAssetPath(a[0]); 34 | 35 | Object o = null; 36 | 37 | for (int i = 0; i < a.Length; i++) 38 | { 39 | o = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(a[i]), typeof(Object)); 40 | 41 | if (o.name == name) 42 | return o; 43 | } 44 | 45 | return null; 46 | } 47 | 48 | public static Object GetAssetFromName(string name, Type type) 49 | { 50 | //Get file/class, gotta make sure that file is named the same 51 | name = name.Split('.').Last(); 52 | 53 | var a = AssetDatabase.FindAssets(name); 54 | var b = AssetDatabase.GUIDToAssetPath(a[0]); 55 | 56 | return AssetDatabase.LoadAssetAtPath(b, type); 57 | } 58 | 59 | public static void ClassObjectField(string className) 60 | { 61 | //Get file/class, gotta make sure that file is named the same 62 | className = className.Split('.').Last(); 63 | 64 | //Get GUID for path 65 | var a = AssetDatabase.FindAssets(className); 66 | var b = AssetDatabase.GUIDToAssetPath(a[0]); 67 | 68 | EditorGUILayout.BeginHorizontal(); 69 | { 70 | className = char.ToUpper(className[0]) + className.Substring(1); 71 | EditorGUILayout.LabelField(className + " Script", GUILayout.ExpandWidth(false)); 72 | EditorGUILayout.ObjectField(AssetDatabase.LoadAssetAtPath(b, typeof(Object)), typeof(Object), false); 73 | } 74 | EditorGUILayout.EndHorizontal(); 75 | } 76 | 77 | public static void ClassObjectField(string className, bool showName) 78 | { 79 | //Get file/class, gotta make sure that file is named the same 80 | className = className.Split('.').Last(); 81 | 82 | //Get GUID for path 83 | var a = AssetDatabase.FindAssets(className); 84 | string b = AssetDatabase.GUIDToAssetPath(a[0]); 85 | 86 | Object o = null; 87 | 88 | for (int i = 0; i < a.Length; i++) 89 | { 90 | o = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(a[i]), typeof(Object)); 91 | 92 | if (o.name == className) 93 | break; 94 | } 95 | 96 | if (showName) 97 | { 98 | className = char.ToUpper(className[0]) + className.Substring(1); 99 | 100 | EditorGUILayout.BeginHorizontal(); 101 | { 102 | EditorGUILayout.LabelField(className + " Script", GUILayout.ExpandWidth(false)); 103 | EditorGUILayout.ObjectField(o, typeof(Object), false); 104 | } 105 | EditorGUILayout.EndHorizontal(); 106 | } 107 | else 108 | { 109 | EditorGUILayout.ObjectField(o, typeof(Object), false); 110 | } 111 | } 112 | 113 | public static void EditorClassField(string className) 114 | { 115 | //Get file/class, gotta make sure that file is named the same 116 | className = className.Split('.').Last(); 117 | 118 | //Get GUID for path 119 | var a = AssetDatabase.FindAssets(className); 120 | var b = AssetDatabase.GUIDToAssetPath(a[0]); 121 | 122 | className = char.ToUpper(className[0]) + className.Substring(1); 123 | EditorGUILayout.LabelField(className + " Script"); 124 | EditorGUILayout.ObjectField(AssetDatabase.LoadAssetAtPath(b, typeof(UnityEngine.Object)), typeof(UnityEngine.Object), false); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /FSM/Scripts/Editor/EditorUtil.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c96e586d652f74e4ba934a27ce0eaeb0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/Editor/StateMachineEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | #pragma warning disable 8 | 9 | namespace FSM.Editors 10 | { 11 | [CustomEditor(typeof(StateMachine))] 12 | public class StateMachineEditor : Editor 13 | { 14 | public struct StateName 15 | { 16 | public readonly string fullName; 17 | public readonly string displayName; 18 | 19 | public StateName(string fullName, string displayName) 20 | { 21 | this.fullName = fullName; 22 | this.displayName = displayName; 23 | } 24 | } 25 | 26 | // State Collections 27 | 28 | /// 29 | /// Groups of states 30 | /// 31 | private static Dictionary> DisplayGroups; 32 | 33 | /// 34 | /// The names of the groups 35 | /// 36 | private static string[] GroupNames; 37 | 38 | /// 39 | /// The full name of the state types 40 | /// 41 | private static string[] StateNames; 42 | 43 | // Target 44 | private StateMachine t; 45 | 46 | // Cache selection data for easier references 47 | private int stateIndex; 48 | private string selectedState; 49 | 50 | private int groupIndex; 51 | private string selectedGroup; 52 | 53 | private string[] displayedStates; 54 | 55 | // GUI 56 | private bool toggleCurrent; 57 | private bool togglePrevious; 58 | 59 | private bool showDebug = false; 60 | 61 | private void OnEnable() 62 | { 63 | t = target as StateMachine; 64 | 65 | SetupCategories(); 66 | 67 | // If the machine has no state assigned (normally on creation, assign the default 68 | // if one exists (normally just state) 69 | if (string.IsNullOrEmpty(t.SerializedState.m_stateName)) 70 | { 71 | groupIndex = 0; 72 | stateIndex = 0; 73 | } 74 | else 75 | { 76 | GetSelectionIndexes(); 77 | } 78 | 79 | UpdateDrawnStates(); 80 | UpdateSerializedState(); 81 | } 82 | 83 | private void SetupCategories() 84 | { 85 | if (DisplayGroups != null && DisplayGroups.Count > 0) 86 | { 87 | return; 88 | } 89 | 90 | // Add all states to list for lookup 91 | Type[] assemblyTypes = UnityReflectionUtil.GetTypesInAssembly(typeof(State)); 92 | List groupsTypes = new List(); 93 | 94 | // We only want to check once as they'll update when Unity recompiles 95 | DisplayGroups = new Dictionary>(); 96 | 97 | // Create cached arrays to have quick references 98 | StateNames = new string[assemblyTypes.Length]; 99 | 100 | // Get each state 101 | for (int i = 0; i < assemblyTypes.Length; i++) 102 | { 103 | // Get state data 104 | string stateName = assemblyTypes[i].FullName; 105 | string displayName = GetStateName(stateName); 106 | 107 | string group = GetGroupName(stateName); 108 | 109 | StateName name = new StateName(stateName, displayName); 110 | if (DisplayGroups.ContainsKey(group)) 111 | { 112 | DisplayGroups[group].Add(name); 113 | } 114 | else 115 | { 116 | DisplayGroups.Add(group, new List() { name }); 117 | } 118 | 119 | StateNames[i] = stateName; 120 | groupsTypes.Add(group); 121 | } 122 | 123 | GroupNames = groupsTypes.ToArray(); 124 | } 125 | 126 | // GUI 127 | public override void OnInspectorGUI() 128 | { 129 | SetupCategories(); 130 | 131 | // Options 132 | t.isRunning = EditorGUILayout.Toggle("Is Running", t.isRunning); 133 | 134 | EditorGUILayout.BeginVertical(EditorStyles.helpBox); 135 | { 136 | EditorGUILayout.LabelField("State Selection", EditorStyles.boldLabel); 137 | 138 | string currentState = string.Format("Current State - {0}", t.SerializedState.m_stateName ?? "No Selected State"); 139 | EditorGUILayout.LabelField(currentState); 140 | 141 | DrawCategories(); 142 | DrawStates(displayedStates); 143 | 144 | EditorGUILayout.Space(); 145 | 146 | DrawRuntimeInfo(); 147 | } 148 | EditorGUILayout.EndVertical(); 149 | 150 | if (serializedObject.UpdateIfRequiredOrScript()) 151 | { 152 | Repaint(); 153 | } 154 | } 155 | 156 | private void DrawCategories() 157 | { 158 | EditorGUI.BeginChangeCheck(); 159 | { 160 | groupIndex = EditorGUILayout.Popup("State Catergory", groupIndex, GroupNames); 161 | } 162 | if (EditorGUI.EndChangeCheck()) 163 | { 164 | UpdateDrawnStates(); 165 | } 166 | } 167 | 168 | private void DrawStates(string[] states) 169 | { 170 | if (states == null) 171 | { 172 | EditorGUILayout.HelpBox("Category has no states", MessageType.Error); 173 | return; 174 | } 175 | 176 | EditorGUI.BeginChangeCheck(); 177 | 178 | //int xCount = Mathf.Min (_states.Length, 3); 179 | 180 | stateIndex = GUILayout.SelectionGrid(stateIndex, states, 3); 181 | 182 | if (EditorGUI.EndChangeCheck()) 183 | { 184 | UpdateSerializedState(); 185 | } 186 | } 187 | 188 | private void DrawRuntimeInfo() 189 | { 190 | EditorGUILayout.LabelField("Runtime State Info", EditorStyles.boldLabel); 191 | 192 | EditorGUI.indentLevel++; 193 | 194 | string currentStateStatus = t.CurrentState == null ? "Null" : t.CurrentState.ToString(); 195 | string previousStateStatus = t.PreviousState == null ? "Null" : t.PreviousState.ToString(); 196 | 197 | string currentStateLabel = string.Format("Current State ({0})", currentStateStatus); 198 | string previousStateLabel = string.Format("Previous State ({0})", previousStateStatus); 199 | 200 | EditorGUI.BeginDisabledGroup(true); 201 | 202 | DrawStateData(currentStateLabel, t.CurrentState, ref toggleCurrent); 203 | DrawStateData(previousStateLabel, t.PreviousState, ref togglePrevious); 204 | 205 | EditorGUI.EndDisabledGroup(); 206 | 207 | EditorGUI.indentLevel--; 208 | } 209 | 210 | // State GUI 211 | 212 | private void DrawStateData(string label, State state, ref bool toggle) 213 | { 214 | bool isNull = state == null; 215 | 216 | string stateName = isNull ? "" : GetStateName(state.ToString()); 217 | float age = isNull ? 0f : state.age; 218 | float fixedAge = isNull ? 0f : state.age; 219 | 220 | toggle = EditorGUILayout.Foldout(toggle, label); 221 | 222 | if (toggle) 223 | { 224 | EditorGUILayout.TextField("State Name", stateName); 225 | 226 | EditorGUILayout.FloatField("Age", age); 227 | EditorGUILayout.FloatField("Fixed", fixedAge); 228 | } 229 | } 230 | 231 | private void UpdateDrawnStates() 232 | { 233 | selectedGroup = GroupNames[groupIndex]; 234 | displayedStates = DisplayGroups[selectedGroup].Select(t => t.displayName).ToArray(); 235 | } 236 | 237 | private void UpdateSerializedState() 238 | { 239 | t.SerializedState.SetStateName(GetSelectedState()); 240 | } 241 | 242 | // Serialized State 243 | 244 | private string GetSelectedState() 245 | { 246 | selectedState = displayedStates[stateIndex]; 247 | return StateNames.FirstOrDefault(state => state.Contains(selectedState)); 248 | } 249 | 250 | private void GetSelectionIndexes() 251 | { 252 | string serializedName = t.SerializedState.m_stateName; 253 | 254 | int i = 0; 255 | foreach (KeyValuePair> group in DisplayGroups) 256 | { 257 | int index = group.Value.FindIndex(name => name.fullName == serializedName); 258 | 259 | if (index != -1) 260 | { 261 | stateIndex = index; 262 | groupIndex = i; 263 | return; 264 | } 265 | 266 | i++; 267 | } 268 | } 269 | 270 | // Helpers 271 | 272 | private string GetStateName(string stateName) 273 | { 274 | int nameIndex = stateName.LastIndexOf('.'); 275 | 276 | stateName = stateName.Substring(nameIndex + 1, stateName.Length - nameIndex - 1); 277 | 278 | // Slight clean up 279 | if (!stateName.Equals("State")) 280 | { 281 | stateName = stateName.Replace("State", ""); 282 | } 283 | 284 | return stateName; 285 | } 286 | 287 | private string GetGroupName(string stateName) 288 | { 289 | int nameIndex = stateName.LastIndexOf('.'); 290 | 291 | string category = stateName.Substring(0, nameIndex); 292 | int categoryIndex = category.LastIndexOf('.'); 293 | 294 | return stateName.Substring(categoryIndex + 1, category.Length - categoryIndex - 1); 295 | } 296 | } 297 | } -------------------------------------------------------------------------------- /FSM/Scripts/Editor/StateMachineEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 608bfbb7e81092c48a92330086da04bd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/Editor/StateManagerEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEditor; 4 | using UnityEditorInternal; 5 | using UnityEngine; 6 | using Object = UnityEngine.Object; 7 | 8 | namespace FSM.Editors 9 | { 10 | //TODO: Better lookup for states, catergories are setup painfully bad 11 | [CustomEditor(typeof(StateManager))] 12 | public class StateManagerEditor : Editor 13 | { 14 | // --- Static Data --- 15 | private static StateInfo[] states; //Editor States 16 | private static string[] stateNames; //Collection of state names 17 | 18 | // --- GUI State --- 19 | private static int catergoryIndex; //Current selected catergory 20 | private static int stateIndex; //Current selected state 21 | private static StateInfo currentState; //The state itself 22 | private static Object stateAsset; 23 | 24 | // Property Convenience 25 | private SerializedProperty stateInfo; 26 | private SerializedProperty fields; 27 | 28 | private Dictionary stateCatergories; 29 | private Dictionary stateCollection; 30 | 31 | // --- Target --- 32 | private static StateManager Target; 33 | 34 | private void OnEnable() 35 | { 36 | Target = target as StateManager; 37 | Initialize(); 38 | } 39 | 40 | private void Initialize() 41 | { 42 | // Create collections 43 | stateCatergories = new Dictionary(); 44 | stateCollection = new Dictionary(); 45 | 46 | states = Target.states.ToArray(); 47 | stateNames = new string[states.Length]; 48 | 49 | for (int i = 0; i < states.Length; i++) 50 | { 51 | StateInfo state = states[i]; 52 | string stateName = stateNames[i] = state.name; 53 | 54 | if (!stateCollection.ContainsKey(stateName)) 55 | { 56 | stateCollection.Add(stateName, state); 57 | } 58 | 59 | string[] stateType = state.stateType.FullName.Split('.'); 60 | string name = stateType.Length == 1 ? stateType[0] : stateType[stateType.Length - 2]; 61 | 62 | if (!stateCatergories.ContainsKey(name)) 63 | { 64 | stateCatergories.Add(name, new EditorCategory(name)); 65 | } 66 | 67 | stateCatergories[name].Add(stateType.Last()); 68 | 69 | } 70 | 71 | UpdateSelection(); 72 | } 73 | 74 | // GUI 75 | public override void OnInspectorGUI() 76 | { 77 | if (states == null) 78 | { 79 | GUILayout.Label("No states to display"); 80 | return; 81 | } 82 | 83 | DrawSelection(); 84 | DrawState(currentState); 85 | } 86 | 87 | private void DrawSelection() 88 | { 89 | EditorGUILayout.LabelField("State Selection", EditorStyles.boldLabel); 90 | 91 | EditorGUI.BeginChangeCheck(); 92 | { 93 | catergoryIndex = EditorGUILayout.Popup("State Catergory", catergoryIndex, stateCatergories.Keys.ToArray()); 94 | 95 | EditorCategory[] catergories = stateCatergories.Values.ToArray(); 96 | 97 | if (catergories != null && catergories.Length != 0) 98 | { 99 | EditorCategory category = catergories[catergoryIndex]; 100 | 101 | if (category != null && category.elements != null && category.elements.Count != 0) 102 | { 103 | stateIndex = GUILayout.SelectionGrid(stateIndex, stateCatergories.Values.ToArray()[catergoryIndex].elements.ToArray(), 3, EditorStyles.toolbarButton); 104 | } 105 | } 106 | } 107 | if (EditorGUI.EndChangeCheck()) 108 | { 109 | UpdateSelection(); 110 | } 111 | } 112 | 113 | private void UpdateSelection() 114 | { 115 | GUI.FocusControl(null); 116 | 117 | EditorCategory[] catergories = stateCatergories.Values.ToArray(); 118 | 119 | if (catergories != null && catergories.Length != 0) 120 | { 121 | List elements = stateCatergories.ToArray()[catergoryIndex].Value.elements; 122 | 123 | if (catergories != null || catergories.Length != 0) 124 | { 125 | stateIndex = Mathf.Clamp(stateIndex, 0, elements.Count); 126 | } 127 | 128 | if (stateIndex >= 0 && stateIndex < elements.Count) 129 | { 130 | currentState = stateCollection[elements[stateIndex]]; 131 | } 132 | else 133 | { 134 | stateIndex = 0; 135 | currentState = stateCollection[elements[0]]; 136 | } 137 | 138 | stateInfo = serializedObject.FindProperty("states").GetArrayElementAtIndex(stateIndex); 139 | fields = stateInfo.FindPropertyRelative("fields"); 140 | } 141 | } 142 | 143 | // State GUI 144 | private void DrawState(StateInfo state) 145 | { 146 | if (state == null) 147 | { 148 | return; 149 | } 150 | 151 | stateAsset = EditorUtil.GetAssetFromName(state.name); 152 | 153 | if (stateAsset == null) 154 | { 155 | stateAsset = EditorUtil.GetAssetFromName("State"); 156 | } 157 | 158 | EditorGUILayout.Space(); 159 | 160 | GUIStyle skin = new GUIStyle(GUI.skin.window) 161 | { 162 | padding = new RectOffset(2, 0, 0, 0), 163 | margin = new RectOffset(0, 0, 0, 0), 164 | }; 165 | 166 | //Draw all catergories in order for hiearchial type display 167 | if (stateAsset != null) 168 | { 169 | EditorGUILayout.InspectorTitlebar(true, stateAsset, false); 170 | } 171 | 172 | List names = new List(); 173 | 174 | //Toggle for convenience 175 | EditorGUI.indentLevel++; 176 | { 177 | for (int i = 0; i < state.fields.Count; i++) 178 | { 179 | StateFieldInfo field = state.fields[i]; 180 | string name = field.info.DeclaringType.Name; 181 | 182 | // Draw based on what the declaring type is 183 | if (!names.Contains(name)) 184 | { 185 | bool isScriptName = i == 0; 186 | 187 | if (!isScriptName) 188 | { 189 | EditorGUILayout.Space(); 190 | } 191 | 192 | if (isScriptName) 193 | { 194 | EditorGUILayout.BeginHorizontal(); 195 | { 196 | EditorGUILayout.LabelField(name, EditorStyles.boldLabel); 197 | EditorGUILayout.ObjectField(stateAsset, typeof(MonoScript), false); 198 | } 199 | EditorGUILayout.EndHorizontal(); 200 | } 201 | else 202 | { 203 | EditorGUILayout.LabelField(name, EditorStyles.boldLabel); 204 | } 205 | 206 | names.Add(name); 207 | } 208 | 209 | DrawField(state.fields[i]); 210 | } 211 | 212 | EditorGUILayout.Space(); 213 | } 214 | EditorGUI.indentLevel--; 215 | 216 | EditorUtility.SetDirty(Target); 217 | } 218 | 219 | private void DrawField(StateFieldInfo info) 220 | { 221 | foreach (object attribute in info.info.GetCustomAttributes(true)) 222 | { 223 | if (attribute is HeaderAttribute header) 224 | { 225 | EditorGUILayout.LabelField(header.header, EditorStyles.boldLabel); 226 | } 227 | } 228 | 229 | switch (info.fieldType) 230 | { 231 | case FieldType.INT: 232 | info.intValue = EditorGUILayout.DelayedIntField(info.name, info.intValue); 233 | break; 234 | case FieldType.FLOAT: 235 | info.floatValue = EditorGUILayout.DelayedFloatField(info.name, info.floatValue); 236 | break; 237 | case FieldType.STRING: 238 | info.stringValue = EditorGUILayout.DelayedTextField(info.name, info.stringValue); 239 | break; 240 | case FieldType.BOOLEAN: 241 | info.boolValue = EditorGUILayout.Toggle(info.name, info.boolValue); 242 | break; 243 | case FieldType.VECTOR3: 244 | info.vector3Value = EditorGUILayout.Vector3Field(info.name, info.vector3Value); 245 | break; 246 | case FieldType.VECTOR2: 247 | info.vector2Value = EditorGUILayout.Vector2Field(info.name, info.vector2Value); 248 | break; 249 | case FieldType.UNITY: 250 | info.unityObjectValue = EditorGUILayout.ObjectField(info.name, info.unityObjectValue, typeof(Object), false); 251 | break; 252 | case FieldType.LAYERMASK: 253 | LayerMask tempMask = EditorGUILayout.MaskField( 254 | info.name, 255 | InternalEditorUtility.LayerMaskToConcatenatedLayersMask(info.layerMaskValue), 256 | InternalEditorUtility.layers); 257 | 258 | info.layerMaskValue = InternalEditorUtility.ConcatenatedLayersMaskToLayerMask(tempMask); 259 | break; 260 | 261 | case FieldType.OBJECT: 262 | 263 | break; 264 | 265 | case FieldType.INVALID: 266 | string invalidFormat = string.Format("Invalid field type of {0} with the name {1}", info.type, info.name); 267 | EditorGUILayout.HelpBox(invalidFormat, MessageType.Warning, true); 268 | break; 269 | 270 | default: 271 | break; 272 | } 273 | 274 | } 275 | 276 | // Helpers 277 | private SerializedProperty GetFieldProperty(int index, StateFieldInfo field) 278 | { 279 | SerializedProperty serializedField = fields.GetArrayElementAtIndex(index); 280 | 281 | switch (field.fieldType) 282 | { 283 | case FieldType.INT: 284 | return serializedField.FindPropertyRelative("intValue"); 285 | 286 | case FieldType.FLOAT: 287 | return serializedField.FindPropertyRelative("floatValue"); 288 | 289 | case FieldType.STRING: 290 | return serializedField.FindPropertyRelative("stringValue"); 291 | 292 | case FieldType.BOOLEAN: 293 | return serializedField.FindPropertyRelative("boolValue"); 294 | 295 | case FieldType.VECTOR2: 296 | return serializedField.FindPropertyRelative("vector2Value"); 297 | 298 | case FieldType.VECTOR3: 299 | return serializedField.FindPropertyRelative("vector3Value"); 300 | 301 | case FieldType.LAYERMASK: 302 | return serializedField.FindPropertyRelative("layerMaskValue"); 303 | 304 | case FieldType.UNITY: 305 | return serializedField.FindPropertyRelative("unityObjectValue"); 306 | 307 | case FieldType.OBJECT: 308 | return serializedField.FindPropertyRelative("objectValue"); 309 | } 310 | 311 | return null; 312 | } 313 | } 314 | } -------------------------------------------------------------------------------- /FSM/Scripts/Editor/StateManagerEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 99b1d0eee3e91fd4aa8fd82ff7221793 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c639abb1b0d43704b87438e0810d8062 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/SerializableState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FSM 4 | { 5 | [Serializable] 6 | public class SerializableState 7 | { 8 | public string m_stateName; 9 | public Type m_stateType; 10 | 11 | public State CreateStateFromType() 12 | { 13 | if (string.IsNullOrEmpty(m_stateName)) 14 | { 15 | return new State(); 16 | } 17 | 18 | m_stateType = Type.GetType(m_stateName); 19 | 20 | if (m_stateType == null) 21 | { 22 | return null; 23 | } 24 | 25 | return (State)Activator.CreateInstance(m_stateType); 26 | } 27 | 28 | /// 29 | /// Set the name of the state to be assigned. Should only really be needed from the editor 30 | /// 31 | /// The full name of the state 32 | public void SetStateName(string name) 33 | { 34 | m_stateName = name; 35 | } 36 | } 37 | } 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/SerializableState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07973ab7861f3494e88ccdd8ebdedcd4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/State.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace FSM 5 | { 6 | /// 7 | /// Base State class 8 | /// 9 | public class State 10 | { 11 | // Connection to main GameObject 12 | protected StateMachine parent; 13 | 14 | //Timers 15 | public float age; 16 | public float fixedAge; 17 | 18 | public State() 19 | { 20 | if (Application.isPlaying) 21 | { 22 | StateManager.SetStateValues(this); 23 | } 24 | } 25 | 26 | /// 27 | /// Called when entering the state. Use for getting references or setting up 28 | /// 29 | public virtual void OnEnter() 30 | { 31 | 32 | } 33 | 34 | /// 35 | /// Called when exiting the state. 36 | /// 37 | public virtual void OnExit() 38 | { 39 | 40 | } 41 | 42 | /// 43 | /// Update tick 44 | /// 45 | public virtual void Tick(float delta) 46 | { 47 | age += Time.deltaTime; 48 | } 49 | 50 | /// 51 | /// Fixed update tick 52 | /// 53 | public virtual void FixedTick(float delta) 54 | { 55 | fixedAge += Time.fixedDeltaTime; 56 | } 57 | 58 | /// 59 | /// Late update tick 60 | /// 61 | public virtual void LateTick(float delta) 62 | { 63 | 64 | } 65 | 66 | /// 67 | /// Debug update tick 68 | /// 69 | public virtual void DebugTick(float delta) 70 | { 71 | 72 | } 73 | 74 | /// 75 | /// Set the parent of this state 76 | /// 77 | /// The state parent 78 | public void SetParent(StateMachine parent) 79 | { 80 | this.parent = parent; 81 | } 82 | 83 | public static explicit operator State(Type v) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/State.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 70b5eb80949db3e4cacc643137a27c27 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/StateMachine.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace FSM 4 | { 5 | public class StateMachine : MonoBehaviour 6 | { 7 | public bool isRunning = true; 8 | 9 | private State m_mainState; 10 | 11 | [SerializeField] private State m_currentState; 12 | [SerializeField] private State m_previousState; 13 | 14 | [SerializeField] private SerializableState m_serializedMainState = new SerializableState(); 15 | 16 | public State CurrentState => m_currentState; 17 | public State PreviousState => m_previousState; 18 | 19 | public SerializableState SerializedState => m_serializedMainState; 20 | 21 | // Unity Methods 22 | 23 | private void OnEnable() 24 | { 25 | m_mainState = m_serializedMainState.CreateStateFromType(); 26 | SetState(m_mainState); 27 | } 28 | 29 | private void Update() 30 | { 31 | if (!isRunning) 32 | { 33 | return; 34 | } 35 | 36 | if (m_currentState != null) 37 | { 38 | m_currentState.Tick(Time.deltaTime); 39 | } 40 | } 41 | 42 | private void FixedUpdate() 43 | { 44 | if (!isRunning) 45 | { 46 | return; 47 | } 48 | 49 | if (m_currentState != null) 50 | { 51 | m_currentState.FixedTick(Time.fixedDeltaTime); 52 | } 53 | } 54 | 55 | private void LateUpdate() 56 | { 57 | if (!isRunning) 58 | { 59 | return; 60 | } 61 | 62 | if (m_currentState != null) 63 | { 64 | m_currentState.LateTick(Time.deltaTime); 65 | } 66 | } 67 | 68 | private void OnDrawGizmos() 69 | { 70 | if (!isRunning) 71 | { 72 | return; 73 | } 74 | 75 | if (m_currentState != null) 76 | { 77 | m_currentState.DebugTick(Time.deltaTime); 78 | } 79 | } 80 | 81 | // State Methods 82 | 83 | /// 84 | /// Set a new state to the instance 85 | /// 86 | /// 87 | public void SetState(State state) 88 | { 89 | if (!isRunning) 90 | { 91 | return; 92 | } 93 | 94 | if (state == null) 95 | { 96 | Debug.LogError("Cannot set null state"); 97 | return; 98 | } 99 | 100 | if (m_currentState != null) 101 | { 102 | m_currentState.OnExit(); 103 | } 104 | 105 | m_previousState = m_currentState; 106 | 107 | // Update State 108 | m_currentState = state; 109 | m_currentState.SetParent(this); 110 | m_currentState.OnEnter(); 111 | } 112 | 113 | /// 114 | /// Revert the current state to the default state assigned to this instance 115 | /// 116 | public void SetToDefault() 117 | { 118 | SetState(m_mainState); 119 | } 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/StateMachine.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c7b71678d7334845a00135535107bc8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/StateManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UnityEngine; 5 | 6 | namespace FSM 7 | { 8 | public enum FieldType 9 | { 10 | //Built-in types 11 | INT = 0, 12 | FLOAT = 1, 13 | STRING = 2, 14 | BOOLEAN = 3, 15 | 16 | //Structs 17 | VECTOR2 = 4, 18 | VECTOR3 = 5, 19 | 20 | LAYERMASK = 6, 21 | 22 | //Extras 23 | UNITY = 7, 24 | OBJECT = 8, 25 | 26 | 27 | INVALID = -1, 28 | } 29 | 30 | [Serializable] 31 | public class StateFieldInfo 32 | { 33 | public string name; 34 | public Type type; 35 | public FieldInfo info; 36 | 37 | public FieldType fieldType = FieldType.INVALID; 38 | 39 | // --- Value --- 40 | public int intValue; 41 | public float floatValue; 42 | public bool boolValue; 43 | public string stringValue; 44 | 45 | public Vector2 vector2Value; 46 | public Vector3 vector3Value; 47 | public LayerMask layerMaskValue; 48 | 49 | public UnityEngine.Object unityObjectValue; 50 | public object objectValue; 51 | 52 | public StateFieldInfo(FieldInfo info) 53 | { 54 | this.info = info; 55 | this.name = info.Name; 56 | this.type = info.FieldType; 57 | 58 | this.fieldType = GetFieldType(); 59 | } 60 | 61 | /// 62 | /// Update the field information. Normally used if the field type is changed 63 | /// 64 | /// The reflected field 65 | /// The type of field 66 | public void UpdateInfo(FieldInfo _info, Type _type) 67 | { 68 | info = _info; 69 | type = _type; 70 | fieldType = GetFieldType(); 71 | } 72 | 73 | public void SetValue(object instance) 74 | { 75 | switch (fieldType) 76 | { 77 | case FieldType.INT: 78 | info.SetValue(instance, intValue); 79 | break; 80 | case FieldType.FLOAT: 81 | info.SetValue(instance, floatValue); 82 | break; 83 | case FieldType.STRING: 84 | info.SetValue(instance, stringValue); 85 | break; 86 | case FieldType.BOOLEAN: 87 | info.SetValue(instance, boolValue); 88 | break; 89 | case FieldType.VECTOR3: 90 | info.SetValue(instance, vector3Value); 91 | break; 92 | case FieldType.VECTOR2: 93 | info.SetValue(instance, vector2Value); 94 | break; 95 | case FieldType.UNITY: 96 | if (unityObjectValue == null) 97 | { 98 | return; 99 | } 100 | 101 | info.SetValue(instance, unityObjectValue); 102 | break; 103 | case FieldType.OBJECT: 104 | info.SetValue(instance, objectValue); 105 | break; 106 | 107 | } 108 | } 109 | 110 | public FieldType GetFieldType() 111 | { 112 | if (type == typeof(int)) 113 | { 114 | return FieldType.INT; 115 | } 116 | else 117 | if (type == typeof(float)) 118 | { 119 | return FieldType.FLOAT; 120 | } 121 | else 122 | if (type == typeof(string)) 123 | { 124 | return FieldType.STRING; 125 | } 126 | else 127 | if (type == typeof(bool)) 128 | { 129 | return FieldType.BOOLEAN; 130 | } 131 | else 132 | if (type == typeof(Vector2)) 133 | { 134 | return FieldType.VECTOR2; 135 | } 136 | else 137 | if (type == typeof(Vector3)) 138 | { 139 | return FieldType.VECTOR3; 140 | } 141 | else 142 | if (type == typeof(LayerMask)) 143 | { 144 | return FieldType.LAYERMASK; 145 | } 146 | else 147 | if (type.IsSubclassOf(typeof(UnityEngine.Object))) 148 | { 149 | return FieldType.UNITY; 150 | } 151 | 152 | return FieldType.OBJECT; 153 | } 154 | } 155 | 156 | [Serializable] 157 | public class StateInfo 158 | { 159 | public string name; 160 | 161 | public Type stateType; 162 | public List fields = new List(); 163 | 164 | public StateInfo(Type type) 165 | { 166 | stateType = type; 167 | name = type.Name; 168 | } 169 | } 170 | 171 | [CreateAssetMenu()] 172 | public class StateManager : ScriptableObject, ISerializationCallbackReceiver 173 | { 174 | // Reflection Flags 175 | private const BindingFlags REFLECTION_FLAGS = BindingFlags.Public | BindingFlags.Instance; 176 | 177 | // Reflection Collections 178 | public static Dictionary RegisteredStates; 179 | public List states = new List(); 180 | 181 | // Get all types 182 | public static Type[] StateTypes; 183 | 184 | #region Serialization 185 | 186 | public void OnBeforeSerialize() 187 | { 188 | OnSerialize(); 189 | } 190 | 191 | public void OnAfterDeserialize() 192 | { 193 | OnSerialize(); 194 | } 195 | 196 | private void OnSerialize() 197 | { 198 | //Get state types 199 | if (StateTypes == null) 200 | { 201 | StateTypes = UnityReflectionUtil.GetTypesInAssembly(typeof(State)); 202 | } 203 | 204 | //Return if no states exist 205 | if (StateTypes == null || StateTypes.Length == 0) 206 | { 207 | Debug.LogWarning("Cannot find any state types in the project."); 208 | return; 209 | } 210 | else 211 | if (RegisteredStates == null) 212 | { 213 | Initialize(); 214 | } 215 | } 216 | 217 | #endregion 218 | 219 | #region Data Setup 220 | 221 | /// 222 | /// Initialize or reset the state information 223 | /// All data will be lost using this, use only when needed 224 | /// 225 | public void Initialize() 226 | { 227 | RegisteredStates = new Dictionary(); 228 | 229 | //Iterate over states and add 230 | for (int i = 0; i < StateTypes.Length; i++) 231 | { 232 | Type stateType = StateTypes[i]; 233 | StateInfo state = states.Find(s => s.name == stateType.Name); 234 | 235 | if (state == null) 236 | { 237 | state = new StateInfo(stateType); 238 | states.Add(state); 239 | } 240 | else 241 | { 242 | state.stateType = stateType; 243 | } 244 | 245 | 246 | CreateStateFields(state); 247 | RegisteredStates.Add(stateType, state); 248 | } 249 | 250 | // Clean up 251 | for (int i = 0; i < states.Count; i++) 252 | { 253 | if (states[i].stateType == null) 254 | { 255 | states.RemoveAt(i); 256 | i--; 257 | } 258 | } 259 | } 260 | 261 | /// 262 | /// OnAwake call for runtime instances to make sure the reflection happens at least once at runtime 263 | /// 264 | public void OnAwake() 265 | { 266 | if (StateTypes == null) 267 | { 268 | OnSerialize(); 269 | } 270 | } 271 | 272 | /// 273 | /// Create all fields for the state given 274 | /// 275 | /// Current state we require fields for 276 | private void CreateStateFields(StateInfo state) 277 | { 278 | FieldInfo[] info = state.stateType.GetFields(REFLECTION_FLAGS); 279 | List stateFields = state.fields; 280 | 281 | //Check for invalid variables that do not exist anymore 282 | for (int i = 0; i < stateFields.Count; i++) 283 | { 284 | StateFieldInfo field = stateFields[i]; 285 | bool exists = false; 286 | 287 | for (int j = 0; j < info.Length; j++) 288 | { 289 | if (field.name == info[j].Name) 290 | { 291 | exists = true; 292 | continue; 293 | } 294 | } 295 | 296 | //If it's an invalid variable, decrement the loop and remove 297 | if (!exists) 298 | { 299 | stateFields.Remove(field); 300 | i--; 301 | } 302 | } 303 | 304 | //Get all fields 305 | for (int i = 0; i < info.Length; i++) 306 | { 307 | FieldInfo field = info[i]; 308 | 309 | bool exists = false; 310 | Type fieldType = field.FieldType; 311 | 312 | // Check if the field already exists 313 | for (int j = 0; j < stateFields.Count; j++) 314 | { 315 | StateFieldInfo stateField = stateFields[j]; 316 | 317 | if (stateField.name == field.Name) 318 | { 319 | stateField.UpdateInfo(field, fieldType); 320 | exists = true; 321 | break; 322 | } 323 | } 324 | 325 | if (exists) 326 | { 327 | continue; 328 | } 329 | 330 | //Add to state fields 331 | stateFields.Add(new StateFieldInfo(field)); 332 | } 333 | 334 | } 335 | 336 | #endregion 337 | 338 | #region Runtime/Editor Updating 339 | 340 | /// 341 | /// Assign the state values from the manager 342 | /// 343 | /// The state to assign values to 344 | public static void SetStateValues(State currentState) 345 | { 346 | if (currentState == null) 347 | { 348 | Debug.Log("Cannot set defaults to a state that is null!"); 349 | return; 350 | } 351 | 352 | Type type = currentState.GetType(); 353 | 354 | if (!RegisteredStates.ContainsKey(type)) 355 | { 356 | Debug.Log("Cannot find state type of " + type); 357 | return; 358 | } 359 | 360 | List fields = RegisteredStates[type].fields; 361 | 362 | for (int i = 0; i < fields.Count; i++) 363 | { 364 | fields[i].SetValue(currentState); 365 | } 366 | } 367 | 368 | #endregion 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/StateManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d29945ea1c40563468fe5a70b1c48380 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/StateManagerComponent.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace FSM 4 | { 5 | public class StateManagerComponent : MonoBehaviour 6 | { 7 | public StateManager manager; 8 | 9 | private void Awake() 10 | { 11 | manager.OnAwake(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /FSM/Scripts/State Machine/StateManagerComponent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9073a68837bb29f4abca895317fb9777 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/Scripts/Utility.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 522e51953812753498e66c82f278d8db 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /FSM/Scripts/Utility/UnityReflectionUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using JetBrains.Annotations; 5 | using UnityEngine; 6 | 7 | public static class UnityReflectionUtil 8 | { 9 | public static void ReflectMethods(BindingFlags flags, bool showParameters) 10 | { 11 | string reflectedString = ""; 12 | 13 | foreach (MethodInfo method in typeof(T).GetMethods(flags)) 14 | { 15 | reflectedString = method.Name + "\n"; 16 | 17 | if (showParameters) 18 | { 19 | ParameterInfo[] parameters = method.GetParameters(); 20 | 21 | if (parameters != null) 22 | { 23 | reflectedString += "("; 24 | 25 | foreach (ParameterInfo info in parameters) 26 | { 27 | reflectedString += $"{info.ParameterType} {info.Name}"; 28 | } 29 | 30 | reflectedString += ")"; 31 | } 32 | } 33 | 34 | //Write Debug, then clear string 35 | Debug.Log(reflectedString); 36 | } 37 | } 38 | 39 | public static void ReflectFields() 40 | { 41 | foreach (FieldInfo field in typeof(T).GetRuntimeFields()) 42 | { 43 | Debug.Log($"{field.FieldType} {field.Name}"); 44 | } 45 | } 46 | 47 | #region Field Info 48 | 49 | public static FieldInfo[] GetFields([NotNull] Type type, BindingFlags flags = BindingFlags.Public) 50 | { 51 | //Assembly assem = type.Assembly; 52 | if (type == null) 53 | { 54 | Debug.LogError("Cannot find type of null!"); 55 | return null; 56 | } 57 | 58 | return type.GetFields(flags); 59 | } 60 | 61 | #endregion 62 | 63 | #region Method Info 64 | 65 | public static MethodInfo[] GetMethods([NotNull] Type type, BindingFlags flags = BindingFlags.Public) 66 | { 67 | //Assembly assem = type.Assembly; 68 | if (type == null) 69 | { 70 | Debug.LogError("Cannot find type of null!"); 71 | return null; 72 | } 73 | 74 | return type.GetMethods(flags); 75 | } 76 | 77 | #endregion 78 | 79 | #region Assemblies 80 | 81 | public static Type[] GetSubclassesInAssembly(Type type) 82 | { 83 | Assembly assem = type.Assembly; 84 | 85 | if (assem == null) 86 | { 87 | Debug.Log("Cannot find assembly of " + type + "!"); 88 | return null; 89 | } 90 | 91 | return assem.GetTypes().Where(t => t.IsSubclassOf(type)).ToArray(); 92 | } 93 | 94 | public static Type[] GetTypesInAssembly(Type type) 95 | { 96 | Assembly assem = type.Assembly; 97 | 98 | if (assem == null) 99 | { 100 | Debug.Log("Cannot find assembly of " + type + "!"); 101 | return null; 102 | } 103 | 104 | return assem.GetTypes().Where(t => t.IsAssignableFrom(type) || t.IsSubclassOf(type)).ToArray(); 105 | } 106 | 107 | #endregion 108 | } -------------------------------------------------------------------------------- /FSM/Scripts/Utility/UnityReflectionUtil.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c8e67f533339af64abbd805525f54ed7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /FSM/State Manager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: d29945ea1c40563468fe5a70b1c48380, type: 3} 13 | m_Name: Entity State Manager 14 | m_EditorClassIdentifier: 15 | states: 16 | - name: State 17 | fields: 18 | - name: age 19 | fieldType: 1 20 | intValue: 0 21 | floatValue: 0 22 | boolValue: 0 23 | stringValue: 24 | vector2Value: {x: 0, y: 0} 25 | vector3Value: {x: 0, y: 0, z: 0} 26 | layerMaskValue: 27 | serializedVersion: 2 28 | m_Bits: 0 29 | unityObjectValue: {fileID: 0} 30 | - name: fixedAge 31 | fieldType: 1 32 | intValue: 0 33 | floatValue: 0 34 | boolValue: 0 35 | stringValue: 36 | vector2Value: {x: 0, y: 0} 37 | vector3Value: {x: 0, y: 0, z: 0} 38 | layerMaskValue: 39 | serializedVersion: 2 40 | m_Bits: 0 41 | unityObjectValue: {fileID: 0} 42 | -------------------------------------------------------------------------------- /FSM/State Manager.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 08c548cbe73ba8046ae38427bba84b8e 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Damian Slocombe 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 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc7185d33d237e74eb5b465575c68f8b 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Unity Finite State Machine (FSM) 4 |

5 | 6 |

A powerful lightweight Finite State Machine for Unity, taking advantage of reflection and the editor.

7 | 8 |

9 | 10 | Unity Download Link 11 | 12 | License MIT 13 |

14 | 15 |

16 | About • 17 | Usage • 18 | Installation • 19 | Support 20 |

21 | 22 | ## About 23 | This Unity FSM is extremely lightweight but carried by the power and utility of the Unity Editor. 24 | 25 | Rather than handling states through ScriptableObjects or MonoBehaviours, all states are light classes that are all collected and edited within the FSM Manager. From the FSM Manager, custom default values can be assigned without requiring any form of recompile or edit to the original script. 26 | 27 |

28 | Unity Download Link 29 |

30 | 31 | All public fields that are within the state will be shown and can be edited from the manager. When an instance changes to that state, all set values will be passed over. 32 | 33 | **Use case examples will be soon added to the repository!** 34 | 35 | ## Usage 36 | 37 | Because of it's extremely light system, there are no transition behaviour or conditions you see in other state machines. It is purely a state only system. On each compile, new created states will be recognized by the state manager, so they can also be edited. 38 | 39 | ### Scene Setup 40 | 41 | Running any FSM is extremely simple - all you require is a manager component so that the states can be retrieved: 42 | ![image](https://user-images.githubusercontent.com/31889435/118346401-01050d80-b533-11eb-9c60-1a0c232d74fd.png) 43 | 44 | And for instances, just add a State Machine to the required game objects. From here you can select the state the instance will start with, and see runtime information on the current state and the previously ran one. 45 | ![image](https://user-images.githubusercontent.com/31889435/118346394-f5b1e200-b532-11eb-96cc-a08a93762e14.png) 46 | 47 | ### State Creation 101 48 | 49 | To create a state, simply extend the base state or any custom state made: 50 | 51 | ```cs 52 | public class ExampleState : State 53 | { 54 | 55 | } 56 | ``` 57 | 58 | States will be grouped in the FSM Manager based on their namespace. This decision was chosen as it was the most convenient and structured way to group states based on the structure of the project. 59 | 60 | For example, if `MoveState` was in `States.Character.Move`, this would appear in the "Move" category in the Manager: 61 | 62 | ![image](https://user-images.githubusercontent.com/31889435/118346093-082b1c00-b531-11eb-900b-0d8b85f88b0c.png) 63 | 64 | Categories will always take the final term in the namespace. 65 | 66 | ### State Methods 67 | 68 | The following methods are the standards in each state: 69 | 70 | ```cs 71 | /// 72 | /// Called when entering the state. Use for getting references or setting up behaviour 73 | /// 74 | public virtual void OnEnter() 75 | { 76 | 77 | } 78 | 79 | /// 80 | /// Called when exiting the state. 81 | /// 82 | public virtual void OnExit() 83 | { 84 | 85 | } 86 | 87 | /// 88 | /// Update tick 89 | /// 90 | public virtual void Tick(float delta) 91 | { 92 | age += Time.deltaTime; 93 | } 94 | 95 | /// 96 | /// Fixed update tick 97 | /// 98 | public virtual void FixedTick(float delta) 99 | { 100 | fixedAge += Time.fixedDeltaTime; 101 | } 102 | 103 | /// 104 | /// Late update tick 105 | /// 106 | public virtual void LateTick(float delta) 107 | { 108 | 109 | } 110 | 111 | /// 112 | /// Debug update tick 113 | /// 114 | public virtual void DebugTick(float delta) 115 | { 116 | 117 | } 118 | ``` 119 | 120 | `OnEnter` - Will be called once when the state is created. 121 | 122 | `OnExit` - Will be called once when the state is exited. 123 | 124 | 125 | Each update method has a delta parameter that will pass Time.DeltaTime. For FixedUpdate this turns into FixedDeltaTime. 126 | 127 | `Tick(float delta)` - Represents Update 128 | 129 | `FixedTick(float delta)` - Represents FixedUpdate 130 | 131 | `LateTick(float delta)` - Represents LateUpdate 132 | 133 | `DebugTick(float delta)` - An additional update method for OnDrawGizmos. Normally used for debugging. 134 | 135 | ### Switching States 136 | 137 | Switching states is done through accessing the FSM MonoBehaviour on the GameObject: 138 | 139 | ```cs 140 | // Inside a State Class... 141 | public override void Tick(float delta) 142 | { 143 | // If the state is older than or equal to 10 seconds 144 | if (age >= 10) 145 | { 146 | parent.SetState(new OtherState()); 147 | } 148 | } 149 | ``` 150 | 151 | ### Switching State Parents 152 | 153 | If for any reason states require a new parent, you can do so through the `SetParent(StateMachine parent)` method. 154 | 155 | ## Installation 156 |

157 | Unity Package • 158 | Zip • 159 | Tar 160 |

161 | 162 | You can also clone the repository through git using the following: 163 | ```git clone https://github.com/WooshiiDev/Unity-FSM.git``` 164 | 165 | ## Support 166 | Please submit any queries, bugs or issues, to the [Issues](https://github.com/WooshiiDev/Unity-FSM/issues) page on this repository. All feedback and support is massively appreciated as it not only helps me, but allows the projects I work on to be improved. 167 | 168 | Reach out to me or see my other work through: 169 | 170 | - Website: https://wooshii.dev/ 171 | - Email: wooshiidev@gmail.com; 172 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3bfefedfc0d4b9d40975d0fd361bc47b 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------