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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Performantly generating objects only near the character in an open world game?

Discussion in 'General Discussion' started by bigdaddy69dev, Dec 30, 2021.

  1. bigdaddy69dev

    bigdaddy69dev

    Joined:
    Jul 10, 2021
    Posts:
    13
    I'm creating a sandbox game featuring a large open level. The whole level is going to be filled up with lots of floors, walls, trees, zombies and props which are going to be generated at run time and all of them will have their own scripts attached to them and will able to be destroyed during run time. I save all of the object data in binary format which includes position, rotation and extra data. I can easily filter the objects near the character position with by looping through the data array. I'm thinking to release the game for PC and mobile.

    Now my biggest challenge is how do I performantly generate objects which are near the character. I know that instantiating and destroying objects during runtime have serious fps drop. What I'm thinking is should I instantiate all of the objects when the level starts, disable all of them and enable only those which are near the character? By doing this, what I'm worried about is it will take too much memory because all of the objects with their own scripts in a large level are going to be loaded onto the memory.

    Is there any good practices and must-follow rules for this kind of object generation? I've done some research on the internet but I don't have any luck. Thanks.
     
    Last edited: Dec 30, 2021
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    You should look up "Object Pools".

    The idea is that you have a zombie, for example. Then the player kills it and it despawns.
    When it despawns, instead of destroying the zombie, you disable it and hide it from the world.

    When you need another zombie, you reuse it. You move it to the right position, reset its stats, and thne make it visible again.

    So you shouldn't instantiate all the objects, only those player can interact with.
     
  3. bigdaddy69dev

    bigdaddy69dev

    Joined:
    Jul 10, 2021
    Posts:
    13
    Thanks for the reply. I'm familiar with object pooling and already been using it for shooting bullets. But for the level generation, to give an example there will be a lot of different types of objects. Let's assume that the game will have 100 different types of objects. The camera can show up to 100 objects at a time. Since how many objects of each type is going to be showed up to the camera is unpredictable, to cover up, 100 type x 100 count = 10000 objects have to be pooled at the start. And they all have different scripts and data so managing this pool would be quite a challenge. I'm wondering is there a way that will be easier to manage than object pooling the whole level.
     
  4. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,152
    You should only see lag if you try to instantiate a bunch of stuff in the same frame, or if the objects you are trying to instantiate have a large activation penalty (Unity Terrain for instance).

    It sounds like most of your objects are very small and cheap to instantiate. If you just limit how many you instantiate in a single frame you'll probably be okay.

    Do keep in mind that these game object prefabs will need to be stored in Resources, and last I checked there is a limit to how much you can have in there when building your project. You are better off using Scenes or addressables.

    If you decide to stick with instantiating prefabs, keep in mind that if you have a very large (memory wise) object that you are trying to load, you may be better off using Resources.LoadAsync to load it. You still have to instantiate it in a single frame so isn't a catch all solution to loading large objects, but it can help.
     
  5. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    Use PREFAB itself as a key to store objects in a pool. All those objects can be stored in the same pool.
    Do not prepool objects at runtime.
     
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,519
    They don't have to be in Resources. You can use AssetBundles or Addressables, as a couple of examples. Or a ScriptableObject with a list of prefabs is another potential approach.

    Personally I'd recommend Addressables for this use case. Usage is almost as simple as Resources, it solves many of the issues with that system, and it also gives you the ability to separate stuff out of your base game build later on if that becomes relevant.
     
  7. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,152
    Sorry, I realize I wasn't clear in what I was talking about, which was specifically the use case I imagine the OP is currently thinking about or already using. That is, having prefabs stored in Resources and loading them using Resources.Load or LoadAsync, then instantiating them.

    But yeah, I agree with you, addressables are better.
     
  8. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,493
    I see the problem. If it is likely that you suddenly need for example 30 of one type and not always just 2-3 (while there are 100 types), pooling will be relatively useless because holding 30 of every type is not feasible and thus the pool will always have to create new instances and throw old ones away.

    Fear the only solution to this is to make your objects much better configurable. 100 Prefabs is really steep. Something of the calibre of WOW. For sure you have not developed 100 REALLY different things, have you?

    Try to consolidate as many of those as possible so that you only need to flip binary switches or maybe assign a "scriptable object" (basically a data-only prefab that can be just assigend to a variable) to your objects to turn object type A into B easily like that.
    Assigning and setting variables is very fast after all and then a traditional pool works again (or maybe a separate one for each of the TOP-types your have consolidated your 100 to).

    Classic examples for such top-types would be "land enemy", "air enemy", "water enemy". Ther main difference being a different pathfinding script for example.
    The actual sprites and which type of attacks are available for every specific enemy of those types is then configured by scriptable objects that are assigned to the generic top-type instances upon creation or reactivation from the pool.
     
    Last edited: Dec 31, 2021
    tmonestudio and bigdaddy69dev like this.
  9. bigdaddy69dev

    bigdaddy69dev

    Joined:
    Jul 10, 2021
    Posts:
    13
    Thanks for the reply. I'll try to configure my objects to easily switchable from one type to another and narrow down the total type count. Will try to load the objects with traditional pooling.
     
  10. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,519
    I think some experimentation is in order. I wouldn't assume that making your objects internally switchable will necessarily be of benefit, because there will be a cost to whatever logic you run to do that, and it may not necessarily be cheaper than the cost of natively instantiating an object.

    You can also set up your pools to expand or shrink as necessary, so unpredictable and different sizes isn't necessarily an issue.

    But moreover, see this:
    If the player can't interact with something then it doesn't need to be a GameObject at all. You can use instanced rendering to display a huge number of the same object at very little cost, each one represented by a single matrix in an array. It doesn't really matter if there's 1 or 1000, because it's only the number of items in the array which changes.

    If the player needs to be able to collide with them then that can be handled with a pool of just a few colliders, because you only need to care about very nearby ones.

    Of course to handle that all nicely you'll probably want some kind of spatial data structure, but a good old grid will get the job done if you want to avoid anything too complicated.
     
  11. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    Sound like premature optimization:
    - you heard instancing is bad
    - you want to find a silver bullet solution before measuring things
    - you don't know how you program will react to that case

    It's okay to ask, but while doing so, just do the basic stuff, you'll be surprise with what you can get away with.
     
    angrypenguin likes this.
  12. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,800
    You have a large list of objects. No need to loop through them per frame. Just increment an index once per frame, check the distance to see whether it should be visible and any other line of sight or other reasons to show or not show. One object it needs to pamper per frame means you can get pretty explicit filtering without losing framerate. Reset the index once it exhausts the list.
     
  13. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,493
    It is an issue in such a case because the poolsize technically needs to be: number_of_types * expected_number_of_one_type_needed_at_once.
    The pool just doesn't help you if you need 20 instances of a different type every time because you'd need to hold 20 of every type in the pool.

    It all comes down to how random the occurrence truly is. If a new type only occurs occasionally or you have something like "biomes", aka regions where only a subset of all your types can occur, then you may work with pools better because your "number_of_types" shrinks to the types that can occur in the current biome.

    Evaluating 100 switch statements most likely will have less of an impact than instantiating a dozen objects every couple of frames.

    Of course premature optimization can easily be a killer, it is naturally recommended to test first ;)
     
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,519
    Then it's not "expanding or shrinking as necessary". ;) But also, there's not necessarily anything wrong with a pool having 20 instances of each type. That might sound terrible to a human, but a computer doesn't care. The pool is there to solve a specific problem: instantiation costs being too high. If that is an issue and the memory usage of the pool isn't then it's a trade worth exploring.

    Keep in mind that the resource heavy part of most GameObjects is the attached assets - textures, meshes, audio clips and so on - and those are loaded once whether there's 1 or 1000 instances of the object loaded.

    See the above. This may not take up as much memory as you think. I would always start by just loading everything up-front if that's an option based on the game's design, and then only "fix" memory usage if it's actually a problem.

    I only use pooling "by default" for things where instantiation / destruction is a part of their mechanical life cycle. For instance, a shooter may have many projectiles getting spawned, flying through the map, and then hitting things / timing out every second. It's also trivial to know when I need a new projectile, and they usually have a very low reset cost, so it makes a lot of sense to pool them and re-use them. In fact, that's in line with the above idea of "pre-instantiate everything" because all of my projectiles can be instantiated when the level loads.*

    Pooling complex objects also has a few downsides. It's possible for the de-pooling cost to be higher than just instantiating a new one, depending on what setup has to be done. It also exposes you to increased possibility of state-related bugs if you forget to clear something, or if other objects have kept references to a thing which has since gone back into the pool.

    So while it doesn't answer the OP's question, this is what they really need to know:
    First of all, there's a significant chance it'll "just work". But even if it doesn't, if you've got something built you can test actual performance to see where the bottlenecks are, rather than guessing and putting a bunch of effort into something that might not help, and could in fact hinder.

    * In the specific case of projectiles they probably wouldn't even be GameObjects, but that's out of scope for this discussion.
     
    Ryiah, Joe-Censored and DragonCoder like this.
  15. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    You don't need to pre-warm your object pool completely or at all. You can design your object pool so when a requested object isn't available in the pool it gets instantiated on the fly.
     
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,519
    And you can drop stuff that hasn't been used for too long, too. Though only do that if there's some benefit. Dormant objects don't cost much.