Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

ScriptableObject.CreateInstance vs Instantiate

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

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    3,536
    what is the performance profile of createinstance?
     
  2. Prastiwar

    Prastiwar

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

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    5,004
    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.
     
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    3,536
    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:
    3,536
    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.
     
    phobos2077, Ryiah and Kiwasi like this.
  8. simonlvschal

    simonlvschal

    Joined:
    Nov 17, 2015
    Posts:
    263
    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.
     
    forcepusher and laurentlavigne 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
    phobos2077, Ryiah and Kiwasi like this.
  11. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,732
    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.
     
  12. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    3,536
    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:
    5,457
    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
     
  15. simonlvschal

    simonlvschal

    Joined:
    Nov 17, 2015
    Posts:
    263
    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:
    5,457
    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:
    5
    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:
    29
    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
  20. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    224
    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
    NoxCaos and NeatWolf like this.
  21. NoxCaos

    NoxCaos

    Joined:
    Jan 14, 2014
    Posts:
    11
    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
    phobos2077 likes this.
  23. NoxCaos

    NoxCaos

    Joined:
    Jan 14, 2014
    Posts:
    11
    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.
     
    phobos2077 likes this.
  24. BloodHound_PL

    BloodHound_PL

    Joined:
    May 12, 2016
    Posts:
    3
    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:
    14
    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:
    826
    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:
    27
    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:
    38
    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. Mercbaker

    Mercbaker

    Joined:
    Apr 18, 2017
    Posts:
    17
    I think SO's are useful simply because you are using Unity to make your applications. Unity is built around the concept, so why would you just utilize something else because of principle? That isn't reactive problem solving.

    By forcing the concept through a full development pipeline, I've found a lot of subtle benefits to a SO pipeline.

    As for Creating an Instance of a SO: I've been using this method to create a copy of a SO that has its own set of references that I need in order to assemble different serializable data into Blobs that get written to my database. Those references can change, and I don't want to care about how they change, I just want to write my data where and when I want to.
     
  30. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    30

    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.
     
  31. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    224
    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.
     
  32. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    30
    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
  33. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    224
    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
  34. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    30
    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
     
  35. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    224
    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.
     
  36. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    30
    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.
  37. athgen113

    athgen113

    Joined:
    May 25, 2018
    Posts:
    46
    thank you
     
  38. Pedro-Paternostro

    Pedro-Paternostro

    Joined:
    Oct 31, 2015
    Posts:
    32
    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
  39. 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. =)
     
  40. Pedro-Paternostro

    Pedro-Paternostro

    Joined:
    Oct 31, 2015
    Posts:
    32
    @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.
unityunity