Unity 2021 Object Pool API - What is Object Pooling and How to Use the NEW API

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
there's a new object pooling api released with a unity 2021 editor but there's really not a lot of good documentation on it in this video we're going to look at it for the most common use case for object pooling which is pooling game objects that you will use repeatedly in your scene hey chris here from mom academy here to help you yes you make your game dead dreams become a reality today we're going to talk about object pooling which is a really important optimization technique that i use in every single game that i develop now some of you may ask what is an object pool or what is object pooling we're going to talk about that start assuming you don't really know anything about what is this technique so an object pool the whole concept of it is that you want to pre-create the objects that you're going to repeatedly use in your scene so that way you don't have this instantiation overhead because creating game objects or creating objects in general in auditorium programming is relatively expensive to do and so is destroying them so what we can do instead is create a bunch of them up front and then recycle them and just disable and re-enable them whenever we want to use them or get rid of them that way we don't have to destroy and recreate them every time the most common use case that you'll hear about whenever we talk about object pooling is using bullets because your bullets all look the same they all behave basically the same at least coming out of the same gun right the other thing that's really important to talk about whenever we're talking about the performance of an object pool is creating objects not only costs more from a cpu cycle perspective but it also means that you're allocating more memory and that is really what gets you the flip side of that is whenever we destroy the object we're freeing that memory which means that the garbage collector needs to come by and pick up your garbage and take it out and what that does is create stutter in your game and if you're using something like bullets that you're creating possibly hundreds of them every second or every few seconds that's a lot of garbage for your garbage collector to take out and that's gonna cause a lot of stutter in your game using the new object pool api there's two really important things that you need to use one of them is called git and one of them called release git is how you get a new object from the pool and releases what you call after you use the object and you like to return it to the pool the overall flow to use an object pool with object pool api is first to find a new object pool you can use the i object pool type if you would only like to use the base interface type or if you're going to use a specific implementation of the object pool you can also define it as that type and then just construct that particular object then whenever you're ready to get objects you use objectpool.git and once you're done maybe the bullet has made impact particle systems have played whatever and then you do object pool.release this object pool api is actually really good because it allows you to define custom creation functions to create your objects with so you can set up any dependencies any behaviors anything like that it also asks you to provide a function to return the object so that way you can do any setup whenever the object is got from the pool so maybe you need to set a velocity that's what we're going to do you can also do things like set up bullet damage anything like that hook up any static variables you have in the scene that are needed on that pooled object whenever you're going to unregister it you do the exact same thing you provide a callback function that the object pool will automatically invoke with the instance so that way you can do things like reset the state of all the objects you can unregister from any static classes that needed to know about it anything like that and then maybe set it to be inactive one other key parameter is what happens whenever the object cannot be returned to the pool because the pool has grown to be too large the size most of the time whenever that happens you just want to destroy that game object but maybe you can do some custom code there to also unregister from anything that's going on from static classes you can specify whether you want collection checks to be done so that way it can throw an exception whenever you try to release an object more than once that usually means that you're doing something kind of weird in your setup if you follow the setup that we're going to use in this tutorial you should never have this case where it comes up but it's still something good to know about and the last two variables on the constructor allow you to control the default size and the maximum size of the pool the default size you want to set it to be as accurate as possible because that will prevent array resizing under the hood of the object pool and that will save you some memory allocations the maximum size should also be a reasonable number because you don't want the object pool to grow to infinite size and use up all the memory so you can't do other key things in your game hey and just really quick i want to give a shout out to my patreon supporters i really appreciate it every bit helps the channel grow reach more people and add value to more people and that means more people are making their game development dreams become a reality if you want to help me in that cause you can show your support on patreon patreon.com academy you can get your name up on the screen you can get a voice shout out starting at the silver tier and some other cool perks special shout outs to rafael and andrew bowen for being the silver tier supporters i am so grateful thank you remember that this new api is only available in unity 2021 or higher so i'm using unity 2021.1.24 f1 that's the most recent version at the time that i'm making this video i've got a box for a level i'm going to be spawning a bunch of bullets in the spawn area that's just a box collider so i can easily visualize the bounds of the spawn area if i go to the project panel and open up the bullet prefab you can see that it's a particle system with a collider on it and a trail renderer basically whenever the bullet is traveling we'll see the trail render to see where it is and once it makes impact with something we're going to play the particle system [Music] to achieve that result and to show the object pooling performance benefits we're going to make two new scripts bullet and bullet spawner for the bullet we're going to use rigid body physics so we'll put a required component type of rigid body at the top we'll also add a private serialized field particle system called impact system and a private rigid body rigid body on awake i'll assign the rigidbody component equal to g component rigidbody i'll define a public void shoot that accepts a vector3 position a vector3 direction and a float speed we're going to call this from the bullet spawner whenever we spawn the bullet to tell the bullet where it should spawn the direction it should go and how fast it should go that way in here we'll do rigidbody.velocity equals vector30 transform position equals position transform forward equals the direction and finally rigidbody.addforce passing in the direction time speed and forcemode.velocitychange we'll then define the magic unity function private void ontriggerenter that accepts a client or other because remember we had that trigger collider on the bullet this will get called automatically whenever the bullet makes contact with something else we'll do impact system dot transform dot forward equals negative one times transform dot forward to make sure that our impact system is facing the right direction the opposite direction of where the pole is coming from because we have a very simple scene this is going to work fine we'll then do impact system.play and set the rigidbody.velocity to be vector30 since it's made impact we want the bullet to stop moving and the final thing we're going to do here is to find a private void on particle system stopped this is another magic unity function that's called by the particle system callback whenever we set up the stop action to be call back in here we'll destroy this game object so what that's going to do is after the particle system has completed playing will automatically destroy this game object you might ask why don't we just go ahead and use the destroy functionality on a particle system and you can do that right now and do the exact same thing but we're setting up for using the object pool now that we've set up our bullet let's go ahead and open up the bullet spawner we'll define a private bullet prefab serialize that field a private serialized field box collider spawn area a private serialized int bullets per second and set that to be 10 by default a private float speed also serialized and set that to 5 by default and a private serialized field bool use object pool and set that to be false by default and finally a private float last spawn time most of these i think are pretty self-explanatory spawn area is going to be that box collider i was talking about a second ago we'll be able to toggle at run time whether we're going to use the object pool or not and we have this last spawn time so that way we can tell when we should spawn another bullet let's jump right into spawning the bullets we'll define a float delay equals 1 divided by bullets per second if the last spawn time plus the delay is less than time that time then we should probably spawn a bullet but because responding bullets in an update that means that we can only spawn as many bullets as we have frame rate because remember update is called once per frame so what we're going to do is define an int bullets to spawn in frame equals map f.s time dot delta time divided by delay that will give us at least one bullet to spawn but sometimes it will be more so that way if we said something like a thousand and we're getting 60 frames it'll spawn something like 16 17 bullets and we'll do while bullets in frame is greater than zero if not use object pool because we haven't defined how we're gonna use object pool yet we'll do bullet instance equals instantiate prefab factor three zero and the quaternion identity for the rotation we'll do instance.transform.setparent to be this transform and say that the child should retain its current world space position and then we'll call a function called spawnbullet and pass in the instance then we'll decrement the bullets to spawn in frame and set the last spawn time to equal time.time at the end of the while loop we'll define a private void spawn bullet that accepts a bullet instance and in here we need to choose a spawn location based on the bounds of our spawn area so what we're going to do is set a vector3 spawn location to be a new vector3 where the x value is spawn area.transform.position.x plus spawnarea.center.x so what that's doing is giving us the world space coordinates with the spawn position and then we're adding in the center point and then what we're going to do is add in a random dot range negative one times spawn area.bounds.extends which is half the size of the bounds dot x and then go on the positive side so on area.balance.extense.x so we're choosing a random location somewhere within the range of this box collider and we're going to go ahead and duplicate that and do this exact same thing for y and z so we can spawn somewhere randomly inside of the box glider and we can adjust the size of the box glider to get a better spawn area then we'll set the instance transform.position to be the spawn location position and we'll finally call instance.shoot passing in the spawn location the spawn area.transform.right so we're always just going to send the bullet straight right and passing in the speed from the bullet spawner and i think this is a pretty typical way to spawn bullets without using an object pool is you're going to instantiate some bullet set it off to go wherever it's going to go and then at the end you'll destroy it one thing before we hop back to the unity editor is we need to make sure we divide one f by bullets per second since bullets per second is an int if we don't tell it that the 1 is a float then we will get integer division and we'll get 0 every time on the delay if we hop back to the unity editor and we select the bullet prefab we'll see that in the main section we've set the stop action to be callback that's where that on particle system stop gets called from the other options are none disable and destroy we'll attach the bullet script and drag the particle system to the particle system reference then we'll create a new game object we'll call it bullet spawner and attach the bullet spawner script we'll drag the bullet prefab to the prefab reference we'll drag the spawn area to the box collider spawn area and we'll leave the other ones alone there's one more thing we need to do though let's go ahead and set up the physics colliders for this world we select the project settings on the tags and layers section we'll add two new layers a spawn area and a bullet if we then go to the physics section we'll make the spawn area not collide with anything and the bullet will not glide with bullets or the spawn area we'll then set the bullet to be on the bullet layer and the spawn area to be on the spawn area layer if we then click play we'll see some pretty slow bullets traveling across the screen we'll see that we're getting a bunch of bullets under the bullet spawner and if we increase this to something like 100 bullets per second and open up the profiler we'll see that the bullet spawner update takes about 3 milliseconds and generates 0.8 kilobytes of garbage every frame almost sometimes even a full kilobyte of garbage every frame and while my powerful pc can handle this pretty okay you don't really see much of a stutter if you talk about a mobile solution or less powerful pc this starts to become a problem so enter in the object pool api that unity introduced in unity 2021 let's refactor our code so we can use the object pool api and compare it to this the first thing we're going to do is actually in the bullet class we're going to define a public delegate void on disable callback that accepts a bullet instance in a public on disabled callback called disable and we'll simply invoke this callback on the on particle system stopped instead of destroying this game object this allows us to control the disable or destroy behavior from the bullet spawner class back in the bullet spawner let's go ahead and define a private object pool of type bullet called bullet pool you could also use the i object pool here but i actually want to show us some properties that are only available in the object pool class for example the number of active objects is not always available in all implementations of the i object pool that's why we're choosing specifically this implementation on awake let's go ahead and define a bullet pool equals a new object pool bullet and in here we see that there's a lot of options here the create is the only required callback i'll pass in create pooled object on action git remember is what is called whenever we're getting an object from the pool so let's go ahead and define that as well as on take from pool action on release is what the object pool will call whenever we are releasing an object back into the pool so we'll define that as on return to pool action on destroy is what's called whenever the object pool has grown too large and we cannot return the object to the pool that we're trying to do so here we'll pass on destroy object collection checks are actually only performed in the editor and it will throw an exception if an instance is already in the pool i'm going to set it to false because with the architecture we're setting up we can actually not return an object to the pool multiple times and for the last two the default capacity i'm going to set to be 200 and the max size to be 100 000 because we're going to get pretty crazy with how many bullets were spawning 200 being the default capacity does not mean that the bullet pool will automatically have 200 objects available in it what that means is that the underlying data structure will be created with size 200. so once we get to the point where that underlying data store needs to be resized then it will resize it but we're setting it to be 200 capacity by default let's go ahead and start defining some of these functions let's start with the create pooled object we'll define a private bullet create pooled object we'll define a bullet instance equals instantiate prefab passing in vector3 0 and quaternion identity for the position in the rotation we'll then assign instance.disable plus equals return object to pool so we're assigning the delegate function to be this function that we're going to find in just a second where we will release this object instead of destroying it we'll then set the instance that game object set actually be false because we don't want bullets hanging around when they're not needed we'll then return the instance remember that on take from pool is called whenever we're actually going to take an object out of the pool create pooled object is only the creation function you can consider it a factory of bullets on take from pool is what's called whenever actually pulling the object out of the pool that's where we'll actually set the instance game object to be active so we'll define private void ontake from pool that accepts a bullet instance we'll set that game object to be active and then we'll spawn the bullet passing the instance remember that's where we're setting up the location and actually calling shoot and finally we'll set the transform parent to be this object saying that yes we do want the world space position to stay next let's define the on return to pool function that accepts a bullet instance and this one we will simply do instance.gameobject.setactive to be false finally let's do the on destroy object that's called whenever we cannot return an object to the pool we'll just call destroy instance.gameobject here and finally let's go ahead and define this return object to pool that we're setting whenever the particle system stops playing and in here we'll do pool dot release passing in the instance which will trigger the on return pool function that we just defined to be called or the on destroy object to be called if the pool is full now that we've all of the functions defined that we need for the object pool let's go ahead and start using it in the update function if use object pool is true we'll scroll down to update and where we have if not use object pool we'll add else block and simply put bullet pool dot git that's all we need to do to actually use this object pool on git from pool we do all the code that we have in this block about doing the spawn bullet so we just need this one line of code i'm going to do one more thing real fast so we can actually see the status of the pool in game i'll define the magic unity function on gui which is called once per frame to draw gooey stuff using the i am gui ui system i'll just draw two gui labels at the top left of the screen that's a total pool size passing the bullet pool dot count all and one of them that says active objects using bullet pool dot count active this also count inactive and inactive plus active should equal all that's why i'm not showing the inactive as well if we hop back to the unity editor and click play i'll set up the speed to be a little bit faster so our bullets are a little bit more engaging let's go ahead and increase the bullets per second to 200 not using the object pool we can see occasionally we'll get some really big spikes we're generally generating between one and two kilobytes of garbage every single frame that's really bad and this big spike is the garbage collector running to clean up all of that memory it also takes generally about somewhere between five and nine milliseconds for a frame to execute just this bullet spawner update if we swap it to the object pool and we actually check out what's happening while the total pool size is expanding you'll see that it's the exact same result getting a bunch of garbage generated because we're instantiating a bunch of new objects we're still destroying the previous ones and really that's one of the only drawbacks that i see to using this object pooling api over rolling your own rolling your own you can pre-instantiate all these objects and prevent that gc ally from happening in your core gameplay loop using this api you'd actually have to get all the objects you'd like to be pre-instantiated before you start running the game and while you can do that it'd be really nice to have an option to pre-instantiate some number of objects whenever you instantiate the object pool if we let it run a little bit longer for all of the previously destroyed ones to go away and for the pool to be fully warmed up you'll see that the total pool size is no longer expanding and the active objects is staying pretty stable in the 800 to 900 range most of the time we now look at the profiler our bullet spawner.update has reduced in execution time to 0.3 milliseconds that's about three percent of the execution time that we were talking about before and if we continue to let it run there won't ever be a spike whenever the garbage collector comes because we're not really generating garbage you'll see that the gc alec at least for bullet spawner is zero now there's still a limit to how many bullets we can spawn though because there are other systems involved such as the physics system and the graphic system if we set it to be 500 the frame rate's not very good but our bullet spawner time is still very low something like one millisecond to execute even with 500 bullets if we turn off the object pool with 500 bullets per second there's much more physics processing time we're actually generating 20 kilobytes of garbage every frame and our execution time of just the bullet spawner is something like 70 somewhere between 70 and 90 100 even it's actually getting worse i hope this helps you visualize and really see the benefits of using an object pool for something like bullets or commonly respawned objects object pooling is a really powerful tool to optimize your game whenever you have a lot of objects that are repeatedly created and destroyed at runtime to prevent you from having memory allocations and from spending cpu cycles on creating those objects i hope you learned about how object pooling works how to use the unity 2021 api for object pooling that's natively supported now huzzah and if you did get a lot of value out of this video please consider liking and subscribing to help the channel grow reach more people and add value to more people there's new videos posted every tuesday and i'll see you next week
Info
Channel: LlamAcademy
Views: 11,941
Rating: undefined out of 5
Keywords: Unity, Tutorial, How to, How to unity, unity how to, gamedev, gamedevelopment, game development, tutorialtuesday, tutorial tuesday, llamacademy, optimization, object pool, unity 2021 object pool api, unity new object pool api, official unity 2021 object pool tutorial, object pool tutorial, how to use unity 2021 object pool api, object pool api, new object pool api, unity 2021 object pool, object pooling unity, why use object pooling, object, pool, 2021, new, api
Id: zyzqA_CPz2E
Channel Id: undefined
Length: 19min 24sec (1164 seconds)
Published: Tue Oct 26 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.