Search Unity

Suggestion: Prefab Variants for any Object (ScriptableObject Variants)

Discussion in 'Prefabs' started by BlaiseRoth, Nov 19, 2018.

  1. BlaiseRoth

    BlaiseRoth

    Joined:
    Oct 11, 2016
    Posts:
    8
    In my game, I have a bunch of settings ScriptableObjects that I use to specify different values for an objects in my game. What I'd like to be able to do is make a new settings object, and only override certain settings, so the rest stay in sync with the original settings object. With prefab variants, this is now possible, but my settings objects need to have an accompanying GameObject, which is not ideal. It makes it clunky to load these settings objects, because I can no longer load them by type, and instead I need to store a reference to them somewhere.

    So what I'd really like is if Prefab Variants were generalised to work on any UnityEngine.Object, so I can make a variant of any asset I like. Mostly I'd use this for ScriptableObjects, but I'm sure there's bound to be other uses for this too.

    Thanks for reading.
     
  2. KazenoZ

    KazenoZ

    Joined:
    Jan 24, 2015
    Posts:
    8
    +1

    Similar use case in my project:
    I made an AI system where I define a behavioural structure of a CP by a collection of traits (Actions they can choose to take), and a few general levers, such as simulated reaction time.

    upload_2019-1-10_9-57-6.png

    I can define different AI difficulty levels or create different behaviours for different game modes by changing the reaction time or values used by each trait to decide whether to choose it at a certain time. But I need to create an asset for each difficulty setting that may very well only differ by a single value being changed (Such as the reaction time), and that means that every time I want to change the basic balance of the core AI, I need to make sure to do it on all difficulty levels separately.

    Would be awesome to have the ability to use variants here.
     
    ModLunar likes this.
  3. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    Same here. I make database using scriptable objects, and I want to be able to make some elements to be variants of others. Using prefabs for such things makes it much more complicated, also that way I can't store whole database as single object anymore. Plus it's still pretty bugged right now and link to prefabs get broken all the time.
     
    ModLunar likes this.
  4. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    +1

    I've managed to build something like this myself, but it would be incredibly rad if there was a feature like this, since my implementation leaves much to be desired
     
    ModLunar likes this.
  5. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    Hi all,

    Assets variants is something completely different to prefab variants and would require a different implementation in our asset database.

    Prefab variants are basically prefab instances + modifications which is simply nesting at the root of a prefab so variants were simple to do while we were already doing nesting.

    So don't get your hopes too high about asset variants.
     
    Last edited: Feb 12, 2019
    joshcamas likes this.
  6. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Unsurprising, that makes a lot of sense. :)
     
  7. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    That's a shame, really. I got used to having inheritance of configs when modding games like Stalker, Max Payne, etc. It seems like a standard tool in game development. Now I see only 2 options to solve this:
    - Use clunky MonoBehaviors for configs and put them in prefabs/variants (a hack at best)
    - Don't use ScriptableObjects at all for configs instead using some kind of INI or YAML format with inheritance/overriding support. Which is also a big shame, you'll lose all the awesome editor inspector functionality and the already existing mechanics of overriding properties of serialized objects

    @SteenLund if you focus on just providing variants for ScriptableObjects would that still be too burdensome to implement? From a user stand point, it feels like a natural next step because you already have the mechanism & UI for overriding serialized fields in objects.
     
    ModLunar and GamerXP like this.
  8. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    ModLunar, SugoiDev and phobos2077 like this.
  9. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    I also made similiar thing with Odin (just inspector part, serializer not required) for my database scripts, but I made it more like Unity's nested prefabs style - it overrides everything you modify and shows it with small blue line on the side, and you can revert it from context menu. Makes much less space than this toggle style (I made it toggle style just like this at first)
     
    SugoiDev and joshcamas like this.
  10. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Sounds super cool! I also am just using the inspector part, not the serializer. I could try to rip it out at some point xD And yeah I personally like having more fidelity with what is overridden and what is not, but I may try out the prefab editor approach, maybe it'll work out :D
     
  11. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    Looks exactly like prefab variants in principle. How exactly are those sub-configs are serialized? Do you have a generic mechanism for overriding any type of config or is it just a custom set of Nullable properties for specific type of config?
     
  12. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Each field is a Parameter<T>, where T is the type of field. That parameter class contains the serialized value, as well as a bool "isSet". It also has the functions Get() and Set(), as well as a reference to the parent object, which is initialized during startup. Get() will either return its personal value if "isSet" is true, and will otherwise return the parent object's value.
     
  13. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    In my case, I made a script that apply attribute "Parented" to all drawers inside database class' editor. This attribute's drawer add override highlighting and "revert" context menu. Also it calls database's method that sync values between entries. That method, in normal Unity's terms, makes SerializedObjects for each database entries, and then copies NOT overridden values from one entry to another. Well, I actually use Odin's ProprtyTrees there, but they work similiar to SerializedObjects.
     
    phobos2077 and joshcamas like this.
  14. RedHillbilly

    RedHillbilly

    Joined:
    Mar 24, 2014
    Posts:
    39
    I'm sorry to necro this old thread, but that's all I've found regarding this topic which I think is really interesting.

    @joshcamas , @GamerXP Do you guys mind shedding a bit more light on the idea? There is something I don't understand, in both your examples the "parent", as well as which fields are actually overridden still need to be serialized, correct?

    Thanks a ton!
     
  15. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    Yes, it serializes all data like normal + additional override flags. Some editor script read those override flags and sync data based on parent-child relations.

    It's even possible to remove those flags from final build since they are for data sync purpose only. You can't do that with #if UNITY_EDITOR though since game will, most likely, crash.
     
  16. RedHillbilly

    RedHillbilly

    Joined:
    Mar 24, 2014
    Posts:
    39
    Ok cool thanks a lot, that makes sense.
    If I understand correctly, the flags are added inside the custom odin attribute, right?

    If I get something usable I will try to push it to github.

    Thanks :)
     
  17. RedHillbilly

    RedHillbilly

    Joined:
    Mar 24, 2014
    Posts:
    39
    Hei, I came up with an implementation, everything is working, although some stuff might be a bit shaky:
    https://github.com/GieziJo/ScriptableObjectVariant

    I managed to save the data of the override + parent in the .meta file using `AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(targetObject)).userData`, which works well but might be an issue if another script stores data there.

    The whole thing works by just adding the attribute 'Parent' to any `ScriptableObject`.

    I'm not a huge fan of how I implemented the attribute part (basically a check for all ScriptableObject types if they have the attribute), so I'm definitely open to suggestions :)

    Thanks for the ideas, definitely helped!
     
    Last edited: Jan 21, 2021
    GamerXP and joshcamas like this.
  18. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    Saving in meta file sounds like a good idea. I don't think it will be a problem since only custom classes can implement that, so it can't conflict with plugins. Though, performance may be not that good if it reads and writes it all the time?
    Implementing it with interface may be an option as well - this saving/loading will be a bit less painful.

    *looks at gif*
    I did with checkboxes at first as well, but rewrote it later - the way prefab variants are implemented feels better. Why won't you try doing it the same way?
    1. It overrides automatically any field that changes from user's input
    2. There is a context menu for each field that adds "Revert" option
    3. It shows small blue rectangle at the left side of the field if it's overridden.
     
  19. RedHillbilly

    RedHillbilly

    Joined:
    Mar 24, 2014
    Posts:
    39
    Thanks for the feedback :)

    I think performance wise it's not too bad from a read and write perspective for override and parent data:
    - data gets only read once, and then a null check on some properties reloads them when they get lost (unity reloading or recompiling or whatever)
    - data gets saved when 1) a new parent is set, 2) when the object gets deselected <- this is probably not fool proof but I haven't seen any problems until now (would make more sense to have it when the rest of the asset gets serialized but there is no delegate for that as far as I know).

    The rest of the SO data gets written and read from as normal.

    I'm not sure how this would be done with an interface, but wouldn't you then loose the advantage of having this as an attribute which is completely ignored at compilation? Here everything only happens in the editor, and the actual SO is modified, the references to the parent and overridden fields don't exist once compiled.

    Yes obviously the way unity handles prefab variants is a lot better, but also in the case of data it's, imo, a bit harder to get in one view what's original and what's overridden. And I honestly have no idea how I would go about the window they add to have an overview of the overridden fields.

    I've done some modifications, the checkbox is now on the left, and I've added as a reference the value of the parent next to the field when it's overridden.



    Honestly, except for the issues I mention on the git page, UI-wise I think this fits my needs pretty well, so I might leave it at that :)

    One bigger issue imo is that when you modify fields in the original, the data gets not propagated to the variants directly. You have to select the individual variants which then get updated (this time automatically, as they get checked on activation). This could be solved if there was a way to capture "OnValueChanged" in the "FieldInfo" but I haven't found anything in that direction for now.
     
  20. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    BTW, did you check if it works with arrays and SerializedReference multi-type fields? I had to do some special checks for those.
     
  21. RedHillbilly

    RedHillbilly

    Joined:
    Mar 24, 2014
    Posts:
    39
    Thanks, you were right! The lists and arrays had some issues, they kept the reference to the parent field and would modify the original instead of just a copy.
    I solved this by checking, for IEnumerable, if the object is the same, and if the case, making a copy via serialization and deserialization. Seems to work:
    Code (CSharp):
    1. // handle copy of list/arrays
    2. if (typeof(IEnumerable).IsAssignableFrom(targetFieldInfo.FieldType) && targetFieldInfo.GetValue(Attribute.Target) == parentFieldInfo.GetValue(Attribute.Parent))
    3. {
    4.     object parentObject = parentFieldInfo.GetValue(Attribute.Parent);
    5.     byte[] parentAsData = SerializationUtility.SerializeValueWeak(parentObject, DataFormat.Binary);
    6.     object parentObjectCopy = SerializationUtility.DeserializeValueWeak(parentAsData, DataFormat.Binary);
    7.     targetFieldInfo.SetValue(Attribute.Target, parentObjectCopy);
    8. }
    I'm not sure what you mean by "SerializedReference multi-type fields", could you give an example? thanks :)

    Also, I implemented propagation of values to children.
     
  22. GamerXP

    GamerXP

    Joined:
    Mar 22, 2014
    Posts:
    78
    I mean things like this:

    [SerializeReference]
    public IYourInterface Field;

    This field can be of any type derived from IYourInterface. This is also true for [OdinSerialize] fields.
     
    mitaywalle likes this.
  23. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    247
    hi from 2022, now we have material Variants. And we on half-road from ScriptableObject Variants. I supose it's the SubAsset system. You can save ScriptableObject inside other ScriptableObject, but you can't save Material under Material. If someone realy need ScriptableObject Variant - you can use MonoBehaviour + Prefabs as alternative. Override fully functional, and you also have same file-based nature. You have no some other features, such as Json-conversation. But in conext of ScriptableObjects Variants it works
     
  24. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    247
    SerializeReference is gorgeus, BUT: change namespace or class name will brake your data. MonoBehaviour/ScriptableObjects have no this refactor-stopping disadvantage
     
    phobos2077 likes this.
  25. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    That's just how serialization works. It's the same for any class reference or field reference as well
     
  26. thomas_superFASTgames

    thomas_superFASTgames

    Joined:
    Jul 15, 2017
    Posts:
    23
    mitaywalle likes this.
  27. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    247
  28. PrecisionCats

    PrecisionCats

    Joined:
    Nov 19, 2017
    Posts:
    40
    I've just released Asset Variants, which allows (almost) any asset to be a variant of any other asset (polymorphic or even entirely unrelated types are allowed; anything in common can be copied over), without requiring Odin Inspector. The main use case is ScriptableObjects and Materials, but almost any asset works out of the box:
    https://forum.unity.com/threads/asset-variants.1310693/



    Every collection (aside from HashSet) has a size override button, which is the .Array.size of regular Unity serialized Arrays/Lists. For Odin serialized collections, the size path is a "fake" path:
    - For Dictionaries, the size override means you can add new keys, or remove old keys; The override button for the value of a KeyValuePair allows you to change the value, but unless the size is overridden the keys must stay the same.
    - Stacks/Queues and Odin serialized Lists/Arrays work like changing the .Array.size value of a Unity serialized Array/List (even though there is no such InspectorProperty in the PropertyTree).
    - HashSets can only be overridden as a whole
     
    Last edited: Nov 10, 2022
    mitaywalle likes this.
  29. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    193
    Since then you guys have figured out Material Variants. Can we get same thing but for sciptable objects now?
     
    Opeth001, Art-Leaping, fnnbrr and 2 others like this.
  30. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    247
    Bump, same question
     
    Opeth001 and Art-Leaping like this.
  31. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    big bump as well!
     
    Opeth001 and Art-Leaping like this.
  32. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    Bumpidy bump + 1 + 10000
     
  33. Epsilon_Delta

    Epsilon_Delta

    Joined:
    Mar 14, 2018
    Posts:
    258
    Scriptable object variants would be greatest thing ever for game data handling
     
    MihaPro_CarX likes this.