Search Unity

Memory Issues in a City Building Game

Discussion in 'Scripting' started by Saad_Khawaja, Aug 19, 2016.

  1. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    Hi all,
    I'm making a city building game for mobile in which the terrain/road structure is fixed.

    I'm struggling with how to manage memory issues. Every time a building is placed, it takes up memory - and obviously this is true for every asset (cars, people, props) instantiated.

    What is the best way to get through this? I'm thinking of:

    1. Destroying assets not in view frustrum
    2. Instantiating assets in the frustrum as the user is moving the camera (in a coroutine)
    3. Simultaneously calling Resources.Unloadunusedassets

    Is there a better way of managing memory? Also, a better way to find out which asset is in frustrum as compared to having a large trigger collider on the camera?
     
  2. ToshoDaimos

    ToshoDaimos

    Joined:
    Jan 30, 2013
    Posts:
    679
    This is a complex problem. There is no quick fix. You need to design your whole game architecture to support large worlds/cities.
     
    lloydsummers likes this.
  3. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    Why is that? Where do you think the architecture needs to be changed?
     
  4. ToshoDaimos

    ToshoDaimos

    Joined:
    Jan 30, 2013
    Posts:
    679
    Example: for each game object in your game you will have some logic representation and and some interface representation. For ex. building can be defined by its type, height, position and orientation (logic). Its mesh and looks are completely separate. You need to handle logic separately and interface separately. For ex. you should not feed into frustrum culling buildings which are at the other side of your city.

    Large C++ projects almost always have "Resource Manager" which handles high level memory management. Making such a manager is not trivial.
     
  5. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    838
    Do you have problems with memory at the moment?
     
    NoBrainer-David likes this.
  6. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    Not at this moment but as the user builds the city, yes the memory keeps on increasing.
     
  7. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    838
    If you have more objects you will always consume more memory, there is no way to magically prevent that. You can try to reduce the amount of memory per object, but you'll always have a limit.

    What is the amount of buildings you are targeting at for your game, and at what point are there problems?
     
  8. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    In any one scene, the maximum number of buildings present can be 500-600.
    Why is destroying the objects out of the view a bad idea or something that can not be done?
     
  9. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    838
    Of course it can be done. But it depends on the game, my bet is that you will spend much more time/memory on destroying and making objects. That is something you specifically dont want to do and will cause GC spikes. Its much better to have a pool of objects and dont change them.

    What i'm also trying to say is see if you have a problem right now, and profile that. You cannot solve something that isnt problematic, premature optimalisation is the root of all evil.
     
    Kiwasi and landon912 like this.
  10. CrymX

    CrymX

    Joined:
    Feb 16, 2015
    Posts:
    179
    Hello there,

    It's remember me some article performance on Unity, check this https://blogs.unity3d.com/2015/12/23/1k-update-calls/

    Maybe you have too many calls useless like this :

    https://docs.unity3d.com/Manual/UnderstandingAutomaticMemoryManagement.html

    and more this code :

    Good :

    Code (CSharp):
    1.   void Update() {
    2.         if (score != oldScore) {
    3.             scoreText = "Score: " + score.ToString();
    4.             scoreBoard.text = scoreText;
    5.             oldScore = score;
    6.         }
    7.     }
    Bad :


    Code (CSharp):
    1. function Update() {
    2.         scoreText = "Score: " + score.ToString();
    3.         scoreBoard.text = scoreText;
    4.         oldScore = score;
    5. }
    I assume you have many scripts on each element of your game and maybe this can help you, let me know

    http://docs.unity3d.com/460/Documentation/Manual/OcclusionCulling.html
     
  11. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    If you fully control the camera of the game, and the user is just along for the ride, then destroying objects that exit the view is reasonable. In this case, you can be sure that your user won't be able to view the objects until you're ready to show them again.

    However, if your user can control the position of the camera, then all of the above doesn't apply anymore. You can't be assured that the user won't turn the camera away from some buildings, then immediately turn it back to view them. In this case, don't destroy objects as they exit the view frustum. Destruction and Instantiation is expensive, even with object pooling.

    I suggest that you focus on limiting the overall memory usage by re-using assets. The expense of a typical 3D asset occurs (from most expensive to least) from the textures that are loaded for it, the mesh to represent the geometry, the transform hierarchy to represent its skinning data, and then any miscellanous stuff such as materials and renderers to reference the above data. So, the best gains in memory come from re-using textures. Let your buildings share as many textures as possible. Again, also share meshes where ever possible. If you're generating buildings procedurally, consider generating a handful of textures and meshes, then use shaders with vertex displacement and color changes to give the illusion of an infinite variety.
     
    Kiwasi likes this.
  12. image28

    image28

    Joined:
    Jul 17, 2013
    Posts:
    457
    Have you tried Unity's Memory Profiler will tell exactly what is using how much memory... it's a life saver really
     
  13. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    Just to add to what is above.

    You have to play a delicate balance.

    Destroy. By constantly destroying and creating objects - especially with colliders - you have a big CPU hit. This is why many games on mobile use object pooling (although, based on my own personal tests, object pooling matters very little - as it is the first time the collider is enabled/disabled and in-view that creates the first big CPU hit).

    Disable. By keeping them all but enabling and disabling them, you will have a big memory hit but save on CPU. This is Occlusion Culling.

    LOD. You can also reduce work on the graphics card by also adding in LOD's for all your objects. This ensures that as things get farther away, it becomes a simpler mesh (but again, the old meshes typically stay in memory, causing a memory use increase).

    Alternatively, the 3 items above only work if you haven't used a system to simplify the meshes into one giant mesh ball.

    You need to decide what your minimum platform is, what it's acceptable CPU use, acceptable Memory use and GFX use. Then you can use profiler to keep tabs and ensure you are within your requirements as you build. MOST systems that are Unity compatible, have an abundance of memory, and limitations of CPU and GFX. So, when you consider your holy trinity, which side should give way first?

    I usually sacrifice memory before anything else. Typically, I have a script that monitors FPS. If the FPS drops too low, it "triggers" a global LOD and quality reduction. If the FPS is doing well enough, it "triggers" a global LOD increase and quality increase. It checks this balance over a set period of time. So, as the game gets sluggish, the quality decreases, allowing the game speed to be maintained. This is least memory stable, but much higher CPU predictable. By running it over a period of time, it prevents it from stepping down and back up immediately by forcing a delay in the response (personal choice, less reactive, but less jittery changes).

    There are also other ways of saving memory use, such as ... what is that called. Where you use a static single reference as the default for everything, and using a getter and setter to change when it is modified, so everything shares a single reference until it modifies it ... Good for mobile, I don't remember (I don't build for mobile anymore).

    Which means I support this wholeheartedly :)
     
    Last edited: Aug 20, 2016
  14. NoBrainer-David

    NoBrainer-David

    Joined:
    Jan 5, 2014
    Posts:
    34
    lloydsummers likes this.
  15. NoBrainer-David

    NoBrainer-David

    Joined:
    Jan 5, 2014
    Posts:
    34
    I think you are referring to the Singleton Design Pattern. However, relying on singletons drastically decreases the flexibility of your code. I would not advise its use. You can think about it, when the structure of your program is set and you need to squeeze out every tiny bit of performance.
     
  16. zugsoft

    zugsoft

    Joined:
    Apr 23, 2014
    Posts:
    453
    To reduce memory on Mobile, I use a pooling system, create 1 static List<Gameobject> for building, one for vehicle.
    Instead of use 100 buildings, I reuse 20 buildinds.
    The unload method doesn't work vety well, if you load a sceneA , check the memory, load a sceneB, call unload method and cg.collect, load again the sceneA and check the memory, you will see the memory
     
  17. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    Good one, I us singleton design patterns for simplifying access to a monobehaviour - usually I keep only one as as GameManager. Which drives access to all my scene elements, core events and data access points.

    What I was thinking of was the flyweight pattern.
     
    image28 likes this.
  18. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    Thank you for the suggestions. Really appreciate them.

    It's actually tricky as the project is half done but the architecture really isn't there. I picked up the project later and it's a question of whether to re do the architecture completely or to paper over the cracks and let it go for now as time is considerably very less.

    It's not exactly a very large city builder game (it's a cross between Age of Empires and a city builder) - as the terrain is about 3000x2000 and can contain maximum of 500 buildings (some being larger like airports, landmarks etc).

    There is very less optimization development wise and here's how the project is at the moment

    1. Each building has a box collider (trigger)
    2. There is no grid at all - nav mesh is baked and each building has a nav mesh obstacle.
    3. Workers are nav mesh agents (maximum number of workers can be 40). You can select a worker and build buildings, move them around.
    4. When a building is being placed, the validity of the placement is checked using colliders (Trigger Stay)
    5. Building selection is done via Raycast
    6. Cosmetic Movement of pedestrians/workers is done using simple path/spline following. These do not have colliders.
    7. Assets are well optimized - low poly and one texture atlas is shared between multiple. Although the mesh is not combined and there are multple (read: 100s of) mesh objects inside a building object.

    Our target device is be iPhone 5S and above. Memory usage after 40-50 buildings is around 300 MB which is a lot but I've been looking at the profiler and have brought that down to 200.

    Considering the overall scheme of things, how do you guys think I should optimize the game as much as possible - both memory and cpu without going back to the basics. Is that possible? Also, is a grid really necessary?
     
  19. zugsoft

    zugsoft

    Joined:
    Apr 23, 2014
    Posts:
    453
    50 Building and 300MB ?? It's very high.
    Share your Profiler screenshoot please.
     
    DroidifyDevs likes this.
  20. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    While looking thru this read I noticed no one asked: How large are the models of the buildings? Also are you using Precomputed GI for lighting? Have you tried Occlusion Culling? I think Occlusion Culling is a better option than constantly destroying and instantiating objects.

    This thread is actually very interesting for me as I'm going to run into this problem too :)
     
  21. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    I don't touch apple products, so I can't help you on that optimization.

    But I'd worry first about the max impact. Create 500 buildings and get an average from there. I expect the bulk of the 200mb is the engine itself regardless of the buildings, so I wouldn't be surprised if 500 buildings had no real impact.

    Unity is a hog by design, you always get everything or nothing with it.
     
  22. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    However 200MB isn't even that much considering that the Samsung S6 has 3GB of RAM. While the iPhone 5S has 1GB (which is pretty bad by today's standards), using 20% of your ram to run a game isn't that bad. On PC games you can barely get by with 8GB of ram, so I wouldn't even be too worried if the maximum amount of RAM used is 200mb. Asphalt 8 takes 300-400MB of RAM on my Samsung Tab 3 (total 1GB ram) so 200MB isn't even that much of a concern.
     
  23. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    It is 200 MB right now but growing with every building, so that's a concern. Also, in my experience, iPhone 5S gives memory warning at 400mb and subsequently kills the app over 500-600 MB (if i'm not wrong).

    Anyway, the buildings are low poly (maximum 6000 poly for a very large stadium). Occlusion culling will probably improve rendering/cpu overhead but definitely not memory and no I don't use precomputed GI for lighting.
     
  24. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    To be honest, the value at 50 buildings kind of doesn't matter yet. The value at the max 500 buildings does matter. Profiler will show you the allocation of the memory. Once that is addressed then you can look at lesser optimisation.

    I don't really have interest in apple products so when it runs low on memory I don't know what it does. But it sounds like you have a target 500mb max, so create 500 buildings and see if you really have a problem or if you are just pulling an IBM and trying to optimize an idle loop (doing work you don't need to yet).

    Edit: Consider as well, editor memory is not the same as on device memory. You need to debug and profile on device to check that.
     
  25. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    True about Occlusion Culling, I simply recommended it because you mentioned destroying and instantiating objects, which could get expensive. Try putting on all 500 buildings and see how much RAM you use.

    At this point could you please post a Profiler screenshot? http://docs.unity3d.com/Manual/ProfilerMemory.html
     
  26. dutchkiller2000

    dutchkiller2000

    Joined:
    May 13, 2016
    Posts:
    121
    would make some LOD script(level of detail) how furtur away you go from a object how lower the details are of a building
     
    DroidifyDevs likes this.
  27. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    Just to reiterate though Occlusion Culling, GI, Object Pooling and LOD do not improve memory. All four sacrifice memory to improve CPU.
     
  28. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    Yes checking at the max sounds like a good idea. Let me instantiate 500 different buildings and get back with the profiler results.
     
    DroidifyDevs and lloydsummers like this.
  29. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    Exactly. CPU usage is high though according to XCode but that isn't affecting the FPS.
     
  30. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    Yeah CPU use is always high, because Unity ramps up the cpu and forces the gpu into 3D. I should have said all four increase FPS by using memory :)
     
  31. Saad_Khawaja

    Saad_Khawaja

    Joined:
    Dec 12, 2013
    Posts:
    31
    I raised a few questions in my last post. What do you think should be the approach to such a game for mobile platform? Is a grid necessary? Will using navmesh, navmesh obstacles, colliders, navmesh agents result in problems later on?
     
  32. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    Good questions. So, a couple of items. Colliders, especially mesh colliders, cause latency on instantiation and destruction. They carry no more memory use than a mesh (often a lot less). It is necessary for mouse press detection and overlap though, so kind of a necessary evil. It impacts FPS.

    I would stay with navmesh and not use a grid. Grid is good for usability, but won't help much with anything else. Plus, I recommend using the built in functionality anywhere you can to simplify your development process. The last thing you want to do is spend time rolling your own tools (let the engine do it for you) or trusting a third party library (that may get pulled out of the store front tomorrow).

    I always take three samples. A relatively empty game, a busy game, and then a maxed out game. Use those three to find your FPS, Memory and CPU use - although to be honest I usually don't care about CPU so much.

    Those samples will tell you where you want to make cuts.

    Too much CPU? This may slow down other apps, but usually causes lots of battery drain. Disable things like gyro, and unneeded tracking systems. Avoid Updates and move them to slower Coroutines for less critical stuff. Force vsync and slow down the FPS to 30.

    Too much Memory? Take a look in profiler on where the memory is going. It will show you if the textures are too big (reduce the texture packing in the quality settings), or to many materials, or too much data. Just follow that trail for memory reduction.

    Too slow? This is what hits MOST systems. This is where you want to look at object pooling, LOD, occlusion culling, light baking etc.

    That's normal, its a very low impact during play, but will slow down instantiate (creation of the building).

    This is just a choice. Adding a grid will really only make it easier for users to select a location - it won't really improve performance (much). NavMesh takes up some memory, it has mapped out an area, and will do some CPU and FPS hits - but it is unlikely to be more impactful than rolling a DIY tool. It allows for some more complex things like agent animations and movements. But what slows down NavMesh is usually Animations.

    I think that sounds cool. I like to rely on navmesh to do work for me.

    That's normal, its a one-time hit to FPS. Probably takes less than 1ms to complete.

    That's actually better than using mouse clicks. One-off raycasts are VERY light on performance. Even large volumes are light on performance, and lighter than using colliders.

    That's good for mobile. On PC you could go all out, but colliders require checking - which slows things down from a FPS perspective.

    There are good and bad problems with nested objects. If it is a scene (i.e. the ground and trees etc) and you don't need them to animate - mark them as Static. Having these cut-up within reason is actually a good thing - Occlusion Culling only works on "parts". A solid mesh won't work with Occlusion Culling.

    You could use something like SimpleMesh to combine the meshes together into slightly bigger groups. And some of those tools support automatic LOD creation as well, which will give you a big performance boost to FPS at a cost of memory. But don't over consolidate your meshes, it will slow the scene down, because it can't pop objects in and out.

    Another item to mention - you can use additive scene loading. So you can stream in and out parts of the scene (i.e. backgrounds, other areas) to speed up loading times and to control some of the memory elements more directly and to seriously speed up things like light baking.

    You can also run audio off disk instead of out of memory.
     
  33. NoBrainer-David

    NoBrainer-David

    Joined:
    Jan 5, 2014
    Posts:
    34
    When I read this, I thought that you should worry about other functionality first. Maybe do a stress test, as Kerm_Ed suggested and then see if that maximum case's FPS is acceptable for you.

    As Quingu said, there has to be an architecture in place for these kinds of optimizations. Additionally, it isn't just a question of code. The assets themselves will have to be set up in a way to allow for smart allocation and reuse of objects.
     
  34. NoBrainer-David

    NoBrainer-David

    Joined:
    Jan 5, 2014
    Posts:
    34
    Ah, I see. Using a GameManager as a singleton does get rid of a lot of instantiation code that passes references around or a lot of manual Unity Editor legwork.

    A case against singletons:
    Consider a tetris-like game. What if you want 4 game states running at the same time for a split-screen session? Will one GameManager suffice?
    If you have references to information that is only relevant to one player (score, for example), then all code that accesses the GameManager will have to adjust their code to introduce some kind of disambiguation.
    If each class that accesses the GameManager had a reference to one, then you could disambiguate the different player's states by passing different references to their attributes.
    Thus, the scripts that access the GameManager will not have to be changed. Only their initialization (e.g. passing a different reference to GameManager during creation).
     
  35. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    You could create a List or Array of Instances, to allow multiple game states running concurrently. But, that is pretty risky stuff.

    Usually, what my general rule is:
    - If every element uses the same method / event the exact same, and you only want a single reference (such as, say, save data or game state changes) then I would run it through a Game Manager
    - If every element uses the same method / event slightly differently, or concurrently, I use inheritance / static utility classes

    That being said, building a 4 state or 8 state system for the games could be done with a Singleton, I would probably still use it. You would just need to have a holder class that oversees the individual game data...

    So, if you were to say,

    Code (CSharp):
    1. InvokeGameQuit(string PlayerID) {
    2. }
    It would become
    Code (CSharp):
    1. InvokeGameQuit(string PlayerID, string GameID) {
    2. }
    Although if you are using PlayerID's, you could over-lap scores and not really worry about it. For example, if you used a GameRegistry static holder class somewhere, you could use:

    Code (CSharp):
    1. InvokeGameQuit(string PlayerID, string GameID) {
    2.   if (GameRegistry.ActiveGames.Any(x=>x.GameID == GameID) {
    3.     // Do Destruction Code for GameID
    4.   }
    5. }
    Or if you did the same for Player Scores
    Code (CSharp):
    1. InvokePlayerScoreChange(string PlayerID, string GameID, int ScoreChange) {
    2.   var player = GameRegistry.ActivePlayer.FirstOrDefault(x=>x.PlayerID==PlayerID);
    3.   if (player!=null) {
    4.       GameRegistry.AddPlayerScore(PlayerID, GameID);
    5.   }
    6. }
    Assuming then that InvokePlayerScoreChange triggered a global event to all players with a different, say PlayerController class. Their individual PlayerController could check their PlayerID against the one provided to InvokePlayerScoreChange, and update the individuals GUI. This lets you separate the users GUI and the Data, so that you could send the data event then off to the server side (so the user can't hack their memory to change their score).

    But I usually work on single instance games myself. I'd love to see what others are doing.
     
    Last edited: Aug 21, 2016
  36. NoBrainer-David

    NoBrainer-David

    Joined:
    Jan 5, 2014
    Posts:
    34
    You just showed exactly why it is inflexible to use a singleton. Of course you can do disambiguation with player IDs or other variables. The drawback is, that you have to change every line of code where the function InvokeGameQuit is called. Depending on the complexity of the project, this can be a lot of manual legwork.
    Additionally, the variable with which you do the disambiguation has to be set individually for each game instance. It is hardly more work to just create multiple instances of GameManager and pass them around. The big upside to that is, that you do not have to alter the code of every script that calls a GameManager function specific to an instance.

    This passing around of references during initialization can be achieved with dependency injection. So it is actually less work and more flexible.

    Anyway, this has gotten way off topic, so if you want to continue the discussion, maybe we should switch to private messages :)