Search Unity

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

Question How can I lower the momory usage of my scriptable objects

Discussion in 'Editor & General Support' started by Bumpty, Jul 22, 2022.

  1. Bumpty

    Bumpty

    Joined:
    Jul 22, 2018
    Posts:
    30
    Hello, I'm following a scriptable object hierarchy in my current project.
    It's based on the Unite Austin 2017 talk about Scriptable Objects, and I'm using the plugin UnityAtoms to have pregenerated scriptable object variables of all sort.

    Basically I'm using scriptable object for everything in my game. Enemies health, enemies damage, enemeis attack delay, projectiles speed, projectiles damage, etc.
    Most of my scriptable objects are instantiated at runtime, so there is instance of scriptable objects (because I don't want all my enemies to share the same health).

    I'm using a pooling system which enable and disable my projectiles, enemies, etc. so I don't instantiate more at runtime. My scriptable object instance get deleted and recreated each time I pool an object.
    But now I'm having huge memory issue with those scriptable objects. Especially when there is a lot of projectiles, enemies and explosions on the screen (so tons of scriptable object).

    Here are some screenshot of memory usage at the start of the game and 30 minutes later.
    I don't think there is memory leak but I'm not sure how to test it. I guess if I load another scene with less object and still have a lot of ScriptableObject in memory it would mean I have memory leak issues?

    Thank you for your help!

    Before :

    Screenshot_42 (1).png

    After :

    Screenshot_40 (1).png
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,895
    So how much of this information actually needs to be on scriptable objects? And how delineated are these objects? Do you have an individual object for every single bit of data, or are you using them to combine data into collections?

    For example, does the enemy health need to be on an SO? Or could it just exist as a component on a instantiated prefab?

    Personally I think making instances of SO's is pointless. It's just the same as making instances of plain classes with more baggage, as you have to clean up after your own mess.

    The right tool for the right job, and Scriptable Object's aren't always the right tool for the job, much as I love them.
     
  3. Bumpty

    Bumpty

    Joined:
    Jul 22, 2018
    Posts:
    30
    I have individual SO for every single bit of data, I'm not combining them into a collections because I want to be able to access the single piece of data.

    Enemy health need to be an SO because it's easier to make modular script this way.
    But it could be a component which only store one variable. Like a FloatVariableMonobehavhiour. Would it use less memory?

    For an example as why I want to be modular : I can have a script which handle bar (health bar for enemies, level bar for player, loading screen bar) which require 2 scriptable object of type float to work. Now I can use this BarHandler script in all my projects and I can add more option to it with time. I could also have a minMaxScript which goal is just to limit a Float between 2 variables. There is tons of applications to have a very modular SO which only own a variable.

    If I couldn't do that I would have to create a script enemy bar handler which would take the whole enemy entity as a reference. I would need to create a loadingBar script as well which take all the loadingParameters and so on.
    I don't find it appealing to make new code for each fonctionality which basically shares the same behaviour. Also everytime I create a new code I have a small compilation time which overtime slow my development.
     
    Last edited: Jul 22, 2022
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,895
    There's no such thing as 'needs to be' or 'has to' when it comes to these things. There's always a different way with with code architecture, each with various pros or cons.

    For example you could easily make this 'bar' script take an interface instead, and components implementing this interface can inject themselves to achieve the same result. No SO needed. In base C# there's no shortage of ways to make code flexible and reusable before you even get to the Unity side of things.

    Just remember that every SO comes with a managed object on the C++ side of things. The more you use, naturally, the more memory they accrue, and this is expectedly more than just using plain C# classes. I've seen this memory bloat before with folks instantiating SO's heavily.

    If too many SO's are taking up too much memory, obviously the solution is to use less SO's. Not really another way around it.
     
  5. Bumpty

    Bumpty

    Joined:
    Jul 22, 2018
    Posts:
    30
    Of course it doesn't need to be an a SO, I should not have used the verb need. I just wanted to explain why I chose to have that many SO. I'd love to use something more optimized to make my code modular. Too bad my whole codebase is now based on SO and SO instance and it would take so much time to stop/reduce their usage now... I did a bit of research before using SO intensively but didn't find anything warning me of memory usage.

    I guess I will need to refactor all of this. Would it be less memory consuming to store the Variable in Monobehaviour (like FloatVariableMonobehaviour for example) ?

    Thank you for your time!
     
  6. BasicallyGames

    BasicallyGames

    Joined:
    Aug 31, 2018
    Posts:
    90
    Maybe I'm mis-understanding what you're trying to accomplish, but it sounds like all you need is a simple class to store your data in. This would essentially function as a scriptable object without the object.

    Something like

    Code (CSharp):
    1. public class EnemyData
    2. {
    3.    public float health;
    4.    public float strength;
    5.    etc...
    6. }
    With that you can create a new EnemyData variable and assign all the info you need to it. No object will be created.

    Scriptable Objects are primarily for creating and modifying data in the inspector to be read from by the game. They can be used on other ways of course, but creating them at runtime to store data isn't necessary when you can just use a class as shown above.
     
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,895
    Well this is the first thing to correct. I think the Unite video with it's 'FloatValue' example is a bit of a trap. You really don't/shouldn't make individual SO's for every single individual value. Not only is it an organisational nightmare, as you can see it leads to the issue you're encountering.

    You should consider SO's that are collections of data. Put all the stats for an enemy in the one SO class, for example. If you need to copy data without affecting the base SO, encapsulate the data in plain classes or structs and copy those instead, saving you the need to instantiate SO's.

    Where flexibly and modularity is needed, C# has plenty of options for that. Look at ways to utilise inheritance and interfaces (composition). Trust me in that understanding this on the C# of things will take you a lot further than SO's on their own.

    Of course I have my own simple SO objects:
    upload_2022-7-22_23-55-55.png

    But I seldom if ever need to use them.
     
  8. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    That does seem horrendously overkill.

    SOs, like any class that derivesUnityEngine.Object, have a C++ counterpart that is not subject to the C# garbage collector. This means that they will stick around in memory until you do one of these things:

    1) Call Destroy() on it;
    2) Load a scene using LoadSceneMode.Single;
    3) Call Resources.UnloadUnusedAssets;

    Instantiating them is also more expensive than creating instances of plain C# classes. Not to mention that if every single float, bool, int, etc in your MonoBehaviours are references to SOs all your code will run a bit slower when reading/writing them because they are scattered around memory instead of being next to each other alongside the MonoBehaviour instance: everything is a cache miss.
     
  9. Bumpty

    Bumpty

    Joined:
    Jul 22, 2018
    Posts:
    30
    Would it be less costly to have reference to multiple MonoBehaviour instead of ScriptableObjects? I don't want to have all my variable in the same Monolithic monobahaviour.

    I prefer to have small chunck of code for each functionality (for example for the UI of the player I prefer to have a script to handle the bar, another one to handle the amount of hp shown as a number, and basically a different script for each different behaviour of the player UI, it would make my code much more reusable.

    But this might be a wrong way of seeing things and actually building a PlayerUI script which handle everything related to playerUI is better, and this playerUI could reference some interface or just reference to different classes like barUI, showNumberUI, etc.

    I have to admit since I upgraded to Unity 2021 LTS I have way longer compilation time and it's anoying to wait for at least 3 seconds each time you change or create a new script. So I tend to avoid creating new script regularly and prefer to use existing monobehaviour.

    Would creating a script which call Ressources.UnloadUnusedAssets regularly help my case?
     
  10. Bumpty

    Bumpty

    Joined:
    Jul 22, 2018
    Posts:
    30
    I think more people should talk about how using "FloatValue" type of SO/class is a trap. Everytime I see someone talking about SO based hierarchy they say it's so nice, modular, highly debuggable, etc. But people should talk about cases where using these kind of SO is overkill or might causes some issues. I'm using SO as collections of data also, and before seeing Unite video I was only using SO like that.

    Do you think it's also bad to have a FloatValue MonoBehaviour, it's the easiest way I can think of for now without rewriting my whole codebase. Next project I will try another approach for my architecture.
     
  11. BasicallyGames

    BasicallyGames

    Joined:
    Aug 31, 2018
    Posts:
    90
    What exactly do you mean by a "FloatValue MonoBehavior"? Could you provide an example? I'm still unsure what it is you're trying to accomplish.
     
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,895
    What they're meaning is replace this:
    Code (CSharp):
    1. [CreateAssetMenu(menuName = "Variables/Float Value")]
    2. public class FloatObject : ScriptableObject
    3. {
    4.     public float FloatValue;
    5. }
    With this:
    Code (CSharp):
    1. public class FloatBehaviour : Monobehaviour
    2. {
    3.     public float FloatValue;
    4. }
    Which I think is a bad, if not worse idea than the scriptable objects.

    Using SO's is fine so long as you use them right. The "right" method is honestly as you were before, as collections of immutable data.

    Admittedly the Unite video never explicitly states to break down everything into individual SO's, I just see a lot of users get the wrong impression from it. Even I did initially.
     
    BasicallyGames likes this.
  13. BasicallyGames

    BasicallyGames

    Joined:
    Aug 31, 2018
    Posts:
    90
    That's what I was afraid they meant, haha.

    Op, I looked into UnityAtoms and I think I see what it is you're trying to do, but it looks like you may have taken it to an extreme that was never intended. UnityAtoms' setup (From what I can see) is intended for linking data that might need to be shared between two or more unrelated objects. Like you've mentioned, it could be handy for allowing your HUD to know how much health your player has without the two referencing each other. It is not, however, intended to be used on such a large scale, like for all your enemies. Not only does it use up way too much memory like you discovered, I also imagine it would be a nightmare to manage once your game reaches a certain level of complexity.

    A better way to do this while still maintaining some level of modularity might be to create a base class for things like your players and enemies to inherit from. For example:

    Code (CSharp):
    1. public class EntityBase : MonoBehavior
    2. {
    3.    public float health;
    4.    public float maxHealth;
    5.    public float strength;
    6.    etc...
    7. }
    Could be your base class. Then, any entity that inherits from it, be it the player or an enemy, will have these variables automatically.

    Code (CSharp):
    1. public class Player : EntityBase
    2. {
    3.    //Put player specific variables here. The variables in EntityBase are already included.
    4. }
    5.  
    6. public class Enemy : EntityBase
    7. {
    8.    //Same deal as the Player class.
    9. }
    Now if you have a health bar script, you don't have to make one specific to Player or Enemy. You can simply have it look for an EntityBase, and you can assign it a Player, Enemy, or any other class that inherits EntityBase:

    Code (CSharp):
    1. public class HealthBar : MonoBehavior
    2. {
    3.    public EntityBase entityToRead;
    4.  
    5.    Update()
    6.    {
    7.       float barVal = entityToRead.health / entityToRead.maxHealth;
    8.       //Use that value to update your health bar.
    9.    }
    10. }
    Is this as modular as UnityAtoms? No, but I think there's such a thing as trying to be too modular. As your games become more complex you will have to come up with solutions specific to certain projects, and solutions specific to certain scenarios within those projects.
     
  14. Bumpty

    Bumpty

    Joined:
    Jul 22, 2018
    Posts:
    30
    Do you think something like that would work, to keep a high degree of modularity?

    An interface which Monobehaviour that want to share their data will implement
    Code (CSharp):
    1. public interface IFloatDataContainer
    2. {
    3.     public float GetData(string key);
    4.     public void SetData(string key, float value);
    5. }
    An example of a Monobehaviour which hold some data with the Interface
    Code (CSharp):
    1. public class PlayerDataExample : MonoBehaviour, IFloatDataContainer
    2. {
    3.     Dictionary<string, float> _floatsData;
    4.     public float GetData(string key)
    5.     {
    6.         if(_floatsData.ContainsKey(key)) return _floatsData[key];
    7.         else
    8.         {
    9.             Debug.LogWarning("Couldn't get data : " + key + " in " + gameObject.name);
    10.             return 0;
    11.         }
    12.     }
    13.  
    14.     public void SetData(string key, float value)
    15.     {
    16.         if (_floatsData.ContainsKey(key)) _floatsData[key] = value;
    17.         else Debug.LogWarning("Couldn't set data : " + key + " in " + gameObject.name);
    18.     }
    19. }
    And a modular script which handle taking damage
    Code (CSharp):
    1.  
    2. public class GettingHitHandler : MonoBehaviour
    3. {
    4.     // This could take a PlayerData, but also anEnemyData, or even a ProjectileData, they just need to implement the interface.
    5.     [SerializeField] IFloatDataContainer DataForHealth;
    6.  
    7.     // This could be a scriptableObj containing only a string so no hard string reference.
    8.     [SerializeField] string healthKey;
    9.  
    10.     //amountOfProtection could also be stored in a Dictionarry if we implement IIntDataContainer so an external script need to access it;
    11.     [SerializeField] int amountOfProtection;
    12.  
    13.     void OnTakeDamage(float damage)
    14.     {
    15.         float health = DataForHealth.GetData(healthKey);
    16.  
    17.         if (amountOfProtection > 0)
    18.         {
    19.             amountOfProtection--;
    20.             return;
    21.         }
    22.  
    23.         health -= damage;
    24.         DataForHealth.SetData(healthKey, health);
    25.     }
    26. }
     
  15. Bumpty

    Bumpty

    Joined:
    Jul 22, 2018
    Posts:
    30
    Can you explain why it is worse?
    I'm not trying to say it is not as you probably have more experience than me. But I'd like to understand why it would be bad to use these.
    I would only use FloatMonobehaviour for internal variable which doesn't need to go out of a prefab
     
  16. BasicallyGames

    BasicallyGames

    Joined:
    Aug 31, 2018
    Posts:
    90
    That would definitely be a better solution than using scriptable objects in this situation. I wouldn't get too carried away with it though, but maybe that's just me. However, I see your comments thinking about using ScriptableObjects for things like assigning strings in the inspector, and I'm not sure I see what purpose that would serve. Your comment states that there would be "no hard string reference", but in this scenario would that be a problem? When would you need to change the healthKey of a large number of objects sharing the same healthKey string ScriptableObject? If this is something you think will be needed, then go for it, but if not then it would just be an unnecessary attempt at keeping things modular. Don't worry about keeping everything modular unless that modularity is truly needed or useful, otherwise you'll overcomplicate things for yourself.

    Also, I'll mention the parent class setup I discussed in my last post again by adding that this setup can be achieved with parent classes as well. If your OnTakeDamage function were part of EntityBase like this:

    Code (CSharp):
    1. public class EntityBase : MonoBehavior
    2. {
    3.    //...Variables from before...
    4.  
    5.    public virtual void OnTakeDamage(float damage)
    6.    {
    7.       health =- damage;
    8.       //Then do stuff if health is out. Since this function is virtual, you can add additional functionality specific to each class that inherits from this one, or even have it do something completely different.
    9.    }
    10. }
    Then you wouldn't need the interface, the DataContainers or a separate script for taking damage. However, if you do want to keep your scripts smaller, then your GettingHitHandler class could simply use an EntityBase variable and modify the values of each enemy/player/whatever directly. There's nothing wrong with having classes reference other classes if you know for certain they will all exist alongside each other.

    One thing I'll throw out there is that if you're instantiating all this data at runtime, you wouldn't need to use MonoBehavior or ScriptableObject. Both of those parent classes will mean your class exists as an object of some sort, and that doesn't seem necessary in this case. You could just do

    Code (CSharp):
    1. public class FloatData
    2. {
    3.     public float floatValue;
    4. }
    and this class could then be used like a variable, but it wouldn't come with the extra memory used by ScriptableObject or MonoBehavior. For example:

    Code (CSharp):
    1. public class Player : MonoBehavior
    2. {
    3.    public FloatData healthData = new FloatData();
    4.  
    5.    //Assign desired health value to healthData.floatValue
    6. }
    7.  
    8. public class HealthBar : MonoBehavior
    9. {
    10.    public FloatData barData = new FloatData();
    11.  
    12.    //Assign Player.healthData to barData the same way you'd assign a newly instantiated ScriptableObject or MonoBehavior.
    I still think this seems overkill on the scale it sounds like you're using it on, but doing this instead of instantiating a new ScriptableObject each time you pool an object should be much more memory efficient.