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
Comments