Search Unity

Custom Editor to edit an array of custom classes

Discussion in 'Scripting' started by fish_heads88, Feb 21, 2017.

  1. fish_heads88

    fish_heads88

    Joined:
    Dec 9, 2015
    Posts:
    6
    So I have a class called GameEvent which can be attached as a component to game objects in the scene that I want to function as trigger objects, to trigger the GameEvent.

    GameEvent has an array of a custom class, GameCommand. GameCommand is an abstract class deriving from ScriptableObject. There are several classes like ShowText, StartBattle, ShowChoices, GiveGold etc which inherit from GameCommand and have their own variables and code to execute. For example, ShowText has a string variable and when it executes it pops up a window that displays the string.

    I'm trying to build a custom editor for GameEvents which lets you modify the array of GameCommands and then modify each GameCommand separately within the array (e.g. have a text field to edit the text if the particular GameCommand is a ShowText command).

    In the custom editor I have buttons for Add and Remove which adds and removes elements from the GameCommand array.
    But... it adds empty elements (see picture).

    How can I make it add ACTUALLY ADD a GameCommand object to the array, which is initially constructed with empty variables so that I can then display and edit those variables in the inspector?



    and here is the relevant code in the custom editor:
    Code (CSharp):
    1. [CustomEditor(typeof(GameEvent))]
    2. public class GameEventEditor : Editor
    3. {
    4.     public SerializedProperty commands;
    5.  
    6.     void OnEnable(){
    7.         commands = serializedObject.FindProperty("Commands");
    8.     }
    9.  
    10.     public override void OnInspectorGUI(){
    11.         serializedObject.Update();
    12.  
    13.         ArrayGUI();
    14.  
    15.         serializedObject.ApplyModifiedProperties();
    16.     }
    17.  
    18.     private void ArrayGUI(){
    19.         for(int i=0; i<commands.arraySize; i++){
    20.             EditorGUILayout.PropertyField(commands.GetArrayElementAtIndex(i),new GUIContent("Command " + (i+1).ToString() + ": "));
    21.         }
    22.  
    23.         GUILayout.BeginHorizontal();
    24.         if(GUILayout.Button("Add Command")){
    25.             commands.arraySize++;
    26.         }
    27.         if(GUILayout.Button("Remove Command")){
    28.             commands.arraySize--;
    29.         }
    30.         GUILayout.EndHorizontal();
    31.     }
    32. }
    as you can see, right now I just have arraySize++, but I can't figure out how to modify what I place in the array....
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    I think you're better off, in this case, avoiding the serializedObject technique. Given the particular functionality you need to do, you're probably better off using target to access and modify GameEvents members directly. You'll lose Undo functionality in the editor, but the ability to directly adjust properties will give you more flexibility.

    If you do, you'll be able to populate the array with anything you want via ( (GameEvents)target).Commands, and you can set them to a new object as you add to the array. It'll also be easier to fill the slots with your derived classes. And you'll be more able to throw in custom editors for these commands anywhere you see fit.
     
  3. fish_heads88

    fish_heads88

    Joined:
    Dec 9, 2015
    Posts:
    6
    Excellent idea!
    I tried that out and it works great so far, however I'm still kind of stuck with it comes to actually editing the GameCommand class. I had tried out a custom editor for the GameCommand class before, which required it to be a serialized object, so I tried that out first because it seemed easiest:

    Code (CSharp):
    1. [CustomEditor(typeof(GameCommand),true)]
    2. public class GameCommandEditor : Editor
    3. {
    4.     private SerializedObject command;
    5.     private SerializedProperty prop;
    6.  
    7.     void OnEnable(){
    8.         command = new SerializedObject(target);
    9.     }
    10.  
    11.     void OnInspectorGUI(){
    12.         command.Update();
    13.  
    14.         prop = command.GetIterator();
    15.         while(prop.NextVisible(true)){
    16.             EditorGUILayout.PropertyField(prop,new GUIContent(prop.ToString()));
    17.         }
    18.  
    19.         command.ApplyModifiedProperties();
    20.     }
    21. }
    and here is the updated code inside the GameEvent custom editor which allows for editing the GameCommands in the array:
    Code (CSharp):
    1.         for(int i=0; i<inspected.Commands.Length; i++){
    2.             SerializedProperty command = serializedObject.FindProperty("Commands.Array.data[" + i.ToString() + "]");
    3.             EditorGUILayout.PropertyField(command,new GUIContent("Command " + (i+1).ToString() + ": "),true);
    4.         }
    and yet for some reason it still looks like this:


    its not a huge deal, I can code some more custom editors for the GameCommands without using them as SerializedProperties or as SerializedObjects.
    I was just hoping I could still edit each individual GameCommand in the Commands array as a property to make things easy on myself.

    EDIT: I should have specified, the ShowText class inherits from GameCommand and has two properties, string NameBox; and string Text; which should be showing up there.
     
  4. fish_heads88

    fish_heads88

    Joined:
    Dec 9, 2015
    Posts:
    6
    Well, I went ahead and made a custom inspector for each of the GameCommand inheriting classes (ShowText, etc) and built it without any SerializableObjects or SerializablePropertys, only editing the values of the GameEvent object directly.

    It worked great... until I pressed play.

    It turns out that the array of GameCommands on the GameEvent object will save just fine, but when the scene changes or you start the game, all the actual GameCommands data gets lost... so you have an array filled with empty elements.

    I can't figure out why, GameCommands should all serialize right? since they are all ScriptableObjects?
     
  5. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    ScriptableObjects, although serialized, always show up as "linked" objects, just like if you were linking a Transform or MonoBehaviour. They're best used for things like storing the properties of something in an asset file.

    If you're not planning on sharing the same GameCommand object between multiple objects, use a basic serialized class:
    Code (csharp):
    1.  
    2. [System.Serializable]
    3. public class GameCommand {
    4.  
    That will expand all its members in the inspector.
     
  6. fish_heads88

    fish_heads88

    Joined:
    Dec 9, 2015
    Posts:
    6
    The reason I was using ScriptableObjects is because GameCommand is an abstract class, and all the actual command data is stored in subclasses such as ShowText, ChoiceMenu, Battle, etc. in a basic command pattern setup.
    I probably jumped the gun on using ScriptableObjects because I don't fully understand them and never used it before. It was just an attempt to get serialization working with inheritence, so I could see it in the editor...
     
  7. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    Oooh. Abstract classes do make that tricky. ScriptableObject is not the solution to that, though.

    I don't have a ready-made answer for you, and it'd take more time researching than I'm willing to put into a forum post at the moment. I can say that digging through the Editor class in the documentation may help you.
     
  8. fish_heads88

    fish_heads88

    Joined:
    Dec 9, 2015
    Posts:
    6
    I appreciate the help, though! Saved me a lot of time figuring things out on my own.

    It looks like I'll either have to restructure the command pattern without inheritance somehow (I guess it wouldn't really be a command patter then, but oh well), or find a custom serialization script in the asset store...
     
  9. Yiming075

    Yiming075

    Joined:
    Mar 24, 2017
    Posts:
    33
    Do you find a better way to do that?