How to Setup An Object Pool (New Built In Method) | Unity Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome today I want to show you the new solution to the most common performance issue you'll run into in unity and that's garbage collection c-sharp manages our memory for us which is awesome and it does this through the garbage collection system so we can instantiate and Destroy things at run time and the garbage collection will free up Ram during a cycle where it finds any objects in the scene that are no longer being referenced and then it frees up that space But this process can be a little bit slow and it can cause lag spikes and performance issues with your game especially if you are targeting mobile or low end PCS so we are going to fix these issues in this project by replacing simple instantiate and Destroy calls on our projectile with an object pooling system and at the very end I'll show you how you can do it with your particle systems as well so by the end of this video you will know how to implement an entire object pooling system from start to finish in your own project let's get started okay so here is our opening scene here we have a player that can move and he has a gun which rotates towards the mouse and if we hold the mouse button down then we can shoot I actually have a great 2D shooting tutorial that you can follow if you want to be able to set up what you basically see right here so what you are seeing here are projectiles being instantiated every single frame and then they are being destroyed either when they hit something or if they go off the edge then they're going to be destroyed after three seconds you'll see right here that eventually this will all clear up there you go now I also want to make note of a couple of things if we click on stats right here and I hold down this button you can see that our Graphics tank to about seems to be averaging around 100 frames per second it bounces up and down and if I release the button then that very quickly starts to go back up to over 300 frames per second now I've also got the profiler open right here and if you don't know where that is you can go to window analysis profiler so I'm just going to let some bullets run for a second and then I'm just going to click in here which will pause the game now I'm going to sort this by garbage collection allocation here you can see if we open up the player Loop and we follow that all the way down we are actually using 96 bytes per frame for the instantiate call on these projectiles and there there's 80 bytes here for the garbage collection allocation now that might not sound like much but that is every single frame and that's definitely something that we're going to want to improve even if you're targeting high-end PCS it's still worth doing the work to improve this by implementing an object pooling system because we always want our games to be running as smoothly as possible so let me just quickly show you what we're working with here the only script we're going to care about on the player is the player aim and shoot script what we care about is the handle gun shooting and what we are going to do in this tutorial is replace these two instantiate calls this one is instantiating the actual bullet and this one is instantiating the particles that you see coming out of the player's gun when he shoots we are going to replace both of these with an object pooling system so where are we going to start with this so there are two sides to this that we want to change right we want to change the instantiate call so let's just take a look at the bullet for a second so we are instantiating this bullet and that is a prefab object right here and if I open that up and go into my bullet script again there's a whole bunch of functionality here that doesn't really matter for the up object pooling tutorial there's some damage being applied and some gravity being applied but the two things we care about in this script are the destroy calls so there's one right here we call destroy on this game object after destroy time which I have set to 3 seconds if it doesn't hit anything and if it does hit something then we are just destroying the game object so in order to replace and instantiate and Destroy process we need to replace the instantiate call and these destroy calls so this is going to take more than one line of code to replace this instantiate line here it's a little bit more work to set up an object pooling system but it is so so so worth it for the performance of your game so in order to handle the actual spawning of the bullet I'm actually going to create a new script called bullet spawner and I'm going to put that on my player game object and open that up so let's actually set up the object pooling system now so in order to start we want to grab a reference to the object pool class and in order to do that we need to go up here and add the using Unity engine dot pool namespace now that we have that we can actually go in here and say public object pool and it needs us to pass in a type and you could put a game object in here in order to instantiate a prefab which is essentially what I'm going to do I just also want to grab a reference to our bullet script at the same time so I'm going to pass in bullet and then we're going to call that pool now let's create a start function and let's actually declare this and set it up so we are going to say pool is equal to new object pool of type bullet okay so now we've got a whole bunch of things that we need to set up it needs a create function it needs an action on get an action on release and action on destroy it needs a value for collection check default capacity and Max size and for the first four I'm going to set up methods for those so I'm just going to comment this out for a second so let's make the create function first I'm going to call this private bullet create bullet now what is this function well this is essentially going to be the function that tells the object pooling system what to do if there are no objects in the pool because the way that an object pooling system works is instead of just instantiating or spawning and destroying when you need stuff you're going to have a large collection or a pool of these objects that you want it could potentially be hundreds that are just sitting in your hierarchy inactive waiting to be used so that when we need one it will simply just activate it but if there's no pool already in existence then we need to tell the object pooling system what to do so if there's no pool already set up well then we need to actually create a bullet which will involve an instantiate call and that's necessary only once just to actually set up the pool and it might actually be worth it for your project to pre-load or pre-spawn a whole bunch of instances or create your pool at the start of the game and that's something that you could set up in the start function but for this tutorial I want to keep it simple so I'm not going to do that we're just going to create them as we need them so we need to grab a reference to two bullets so I'm going to say bullet we'll call that bullet is equal to instantiate now we need to tell it what to instantiate and currently right now if you look in my inspector I want to instantiate this bullet object right here which is actually sitting in my player aim and shoot script so I could move this bullet over to the bullet spawner over here we could do that but I'm just going to grab a reference to the player aim and shoot and just grab the bullet from there directly because I just kind of like having all these references in one place on my player aim and shoot script so I'm going to grab a reference to this and just grab the bullet from there so back in our bullet spawner script let's say private player aim and shoot I'm going to call that player aim and shoot let's grab a reference to that in our start function and they're both just sitting on the player so I can just do get component player aim and shoot so now back in our instantiate function what are we instantiating player aim and shoot dot bullet okay and I also need to pass in the transform as well as the rotation so what I'm actually going to do is go back to my player aim and shoot and look at what we did there well for the transform position we said the bullet spawn point dot position and for the rotation we said the gun.transform.rotation so let's just copy that over here player aim and shoot dot bullet spawn point dot position and for the rotation player aim and shoot dot gun dot transform dot rotation now you can see that I've got an error here that is because I am passing this in as a type bullet but over here in my player aim and shoot bullet is actually set up as a public game object so while we're in here let's just go ahead and comment this out because we don't want to use that anymore and let's go up here and change bullet to Bullet instead and if we change the type we're going to need to go back into the inspector and reassign it so you can see we changed the type now it says type mismatch but I can just drag that back on there again okay and now that error is gone over here okay so almost done with this function so what do we do when we're trying to use the object pooling system but there's no pool setup well we're creating a bullet by instantiating a new bullet so the way that this object pooling system is going to work is that these objects are going to be added back into the pool and deactivated at the place where we would normally destroy them and we are destroying the bullets in the bullet script over here there's one right here and one right here so we know that these two scripts are going to be dealing with the same pool so to make this easy I'm going to set up a public method in the bullet script called public void set pool and we're going to pass in we want to pass in an object pool but I need to add the namespace up here first using Unity engine.pool there we go now I can go down here and add an object pool again it's going to be a type bullet and let's call that pool let's also go up to the top here and add a private object pool and bullet again call that pool okay now we can go down into set pool and we need to point to what this pool is so what we're going to do is go in here and say underscore pool is equal to pool which is this one right here which we are going to pass in and call this set pool right here in our bullet spawner okay so I actually don't need to grab a reference to the bullet script because we have it right here so I can just say bullet dot set pool what's our pool name well it's this one here which we are setting up right here okay so now we actually have an object pool set up in our bullet script which we are going to need and it is now pointing to the same one that the bullet spawner is because of this little method here which is great and finally to get rid of this error we need an actual return type because we made this a private bullet so let's say return bullet okay so if we go back up here we can now add our create bullet method as the create function now it wants an action on get and I'm going to create these at the same time the action on get the action on release and the action on destroy those are all very very easy so the first one is going to be a private void on take bullet from pool gonna pass in bullet called bullet so this is literally what do we want to do when we take an object from our object pool so what do we want to do when we take an object from our object pool well if we don't have an object pool then we are instantiating a bullet at the bullet spawn point and at the guns transform rotation now if we actually have something in the pool we're going to want to re-enable that game object but we're also going to want to reset its transform position and its rotation otherwise it's just going to be re-enabling in the exact same place and at the exact same rotation that it was at when it was deactivated in the first place so we'll set the transform and rotation and then we'll activate the game object so we've already got a parameter of type bullet here so we can say bullet dot transform.position is equal to player aim and shoot dot bullet spawn point dot position and the rotation I'm actually going to do that by setting the transform dot right directly as equal to the player aim and shoot dot gun.transform.rotation sorry gun.transform.write then to activate it we'll finally say bullet.gameobject dot set active true okay so we can add on take bullet from pool up here now it wants an action on release to be created let's set up a new method called private void on return bullet to pool and we're going to pass in bullet called bullet so when we actually want to return it to the pool there's nothing else that we need to do except to deactivate it that's literally all we need so we can say bullet.gameobject dot set active false now we can go up here on return bullet to pool and we need an action on Destroy well why does it need to destroy anything well that's because the way that this function works is the pool will always spawn objects okay it's always going to spawn them even if it puts us above the maximum pool threshold you'll see that this function is going to ask us for a default capacity and a maximum capacity in just a minute but it's always going to spawn objects even if it puts us above the max that we set but what it'll do is once it's going to return the object to the pool if the pool is already at maximum capacity which we will manually set if it's at maximum capacity at that point then it will destroy the game object instead of returning it to the pool so it still has instantiate and Destroy built in but we can manually assign the size that we want this pool to be and you'll want to play around with the numbers because really you can think of an object pool as like a big array and let's say if we have an array that holds 100 objects even if that array is empty if you set it to have an array of a hundred then it's going to allocate that much memory from your RAM for those hundred objects well an object pooling system is very much the same it takes the memory even if it's not being used so we want to tweak those values to be around what we expect the normal to actually be at it doesn't make a lot of sense to have an object pool with like a thousand objects in it if you're only ever going to have like a maximum of five or six in your scene that just doesn't make any sense so we can save ourselves a little bit of memory by being smart about those numbers but hopefully that makes sense that is why even though we're trying to replace and instantiate and Destroy call we still need instantiate and Destroy calls because this function has that functionality built in where we can manually control the size of the pool so let's go down here and finally set that up call that private void on Destroy bullet okay so literally that's just going to be destroy bullet Dot Game object okay and we can go up here and for our action on Destroy pass in on Destroy bullet okay now we're on our collection check so I'm going to set this to true but what this is is this makes sure that you're not able to return something to the pool that's already been returned to the pool this function has a check for that because that could result in a lot of errors but if you are absolutely 100 sure that your code is going to work and there's not going to be any problems then you can set this to false and it'll be even faster by saving some CPU Cycles but I'm going to set that to true because I like having checks put in place okay so now we're at default capacity and Max size and this can be a little bit confusing for people it certainly confused me at first but I find it easiest to think about this the default capacity think of that as your array size for your pool your typical size that you think you're going to need now I'm spawning projectiles into the scene every single frame so mine should be hundreds and hundreds because I'm not just throwing the bullets for like three seconds and my game runs at about two to three hundred frames per second so I'll probably want something around a thousand that's kind of my expectation for about the size of the array that I might need now if that's the case we've set our default capacity what the heck is the max size then so if you think of default capacity as the size of your object pool array your max size is how much your array can actually expand by so let's just say for the sake of example that I put 2000 in here as our Max Capacity what that means is it's going to create an array the size of a thousand it's going to hold memory for enough objects for a thousand and if it needs to it can expand by instantiating new ones and then adding them into the pool when they are released and increase the max size of this array up to potentially two thousand if I make this a thousand then we can still have more than a thousand projectiles being spawned into the scene it's just any bullets that are over the Thousand count they are not going to be returned to the pool they will be destroyed instead so I hope that makes sense but now we have this all set up so how the heck do we actually replace this instantiate call now with the object pooling well first let's grab a reference to our bullet spawner script I'm just going to call that bullet spawner let's grab a reference to that in a start function bullet spawner equals get component bullet spawner there we go now down here instead of this instantiate call we can just say bullet spawner dot pool dot get and that is literally it now I am going to test this but I do expect to see some problems let's see what happens so actually there's no problems happening and I forgot that's because I haven't actually replaced the destroy calls yet so this is actually working exactly the same as it was before with the exact same performance problems where basically just we just set up a very fancy way of instantiating these bullets into the scene so let's actually finish this first so we replaced this here instead of our instantiate but none of those are getting returned to the pool because in our actual bullet script we still are destroying the game object instead of returning it to the pool so what's great is because we already set the pool in here this already is linked to the pool in bullet spawner so actually I can come in here and comment out that destroy call and say pool dot release this okay so that's that one taken care of now we also have this one here that destroys the game object after destroy time which is three seconds what I'm going to do instead is create a small little co-routine to handle that with the object pooling instead so let's create private co-routine destroy bullet After Time Co routine all right now I'm going to go down here say private I enumerator actually this shouldn't be destroyed this should be deactivate and I'll call this deactivate bullet After Time set up a float called elapsed time and default that to zero and we'll say while that elapsed time is less than our destroy time then we'll iterate the elapsed time plus equals time dot Delta time yield return null to make it wait till the end of the frame and then after that timer is over we'll again say pool dot release this and now let's actually call this Co routine from our start function so we'll do that by grabbing that say that is equal to dark color routine deactivate bullet After Time okay so we have replaced the instantiate calls and the destroy calls now let's give this a test there's the problem I was expecting okay so our bullets are not behaving the way that I would expect and the reason for that is because of what's going on in our bullet script when you just instantiate and Destroy things it's fine to just grab things once in your start function but when you're reusing a prefab over and over and it's being deactivated and activated instead of instantiated and destroyed well we just need to change a couple of things about how this works for example grabbing our rigid body component that is fine to stay and start we only ever need to grab a reference to that one time however this deactivate after a timer co-routine that is going to need to be reset every single time that the bullet gets activated so let's create an on enable function so on enable actually gets called right after awake even before start but on enable also gets called every single time that the object is enabled so let's move this into our on enable function here I'm actually applying some gravity to our bullets which is really just changing the rigid body's gravity scale so that also is fine to only be called once but if we look at set physics velocity that is currently because it's in the start function the very first frame that it was instantiated it's setting the velocity of the rigid body to be in the direction that it's looking multiplied by speed so this also needs to be moved into on enable instead that's why our bullets were not behaving the way that we expected let's try that out now okay and this is where the order of execution comes in really handy I should know better because I literally just showed you that on enable is called before the start function and yet if we go to our bullet script the change that I literally just made is to grab the rigid body component in the start function for the very first time but we know that this is actually called First and it's trying to apply velocity to a rigid body that it doesn't have yet so actually we should change this to awake because nothing is called before awake so that will now grab the rigid body component and be able to properly really set the physics velocity let's try that one last time okay there we go bullets are behaving as expected and you can also see over in my hierarchy over here got this long list of bullet clones here and you'll see them randomly activating and deactivating based on their needs from the object pooling system so based on the size of my scroll bar here it looks like it is having to instantiate and destroy a couple of things every once in a while so we're probably very very close with this thousand here but what I'm actually going to do is increase our maximum size to 1500 just to be safe okay awesome now let's do the same thing with these particles that are shooting out of the gun so that you can see how that works back in our player aim and shoot we are literally just we are literally just instantiating particles at the bullet spawn point and at the guns transform rotation very very much the same as our bullet now these particles are being instantiated and then you can see that our stop action within the actual particle system when it stops IT destroys itself okay so basically this is also an instantiate and destroy scenario and we want to replace this with an object pooling system as well so what we're going to do is something very very similar we're going to set up a particle spawner script I'm also going to add that to my player and open that up so we first need to add our namespace then we want to set up a public object pool this time it's going to be of type particle system and we'll call that particle pool now we already know that we're going to need to do a create function and that's going to need to have an instantiate call that also needs the bullet spawn point and the guns transform rotation so I'm also going to need a reference to the player aim and shoot in this script as well and we'll call that player aim and shoot and we'll grab that in our start function and this script is attached to the player so we can just do get component and I am trying to get into the habit of doing proper naming conventions but I keep forgetting there I'm going to add an underscore to that now let's actually set up the particle pool that's going to be equal to a new object pool of type particle system and now again we need to create a create function an action on get an action on release and action on Destroy so let's just very quickly set all of those up so say private particle system called create particles we'll need an action on take so private void on take particles from pool and we'll pass in a particle system called particles and now we need an on release function so let's set up private void on return particles to pool and pass in particle system called particles and we need an on Destroy action as well so we'll call that private void on Destroy particles passed in particle system and again call it particles so I'll fill these in in just a second so what we're going to do is pass in create particles we'll pass in ontake particles from pool on return particles to pull and on Destroy particles I will keep collection check set to True like before and I'll also use my same numbers 1000 and 1500 okay so there's that so let's actually fill these in now so we'll set up a particle system called particles is equal to instantiate what are we instantiating we are instantiating the player aim and shoot particles right here so we'll say player aim and shoot dot particles what's the transform parent well that's player aim and shoot dot bullet spawn point dot position and the rotation player aim and shoot dot gun dot transform dot rotation okay and then we just need to return particles next when we want to actually take an object from the pool kind of like instantiate works we are again going to want to set the transform position and the transform rotation and then enable it so particles.transform.position is equal to player aim and shoot dot bullet spawn point dot position and particles dot transform dot right is equal to player aim and shoot dot gun dot transform dot right and then we'll actually activate the game object we'll do that the same particles.gameobject dot set active true when we want to return them to the pool we're simply going to deactivate them particles.gameobject.setactive false and to destroy them we're just going to call destroy particles Dot Game object so now let's actually handle replacing this instantiate call in the player aim and shoot before we handle replacing the destroy so let's comment this out what we're going to need is a reference to our particle spawner script go up here say private particle spawner particle spawner and I know I'm forgetting the underscores again here but none of the rest here have them so I'll try to do better going forward but for now let's just grab this and say that is equal to get component particle spawner then finally down here we can say particlespawner dot particlepool.get okay now this is not actually going to be a proper object pooling system until we replace the destroy call and you'll remember if we go into this particle system we have our stop action set to destroy what we're going to want to do is change that to callback and I actually need to set up one last script and attach it to this particle system I'm going to call that particle pool okay let's open that up so I want to add the pool namespace again so I want to set up a private object pool of type particle system called pool now I actually want to assign this pool to be the same pool as our particle spawner pool now this particle pool script is attached to a prefab particle system game object so I can't just do a get component and grab it from the particle spawner now it's not the fastest call in the world but we are going to say pool is equal to gameobject dot find game object with tag let's find the player tag let's also make sure that our player is tagged as player and we'll finish that by saying dot get components particlespawner dot particle pool so that's not the fastest but I do have to have particle pools set up on top of our particle pool object and that's because I need to call the on particle system stopped function and as you can see here this is called when all particles in the system have died and no new particles will be born so when this particle system is completely done playing well then we want to say pool dot release and what do we want to release we want to release the particle system so let's actually just grab a reference to the very quick private particle system call that particle system let's grab that in our start function particle system is equal to get component particle system now we can say release the particle system also it might be worth setting the particles to callback in the code as well this is totally up to you but I am very very likely to forget to set the stop action to callback right here so this is completely optional but I'm going to set it in code I'm going to create a variable called particlesystem dot main module we'll call that Main and that's equal to our particlesystem DOT Main and then you can say main.stop action is equal to particle system stop action dot callback and you know what I can't do this I cannot do it this way it's too slow I'm going on the Fly here but what I actually want to do is we set up a bullet.set pool right here I would like to do something similar to that with our particles as well so in our particle spawner when we actually create the particles let's assign it there so let's go to our particle pool and set up a public method public void set pool going to take in an object pool of type particle system called pool and we'll say our underscore pool is equal to our pool just going to delete that now back in our particle spawner now I've kind of got two options I could actually use a get component call from this particle system and grab the particle pool script or I could actually change this to particle pool instead of particle system let's try that what the heck let's say particle pull instead copy that and put that here and here and here and here and here and here that's going to give us an error here that is because our player a man shoot has a public particle system this should be a particle pool and again back in our inspector it will be a type mismatch again but we can just drag that on there now I don't need to do a get component to find this because it's already being referenced right here in our object cooling system so I can just say particles dot set pool what are we setting it to our particle pool and that's going to give us an error because we need to go into particle pool and change this to a particle cool instead and same with this and since we're calling pool dot release in the particle pull script we can just change this to this okay now we got rid of that awful tag call which is going to slow things down and instead we're just setting the pool directly right here when we actually create the particles in the particle spawner script which is in my opinion a lot cleaner and a lot nicer okay so let's give this a test there you go particles are working as intended you can see now we've got a whole bunch of bullet clones as well as a whole bunch of particle system clones our frames per second have jumped up by about 20 to 25 frames per second and if you take a look at our profiler now the instantiate call is now gone entirely we have a few bytes being allocated to gameobject.activate but that cannot be helped but our goal was to get rid of instantiate because that is not necessary when object pooling systems are available so we have successfully decreased the amount of bytes that we are using on every single frame which is resulting in a higher frame rate on average there you go guys thank you so much for choosing this tutorial to learn about object pooling it really really means the world to me please let me know in the comments below if you found this tutorial helpful or if there's anything that you would add or change I always love hearing from you guys and I'm working very hard to grow this Channel and I really want YouTube to like me more so if you found this video helpful to you in any way then please give it a like and consider subscribing thanks and I hope that you have an awesome day bye
Info
Channel: Sasquatch B Studios
Views: 8,103
Rating: undefined out of 5
Keywords: unity tutorial, unity 2d, unity, object pooling, object pooling unity, game development, game dev, replace instantiate and destroy with object pool, unity profiler, unity spawn objects, unity optimization, sasquatch b, sasquatch b studios, pooling, object pooling tutorial, unity object pooling, object pool in unity, object pool using built in unity method, object-pooling, object pool, built-in unity object pooling, unity pooling namespace, unity pooling tutorial
Id: GMbMPLykFQU
Channel Id: undefined
Length: 29min 20sec (1760 seconds)
Published: Thu Jan 26 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.