/// <summary> /// Inserts a new state element in the state array. /// </summary> private static Opsive.Shared.StateSystem.State[] InsertStateElement(Opsive.Shared.StateSystem.State[] states, ReorderableList reorderableList, string selectedIndexKey, string name, PersistablePreset preset) { // The name has to be unique to prevent it from interferring with other state names. if (!IsUniqueName(states, name)) { var postfixIndex = 1; while (!IsUniqueName(states, name + " " + postfixIndex)) { postfixIndex++; } name += " " + postfixIndex; } // Create the element. var state = new Opsive.Shared.StateSystem.State(name, false); state.Preset = preset; var stateList = new List <Opsive.Shared.StateSystem.State>(states); stateList.Insert(0, state); reorderableList.displayRemove = stateList.Count > 1; // Select the new element. reorderableList.index = stateList.Count - 1; EditorPrefs.SetInt(selectedIndexKey, reorderableList.index); return(stateList.ToArray()); }
/// <summary> /// Draws all of the added states. /// </summary> public static void OnStateListDraw(object obj, Opsive.Shared.StateSystem.State[] states, SerializedProperty statesProperty, Rect rect, int index) { if (rect.width < 0) { return; } // States cannot be edited at runtime, nor can the Default state ever be edited. GUI.enabled = !Application.isPlaying && index != states.Length - 1; rect.x -= EditorGUI.indentLevel * Utility.InspectorUtility.IndentWidth; // Ensure the default state doesn't get changed. var state = states[index]; var stateProperty = (statesProperty != null ? statesProperty.GetArrayElementAtIndex(index) : null); if (!Application.isPlaying && index == states.Length - 1) { if (statesProperty != null && stateProperty == null) { statesProperty.InsertArrayElementAtIndex(index); stateProperty = statesProperty.GetArrayElementAtIndex(index); stateProperty.FindPropertyRelative("m_Name").stringValue = "Default"; stateProperty.FindPropertyRelative("m_Default").boolValue = true; } else if (statesProperty == null && state == null) { states[index] = state = new Opsive.Shared.StateSystem.State("Default", true); GUI.changed = true; } if (state.Name != "Default") { if (stateProperty != null) { stateProperty.FindPropertyRelative("m_Name").stringValue = "Default"; } else { state.Name = "Default"; } GUI.changed = true; } if (!state.Default) { if (stateProperty != null) { stateProperty.FindPropertyRelative("m_Default").boolValue = true; } else { state.Default = true; } GUI.changed = true; } } // Setup the field sizings. var fieldWidth = rect.width / 5; var blockedByWidth = Mathf.Max(c_MinBlockedByWidth, Mathf.Min(c_MaxBlockedByWidth, fieldWidth)) + EditorGUI.indentLevel * Utility.InspectorUtility.IndentWidth; fieldWidth = rect.width / 7; var persistWidth = Mathf.Max(c_MinPersistWidth, Mathf.Min(c_MaxPersistWidth, fieldWidth)); var activateWidth = Mathf.Max(c_MinActivateWidth, Mathf.Min(c_MaxActivateWidth, fieldWidth)); fieldWidth = (rect.width - blockedByWidth - persistWidth - activateWidth) / 2 - (c_WidthBuffer * 3); var presetWidth = Mathf.Min(c_MaxPresetWidth, fieldWidth) + EditorGUI.indentLevel * 30; var nameWidth = Mathf.Max(0, rect.width - presetWidth - blockedByWidth - persistWidth - activateWidth - (c_WidthBuffer * 6)) + EditorGUI.indentLevel * 45; var startRectX = rect.x; // The state name has to be unique. var active = state.Active && !state.IsBlocked(); var desiredName = EditorGUI.TextField(new Rect(startRectX, rect.y + 1, nameWidth, EditorGUIUtility.singleLineHeight), state.Name + (active ? " (Active)" : string.Empty), (active ? Utility.InspectorStyles.BoldTextField : EditorStyles.textField)); if (!Application.isPlaying && desiredName != state.Name && IsUniqueName(states, desiredName)) { // The name of the state that is blocking the current state should be updated. for (int i = 0; i < states.Length; ++i) { if (states[i] == state) { continue; } if (states[i].BlockList != null) { for (int j = 0; j < states[i].BlockList.Length; ++j) { if (states[i].BlockList[j] == state.Name) { if (stateProperty != null) { statesProperty.GetArrayElementAtIndex(i).FindPropertyRelative("m_BlockList").GetArrayElementAtIndex(j).stringValue = desiredName; } else { states[i].BlockList[j] = desiredName; } } } } } if (stateProperty != null) { stateProperty.FindPropertyRelative("m_Name").stringValue = desiredName; } else { state.Name = desiredName; } } startRectX += nameWidth + c_WidthBuffer - EditorGUI.indentLevel * Utility.InspectorUtility.IndentWidth; // The preset cannot be null. var desiredPreset = EditorGUI.ObjectField(new Rect(startRectX, rect.y + 1, presetWidth, EditorGUIUtility.singleLineHeight), string.Empty, state.Preset, typeof(PersistablePreset), false) as PersistablePreset; if (desiredPreset != null) { var objType = TypeUtility.GetType(desiredPreset.Data.ObjectType); if (objType != null && objType.IsInstanceOfType(obj)) { if (stateProperty != null) { stateProperty.FindPropertyRelative("m_Preset").objectReferenceValue = desiredPreset; } else { state.Preset = desiredPreset; } } else { Debug.LogError($"Error: Unable to add preset. {desiredPreset.name} ({desiredPreset.Data.ObjectType}) doesn't use the same object type ({obj.GetType().FullName})."); } } startRectX += presetWidth + c_WidthBuffer - EditorGUI.indentLevel * Utility.InspectorUtility.IndentWidth; // Create a popup of the states that can block the current state. There are several conditions which would prevent a state from being able to block // another state so this popup has to first be filtered. var stateName = state.Name; var blockList = state.BlockList; var allStates = new List <string>(); var selected = 0; for (int i = 0; i < states.Length; ++i) { var currentState = states[i]; if (currentState == null) { states[i] = currentState = new Opsive.Shared.StateSystem.State(); } // The current state cannot block the default state. if (currentState.Default) { continue; } string name; // The current state cannot block itself. if ((name = currentState.Name) == stateName) { continue; } // The selected state cannot block the current state if the current state blocks the selected state. var currentStateBlockList = currentState.BlockList; var canAdd = true; if (currentStateBlockList != null) { for (int j = 0; j < currentStateBlockList.Length; ++j) { if (stateName == currentStateBlockList[j]) { canAdd = false; break; } } } // canAdd will be false if the current state is blocking the selected state. if (!canAdd) { continue; } // The current state can block the selected state. Add the name to the popup and determine if the state is selected. A mask is used // to allow multiple selected states. allStates.Add(name); if (blockList != null) { for (int j = 0; j < blockList.Length; ++j) { if (allStates[allStates.Count - 1] == blockList[j]) { selected |= 1 << (allStates.Count - 1); break; } } } } // At least one value needs to exist. if (allStates.Count == 0) { allStates.Add("Nothing"); } // Draw the actual popup. var blockMask = EditorGUI.MaskField(new Rect(startRectX, rect.y + 1, blockedByWidth, EditorGUIUtility.singleLineHeight), string.Empty, selected, allStates.ToArray()); if (blockMask != selected) { var stateNames = new List <string>(); var blockListProperty = (stateProperty != null ? stateProperty.FindPropertyRelative("m_BlockList") : null); if (blockListProperty != null) { blockListProperty.ClearArray(); } for (int i = 0; i < allStates.Count; ++i) { // If the state index is within the block mask then that state should be added to the list. A blockMask of -1 indicates Everything. if (((1 << i) & blockMask) != 0 || blockMask == -1) { if (blockListProperty != null) { blockListProperty.InsertArrayElementAtIndex(blockListProperty.arraySize); blockListProperty.GetArrayElementAtIndex(blockListProperty.arraySize - 1).stringValue = allStates[i]; } else { stateNames.Add(allStates[i]); } } } if (blockListProperty == null) { state.BlockList = stateNames.ToArray(); } } startRectX += blockedByWidth + c_WidthBuffer; GUI.enabled = index < states.Length - 1; if (GUI.Button(new Rect(startRectX + persistWidth / 2, rect.y + 1, 18, EditorGUIUtility.singleLineHeight), Utility.InspectorStyles.PersistIcon, Utility.InspectorStyles.NoPaddingButtonStyle)) { // Populate the position map so ObjectInspector.DrawProperties to know which properties already exist. var valuePositionMap = new Dictionary <int, int>(desiredPreset.Data.ValueHashes.Length); for (int i = 0; i < desiredPreset.Data.ValueHashes.Length; ++i) { valuePositionMap.Add(desiredPreset.Data.ValueHashes[i], i); } // Loop through all of the properties on the object. var properties = Serialization.GetSerializedProperties(obj.GetType(), MemberVisibility.Public); var bitwiseHash = new System.Version(desiredPreset.Data.Version).CompareTo(new System.Version("3.1")) >= 0; // Remove and add the properties that are being serialized. for (int i = 0; i < properties.Length; ++i) { var hash = Serialization.StringHash(properties[i].PropertyType.FullName) + Serialization.StringHash(properties[i].Name); // The property is currently being serialized. if (valuePositionMap.ContainsKey(hash)) { // Add the new property to the serialization. object value = null; var property = properties[i]; if (!typeof(Object).IsAssignableFrom(property.PropertyType)) { var unityObjectIndexes = new List <int>(); Serialization.GetUnityObjectIndexes(ref unityObjectIndexes, property.PropertyType, property.Name, 0, valuePositionMap, desiredPreset.Data.ValueHashes, desiredPreset.Data.ValuePositions, desiredPreset.Data.Values, false, MemberVisibility.Public, bitwiseHash); Serialization.RemoveProperty(i, unityObjectIndexes, desiredPreset.Data, MemberVisibility.Public, bitwiseHash); // Get the current value of the active object. var getMethod = property.GetGetMethod(); if (getMethod != null) { value = getMethod.Invoke(obj, null); } // Add the property back with the updated value. Serialization.AddProperty(property, value, unityObjectIndexes, desiredPreset.Data, MemberVisibility.Public); } } } } startRectX += persistWidth + c_WidthBuffer; GUI.enabled = Application.isPlaying && index < states.Length - 1; if (GUI.Button(new Rect(startRectX + activateWidth / 2, rect.y + 1, 18, EditorGUIUtility.singleLineHeight), Utility.InspectorStyles.ActivateIcon, Utility.InspectorStyles.NoPaddingButtonStyle)) { StateManager.ActivateState(states[index], !states[index].Active, states); } GUI.enabled = true; }