示例#1
0
        /// <summary>
        /// Draws all of the serialized properties.
        /// </summary>
        /// <param name="type">The type of object to draw.</param>
        /// <param name="obj">The drawn object. This value can be null (such as if it hasn't been serialized).</param>
        /// <param name="hashPrefix">The prefix of the hash from the parent class. This value will prevent collisions with similarly named objects.</param>
        /// <param name="valuePositionMap">A map between the value hash and the position within the positions array.</param>
        /// <param name="serialization">The serialized data.</param>
        /// <param name="visibility">Specifies the visibility of the properties that should be drawn.</param>
        /// <param name="startDrawElementCallback">Callback issued before the elements are drawn.</param>
        /// <param name="endDrawElementCallback">Callback issued after the elemts are drawn.</param>
        /// <returns>The updated object value based on the drawn properties.</returns>
        public static object DrawProperties(Type type, object obj, int hashPrefix, Dictionary <int, int> valuePositionMap, Serialization serialization, MemberVisibility visibility, Action startDrawElementCallback, Func <int, List <int>, bool> endDrawElementCallback)
        {
            // Only the serialized properties need to be drawn.
            var properties = Serialization.GetSerializedProperties(type, visibility);

            for (int i = 0; i < properties.Length; ++i)
            {
                var bitwiseHash = new Version(serialization.Version).CompareTo(new Version("3.1")) >= 0;
                int hash;
                if (bitwiseHash)
                {
                    hash = (hashPrefix * Serialization.HashMultiplier) ^ (Serialization.StringHash(properties[i].PropertyType.FullName) + Serialization.StringHash(properties[i].Name));
                }
                else
                {
                    hash = hashPrefix + Serialization.StringHash(properties[i].PropertyType.FullName) + Serialization.StringHash(properties[i].Name);
                }
                int position;
                // The value may not be serialized.
                if (!valuePositionMap.TryGetValue(hash, out position))
                {
                    continue;
                }

                // Issue the start callback before the value is drawn.
                if (startDrawElementCallback != null)
                {
                    startDrawElementCallback();
                }
                var value = Serializer.BytesToValue(properties[i].PropertyType, properties[i].Name, valuePositionMap, hashPrefix, serialization.Values,
                                                    serialization.ValuePositions, serialization.UnityObjects, false, visibility, bitwiseHash);

                EditorGUI.BeginChangeCheck();

                // Get a list of Unity Objects before the property is drawn. This will be used if the property is deleted and the Unity Object array needs to be cleaned up.
                var unityObjectIndexes = new List <int>();
                Serialization.GetUnityObjectIndexes(ref unityObjectIndexes, properties[i].PropertyType, properties[i].Name, hashPrefix, valuePositionMap, serialization.ValueHashes, serialization.ValuePositions,
                                                    serialization.Values, false, visibility, bitwiseHash);

                // Draw the property.
                value = DrawObject(new GUIContent(InspectorUtility.SplitCamelCase(properties[i].Name)), properties[i].PropertyType, value, properties[i].Name, hashPrefix, valuePositionMap, serialization, properties[i], false);

                // Issue the end callback after the value is drawn.
                if (endDrawElementCallback != null)
                {
                    var elementRemoved = endDrawElementCallback(i, unityObjectIndexes);
                    if (elementRemoved)
                    {
                        return(obj);
                    }
                }

                if (EditorGUI.EndChangeCheck())
                {
                    // If the hash prefix isn't zero then the object shouldn't be serialized immediately because it isn't a base property. Set the value of the parent object and mark the GUI as changed
                    // so it'll be serialized by the base property. For example, if ClassA exists and has properties One and Two. If the One value is changed the serializer should serialize the ClassA object
                    // rather then the One object.
                    if (hashPrefix != 0)
                    {
                        GUI.changed = true;
                        var setMethod = properties[i].GetSetMethod();
                        if (setMethod != null)
                        {
                            setMethod.Invoke(obj, new object[] { value });
                        }
                        return(obj);
                    }

                    // Remove the current element and then add it back at the end. The order of the values doesn't matter and this prevents each subsequent element from needing to be modified because the current
                    // value could have changed sizes.
                    Serialization.RemoveProperty(i, unityObjectIndexes, serialization, visibility, bitwiseHash);

                    // Add the property to the Serialization data.
                    Serialization.AddProperty(properties[i], value, unityObjectIndexes, serialization, visibility);

                    break;
                }
            }
            return(obj);
        }
示例#2
0
        /// <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;
        }
示例#3
0
        /// <summary>
        /// Draws the preset values within the inspector.
        /// </summary>
        public override void OnInspectorGUI()
        {
            // Show all of the fields.
            serializedObject.Update();

            EditorGUI.BeginChangeCheck();

            EditorGUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            var preset    = target as PersistablePreset;
            var fullName  = preset.Data.ObjectType;
            var splitName = fullName.Split('.');

            GUILayout.Label(splitName[splitName.Length - 1] + " Preset", EditorStyles.boldLabel);
            GUILayout.FlexibleSpace();
            EditorGUILayout.EndHorizontal();

            // Show the property values within a table.
            var objType = UnityEngineUtility.GetType(preset.Data.ObjectType);

            if (objType != null)
            {
                // Populate the position map so ObjectInspector.DrawProperties to know which properties to draw.
                var valuePositionMap = new Dictionary <int, int>(preset.Data.ValueHashes.Length);
                for (int i = 0; i < preset.Data.ValueHashes.Length; ++i)
                {
                    valuePositionMap.Add(preset.Data.ValueHashes[i], i);
                }

                // Draw all of the serialized properties. Implement the start and end callbacks so the delete button can be drawn next to a foldout in the case of a list, class, or struct.
                ObjectInspector.DrawProperties(objType, null, 0, valuePositionMap, preset.Data, m_Visiblity, () => { GUILayout.BeginHorizontal(); }, (int index, List <int> unityObjectIndexes) =>
                {
                    InspectorUtility.RecordUndoDirtyObject(preset, "Change Value");
                    var removed = false;
                    if (GUILayout.Button(InspectorStyles.DeleteIcon, InspectorStyles.NoPaddingButtonStyle, GUILayout.Width(16)))
                    {
                        RemoveElement(index, unityObjectIndexes);
                        removed = true;
                    }
                    serializedObject.ApplyModifiedProperties();
                    GUILayout.EndHorizontal();
                    return(removed);
                });
            }

            GUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            GUI.enabled = m_AvailableProperies.Count > 0 && !Application.isPlaying; // Only allow the popup if properties can be selected.
            var selectedPropertyIndex = EditorGUILayout.Popup(0, m_AvailablePropertyNames, GUILayout.MaxWidth(150));

            GUI.enabled = true;
            GUILayout.EndHorizontal();
            // If the selected property index isn't 0 then a property should be added.
            if (selectedPropertyIndex != 0)
            {
                var property = m_AvailableProperies[selectedPropertyIndex - 1];
                if (property != null)
                {
                    // Add the new property to the serialization.
                    object value = null;
                    if (!typeof(UnityEngine.Object).IsAssignableFrom(property.PropertyType))
                    {
                        // Lists require special handling.
                        if (typeof(IList).IsAssignableFrom(property.PropertyType))
                        {
                            if (property.PropertyType.IsArray)
                            {
                                var elementType = property.PropertyType.GetElementType();
                                value = Array.CreateInstance(elementType, 0);
                            }
                            else
                            {
                                var baseType = property.PropertyType;
                                while (!baseType.IsGenericType)
                                {
                                    baseType = baseType.BaseType;
                                }
                                var elementType = baseType.GetGenericArguments()[0];
                                if (property.PropertyType.IsGenericType)
                                {
                                    value = Activator.CreateInstance(typeof(List <>).MakeGenericType(elementType)) as IList;
                                }
                                else
                                {
                                    value = Activator.CreateInstance(property.PropertyType) as IList;
                                }
                            }
                        }
                        else
                        {
                            var getMethod = property.GetGetMethod();
                            if (getMethod != null)
                            {
                                // A new GameObject must be created so the component can be added to it. MonoBehaviours cannot use Activator.CreateInstance.
                                GameObject gameObject = null;
                                object     obj;
                                var        objectType = UnityEngineUtility.GetType(preset.Data.ObjectType);
                                if (typeof(MonoBehaviour).IsAssignableFrom(objectType))
                                {
                                    gameObject = new GameObject();
                                    obj        = gameObject.AddComponent(objectType);
                                }
                                else
                                {
                                    obj = Activator.CreateInstance(objectType);
                                }
                                value = getMethod.Invoke(obj, null);
                                if (value == null)
                                {
                                    if (getMethod.ReturnType == typeof(string))
                                    {
                                        value = string.Empty;
                                    }
                                    else
                                    {
                                        value = Activator.CreateInstance(getMethod.ReturnType);
                                    }
                                }
                                if (gameObject != null)
                                {
                                    DestroyImmediate(gameObject);
                                }
                            }
                        }
                    }
                    Serialization.AddProperty(property, value, null, preset.Data, m_Visiblity);
                    InitializeAvailablePropertyArray();
                }
            }

            if (EditorGUI.EndChangeCheck())
            {
                InspectorUtility.RecordUndoDirtyObject(preset, "Change Value");
                serializedObject.ApplyModifiedProperties();
            }
        }