Welcome to part 5 of this Unity tutorial series where we’re creating a survival shooter
game using Unity’s new implementation of the Entity-Component-System pattern, or Unity
ECS for short.
In this video we’ll be implementing player shooting by using a more “pure” approach
to Unity ECS.
Now we’ll be covering a lot of new concepts, so we won’t be able to complete all of the
logic in this one video.
But don’t worry because what we don’t complete in part 5, we’ll definitely finish
in parts 6 and 7.
Well without further ado, let’s get right into the video.
Before we dive into the code we should give our character avatar a weapon.
We are implementing player shooting after all.
For now I’ll just use a cube as a placeholder.
The art team can come in later and replace all of the models.
Perfect, looks just like the guns in Westworld!
Okay fine, it doesn’t.
But you aren’t watching this for my artistic abilities.
As basic as it is, it’ll definitely work as a point of reference for where we should
spawn the player’s bullets.
Let’s add an empty GameObject to the player prefab now to serve this purpose.
Our logic will use this GameObject to create bullets with the right position and rotation,
so we’ll need to convert it into an ECS entity
We’ll accomplish this by adding a GameObjectEntity component that’ll register this GameObject
as an entity in the active world.
Now if we were taking a hybrid approach, this would be enough.
Our system code would simply use the GameObject’s Transform component to retrieve the position
and rotation.
But we’re gonna write pure ECS code that uses the C# job system, so we’ll need to
use components that are compatible.
To illustrate this, let’s create a class file called WeaponComponent and open it in
the IDE.
Unity’s C# job system only supports structs and value types so we can’t use MonoBehaviour
based components anymore.
Pure ECS components are structs the derive from IComponentData.
But how to get this component onto our “Bullet Spawn” GameObject?
Well, since Pure ECS isn’t fully fleshed out yet the developers over at Unity have
included a few helper classes to make the transition from Hybrid ECS a little easier.
One such helper is the ComponentDataWrapper class, which is itself derived from MonoBehaviour.
Behind the scenes, the ComponentDataWrapper class uses its generic type parameter to add
pure ECS components to GameObject based entities.
All we need to do is attach the WeaponComponent to the “Bullet Spawn” GameObject.
We can even run the scene and see that the weapon component has been to the entity.
Now we can apply this same concept again using some pure ECS components that come pre-packaged
with Unity ECS.
Let’s add the Position and Rotation components now.
We’ll also need to utilize another one of those helper classes, called “CopyTransformFromGameObjectComponent”.
This should be pretty self explanatory.
But just in case it’s not, this component will copy the position and rotation values
of the GameObject’s Transform component to the respective pure ECS components.
Again, we can see this in action by running the scene.
The position and rotation components are updated when the player avatar moves around the scene.
With our entity and components in place, it’s time to write our first system.
I’ll call it “PlayerShootingSystem” and open it in the IDE.
Now the core logic for player shooting will be split up into two different systems.
One system, let’s call it the “FiringSystem,” will be responsible solely for creating bullets.
And the other system, the one we have open now, will be responsible for notifying the
FiringSystem when it’s time to create bullets for the player.
It’ll do this by monitoring input and adding a special tag component to the player’s
weapon entity whenever the fire button is pressed.
The FiringSystem will be designed to react to this component and create bullets whenever
an entity is tagged.
Why don’t we go ahead and create this component now?
We can simply call it “Firing”.
Again, we’re writing pure ecs code so our components need to be structs that derive
from IComponentData.
Likewise, our systems need to derive from JobComponentSystem and override the OnUpdate
method.
The OnUpdate method receives and returns a JobHandle object.
The JobHandle class is a part of Unity’s new C# Job System.
If you aren’t familiar with it already, I highly recommend checking out my video on
the subject.
But basically the C# Job System provides a new way to write highly efficient, multithreaded
code, and it’s used heavily with pure ECS.
The OnUpdate method expects us to schedules a job that holds out system’s logic.
Let’s write now.
I’ll call it “PlayerShootingJob”.
And I’ll have it derive from IJobParallelFor, which is one of the many job types we can
create.
IJobParallelFor jobs are basically for-loops that run in parallel.
The Execute method, which is called when you schedule a job, takes an index which signifies
where the program is in the loop at any given time.
This system is responsible for tagging specific entities with the firing component.
So let’s add an EntityArray field that we can reference in our logic.
We’ll need to add components to these entities, as well, but unfortunately we don’t have
access to the EntityManager here.
So we’ll need to add something called an “EntityCommandBuffer ”.
It isn’t safe to add, delete, or modify entities while a job is still running.
Command buffers allow you to queue up these types of actions so that Unity can perform
them at the appropriate time.
Concurrent buffers are specifically used for jobs that run in parallel, or concurrently.
Let’s use the EntityCommandBuffer to add a firing component to each entity in our EntityArray.
Now we only want to add the firing component when the player is actually firing.
Which, in our case, will be when they press the “Fire 1” button.
Let’s add a boolean field and use it as a guard clause in our logic.
There.
Now we’ll only add firing components to entities when the player is firing.
But which entities do we want to add the firing component to?
We need to add a data struct to this system to define our entity type just like we’ve
done in the past.
It should include an int called “Length” (which now must be a readonly field), an EntityArray,
and a ComponentDataArray that’ll be used by Unity to only grab the entities that have
the Weapon component.
And, just like before, we’ll inject our struct into a private field.
We’re almost ready to schedule the PlayerShootingJob, but there’s one more thing we need to do.
We’re going to create something called a “barrier”.
Barriers live on the main thread and are used to create valid EntityCommandBuffers for our
system jobs.
Let’s create one now called “PlayerShootingBarrier” and derive it from BarrierSytem.
Then inject it into the PlayerShootingSystem.
Now we’re ready to schedule the PlayerShootingJob.
Calling schedule on a job results in a JobHandle, so we can stick this in a return statement.
IJobParallelFor’s Schedule method takes a length, a batch size, and another JobHandle.
And lastly we can populate our job’s field variables.
And that’s all there is to it.
Well, almost.
I suspect we’re going to run into a small problem.
Let’s run the scene and find out.
Yup.
The system runs fine the first time, but afterwards it fails because we’re trying to add the
firing component to an entity that already has it.
This’ll be a quick fix.
We just need to add a SubtractiveCompnent to our data struct for the Firing component.
This will ensure that this system only operates on entities that have a Weapon component and
don’t already have a Firing component.
Beautiful; no more errors!
And we can check the entity debugger and confirm that the weapon now has the firing component.
If you’d like to download the project files for this video and support my content at the
same time, please consider becoming a tier-2 patron today.
You can find a download link in the description and comments below.
So we just covered a lot of new concepts.
Don’t worry if you didn’t catch it all the first time.
In the next video we’ll be doing a little bit of a review by implementing a similar
system that’ll be responsible for the rate of fire.
If you found this Unity tutorial helpful, please leave a like and a comment letting
me know what you thought.
And for more Unity tutorials just like this one, don't’ forget to subscribe with notifications
on.
I'll catch you in the next video.