Main

ECS Mastery: Object Placement with Unity's Physics and Input Systems

Support the channel : - Donation : https://wayn.games/ko-fi - AssetStore : https://wayn.games/assetstore-affiliate - Likes & Share 👍 Follow us on : - Discord : https://wayn.games/discord - X / Twitter : https://wayn.games/twitter - GitHub : https://wayn.games/github Resources ------------------------------------------------------------------------------------------------------------------ Learn Unity DOTS : https://github.com/WAYN-Games/DOTS-Training Baking workflow in details : https://youtu.be/nXcLOnRGG0w DOTS Assets : https://wayn.games/DOTS-Assets Super Singleton : https://youtu.be/PRfFjvULgY0?si=CKxkbh64MIZ9EqIj Key concepts ------------------------------------------------------------------------------------------------------------------ Physics Package Input System Package Prefabs MonoBehaviours to Entities Physics Layer Management Raycasting Overlap Sphere Collision Detection Singleton Entities Dynamic Buffers Native Containers Description ------------------------------------------------------------------------------------------------------------------ In this video, we delve into leveraging Unity's physics package and input system package to strategically position your towers. We guide you through importing the necessary packages, including the Unity physics package and input system package. Explore the structure of our tower placement scene, uncovering prefabs for different tower types and MonoBehaviours for player input capture. Dive deeper into capturing player input using the new input system and discover how to manage singleton entities to effectively communicate from MonoBehaviours to ECS. In our tower placement system, we ensure seamless tower spawning by navigating physics operations and implementing collision filters. Witness the system in action as we dynamically instantiate towers based on player input, while preventing overlap and ensuring accurate tower placement. Unlock the power of Unity's Entity Component System (ECS) and take your tower defense game to the next level! Watch now and stay tuned for more exciting content. Thank you for watching, and see you in the next video! Chapters ------------------------------------------------------------------------------------------------------------------ 00:00 Introduction to Tower Placement in Unity 00:07 Setting Up the Physics and Input Packages 01:10 Exploring the Folder and Scene Structure 03:20 Understanding the Input System 04:04 Creating the Tower Placement Input Manager 07:25 Understanding Singleton Entities and Data 11:11 Building the Tower Placement System 20:56 Testing the Tower Placement System 21:52 Conclusion and Next Steps 22:18 Supporting the Channel and Exploring DOTS Assets

WAYN Games

5 days ago

Hello everyone, today we'll see how we can place towers in our tower defense game using the physics package and input system package. So if you downloaded the code from the github repository linked in the description below and imported it into your project, you will have a bunch of errors like this. And that's because we didn't actually, during the setup, import the physics package and input package. So you can go to the package manager and open the unity registry, search for physics and import
the unity physics package, not the Havok physics for unity, unity physics package and install it. Once the physics package is imported we will import the other package, I just want to point out that in the previous versions of Unity, we had to use custom physics authoring, so they are still available here in the samples of the Unity physics, but we won't use them because we will use the built in physics components, that are now converted to ECS physics. And after that you can also take the input
package, so input system package, and install it. Now we should no longer have any error, so that's fine. And we can now go back to the 4th folder, tower placement, and open the tower placement scene. We can open the subscene also, and we can have a look at the folder structure and the actual scene structure for this episode. So what we can see in the folder structure is that we have the prefabs that will contain our two different types of towers that we can place. And we also have the MonoBeha
viours which will allow us to capture the player input and know where exactly we want to place the tower. If we have a look at the subscene, we can see that we have in the subscene the floor, which is this green patch of grass. We have just a simple entity or simple gameobject with a box collider and the actual model is below it. And same thing, we have what is called mountains, but it's just a sand or another type of ground that we won't be able to place towers on. And for this one, actually, y
ou see, it's sand that's called mountain and that belongs to the layer water, so. It's just something that we won't be able to place any tower on. And for the floor, actually normally if you click on floor, you should see that the layer is empty. So that's because I've set it to another layer that is no longer named in the project. So if you add the layer and set the 30th layer to Terrain, something like that. And go back to the floor, we should see now that it was the 30th layer, so it's the te
rrain layer. Actually, what we can do also is in the add layer, set the 31th layer to input, so that it's quite clear what we use. And if I look at the floor, so it's terrain. If I look at the Tower placement input manager, now, we can see that the tower placement input manager, will belong to the input physics layer, and it will collide with the terrain physics layer. That's to say that our input will collide with the terrain to, to check where it can place it. And that's how we know that we
won't be able to place any tower on the sandy mountain water physics layer. And for the input, I'm actually using the new input system, which allows us to define with a scriptable object or with here a direct input reference the input we will use. So here I'm using the modifier left button. So it means that the input will be only triggered when I'm clicking on the left button. And the actual input that I'm getting is the mouse position. There is the same for support of touch screens. So with a s
ingle input asset, I can define multiple input device. If you're interested in more detailed explanation about the new input system, let me know in the comments below and I will see if I have time to create a video for that. Going back to the other field. So we have the tower index, and the tower index actually is, which. tower I will place, so. We will be able, for this example, to put either 0 or 1. And basically that's the index of the tower we will place, based on the tower store that we hav
e here. And the tower store will just be an entity with a dynamic buffer that reference our prefabs for the towers. So tower A will be index 0 and tower B will be index 1. And if we look at the actual baking or baker for this one, we can see that same thing, it's rather simple. We don't care where the actual store is, so it doesn't need any transform flags. We have a dynamic buffer, which contains the list of towers. And for the towers, we actually need them to be in world space, so let's fix
that. We need to have them placed in the world, and they don't move, so we don't care about the dynamic flag I've put just before. That's actually the only authoring component we have, because the rest are just simple prefabs, so they will just be converted to entities. There is no logic with, with them yet. We will see that in Next episode or the one after that actually what we can have a look at next is the actual input. So if I look again back here in the tower placement input manager, we hav
e the Mono behavior script. So since this game object is not in the subscene It will not be converted to an entity which will remain a game object And for that one, I want to capture the input using the new input system and forward that input to my ECS world so that I can use it in the system. So let's look how we can do that. So first thing, we have the different fields that we expose in the inspector with the tooltip, so fairly self explanatory. We actually need a reference to the camera that
I will cache in private. And I will. Store also different internal fields or private fields, one for an entity and one for the ECS world. When I enable my component, so when the game starts, I will cache the reference to my main camera. I will enable the new input, so same thing, if you want more details on how that's done, leave a comment below, and I will try to make a video for that. And I'm caching the ECS world, so every system that we have and acts on component, Live inside a world. We ca
n actually have several worlds especially in netcode projects. And we can get a reference from anywhere to the default injection world. So the default world we have, in the case of a single player game, there is most likely only one world. So we can get that from the default GameObject injection world property, that is a static property from the world class, or world struct, class, actually. So we can cache this, so we can use it for later reference, and we also cache so here internally it's jus
t to avoid having to repeat that every time, but we can cache the definition of the collision filter, so What it belongs to. So here it's input based on what we set in the inspector. And here it will be terrain based on what we placed in the inspector. So I cached the world here to be able to create a singleton entity. So, I will be able to create an entity that is accessible from my game object. And I will cache it in the entity field above. And that entity will be What is called a singleton da
ta. So we have already seen the notion of singleton or the entity command buffer in the previous video. Singleton entity and singleton data is basically there is only one instance of that particular data in the whole ECS world. So you can actually create as many as you want, but every time you create a singleton entity, like I will do it here, it will reserve an archetype for this. Particular entity with only one component that it will reserve a whole chunk of memory just for that component. So
that's why you can also do the super singleton pattern that was described in the Hot Path Show show episode of Turbo So I will put a link in the description below if you want to check it out and understand a bit more about that The basic idea is to have a tag component, which tells you, okay, this entity is my default singleton entity, and then you can add as many components as you want to that same singleton entity, but you have more efficient usage of the chunk memory and memory allocation. Go
ing back to this particular example with a simple singleton not a super singleton, let's say. What I do here is when the mouse is clicked, so mouseClick method, the first thing I will do is to check did I already create my singleton entity? If I did not create that singleton entity, I will create a new entity using the word reference that I cached. And the EntityManager to create an entity. And for that entity, I will add a DynamicBuffer actually which is a TowerPlacementInput, and that particul
ar component only contains the RaycastInput that we will build a bit later, so you will see that, and the StoreIndex, so which tower I will spawn. I'm using a DynamicBuffer here and not a component. Because it allows me to know when the input has been consumed and if there are different execution rate between the input capture and the physics it will allow me to not miss any input. For now, what we want is to be able to basically capture the input of the user. We will get the screen position o
f where the mouse was clicked. And from that screen position we use the camera to get the ray from the camera to that particular place in the world and we build a raycast input from that ray using the filter and as the end of the ray I will use the camera far clip plane so as far as the camera allows you to see. And that allows me to build the tower placement input that I put into my buffer. So, put it into the buffer, I get the buffer , from my entity that I created above, and I simply add the
element to the buffer. One thing to not forget is the actual disabled method of the GameObject. So when the GameObject is disabled or destroyed, I need to do two things. So the first one is to disable the input, actually, because I don't care anymore to listen about that and I also need to actually get rid of my singleton entity so same thing in case you are using the super singleton pattern here. You don't actually want to destroy the entity, but you want to grab the singleton entity and remove
the component from that singleton entity . Let's take a look at the system now. So we look at the tower placement system. So the first thing to notice is that we have here at the top a few attributes. The first one is update after the fixed step simulation system group because the physics world so all the collision and colliders and so on are managed inside the fixed update loop, so it's handled by the fixed step simulation system group. By updating after that, I make sure that I'm updating aft
er all the physics world has been updated. And I'm updating before another system, which is the clear input system which just takes the dynamic buffer, tower placement input, and clears it. So that's what I, I said earlier, I want to be able to pile up the input of the user, and whenever I'm done processing that input, I just clear all the input from the buffer. Going back to the tower placement system, we have on the onCreate method a few singletons that we need. So the first one will be, lik
e in the previous episode, the system that allows us to queue up or to create entity command buffers that we will need to spawn our tower. The physics word singleton, which is where the Singleton we will use to actually perform our cast array operations and the actual tower prefabs singleton which is our tower store we define in the subscene which holds all the references of the different kinds of tower we can spawn. On update, we grab a reference to few singletons. So for the physics world,
we can just get the singleton. For the prefabs. Actually, it's a dynamic buffer, so it's not a singleton component, but a singleton buffer. So there is a get singleton buffer. It's not from the system API. And we also grab again and create our anti command buffer that we will use. And , here again, since I'm creating something, I will do it in the begin initialization system which is why I'm using this one. Next, for our actual execution code for the system, what we want to do is to look at all
the inputs we got. So we query for the dynamic buffer of to our placement input. And from that input, we loop through all of the inputs. And for each of the input, we cast a ray, so we use the physics world, and we cast a ray from the placement input, which, as a reminder, is a raycast input we built in the MonoBehaviour, and we get, out of that, a raycast hit, so here I can cast a ray from the placement input, and we get, out of that, a raycast hit, so here I can cast make it more clear, so we
get a struct which is a raycast hit, and that raycast hit contains something that is the position where my ray has hit the terrain because I'm only hitting a terrain layer, and that will give me the exact position where my tower should be created. Now, I don't want to spawn the tower exactly at that position Because if there is something else in the way, like another tower, I don't want to be able to place the tower on top of each other. So, what I will do is to actually I will use another phy
sics operation and for that I will use a new filter. So by building the collision filter default, it will collide with everything. But I want to check everything but the terrain. Because if I check again, is there terrain at the spot I want to place the tower? Obviously there is terrain and it doesn't matter. I'm fine with there being a terrain. So, I will actually use the tilde, I don't know how you, you call that, but basically it's the character that is with the number two of, of your keyboar
d and we, it will basically invert whatever , filter I give it. So, here, I'm defining a collider default. The collision filter default collides with everything and belongs to everything. And I'm actually overloading the collideWith or assigning the collideWith everything but the input value collision filter collideWith. Which, as a reminder, is the terrain. So here, what I'm saying basically is I want a collision filter that belongs to everything but collides with everything but the terrain. W
ith that filter, I can now use another physics operation which is the overlap sphere. So here I'm not casting anything. As a reminder, a difference between a cast overlap. When I cast, it means that I will move along a distance. So if I cast a ray, it's just a single point that is moving along a distance, which constitutes a ray. If I were to do a capsule cast, I would have a capsule, which is a 3D representation volume. And that would be cast along a distance. So all Other colliders that would
overlap, or that would collide with the capsule along that path would raise a collision event, or result in a hit but here, I am not doing a cast, I am just doing an overlap. So I am just taking the volume that is occupied by that capsule, and checking, okay, in that volume, is there any other colliders? If there is another collider, it means that there is something else. It can be a tower, it can be a path, it can be a mountain, it can be anything but terrain. Because my collision filter here
is saying everything but terrain. In that case, I just don't create the tower. I don't want to have the tower overlap with anything else. When I do my overlap sphere operation, I can get several colliders overlapping the same or occupying the same space as my sphere. So, I want to be able to collect several hits, and for that I will use a native list. NativeList is one of the native containers that is defined by the collections package. And it behaves similarly to a list. Actually, I think I wi
ll make a video explaining different native containers because there are a few ones that are quite tricky to understand. Basically, a native list is a normal list. It's just that it's usable in the context of jobs and burst compiler. So I need to provide it what is called an allocator. So the allocator will define the type of memory I will allocate. So is it a very short time and short lived memory? Or is it a bit more , persistent? Or is it fully persistent? And actually, we can see that we hav
e a few types of allocators. We have the temp, which I was using. The temp job, which we can use to allocate memory within jobs and before jobs that will execute during or be manipulated. During a job and the persistent for allocations that needs to remain alive for the whole application lifetime, whole game lifetime. In this case, I'm in a scope that is very, very short lived. I only need that list within this method so I can use the allocator temp, which is the fastest allocator. And it doesn'
t need either to be disposed of because the two other types of allocators, so temp job and persistent need to be disposed of. Otherwise it will create a memory leak. And basically it's a memory that is allocated but not used by anyone, cannot be reached by any program and it will fill up your RAM, your computer RAM or your device RAM over time if you leave these kinds of operations without disposing of the memory. If I didn't get a hit, it means that there is nothing else in the way of placing t
he tower. So I can debug that I placed the tower and actually instantiate my tower. So here I'm grabbing the reference of the entity prefab I want to spawn. From the towers singleton buffer, I'm getting the index from the placement input and the prefab reference, which is an entity reference. So here, just a quick note that I didn't mention earlier, in the previous episode, but if we go back to the actual baker for this tower store, you can see that I'm actually getting an entity, so my prefab,
and storing the entity reference in that prefab. You can do that in a baker. Only if you are storing the entity reference you get from gate entity, you can store that only in an IComponentData or in an IBufferElement because when you are baking here, you are not actually getting the runtime entity, but you are getting another entity that is part of the baking world. You can actually have more information about that. In my other video about the baking workflow, I will link it in the description.
And if you store that in something else like BlobAsset, which we will see in a future video, you will not actually get the correct entity reference. So, best case scenario you get an invalid entity reference, so you can't spawn it and you can't do anything, you will get an error. And worst case scenario, you can actually get a valid entity reference, but to a completely different entity. So instead of, I don't know, spawning a tower, you will spawn a mountain. So, be careful never store entity r
eference in anything. But iComponentData or iBufferElementData. Going back to the system, so here I'm grabbing my reference, instantiating the object. And I'm actually setting the position of my entity using a transform identity. So I'm just getting a local transform. Blank, so no rotation, no orientation, no position, 0, 0, 0 for everything. And I'm setting the position of that local transform to the tower position, which is the hit position I've got from my raycast. And I set the component usi
ng the EntityCommandBuffer. And that's how the whole system works. So, we can see it in action now. So if I go back to Unity and enter play mode, I now can place a tower when I click on the green patch. If I click anywhere else, it's not placing the tower. If I'm placing a tower On the tower, it's not working, you can see, and that's actually because our prefab here also have a collider, so I forgot to mention that, but they do have a box collider, and actually they belong to a layer that is not
named either, but I think it's the 29th, so I can put tower, yes, okay, so it was the 29th layer, so which is tower, and I cannot place two tower on top of each other. If I want I can place another kind of tower, so if I look at the tower placement input manager and not pick the first one, not pick the index 0 but index 1, I can now place a different kind of tower, which is the same one but scaled up. That covers how we can use physics in an ECS project, ECS game. And in the next episode, we wi
ll continue using physics to introduce the notion of job and the job system. And we'll also have a look at the component lookups to define a game over condition for our game. As usual, if you liked the video don't forget to like and share the video. If you have any question, you can leave a comment below or join us on discord to have a chat about your particular question. If you want to support the channel, you can do it on Ko Fi with a simple one time donation or a monthly subscription. And if
you are looking for a cool package that uses dots on the asset store, can also use the link in the description below, which will take you to the list of all the assets that uses either ECS, Burst, or the job system. And I would give a special mention to DOTS third person camera, Rukhanka ECS animation, and the agents navigation, which I think are the three package that covers and work around most of the drawbacks of using ECS right now. That's it, thank you for watching and see you in the next
video.

Comments