0
\$\begingroup\$

I am currently working on an editor script for a custom component I created, I am using UIElements to do this, the UI is constructed and working all apart from when I try to modify a list item of the component, I can add new list items just fine which is where my confusion is coming in.

The list in question is a list of "Reactions" a reaction is a class that contains an element(Eg:Fire, Water) definition and a list of actions to perform for that element, should it be affected by reactions element. It also contains a method for iterating over each action in the list and performing them.

[System.Serializable]
public class WidgetReaction
{ 
    public string reactionName;

    public BaseElement element;
    public WidgetAction currentAction;
    public List<WidgetAction> actions;
    public List<WidgetAction> Actions
    {
        get
        {
            if (actions != null)
            {
                return actions;
            }
            else
            {
                actions = new List<WidgetAction>();
                return actions;
            }
        }
    }
    public bool removeOnReact = false;
    [HideInInspector]
    public Widget affectorWidget = null;

    public IEnumerator DoActions(Widget _parentWidget)
    {
        foreach (var action in actions)
        {
            currentAction = action;

            if (currentAction != null)
            {
                yield return currentAction.Do(_parentWidget, this);
            }

        }
        yield break;
    }

    public void AddAction(WidgetAction action)
    {
        Actions.Add(action);
    }
}

And here is the code for Visual Element I generate for each reaction, there is another parent script that creates the rest of the UI, the reaction actions being the last one, this class is called for each 'Reaction'

public class ReactionsList : VisualElement
{
    Widget_Reaction_Component _Target;
    WidgetReaction_Inspector _Inspector;
    WidgetReaction _Reaction;
    SerializedObject serializedObject;


    public ReactionsList(WidgetReaction_Inspector _inspector, WidgetReaction widgetReaction, Widget_Reaction_Component target)
    {
        this._Inspector = _inspector;
        this._Reaction = widgetReaction;
        this._Target = target;

        this.serializedObject = new SerializedObject(_Target);
        this.serializedObject.Update();

        var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/ReactionsList.uxml");
        visualTree.CloneTree(this);

        var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/ReactionsList.uss");
        this.styleSheets.Add(styleSheet);


        #region Each Reaction's UI
        TextField reactionName = this.Query("ReactionName").First() as TextField;
        reactionName.value = _Reaction.reactionName;
        reactionName.RegisterCallback<ChangeEvent<string>>(
            e =>
            {
                this._Reaction.reactionName = e.newValue;

            }
        );

        ObjectField reactionElement = this.Query("ReactionElementField").First() as ObjectField;
        reactionElement.value = _Reaction.element;
        reactionElement.objectType = typeof(BaseElement);
        reactionElement.RegisterCallback<ChangeEvent<UnityEngine.Object>>(
            e =>
            {
                this._Reaction.element = (BaseElement)e.newValue;

            }
        );

        //container for actions
        VisualElement actionsContainer = this.Query("ActionsContainer").First() as VisualElement;


        if (this._Reaction.element)
        {
            this.style.backgroundColor = this._Reaction.element.color;
        }


        #endregion

        #region Each Reaction's Actions

        DisplayActions(actionsContainer);
       // DisplaySerializedActions(actionsContainer);

        #endregion

        #region End of Widget Reaction UI
        Button newActionButton = new Button();
        newActionButton.text = "Add Action";
        newActionButton.clicked += () => CreateNewActionFields();

        Button removeAllActionsButton = new Button();
        removeAllActionsButton.text = "Clear Reactions Action";
        removeAllActionsButton.clicked += () => _Reaction.Actions.Clear();


        this.Add(newActionButton);
        this.Add(removeAllActionsButton);
        #endregion

    }

    private void DisplayActions(VisualElement container)
    {

        for (int i = 0; i < this._Reaction.Actions.Count; i++)
        {
            ObjectField ObjectFieldForAction = new ObjectField();
            ObjectFieldForAction.objectType = typeof(WidgetAction);
            ObjectFieldForAction.value = this._Reaction.Actions[i];
            ObjectFieldForAction.RegisterCallback<ChangeEvent<UnityEngine.Object>>(
                e =>
                {


                    ObjectFieldForAction.value = (WidgetAction)e.newValue;
       //throws ArguementOutOfRange error, maybe because the call back is being fired at a time when the list doesnt exist?
                    this._Reaction.Actions[i] = ObjectFieldForAction.value as System.Object as WidgetAction;
                    EditorUtility.SetDirty(_Inspector);
                }
            );

            container.Add(ObjectFieldForAction);
        }
    }


    //Display the serialized actions of this objects widgetReactions list
    private void DisplaySerializedActions(VisualElement container)
    {
        //get widget reactions list from target of inspector (Widget Reaction Component)
        SerializedProperty seraliziedWidgetReactions = serializedObject.FindProperty("widgetReactions");

        for (int i = 0; i < seraliziedWidgetReactions.arraySize; i++)
        {

            //we only care about information on the current reaction
            if (seraliziedWidgetReactions.GetArrayElementAtIndex(i).exposedReferenceValue == _Reaction.element)
            {
                //get the WidgetActions list for this reaction
                SerializedProperty actionsForReaction = seraliziedWidgetReactions.GetArrayElementAtIndex(i).FindPropertyRelative("actions");

                //get each action from the list
                for (int y = 0; y < actionsForReaction.arraySize; y++)
                {
                    //this *should* be each item in the list for the current inspector target
                    SerializedProperty reactionAction = seraliziedWidgetReactions.GetArrayElementAtIndex(i).FindPropertyRelative("actions").GetArrayElementAtIndex(y);
                    WidgetAction action = reactionAction as System.Object as WidgetAction;

                    ObjectField actionField = new ObjectField();
                    actionField.objectType = typeof(WidgetAction);
                    actionField.value = action;
                    actionField.RegisterCallback<ChangeEvent<UnityEngine.Object>>(
                        e =>
                        {
                            //whenver field is updated, set the new value
                            actionField.value = (WidgetAction)e.newValue;
                            //try do same for list
                            action = (WidgetAction)e.newValue;
                            EditorUtility.SetDirty(_Inspector);
                        }
                    );

                    //add each action field to the container
                    container.Add(actionField);
                }
            }
        }

        //applying changes to parent serialized object (Widget_Reaction_Component)
        serializedObject.ApplyModifiedProperties();
    }

    //Function for Create New Action Button
    private void CreateNewActionFields()
    {
        VisualElement container = new VisualElement();
        container.style.flexGrow = 1;
        container.style.flexDirection = FlexDirection.Row;

        ObjectField newObjectField = new ObjectField();
        newObjectField.EnableInClassList("row", true);
        newObjectField.objectType = typeof(WidgetAction);
        newObjectField.value = ScriptableObject.CreateInstance<WAction_DoNothing>();
        newObjectField.RegisterCallback<ChangeEvent<UnityEngine.Object>>(
                    e =>
                    {
                        newObjectField.value = (WidgetAction)e.newValue;
                        //also add new action to objects list when field is changed
                        //this works correctly add adds a new list item serializing properly.
                        this._Reaction.Actions.Add((WidgetAction)newObjectField.value);
                        EditorUtility.SetDirty(_Inspector);
                    }
                );

        //Does nothing for now
        Button newObjectDeleteButton = new Button();
        newObjectDeleteButton.EnableInClassList("row", true);
        newObjectDeleteButton.text = "Delete Action";
        newObjectDeleteButton.style.backgroundColor = Color.red;
        newObjectDeleteButton.style.color = Color.white;

        container.Add(newObjectField);
        container.Add(newObjectDeleteButton);
        this.Add(container);
    }
}

As you may notice I have created two functions "DisplayActions" which iterates over the _Reactions array for this particular editor 'target' and "DisplaySerialisedActions" to do the same for the targets SerializedObject(Widget_Reaction_Comp) properties (widgetReactions list).

As I said above, the code works when adding a new action (CreateNewActionFields), it serializes as it should etc, but when I try to edit an existing 'Reaction' action from the list either by directly editing the list or through ObjectField call back (DisplayActions or DisplaySerialisedActions) I get error ArguementOutOfRange and it does nothing.

Why am I getting this behaviour when every other part of the UI has worked fine, including the reaction itself(that is I can change edit/remove reactions themselves just fine), it's just the reactions action list that is causing me problems.

I am also adding a picture of the component itself incase that helps provide some context.

Edit: Incase there is any confusion over the two functions for displaying actions, I am simply trying to get the UI to react, in reality ill only be using whichever I need.

Thank you

enter image description here

\$\endgroup\$
1
\$\begingroup\$

So I ended up figuring out what has changed I wasn't accessing the correct variable when doing my loop for one, BUT MORE IMPORTANTLY, If you've ever done any editor work of any kind you learn to slap EditorUtility.SetDirty(object) almost everywhere you make a change but in 5.3 this no longer works, it throws an error to console and does nothing.

Now it seems to get Unity to save the changes you need to call Undo.RecordObject(Object);.

So for me thats Undo.RecordObject(_target, "Adding Reaction"); "_target" being the currently targetted game object and the second parameter just a string describing the action.

Now this should be enough if you are editing a prefab but if you are editing a prefab instance like me then you also need to call PrefabUtility.RecordPrefabInstancePropertyModifications(_target);

If anyone needs I can edit with fully working script.

Here for example is where I add a reaction, to the prefabs list

    private void CreateNewReaction(BaseElement element)
    {
        var newReaction = new WidgetReaction();
        newReaction.reactionName = "New Reaction";
        newReaction.element = element;
        _target.widgetReactions.Add(newReaction);
        //THIS SAVES IT
        Undo.RecordObject(_target, "Adding Reaction");
        //THIS NEEDS TO BE CALLED ALSO IF YOURE WORKING WITH AN INSTANCE OF A PREFAB
        PrefabUtility.RecordPrefabInstancePropertyModifications(_target);
    }
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.