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

ScriptableObject.CreateInstance vs Instantiate

Discussion in 'Scripting' started by laurentlavigne, Feb 4, 2018.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    what is the performance profile of createinstance?
     
    Kokowolo likes this.
  2. Prastiwar

    Prastiwar

    Joined:
    Jul 29, 2017
    Posts:
    125
    Last edited: Feb 5, 2018
    B1Gone likes this.
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,196
    Nope. You can use ScriptableObject.CreateInstance as much as you want at runtime.


    For OP's question, Are you wondering if it's faster to eg. create a ton of copies of an existing SO than to create a ton of new SOs? It's not that hard to check!

    Code (csharp):
    1. public class Create : MonoBehaviour
    2. {
    3.     public SomeSO toClone;
    4.  
    5.     void Update()
    6.     {
    7.         if (Input.GetKeyDown(KeyCode.Space))
    8.         {
    9.             var stop = Stopwatch.StartNew();
    10.             for (int i = 0; i < 10000; i++)
    11.             {
    12.                 var so = ScriptableObject.CreateInstance<SomeSO>();
    13.             }
    14.  
    15.             stop.Stop();
    16.             Debug.Log(stop.Elapsed);
    17.         }
    18.        
    19.         if (Input.GetKeyDown(KeyCode.Alpha0))
    20.         {
    21.             var stop = Stopwatch.StartNew();
    22.             for (int i = 0; i < 10000; i++)
    23.             {
    24.                 var so = Instantiate(toClone);
    25.             }
    26.  
    27.             stop.Stop();
    28.             Debug.Log(stop.Elapsed);
    29.         }
    30.     }
    31. }
    The results are that CreateInstance is in the ballpark of 12-13 ms, while Instantiate takes about 4.
     
    curbol, rocky1138, Alaadel and 15 others like this.
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    3x slower is shockingly bad. You know who made this?
     
  5. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    They're both pretty irrelevant actually- making copies of ScriptableObjects during runtime is pretty pointless in most cases, and if you're doing it in the editor then it doesn't matter what the performance hit is (which is why they still use the OnGUI system for editor scripts despite its terrible performance).

    ScriptableObjects don't benefit from any of the built-in tools at runtime, since the AssetDatabase is an editor class, so it's essentially the same as using any serializable class type since you need to write and load it from the disk yourself manually either way. I could see using it as a factory pattern I suppose, treating the pre-made SOs like blueprints and just making copies when they're needed, but that would be trivial to handle during scene loads rather than mid-play, and CreateInstance wouldn't be used at all in that scenario. In my own solutions, the pre-made SOs ("definitions", "blueprints", w/e) simply generate non-SOs, so there's no extra overhead for saving the objects to disk.

    I actually can't imagine a single scenario where CreateInstance is ever actually useful at runtime, to be perfectly honest.
     
  6. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    i instantiated SO at runtime as data and function buckets, so you say createinstance is in fact making an asset?
     
  7. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    It's just got a lot of pointless overhead for no real benefit outside of the editor. You can use a non-ScriptableObject and just make a new instance with the "new" keyword easily enough, so what's the point of it being a ScriptableObject in the first place? If it was for a factory pattern, then Instantiate would be the function to use so that you can make a duplicate of the original.
     
    jemrie, Kokowolo, phobos2077 and 2 others like this.
  8. simonlvschal

    simonlvschal

    Joined:
    Nov 17, 2015
    Posts:
    266
    there is no point in Using ScriptableObjects.CreateInstance. since scriptable Objects should just be used as Data Containers.. and also i am pretty sure u cannot use Instantiate on a Scriptable Object. since it has no objects or data that can be used to create a object
     
  9. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    you really need to watch some of the more recent unity vids on how to use scriptableobjects if you still think that.
     
    DeadCastles, Nefisto, NotaNaN and 4 others like this.
  10. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Instantiate can absolutely be used on ScriptableObjects- they're derived from UnityEngine.Object after all.

    @LeftyRighty To be fair, some of the recent Unity vids abuse ScriptableObjects in really bizarre ways- ways that aren't even supported properly, and which cause some pretty serious side effects. For instance, instantiating new ScriptableObjects without saving them to the disk as assets, and then assigning them to ScriptableObject references in a scene is actually done in one of those tutorials, made me cringe like crazy while watching it, and in practice creates all sorts of problems in the Editor, like dirtying every open scene every time its done, not having a proper reference target in the inspector, creating a bunch of garbage to be GCed, etc...

    They're inherently just YAML files, which means they are, at their core, just data containers, like XML or JSON files. The extra functionality given by serializing them and giving them custom editors isn't much different than writing inspectors for any other asset type, like ZIP files for instance, which is easy enough.

    Out of curiosity, what tutorials on ScriptableObjects are you referring to, that they aren't just treated like data containers with a custom inspector?
     
    Last edited: Feb 6, 2018
  11. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Yeah, I see very little benefit to creating a new ScriptableObject at runtime. The only real difference between a ScriptableObject and a vanilla class is that you can make a ScriptableObject into an asset in the editor. There seems to be little point to doing this at runtime.

    I suppose you could use it if you want to pump a regular class through the same Instantiation/Enable/Disable/Destroy pipeline that GameObjects follow. But it doesn't seem especially smart to do this.
     
    Kokowolo and Patrrashitel like this.
  12. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    it might be what i'm doing.

    i use them to avoid making custom inspector, all I have is this thing which shows the content of all referenced object within the inspector - it's convenient.

    i use SO in the following scenario:
    Actor is a component attached to each agent, it has a list of Action SO
    when the AI triggers an action, it sends an reference of an instance of the Action SO's to a centralized ActionManager, packaged alongside a target and other parameters which allow me to conveniently format all actions the same way.
    the ActionManager calls the Action's precondition, if the preconditions are met it locks the target, adds the Action package to the list, processes that list each frame. the Action Package includes the Action and things like time it takes for the action to complete, callbacks when completed or failed etc...

    instantiation is now no longer an issue because i've capped the AI ability to spawn actions @ 1 action/agent so i pre-instantiate the SO at agent startup but I want to hear how you guys use SO and how you handle actor->action->target
     
    phobos2077 likes this.
  13. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,913
    For example:



    Also you can use them as expandable enums (by designer), although it's more or less data storing feature.
     
    athgen113 and phobos2077 like this.
  14. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    pipped to it, but specifically the AI tutorial and the "monobehaviour tyranny" talk where they discuss "plug and socket" architecture to use functions on scriptableobjects, rather than just using them as data holders
     
    Kokowolo likes this.
  15. simonlvschal

    simonlvschal

    Joined:
    Nov 17, 2015
    Posts:
    266
    no. i dont. cause scriptable objects are normally used for Data Containers. and some functions since it lives as a asset
     
  16. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,913
    It's obviously up to you what new techniques you want to learn or not, but that statement, that the scriptableobjects "normally" used as data containers is, well, silly. SOs have multiple functions, multiple goals and multiple usage. It's up to you if you utilize those or not. But that's your shortcoming if you don't.
     
  17. LocalsOnlyCoop

    LocalsOnlyCoop

    Joined:
    Mar 8, 2019
    Posts:
    9
    That second video was fantastic! (I know this is an old topic). I was curious how he gets around the One Object / One Variable problem. If I wanted to clone an enemy several times, they couldn't all reference the same HP variable. Also I imagine your project would become quite full of ScriptableObject instances.
     
    lmraddmb, FuBaa and DeletedFragment like this.
  18. denlee0710

    denlee0710

    Joined:
    Mar 9, 2015
    Posts:
    36
    You can create .asset(instances) and of a scriptable object in editor time and save it. Without SO you can accomplish the same by hardcoding it with new(), albeit would be pretty ugly code.

    If you are editing and saving SO.asset at runtime, it’s equivalent to serializing a regular object.

    The way Unity explain and position SO is very confusing when I first learned about it and tried to understand it. Eventually I realized ScriptableObjects are just regular objects that which you can instantiate and initialize in editor time. Otherwise they aren’t any different. No more confusion!
     
  19. FuBaa

    FuBaa

    Joined:
    Aug 22, 2012
    Posts:
    1
    Bit late, sorry.

    If we have a
    GameObject Enemy
    , all we need to do is make a decision about who is concerned (which other Objects) are interested in the data it holds. If other objects are concerned with it, we should use a
    ScriptableObject
    , and reference that data. Or, if no other Objects are concerned with the data, then our
    Enemy
    can just have an internal Instance Variable instead.

    Let's use a little example, and say that our
    Enemy
    has three variables it's interested in,
    health.current
    ,
    health.max
    and
    movement.speed
    .

    In our game there's also floating health bars that appear above an enemies head (something like this), which is looked after by our players HUD (probably a Canvas UI element billboarded). As such, our
    Enemy
    Object needs some way of telling the
    Player
    's HUD Object about what its
    health.current
    and
    health.max
    values are. This is where
    ScriptableObject
    s would be used, and both the
    Enemy
    and the
    Player
    's HUD would reference that same
    ScriptableObject
    Data.

    That leaves us with the
    Enemy
    's
    movement.speed
    ... Which, at the moment at least, nothing else in our game is concerned with. The Enemy knows it's movement speed is 4f (for example), and it can do its internal calculations to move the model at that speed quite happily. Our Player doesn't care what the Enemy's movement speed is, neither does our HUD. Therefore, the
    Enemy.movement.speed
    can just be stored as an Instance Variable inside of our
    Enemy
    Object.


    If you need to generate a new
    Enemy
    at runtime (then you're likely procedurally generating stuff, which is awesome, keep it up!), then you could use something like:


    RefInt.cs:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class RefInt : ScriptableObject
    4. {
    5.   public int Value;
    6. }
    Enemy.cs:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Enemy : MonoBehaviour
    4. {
    5.   private void Awake()
    6.   {
    7.     // We create a new Integer ScriptableObject to hold our newly created Enemy's Current HP data.
    8.     RefInt enemyCurHP = ScriptableObject.CreateInstance<RefInt>();
    9.  
    10.     // We assign a value to that
    11.     enemyCurHP.Value = 5;
    12.  
    13.     // We send a message to the Player's HUD object, saying:
    14.     //  "Hey! I'm a new Enemy!
    15.     //   I think you're interested in knowing what my current HP value is.
    16.     //   I'm currently keeping track of it myself in this ScriptableObject...
    17.     //   Why don't we both just reference the same number, rather than all this chat?"
    18.     PlayerHUD.TrackNewValue(gameObject, enemyCurHP);
    19.   }
    20. }
    PlayerHUD.cs:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class PlayerHUD : MonoBehaviour
    5. {
    6.   // Use Polymorphism to keep the referenced data nice and general.
    7.   private static Dictionary<GameObject, ScriptableObject> _refData =
    8.       new Dictionary<GameObject, ScriptableObject>();
    9.  
    10.  
    11.   // We use this to keep track of any new data that the PlayerHUD needs to know about.
    12.   public static void TrackNewValue(GameObject obj, ScriptableObject reference)
    13.   {
    14.     _refData.Add(obj, reference);
    15.   }
    16.  
    17.  
    18.   // We can then Update the screen to represent any changes to this data, independently
    19.   // of how, when or why it's changed.
    20.   private void Update()
    21.   {
    22.     // Just some Pseudo code to give an example...
    23.     someUIElement.text = _refData[anEnemyThatsInRangeOfThePlayer];
    24.   }
    25. }
    This is just a thrown together example (please don't use it; there are better ways of doing pretty much everything that's in there). But it gives the general idea... And be sure to remove the references when you're done, to avoid memory leaks.


    As for ending up with hundreds, thousands, tens of thousands of pieces of ScriptableObject Data, it's not a problem, as internally that data will exist in a
    Serialized
    form, whether or not its an Instance Variable or a ScriptableObject. The Engine doesn't care where the data comes from, and indeed, won't know.

    You're not creating more data (each enemy is still going to have HP, right?), you're just changing how the data is used (referencing shared data, instead of asking for it a lot, or storing it multiple times). At runtime, you can use something similar to the above outlined criteria. In the editor, you'll likely have lots of ScriptableObject data files (.asset files)... This is completely normal. Just keep them organised (folders/directories are your friends here), and you shouldn't encounter any issues.

    The big difference (and the point of all this) is that rather than storing the
    EnemyCurHP
    in multiple places (or constantly asking the
    Enemy
    Object, "Hey! What's your HP?", "What's your HP?", "What's your HP?"), you're instead storing it once, and then every object that wants to know about that value can just reference it.

    All this is good, and the Engine will reward you with blazzingly fast FPS on potato devices for your trouble!
     
    Last edited: Apr 30, 2019
    krearthur and JoeLin990 like this.
  20. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    Have you tried interfaces? Just use them instead of SO's if all you need is to reduce coupling between components (like player health). No need to overcomplicate the structure of your MonoBehaviors with extra SO's.
    Interface fields are not normally supported by Unity's inspector, but there is a handy workaround for this. I can show you if you want. Basically you create a special InterfaceReference class and a PropertyDrawer and vuola! - your components don't care if you put another MonoBehavior or ScriptableObject reference into the field - all they know is the object implements certain interface. So your code is clean and no need to restructure your components.

    PS: your UI code is inefficient. You ideally shouldn't update e.g. "text" property every update. Use Observer pattern to resolve this (plain simple C# public events) or use UniRx - it's awesome.
     
    Last edited: Jun 10, 2019
    Patrrashitel, madebynoxc and NeatWolf like this.
  21. madebynoxc

    madebynoxc

    Joined:
    Jan 14, 2014
    Posts:
    10
    I agree with this. Instead of trying to create refs like that, it is much better to go with Observer pattern. Way less updates for UI, much higher FPS.

    I would still want to create an instance for SO. Let's say I have SO that describes basic enemy parameters, like attack, defence, speed and health. First three is something that should not change, but health is changing during the game. But if all enemies have reference to the same SO, changing health will change it for all enemies in the game.

    So I guess my question is: is that still okay to use Instantiate to create SO instances if it has [NotSerialized] property? If I want to handle logic in one 'System' of enemies, it is easier to have access to all data right away, rather than referencing source of 'static' data (SO) and custom class of 'dynamic' data.
     
  22. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    You wouldn't want to create an instance for the SO though. The point is that the SO doesn't contain data that needs to be instantiated per consumer, it's shared data instead- if it does contain per-GO instance data, then it being an SO isn't serving any real purpose. Use a completely normal POCO object and "new SomeClass()" instead and put your instance data in there.

    My answer is still the same now as it was over a year ago when I originally posted here: if you're instantiating an SO to use for data, you're taking a really bizarre and inefficient route to the goalpost. That doesn't mean it's not technically viable, but if someone asks me directly if it's "okay", I'm going to have to say "not really". It feels like using the wrong tool for the job. Having one source of static data that's shared between a bunch of different instances and doesn't change at runtime, and one source that's purely per-GO for data that can actually change: those distinctions are what makes the SO a gain in efficiency.

    The problem isn't the non-serialized fields, but the serialized ones- if you're duplicating all of that information on a per-GO basis, that gain is lost, and you have the overhead of all of the SO inherited behaviours that now can't even be used at runtime.

    Anyways, using the right interface, you can abstract out which properties pull information from the SO and which pull from the dynamic data- it's simply an implementation detail, and you save time and energy by minimizing the dynamic data you have to serialize yourself for save files. Those are a lot of little victories to throw away because you don't like having two data sources for some reason.

    But now, and as always, do whatever you like- it's your game, and most pegs can be hammered into most holes if you're willing to hit them hard enough, I've just never personally been fond of hammers is all. =)
     
    Last edited: Jul 21, 2019
  23. madebynoxc

    madebynoxc

    Joined:
    Jan 14, 2014
    Posts:
    10
    Thank you a lot for reply, it really clarifies a lot of things for me and the point that you were making a year ago. This is really confusing, since so many people just suddenly started to use SO for literally everything. Even Unity themselves have different videos and articles that have different architecture approaches regarding SO.

    I see your point and I agree with you. Essentially making a copy of static data is a waste of resources.
     
    muzboz and phobos2077 like this.
  24. BloodHound_PL

    BloodHound_PL

    Joined:
    May 12, 2016
    Posts:
    4
    I want to create Task System for AI-bots capable of using tools and machines in a variety of contexts. I consider SO in its static nature as universal recipe for using drills, lawnmowers or bulldozers, but since it can be instantiated it could also store/find level-dependent data like designated drive-by/patrol route (one of many possible), mission objective or simulated accident coordinates. I'm going to test this right away, thanks for that thread.
     
  25. patrykszylindev

    patrykszylindev

    Joined:
    Oct 28, 2019
    Posts:
    23
    Would you be so kind and share how to create special interface references? I've been at it for past two days, and I have to admit. It is quite difficult to wrap my head around it. I'd much prefer to follow SOLID instead of hacking this sort of problem with SOs. I'd appreciate it a lot!

    Thanks,
    Patryk.
     
  26. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    Sorry but these videos aren't really related. You should use SOs only as data containers (immutable data) like json and XML files. In a few situations, you can make use of SOs with mutable data.

    Using "CreateInstance" is completely awful in order to create a new instance of an SO. You can utilize normal pure classes (containing data and method) and populate them with the SO.
    If you have only one unique object(such as inventory system, player data, etc), it may be suitable to employ SO containing mutable data and method.
     
    Last edited: Dec 14, 2019
    phobos2077 likes this.
  27. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Sorry if this is too much of a necropost. This thread has come up a lot for me in Google lately.

    I do think that it can be very useful to use SOs as containers for values that should be instantiated multiple times, like the health of an enemy, but I also think it is usually a very bad idea to instantiate SOs in code. Instantiating them kind of defeats the point of using them. Basically, what I do is I use SOs as keys in a dictionary of values (I actually use them as keys in a dictionary of indexes of a list of values, for some performance tricks, but that is not very important right now). The dictionaries are stored in special MonoBehaviours.

    This allows me modularize and reuse my code a lot, because the same MonoBehaviours can modify different sources of data and it's very easy to manually share values between scripts without each of them having to reference one another explicitly (and without having to write a lot of interfaces). I also can make prefabs with this without needing multiple SOs. This helps with more than MonoBehaviours; I've actually made FSM and GOAP systems where the same graph file can be used on a lot of very different entities because they reference the same SOs as each entities behaviours and, at the same time, those SOs values are unique per entity (or, optionally, can be global too).

    I'll share this system when I polish it a bit more, but I wanted to say: TL;DR. Scriptable Objects can be very useful, they can make Game Design a lot easier. You can get a lot of what you need from Scriptable Objects if you get a bit creative with your systems. You don't need to instantiate them most of the time, though; that's usually an antipattern.
     
    Last edited: Jan 8, 2020
  28. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,108
    Let me add my 3 cents.
    If I am correct, the whole idea of unity object is to allow user interaction with runtime objects, setuping, saving, connecting them via graphical interface to make everything scalable and easy to use. Scriptable objects are data containers, but at the same time allow to plug/connect assets and data. In addition inheritance can be handled well (unlike serialized classes), so using them to build and save data structures sounds like good idea.

    Consider this example: There is some more complex structure like FSM or behaviour tree based on scriptable objects.
    I can make AI graph and plug it into some MonoBehaviour on scene, the problem starts when I need to run several AI agents. This graph may need to store some runtime state in private memebers, but as it is SO asset, it is shared among all instances of enemies and it does not work. On the other hand, I could instantiate a runtime copy for each enemy, so all units have own graph and state.
    The only way to avoid situation above would be to make corresponding standard c# class for each node, but creating 20 (or more) another similar classes for each node sounds ridiculous.

    Edit: Actually this is not the best example, because single Instantiate would make copy only for the top most object. To make it work it's needed to traverse and copy all nodes.
     
    Last edited: Jan 25, 2020
  29. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    52

    This is exactly what I wanted to add, I've been wrestling with this for 2 years now going back and forth for the best approach, I'm making a rouge-like game with a lot of randomization, generation at run time.

    I'm using them to plug certain behaviors to my AI and I can mix 50 scripted behaviors very easily even at run time by using SOs, for example, at run time I can drag drop the "ConstantChase" behavior to my AI and make it start acting. but since the logic needs a lot of instance variables, I can't share the same SO, so I have one for it in my project but each AI agent makes a runtime instance before using it.

    Now this cycle I have been doing for 2 years goes as follows:

    • Using POCO: no global config from the editor, don't want to make my default values in code! can't be serialized polymorphiclly (this changed with the recent TypeReference attribute but it's still highly unstable), can't be drag dropped, can't "click" on it and edited it at runtime! (unless if serialized polymorphiclly)
    • Using SO: global config from the editor, one instance for the entire project which makes it extremely memory efficient, but no instance variables, can't hold a state per AI, thought about making a dictionary in the AI to hold the state, extremely filthy and unreadable code, and felt like implementing DOTS from scratch at some point.
    • Using SO instances: gets the best of 2 worlds aside from "not using SO for their original purpose", whatever that means, dunno why this matters anyways, and I'm not sure about garbage collection and memory, is it worse than POCO ? by how much?
    I understand that using interfaces with normal C# classes is the best way to go in general programming sense. but unity doesn't get along with interfaces really, nor with polymorphism in general
    I would love if DonLoquacious can give his take on this since you were very persistent about instantiating SO at runtime has no advantage over normal POCO instances.
     
    yowhatitlooklike likes this.
  30. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    What's the problem with using SO for configs and POCO for runtime data? Your POCO classes have references to SO. So basically configs are things that don't change, everything else in ECS Components, MonoBehaviour fields or whatever. That's what we do and it's working perfectly fine.

    The argued benefit of instantiated SO is that you have all properties (static and dynamic) in one class (kind of a prototype approach), but I think the separation only helps.
     
  31. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    52
    it's always a trade off, suppose I have 100 class total,( I have way more ), this means I need to double the classes with the exact same params, but include the logic in the POCO and keep the SOs data only.

    and you will need a way to link which SO belongs to which POCO, probably with some interfacing and editor scripting you can reach good results, but it's still a downside. Since I configure my prefabs with SO references not POCOs, the SOs should be able to grant in instance of the POCO to whoever needs it, I went as far as reflection and decided to not go with this approach.

    Lastly, you can't edit the values of POCO classes at runtime since they implement an interface and the AI deals with the interface which can't be serialized polymorphiclly in unity as I mentioned before, unless you want to use the new TypeReference attribute with some custom editor scripts. and from my experience it's still not ready for production
     
    Last edited: May 4, 2020
  32. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    I don't know your exact architecture but as I said, POCO classes are for runtime data, SO for configuration that doesn't change. Usually these are completely separate, so you don't duplicate anything, you just use the right tool for the job. But each game is very different, that's what I've learned from my experience. You can't use the same approaches for each and every project. You need to understand the tools at your disposal and how they work and choose appropriately.

    I just find it wrong to use instantiated ScriptableObject for runtime stuff outside of some testing scenarios and editor scripts. Simply because ScriptableObject is wrapped around C++ object which is an in-engine entity that you create that's not really needed for runtime. In my opinion runtime code should be clean and if you need some editing functionality, you build your in-editor play-mode tools around it in a non-intrusive way.

    As always, practical considerations come first, so if for your project your approach doesn't create any performance issues and you're fine sub-classing UnityEngine.Object for everything, it's perfectly fine. I think we should distinguish "best practices" from the best solution for a given project. Best practices are things that you should consider "by default" for every project, but not adopt blindly. There's always exceptions to rules.

    Polymorphic classes work perfectly fine if you have Odin Inspector, for example. SerializedReference attribute had a lot of fixes lately as well, hopefully it will be ready in the nearest future.
     
    Last edited: May 4, 2020
    erzherzog13 likes this.
  33. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    52
    well said, I agree for the most part. Maybe the SerializedReference attribute will help in the future.

    just to point out the issue in a clear way:

    you have a ScriptableObject with the following definition:

    Code (CSharp):
    1. public class ChaseTarget : ScriptableObject{
    2.      public float chaseSpeed;
    3.      public float reactionTime;
    4.     // etc
    5. }
    6.  
    now the POCO class should have the same variables AND the logic:

    Code (CSharp):
    1. public class ChaseTargetPOCO {
    2.      public float chaseSpeed;
    3.      public float reactionTime;
    4.     // etc
    5.  
    6.    // logic part
    7. }
    8.  
    this leads to a lot of duplication and manual definition for each constructor or even worse one by one value copying


    Code (CSharp):
    1. ChaseTargetPOCO x = new ChaseTargetPOCO();
    2. x.chaseSpeed = so.chaseSpeed;
    3. x.reactionTime = so.reactionTime
    Imagine doing this for 300+ scripts and still growing
     
  34. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    Yeah I totally get that. But this comes from the way you've structured your game. In my current project, there are clear separation because we use ECS for all runtime logic and SO for all editor-time configuration. Basically you copy some data from SO to ECS components when creating an entity. The "downside" (although it's not a problem in our case) is copying values that exist both in runtime and config data, like starting health, etc. But the upside is that our config data is free of runtime state data and we are able to use the benefits of both ECS scalability and editing experience of SO.

    I'm not suggesting anything but in .NET world there is a widely used Automapper library that does use reflection to copy values between C# classes. This is great for maintaining separation between various layers in your code. Might be overkill for some projects, but still valuable tool to have. You can write your own tool or find something pre-made and optimized for Unity I'm sure. All the reflection queries are usually cached in a smart way so performance is rarely an issue with these, but I'm not so sure about that if you need to copy a lot of entities every frame.
     
  35. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    52
    yeah, as I mentioned in my first comment, at a certain point it felt like I'm making my own ECS - and a very bad one -, this whole things is about being data driven.

    I'm not ready to use ECS for now so I will have to make due with one of the other options
     
    phobos2077 likes this.
  36. athgen113

    athgen113

    Joined:
    May 25, 2018
    Posts:
    46
    thank you
     
  37. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    Could you elaborate on how does that dirty every open scene? Also I'm not sure what you meant by "not having a proper reference target in the inspector". I'm currently using SOs for an inventory system, and when enchanting items, I create a new instance of the SO in question and replace the reference to the SO asset by the one I just created, then modify it's values. I currently don't save them as assets.

    Can you say how that could cause unwanted behaviour? I couldn't find any other information about this.
     
    Last edited: Jul 24, 2020
  38. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Responded more thoroughly in email, but this is an old topic by this point and I'm no longer aware of how ScriptableObjects are really treated, and what's supported properly and what's buggy or not when they're used at runtime as anything other than flat file references. It felt at this point that Unity was fully intending to support runtime instantiation of SOs, as several conference demonstrations I've seen have used them that way, so by now (a year later), there probably aren't any of the real issues I was annoyed with left. I can't confirm that right now though, maybe someone else will. =)
     
    muzboz likes this.
  39. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    @DonLoquacious thank you so much for your attention. I wrote a small script to test the scene dirtying and it seems to run fine (no unsaved changes appearing after running it). I hope that's enough to confirm at least that part.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class InstantiationTester : MonoBehaviour
    4. {
    5.     public Person personAsset;
    6.     public Person newPerson;
    7.  
    8.     void Start()
    9.     {
    10.         newPerson = personAsset.GetInstance();
    11.         Debug.Log(newPerson.ToString());
    12.  
    13.         newPerson.name = "Charles the Old";
    14.         newPerson.age = 87;
    15.  
    16.         Debug.Log(newPerson.ToString());
    17.     }
    18. }
    19.  
    20. [CreateAssetMenu(fileName = "Person", menuName = "SO/Person")]
    21. public class Person : ScriptableObject
    22. {
    23.     public string name = "Joe the Infant";
    24.  
    25.     public int age = 5;
    26.  
    27.     public Person GetInstance()
    28.     {
    29.         return Instantiate(this);
    30.     }
    31.  
    32.     public override string ToString()
    33.     {
    34.         return "My name is " + name + " and I'm " + age + " years old.";
    35.     }
    36. }
     
    DonLoquacious likes this.
  40. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    I know it's been 2 years since this comment, but can anyone elaborate on how you actually do this in practice? If the ScriptableObject contains SerializeReference fields, then won't the copy also refer to the same elements?

    For example, let's say you have AbilityData ScriptableObjects, and those objects are a complicated data structure with a nested hierarchy, e.g. AbilityData contains Actions which contain Effects, and those nested classes are fields with SerializeReference to support polymorphism.

    If you want each separate instance of an ability to have a separate AbilityData object (e.g. so that the cooldown of all Fireball abilities isn't shared), what's the propery way to copy the AbilityData? You could make a separate AbililtyState object that contains all the instance data, but this becomes very difficult when some of the instance data is specific to nested classes. For example if there's a ChargeEffect that stores the number of times it's been used, then you only need that state if the AbilityData has a ChargeEffect, so in terms of code organization it's cleanest to add it to ChargeEffect, not AbilityState. Otherwise AbilityState would become a god class that has every possible state it could need access to.
     
  41. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,196
    SerializeReference fields didn't exist 2 years ago, so that wasn't an issue.

    I have no idea what happens if you instantiate something that has internal SerializeReference fields. Ideally that would work the same way as any other data, where internal references are remapped to the same internal object. It shouldn't be that hard to whip up a test for that, though!
     
  42. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    The problem is that you don't want the clone to refer to the same objects if you're using SerializeReference to get polymorphism. For example, if you have an AbilityData ScriptableObject which contains nested SerializeReference fields to Actions which contain Effects, then you don't want your cloned AbilityData to refer to the same Effect objects, or you'd have stuff like all Bullet Storm abilities with some internal ammo state share the same state (which defeats the purpose of cloning).
     
  43. Kokowolo

    Kokowolo

    Joined:
    Mar 26, 2020
    Posts:
    41
    Okay, so this is probably pointless to post this on an old thread, but I personally use ScriptableObjects for both data containers and also, like LeftyRighty mentioned, function plug ins. I find them to be really useful for rapidly prototyping or modularizing, for example, in the use case of "Abilities" or "Skills" for a player or a unit. This way you can simply use the Editor (or even a script) as a drag and drop tool to assign new functions (and of course data) to a component/character/class/etc.

    Maybe this is a bad way to do this, in which case, please, I'd be thrilled to hear how to improve my practices.
     
  44. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Did you try this? I'm doing this a lot and I haven't had problems. Are you having issues with this or is it purely theoretical? Now you got me a little worried that there could be a potential bug waiting to wreak my project. :). In your example, are Actions and Effects plain, non-Unity objects?

    My understanding is that
    Object.Instantiate
    deserializes the Object and then serializes it into a new one, so, if none of the nested objects that you want to clone are ScriptableObjects or something like that, they should be copied like any other serialized data.

    EDIT
    Sorry, I didn't see the original post you were quoting. I get that your question is how to go about not generating a new SO for each copy you make; in other words, how to make your SO's generate POCOs with cloned data when you have nested objects in them. Am I right?

    My guess is that you'd do it manually. I do it sometimes for performance reasons. You could also do it with reflection, but then you'd lose the performance benefits. IMHO, performance is the only good reason to do this. Instantiating the SO is just so much easier to use and maintain most of the time.

    I get that SOs can feel like too much, or like the wrong tool for the problem, but using stuff that isn't the exact tool for a problem is a normal part of a game engine. Almost everything does a lot more than what you need, and sooner or later nothing will fit exactly to your case. One often has to weigh implementing a custom feature from scratch versus just making the engine work. When performance is not an issue, what's the point of using a game engine if you don't choose the easiest option?
     
    Last edited: Feb 25, 2021
  45. R1PFake

    R1PFake

    Joined:
    Aug 7, 2015
    Posts:
    507
    Im working on a Ability system and also had thoughts about ScriptableObjects etc. also had the problem "how do deal with variables that should not be shared" and was thinking about creating instance copies of the ScriptableObject, but creating runtime copies of a ScriptableObject just to store some "runtime values" felt wrong which lead me to this thread.

    After some more research I ended up with the following solution:
    - I have ScriptableObjects which contain only the shared data and the logic of the ability
    - I have a additional plain C# class called "AbilitySlot" that stores a reference to the Ability ScriptableObject and also a generic* property where the ability can store any additional data it needs at runtime / per character
    - Every character creates unique instances of this "AbilitySlot", actually the SO itself has the logic to create and setup the instance, but every character calls this method to get it's own instance

    (* depending on how far you want to go you can either create a fully generic system or just use object as property type and call it something like object AdditionalData and cast the instance in the Ability SO methods, I used the "full generic" version)

    The Ability Scriptable object has 3 relevant methods
    - StartAbility, UpdateAbility, StopAbility
    - Each of these methods get the Character that is using the Ability and the AbilitySlot as parameter
    - Now the Ability SO can use these parameters to handle / store and runtime data there instead of storing it directly in the SO itself

    Sure it's one extra step / layer, but it was worth it for me and everything works fine and I don't have to deal with SO CreateInstance or anything like that and it also feels cleaner to me, because creating ScriptableObject copies at runtime felt awkward to me even if it works.
     
    oscarAbraham likes this.
  46. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Hi. Today I had to solve this exact problem with a generic solution for multiple use cases. I remembered you and thought that it could be useful for other people. It's worked well so far, with surprisingly good performance when compared to cloning SOs. Here it is in case it can help anyone, a ScriptableObject that creates deep clones of plain objects or structs:
    https://gist.github.com/OscarAbraham/72a636234c2df2abe407cb6d632ca57b
     
    MaxPirat likes this.
  47. Rubakai

    Rubakai

    Joined:
    Aug 27, 2015
    Posts:
    32
    Found this thread interesting and useful as I was pondering around this subject for a while trying to decide whether to fully go for scriptable objects. I saw the obvious use in storing static data but I held off the further uses outlined in all the Unity talk videos just because it didnt feel like the right way to use them. However I am not a convert in a lot of cases and find the usefulness of sharing the data between objhect and even using them in a event system outways some of the negatives (ie when it comes to using them in the inspector and the design). One example would be an inventory for a player. I always use to keep this in code (POCO) but when I tried the scriptable object solution it just clicked. Being able to access the scriptable object from all areas, adding items and testing at runtime, having different inventories ready for testing in a matter of sections was great.

    However I am now at the stage of implementing all the randomised enemies with their oen inventory system and now stuck in indecision land of how to implement this. Obviously all the enemies will require their own scriptable object and I was going to head down the createinstance route but now some of this chat has put me off. What puts me off using the SO data to create a POCO is the constructor and having to constantly change and update this if I want to add more data to the SO.

    I'm going to try the createinstance route. Mainly because it used to take me a long time to just go with an idea, as I was obesessed with finding the most "effecient solution", instead of just building it. Its a unity editor for a reason with an inspector for a reason, and the unity people obviously suggest it for a reason so it cant be too bad.

    However if people have found other solutions to this they feel has worked well would still be intrigued to hear about it.
     
    yowhatitlooklike and tonytopper like this.
  48. pointcache

    pointcache

    Joined:
    Sep 22, 2012
    Posts:
    576
    Whats the point of derailing the whole thread with your opinions on unrelated topic.
     
    Saniell likes this.
  49. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,196
    What's the point of necroing a thread to complain about something somebody did three and a half years ago?
     
    muzboz, Kokowolo, Bunny83 and 2 others like this.
  50. UnityMaru

    UnityMaru

    Community Engagement Manager Unity Technologies

    Joined:
    Mar 16, 2016
    Posts:
    1,227
    Please don't necropost.