Search Unity

Resolved ScriptableObjects all the way down: is there a better way?

Discussion in 'Scripting' started by chemicalcrux, Mar 26, 2023.

  1. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    I'm working on an item system for my game. One kind of item is the Consumable: you use it and you get some effects. Bog standard stuff (and "effects" are going to be used in many other places, like weapons).

    Each Consumable is defined by a "consumable spec" SO: a specification of what the consumable does. The spec is just a name and a list of "effects". Each effect is a ScriptableObject asset.

    So, for example, here's my class hierarchy at the moment:
    • ScriptableObject
      • Consumable Spec
        • string field: the name of the consumable
        • list field: all of the Effects this thing has
      • Effect
        • Add Health
          • float field: the amount of health to gain
    Here's an example spec:

    upload_2023-3-26_8-40-53.png

    And here's the effect:

    upload_2023-3-26_8-41-2.png

    This works well enough, but there's an annoyance: I need to create an entire asset to store that effect. This would make sense if it got reused in many places, but I generally do not want to tie the exact numbers for different consumables together. If I buff the healing potion, I should not also be buffing some random enemy that happened to have a spell that heals 60 health.

    (also, what if I change it to heal 70 health and forget to change the name? :p )

    This whole thing reminds me of this gigantic CSS anti-pattern...

    Code (CSharp):
    1. .margin16 {
    2.   margin: 16;
    3. }
    I solved a similar issue in an ECS project with a custom editor. I store a list of structs, which store the fields for all possible effects. A custom property drawer only draws the fields for the effect that is selected.

    That works, but it requires a property drawer that handles every kind of effect, and it also means that I'm making a huge struct. It felt like less of a big deal, though, as this was fed into a Baker that generated a nice, compact blob, so it wasn't like I was passing 500-byte structs around at runtime.

    What I'd like to do here is have "inline" SOs: so, rather than storing a reference to an asset, I'd just embed the effect directly into the Consumable Spec.

    Here's my very rough mock-up of what that'd be like...

    upload_2023-3-26_8-49-35.png

    Is that doable? I've seen some examples of nested editors, but that still has the child SO stored in its own asset.

    SuperUnityBuild (a very nice package, by the way!) seems to do what I'm looking for. You pick the type you want from a dropdown and hit a button to add it to the list:

    upload_2023-3-26_9-3-56.png

    I'm still trying to grok how it all works, though, since it's a whole lot of custom editors. The individual kinds of build platforms (e.g. PC) are ScriptableObjects, so that sounds very close to what I'm trying to do here.

    So, if anyone has advice on where to begin here, it'd be much appreciated :)
     
  2. CPTANT

    CPTANT

    Joined:
    Mar 31, 2016
    Posts:
    80
    Have a look at this video from Jason Weiman, I think you will get a lot of insight on this question from it:
     
    Ryiah and chemicalcrux like this.
  3. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    I had a feeling I'd be seeing SerializeReference, haha :p

    I'll give it a proper look!
     
  4. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Okay, yes, this sounds very reasonable. I actually think I saw this exact Reddit thread at one point, but I guess I wasn't actually implementing something like that at the time, so I didn't think too much about it.

    Thank you!

    edit: and with a little bit of custom editor scripting:

    upload_2023-3-26_10-50-50.png

    I'd prefer to replace "Element 0" with "Heal", but hey, close enough!

    edit 2: figured that out!

    upload_2023-3-26_11-20-48.png

    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(Effect))]
    2. public class EffectDrawer : PropertyDrawer
    3. {
    4.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    5.     {
    6.         VisualElement elem = new();
    7.         var field = new PropertyField(property);
    8.         field.label = (property.managedReferenceValue as Effect).LabelKey;
    9.         elem.Add(field);
    10.         return elem;
    11.     }
    12. }
    (note that LabelKey is a property on my Effect class that I use to look the name up in a string table; if you want to adapt this to something else, then you'd need to provide your own string)
     
    Last edited: Mar 26, 2023
    spiney199 likes this.
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,203
    Aww, you beat me to it or to be more precise you beat ChatGPT to it. Since the OP seems to have already started integrating it I thought I would just post this. I love how this thing tries to pass off that it's cutoff date is Sep 2021 and then proceeds to show knowledge of a three month old YouTube video.

    upload_2023-3-26_11-19-3.png
     
  6. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    LLMs are very good at creating language

    other qualities are not guaranteed ;)
     
  7. CPTANT

    CPTANT

    Joined:
    Mar 31, 2016
    Posts:
    80
    It is literally guessing. It has no knowledge of this but it always tries to give an answer. It quite frequently gives complete nonsense answers given with absolute confidence. This time it got it right because the question is already steering in the right direction.
     
  8. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,203
    GPT-3.5 is definitely guessing based off of my prompt but GPT-4 gave an extensive example with an explanation so I'm much less convinced it doesn't actually "know" (in the sense that a statistical model can know). Granted I didn't spend that long verifying everything because the OP already implemented their solution.