Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Are custom class references serializable on ScriptableObjects?

Discussion in 'Scripting' started by BenevolenceGames, Feb 17, 2023.

  1. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128
    So I have an item with a ScriptableObject base. I would like it to carry a reference to a class "Action", which I can assign through the inspector. It seems however that no matter what I do I can not get the field assignable.

    I have made it
    [System.Serializable]
    , I have tried making it derive from Monobehaviour, making it a
    [SerializeField]
    , just nothing seems to get it done. Searches turn up hundreds of results for custom inspectors for Scriptable Objects, and how to assign other types of references to a scriptable Object, but I can't seem to find a direct answer to this. I know it probably exists, but I just can't find it. Thanks in advance.

    -- Action Class --

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Animations.Rigging;
    5.  
    6. [System.Serializable]
    7. public class Action : MonoBehaviour
    8. {
    9.     public void Execute(GameObject playerToolReference, Rig toolRig){
    10.  
    11.     }
    12. }


    --Implementation--

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [CreateAssetMenu(fileName = "New Tool", menuName = "Inventory/Tools/New Tool Data")]
    4. public class ToolData : ItemData
    5. {
    6.     [SerializeField] public Action action;
    7. }

    ScriptableObjectscreenshot.png


    - But of course if I put it on a Monobehaviour it behaves as expected --

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class WorldItem : MonoBehaviour
    6. {
    7.     public ItemData itemData;
    8.     [Min(1)] public int quantity = 1;
    9.  
    10.     public bool isLoose = false;
    11.  
    12.     [Min(.01f)] public float itemMoveSpeed = 5.0f;
    13.  
    14.     public Action action;
    15.  
    16.     void Awake(){
    17.     }
    18.  
    19.     public void AcquireWorldItem(Vector3 position){
    20.         StartCoroutine(AcquireWorldItemRoutine(position));
    21.     }
    MonobehaviourScreenshot.png
     
  2. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    BenevolenceGames likes this.
  3. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128
    Code (CSharp):
    1. public class ToolData : ItemData
    2. {
    3.     [SerializeField] public Tool_Action toolAction;
    4. }
    5.  
    6. [Serializable]
    7. public class Tool_Action{
    8.     public virtual void Execute(GameObject playerToolReference, Rig toolRig){}
    9. }
    NewTrialScreenshot.png
     
  4. tomfulghum

    tomfulghum

    Joined:
    May 8, 2017
    Posts:
    69
    If a serializable class has no serializable fields, the inspector only draws the label. If you want an object reference to drag into an object field outside of a scene, you'll have to make your Script derive from ScriptableObject and create an asset for it.

    Code (CSharp):
    1. public class ToolData : ItemData
    2. {
    3.     [SerializeField]
    4.     private ToolAction toolAction;
    5. }
    6.  
    7. public class ToolAction : ScriptableObject
    8. {
    9.     public virtual void Execute(GameObject playerToolReference, Rig toolRig) { }
    10. }
    11.  
    12. [CreateAssetMenu("MyGame/SpecificToolAction")]
    13. public class SpecificToolAction : ToolAction
    14. {
    15.     public override void Execute(GameObject playerToolReference, Rig toolRig)
    16.     {
    17.         // Do tool things
    18.     }
    19. }
    Create an asset like you would create any other asset (Right click -> Create -> MyGame -> SpecificToolAction). You should then be able to drag the asset into the object field.
     
    Last edited: Feb 17, 2023
  5. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128
    The reference is to a custom class from the scriptable object. The ScriptableObject "ItemData" defines an items data, the ScriptableObject "ToolData" extends that for tools and should carry a reference to a custom class (not a scriptable Object, just a basic class) called "Action", which I would then derive from to define each action, then drag it in the inspector and assign it to the scriptable object for that tool. That way I can just call
    someTool.action.Execute(this, toolRig)
    and whatever tool it is will just do it's behaviour.

    I hope that clears it up. In summary, not assigning a ScriptableObject to a ScriptableObject reference, need a ScriptableObject to have a serialized field where I can assign a simple class.
     
  6. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128
    Code (CSharp):
    1. using System.Collections;
    2. using System;
    3. using System.Collections.Generic;
    4. using UnityEngine.Animations.Rigging;
    5. using UnityEngine;
    6.  
    7. [CreateAssetMenu(fileName = "New Tool", menuName = "Inventory/Tools/New Tool Data")]
    8. public class ToolData : ItemData
    9. {
    10.     [SerializeField] public Tool_Action toolAction;
    11. }
    12.  
    13. [Serializable]
    14. public class Tool_Action : IDoSomething{
    15.     public virtual void Execute(GameObject playerToolReference, Rig toolRig){}
    16. }
    17.  
    18. public interface IDoSomething{
    19.     public void Execute(GameObject playerToolReference, Rig toolRig);
    20.  
    21. }
    22. [Serializable]
    23. public class HoeAction : Tool_Action{
    24.     public List<int> newList;
    25.     public override void Execute(GameObject playerToolReferenced, Rig toolRig){
    26.  
    27.     }
    28. }
    29.  
    Still not assignable. :(
     
  7. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128
    Oh my god. I just re-read your reply, Tom. I'm sorry, it was me who was confused. So you're saying if my class has no Serializable fields, it will be drawn blank in the inspector. If I want to get around that, turn my simple class into it's own scriptable object, then it will be assignable, right?
     
    tomfulghum likes this.
  8. tomfulghum

    tomfulghum

    Joined:
    May 8, 2017
    Posts:
    69
    Exactly!
     
  9. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128
    So, out of curiosity, is this specific only to Scriptable Objects? Because using the same set-up on a script that derives from MonoBehaviour yields expected results. You can see that in the pics and codes in the OP.

    I guess I'm wondering why a Monobehaviour can hold a reference to a blank simple class type, but a scriptable object can not?
     
  10. tomfulghum

    tomfulghum

    Joined:
    May 8, 2017
    Posts:
    69
    I think in your first example, Action derives from MonoBehaviour, which is why the inspector draws the object field. A simple empty class should only draw the Label, regardless if it's contained in a MonoBehaviour or ScriptableObject.
     
  11. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128
    Between that and the possible disambiguation between my Action and the native Action, that probably explains it. Thanks!
     
  12. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128

    Ok, further question -- If I make the ToolAction derive from Monobehaviour, it becomes serialized in the scriptable Object (it has a slot for assignment) yet there is no assignable scripts in the list nor can I drag and drop the appropriate one there. Why is that?

    Code (CSharp):
    1. [CreateAssetMenu(fileName = "New Tool", menuName = "Inventory/Tools/New Tool Data")]
    2. public class ToolData : ItemData
    3. {
    4.     [SerializeField] public ToolAction toolAction;
    5. }
    6.  

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Animations.Rigging;
    5.  
    6. [System.Serializable]
    7. public class ToolAction : MonoBehaviour{
    8.     public virtual void Execute(GameObject playerToolReference, Rig toolRig){}
    9. }
    Code (CSharp):
    1.  using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Animations.Rigging;
    5.  
    6. [System.Serializable]
    7. public class HoeAction : ToolAction{
    8.     public List<int> newList;
    9.     public override void Execute(GameObject playerToolReferenced, Rig toolRig){
    10.         Debug.Log("Job Done");
    11.     }
    12. }
    TrialsScrrenshot1.png TrialsScreenshot23.png TrialsScreenshot3.png
     
  13. tomfulghum

    tomfulghum

    Joined:
    May 8, 2017
    Posts:
    69
    In your last picture, HoeAction and ToolAction aren't MonoBehaviours, they're script assets that contain the code for a MonoBehaviour. In order to be able to drag them into the object field, you would have to create a prefab and add a ToolAction (or HoeAction) component to it. But then you would have to instantiate that prefab in order obtain an instance on which you could call
    Execute
    , which can be avoided by instead using a ScriptableObject asset, which is instantiated automatically.
     
  14. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128

    Hey thanks for the information. I guess I'm going to reconsider my inventory system. It seems like a waste of data to create :

    1. Scriptable Object to hold item data
    2. Derivative of that Scriptable Object to hold ToolData
    3. A new Scriptable Object to hold the tools actions
    4. A Monobehaviour of each item for in world interaction
    5. An instance of the base SOs for each item

    etc.... I really want to minimize redundant data and this spaghetification of code I'm creating....

    I'm making a Harvest Moon style game, the inventory is just a bar across the top and depending on which item is "highlighted" the action button should trigger different behaviors. EG - you highlight an apple and press space, the player eats it. You highlight the hoe and press space, it tills the ground (matching animations). And so on and so forth.....

    I've just been stuck for a while (made some other posts for help too) on this system. It sucks because it's really holding back forward production on the rest. If you have any suggestions, I'd be open to them...
     
  15. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    If you want to embed behaviour without the need for making instances of scriptable objects or monobehaviours, then you might want to look at
    [SerializeReference]
    , which lets you serialise instances of plain C# classes with polymorphism (so base class + child classes).

    There's no inspector support out of the box however, but there should be some packages out there that add this. Odin Inspector does the best job of supporting this out of the box with nothing else required.

    I use it a lot and it makes things so much easier.

    Such as here where all the wide dropdowns are SerializeReference fields:
    upload_2023-2-18_9-4-23.png
     
    BenevolenceGames likes this.
  16. BenevolenceGames

    BenevolenceGames

    Joined:
    Feb 17, 2021
    Posts:
    128

    I have Odin, but I've never really looked at it. To be honest, I kind of feel like Editor and script based assets from the store come with such a learning curve and unnecessary bloat that it's often less time-consuming to just write the things I need myself. Now for this, I might have to take a look. I just want to be done with Inventory and on to other things. I may just download a complete solution for that from the asset store.

    Yours looks so clean, so it def might be something worth looking into. Thanks!