Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Epicepicness · Feb 13, 2020 at 01:24 PM · listserializationscriptableobjectcustom-inspector

Custom Inspector; adding to list not getting saved

Heya,

I'm trying to add actions to a list on a ScriptableObject, through a custom inspector. The list is of an abstract class (ActionBase), the things I want to add are derived from this class. The only way I found of achieving this is by adding these actions per button in the inspector.


Initially the button adds the action, and it's displayed in the inspector. However, whenever the inspector script changes; Unity's play button is pressed; or I reopen Unity, these actions are lost (all other data gets saved correctly).

I figure this is because of some serialization issue; but this is a topic I've never fully managed to wrap my head around, and after a lot of reading and searching I haven't been able to find a solution that fixes this.


 // The Scriptable Object; with a list of actions.
 public class Ability : ScriptableObject {
     public List<ActionBase> eventActions = new List<ActionBase> ();
 }
 
 // The base action class
 [System.Serializable]
 public abstract class ActionBase {
     public virtual void DrawCustomInspector () {
         // Some stuff to draw relevant inspector stuff.
     }
 }
 
 // One of the derived classes we want to add to the list.
 [System.Serializable]
 public class ActionDamage : ActionBase {
     public override void DrawCustomInspector () {
         // Additional stuff to draw relevant inspector stuff.
     }
 }


The Custom Inspector script: If a button is pressed, add a new Action to the list; then call the draw functions for each action.

 [CustomEditor (typeof(Ability))]
 public class AbilityCustomInspector : Editor {
 
     public override void OnInspectorGUI () {
         serializedObject.Update ();
         
         Ability ability = (Ability)target;
         if (GUILayout.Button ("Damage", GUILayout.Width (60))) { 
             ability.abilityActions.Add (new ActionDamage ()); 
         }
         for (int i = 0; i < ability.abilityActions.Count; i++) {
             ability.abilityActions [i].DrawCustomInspector ();
         }
         
         serializedObject.ApplyModifiedProperties ();
     }
 }


Note: In the actual script there's another step in between; ability has an array of events, these events have a list of actions. These events don't have a separate inspector class nor function. I don't expect this is relevant to the issue so I left it out to keep the example simple. Mentioning it here, just in case.


Thank you very much in advance. :)

Comment
Add comment · Show 10
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bonfire-Boy · Feb 13, 2020 at 02:18 PM 0
Share

First thing to note is that you want to top-and-tail your OnInspectorGUI function with code to make it update and save, something like this

 SerializedObject serializedObj = new SerializedObject(target);
 
 OnEnable()
 {
     serializedObj = new SerializedObject(target);
 }
 
 public override void OnInspectorGUI()
 {
     serializedObj.Update();
 
     // do stuff
             
     serializedObj.Apply$$anonymous$$odifiedProperties();        
 }
 
avatar image Epicepicness Bonfire-Boy · Feb 13, 2020 at 02:37 PM 0
Share

First off, thanks for the reply! :D Actually already had Update and Apply$$anonymous$$odifiedProperties in my code; had forgotten to include those in the example I gave (updated that as well). So it's closer to this:

 [CustomEditor (typeof(Ability))]
 public class AbilityCustomInspector : Editor {
 
     public override void OnInspectorGUI () {
         serializedObject.Update ();
         
         Ability ability = (Ability)target;
         if (GUILayout.Button ("Damage", GUILayout.Width (60))) { 
             ability.abilityActions.Add (new ActionDamage ()); 
         }
         for (int i = 0; i < ability.abilityActions.Count; i++) {
             ability.abilityActions [i].DrawCustomInspector ();
         }
         
         serializedObject.Apply$$anonymous$$odifiedProperties ();
     }
 }

Don't know if there's a benefit to making a new SerializedObject, as the built-in Editor.serializedObject seems to be the same thing? Regardless; the OnInspectorGUI edits a whole bunch of things correctly (that do get saved) such as the ability's name and description. The only thing that doesn't get saved correctly is specifically the new AbilityAction in the list.

avatar image Bonfire-Boy Epicepicness · Feb 13, 2020 at 02:45 PM 0
Share

Ok, well then the next thing I'd consider is your approach to serialisation of members of a class hierarchy. I'm not sure that what you're trying to do can work. You're serialising using the base class - how can it know what type to deserialise into?

I suspect that there is a solution to that, which would involve doing more than implementing an Inspector.

But I've tended to handle this a different way, which is to make the Action classes also ScriptableObjects. It makes it easy to change the configuration, makes it possible to share Actions between Abilities, and makes the serialisation side of things more straightforward.

When I do this, I often have a toggle in the enclosing type's inspector, that lets me switch between looking at a simple list of the actions (which are just fields referencing the action assets), and a more detailed view in which those actions are displayed in detail allowing me to edit them from there.

Show more comments
Show more comments

2 Replies

· Add your reply
  • Sort: 
avatar image
1
Best Answer

Answer by jkpenner · Feb 14, 2020 at 05:52 AM

There are a few things that I believe are causing you propblem. From my experience with custom editors, you are mixing two different systems. I believe the serializedObject.ApplyModifiedProperites only apply changes to that serializedObject and not to changes that effect the Editor target value. So you may need something similar to below:

 public override void OnInspectorGUI () {
     serializedObject.Update();
 
     // Find the property off the serializedObject
     var abilities = serializedObject.FindProperty("eventActions");
 
     if (GUIlayout.Button("Damage")) {
         // Adds a new element at the end of the abilities array
         // this will copy the last element into the new index
         abilities.arraySize++;
 
         // Gets a serializedProperty for the new element
         var element = abilities.GetArrayElementAtIndex(abilities.arraySize - 1);
 
         // Assign the objectReference to the ActionDamage
         element.objectReferenceValue = new ActionDamage();
     }
 
     // Loop through all elements of the list
     for(var i = 0; i < abilities.arraySize; i++) {
         // Gets a serializedProperty for the element at the index
         var element = abilities.GetArrayElementAtIndex(i);
 
         // Use the serializedProperty and draw default property
         EditorGUILayout.PropertyField(element);
     }
 
     serializedObject.ApplyModifiedProperties();
 }



There is one other change that you will need to make, since unity doesn't defaultly serialized subclasses. You can make your ActionBase a ScriptableObject and then use the AssetDatabase to combine it with your Ability asset.

 [System.Serializable]
 public abstract class ActionBase : ScriptableObject {
     // action list
 }



Then in the above create damage action button you can add the following

 // Create an instance of the scriptableObject action
 var asset = ScriptableObject.CreateInstance(typeof(ActionDamage));
 // Assign the asset as the element's reference
 element.objectReferenceValue = asset;
 // Add the new action asset to the current object
 AssetDatabase.AddObjectToAsset(serializedObject.targetObject, asset);
 
 // Save the changes to the asset database
 AssetDatabase.SaveAssets();
 // Refresh to show changes in the editor
 AssetDatabase.Refresh();



I hope that all works, I didn't have unity when writing this up.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
0

Answer by Epicepicness · Feb 15, 2020 at 10:11 PM

Heya,

Thank you very much for the answer. Ended up going with jkpenner's solution, and managed to get it to work. Posting my final result below, so in case someone stumbles upon this in the future they might get some use out it maybe. It's not the most elegant creation ever, but it works (mostly).


 [System.Serializable]
 public class Ability : ScriptableObject { public ActionBase[] actions; }
 
 [System.Serializable]
 public abstract class ActionBase : ScriptableObject { // Some variables }
 [System.Serializable]
 public class ActionDamage : ActionBase { // Some more variables }
  

And then the actual Editor script:


 [CustomEditor (typeof (Ability))]
 public class AbilityCustomInspector : Editor {
 
     AbilityData ability;
 
     public void OnEnable () {
         ability = (AbilityData) target;
     }
 
     public override void OnInspectorGUI () {
         serializedObject.Update ();
 
         // Get a reference to the actions array.
         SerializedProperty actions = serializedObject.FindProperty ("actions");
 
         // Way of increasing the size of the array. (arraySize++ caused some index issues between the SerializedProperty and target).
         GUIContent content = new GUIContent ("Number of Actions", "The number of actions related to this event.");
         EditorGUILayout.PropertyField (actions.FindPropertyRelative ("Array.size"), content, GUILayout.Width (200));
         if (ability.actions.Length < actions.arraySize) { // This is needed, because it copys the value of the previous index in the array, and we need it on null.
             for (int i = ability.actions.Length; i < actions.arraySize; i++) {
                 actions.GetArrayElementAtIndex (i).objectReferenceValue = null;         
             }
         }
     
         for (int i = 0; i < ability.actions.Length; i++) {
                 SerializedProperty element = actions.GetArrayElementAtIndex (i);
 
                 if (element.objectReferenceValue == null) {
                     // If the action in the SerializedProperty array hasn't been set yet, show buttons to make actions of each type. 
                     if (GUILayout.Button ("Damage", GUILayout.Width (60))) {
                         element.objectReferenceValue = AddNewAsset<ActionDamage> (ref targetAction);
                     }
                 }
                 else {
                     // Otherwise simply show the property in the inspector normally.
                     EditorGUILayout.PropertyField (element);
                 }
             }
         serializedObject.ApplyModifiedProperties ();
     }
 
     // This creates a new asset of the given type, and returns the created asset so it can be set in the current array.
     private Object AddNewAsset<T> (ref ActionBase targetAction) where T : ActionBase {
         ScriptableObject asset = ScriptableObject.CreateInstance (typeof (T));
         targetAction = (T) asset;
 
         AssetDatabase.AddObjectToAsset (targetAction, ability);
         AssetDatabase.ImportAsset (AssetDatabase.GetAssetPath (targetAction));
 
         return asset;
     }
 }

Thanks again for the help (you as well, Bonfire-Boy)! :)

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

137 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Scriptable Object's Data Gets Lost After Re-opening Unity!!! 1 Answer

List of ScriptableObjects lost on project reload 1 Answer

ScriptableObject with Custom Editor resetting data in inspector 1 Answer

Unable to serialize my list in a Unity Custom Editor script. 1 Answer

Difference between assigning a value in inspector and with a custom editor script 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges