(boisterous laugh)
- One of the most fun things to make
in game development are boss battles.
- You face JARAXXUS!
- Get away from me
- Oh no! (squeaks)
- A good boss fight
is not only the ultimate test of your player's skill.
It can also be a really creative and varied experience
that can be divided into multiple stages.
Games such as "Cuphead" or "The Binding of Isaac",
featured traditional motive stage boss fights.
Where the boss will enter different states
depending on where you are in the fight.
The individual mechanics changes from fight to fight,
but the overall structure often remains the same.
Take for example, this fight from "The Binding of Isaac".
Here the boss starts off stationary,
stinging time attacks towards the player.
Then once the player does enough damage,
the boss enters a second stage,
he changes behaviour completely,
and he becomes more difficult to fight.
And it's classic bosses like these
that can be perfectly described
using something really cool called state machines.
But first this video is sponsored by NordLocker.
When making a video game, you often have to handle
and share sensitive information.
Whether it's information about your game or user data,
it can be very important to keep this safe.
NordLocker is a super fast data encryption service
that by using ECC,
helps you take control of your security and privacy.
Simply drag and drop files into your encrypted folder,
and they are secured from the outside world.
The free version offers up to five gigabytes storage
with unlimited gigabytes to be encrypted.
It is compatible with most public cloud services
and you can also share data
through email, messaging apps, Ad drop, and so on.
Get 37% off NordLocker and get an additional 5% off
by clicking the link, Nordlocker.net/brackeys
and enter the code "BRACKEYS".
Now, what is a state machine?
Well, a Finite State Machine or FSM is a system
that can be in exactly one of a finite number of states
at any given time.
So what does that mean?
Well, here's an example of Finite state machine diagram,
as you can see, we have some different states
as well as some transitions between them.
To understand this system,
all we have to do is start at the beginning state
and then follow the transitions through the system.
The transitions often have conditions that need to be met
in order to continue to the next state.
This is perfect for creating a boss
because we can easily create state for our boss,
such as IDLE, RUN, ATTACK, and SHOOT,
and then create transitions between them
to create interesting gameplay.
Now, creating a system like this
from scratch can get pretty programming heavy,
especially if you want to visualise it like this,
but Unity actually already has a state machine built-in.
I'm talking about the Animator.
Well, this is created for determining
what animations to play.
It's actually set up completely like a state machine.
That means that we can use the fact
that we want to play an animation for each state.
Then set up some transitions between the animations
and add some behaviour
or gameplay logic to each one of the states.
It's a cool way to work,
and it allows us to build really complex boss
and AI behaviour visually.
So enough talking about it, boss time.
(chuckles)
So as you can see, I've gone ahead
and set up a quick example scene here.
And mostly what we have in here
are a few environment sprites as well as a player.
And if we go ahead and hit play,
we can see that this player can move around,
jump as well as fire some fireballs.
And if we have a look at the component
that make up our player, you can see
that we have a player movement script,
we have a prefab weapon script that fires the bullets,
as well as some player health.
This is currently just static and stays at a hundred health,
but it will allow us to damage the player later.
And of course, all of this stuff is something
that we show how to set up in previous tutorials.
We'll of course have a link in the description
to everything that you see.
And for some of the effects such as the fireballs here,
I'm using a free pack from the Unity Asset Store.
I'm using this Knight Spread Sheet.
We'll of course have a link to that as well.
And for the graphics I'm using this player character,
which is also free on the Asset Store.
and I've actually gone ahead
and modified this character a bit
in order to create our boss as well.
I've just scaled him up a bit
and changed the tint of some of these sprites.
I've also added some animations for the boss here.
So if we go window, animation
and open up the animation window,
we can preview some of them.
As you can see, I have an Idle animation
an Intro animation, a Run animation, an Attack animation,
and I've also created a few animation
for stage two of this boss.
As you can see, I have this enrage here
where he becomes more powerful,
and that leads into an EnragedRun
as well as an EnragedAttack.
Now all of this is just sitting there.
If we hit play right now, you will see that he does nothing.
He just plays an Idle animation over and over.
But if we look at our boss,
I've actually gone in and added quite a few components.
He of course has the animator
that is currently just playing the Idle animation,
he has a Rigidbody 2D that is set to kinematic.
He has a Box Collider 2D as well as three scripts.
Currently, these do nothing,
but there are functions here for damaging the boss,
attacking the player,
and flipping the boss to point towards the player.
It's nothing advanced and we'll have a look at them
as we need them.
Again, all this stuff is described in other videos.
And the final thing that we have in here
is just a very quick HP bar up here, as you can see,
this is just a UI element called a Health Bar.
If this is something you would like to see a video on,
definitely let us know in the comments.
Now let's start setting up
some different states for our boss.
To do this we want to open up the animator.
So we'll go window, animation, animator.
Now, if you've never used the animator before
I recommend checking out our video
on 2D animation before continuing,
and as you can see here, all the animator is currently doing
is playing the Idle animation.
So let's drag in some more animations here,
I go into my animation folder, under boss,
and also link the Attack, Intro, and Run animations.
Let's just drag them in.
So I'll make some room here.
And right now, as you can see, the Idle animation
is the default animation that is going to play.
I want to change this to the Intro animation.
so that our boss makes a cool entrance.
So let's right click on Intro
and hit set as layer default state.
I'm actually also going to get completely rid of
our Idle animation since I'm not going to be using it.
So I'll just hit delete.
So now our bar should automatically play
the Intro animation.
Of course, we naturally want him
to continue to another state.
So let's go from Intro to Run.
Whenever our Intro animation has finished playing
that's directly gone Intro, he'd make transition
and click on Run.
And if we now click on this transition,
we can choose when and how we transition to this state.
As you can see, this is currently set to have exit time.
This means they will wait for a portion
of the first animation to play
before we continue to the next one.
In fact, we can go into settings
and find how much of the animation we want to play.
So I want to set this to something like 0.9
so we'll play 90% of the animation,
and then we can choose how long our transition should be.
I'm going to remove fixed duration here
so we can choose a percentage.
And I'm going to set this to 0.1
This means that we are going to play 90%
of the Intro animation,
and then use the last 10% to transition to the new one.
And we can actually see that, in our preview window here.
If I zoom out, we can see that it plays our Intro animation
and then transitions to running,
and we can immediately see this working
in play mode as well.
But right now we are of course stuck in the RunState.
We also want to be able to transition to our Attack.
So to do that, let's right click on Run,
hit make transition and click on Attack.
And we don't want this transition to happen automatically.
We only want to go to our Attack state
if an attack is actually triggered.
So for this, we'll go to the parameters section
and add a new parameter, and we'll add a trigger.
Let's call this TriggerAttack.
And now under our transition, we can say
that the condition for this transition
is that attack is triggered.
We can also go in here
and choose how we want the transition to occur.
So this one we don't want exit time on
because we want this to happen
as soon as the attack is triggered,
and then we can set the transition duration
to something like 0.1 second,
and we can see that transition happening here.
We also want to be able to go back from our Attack to Run.
So we'll right click make transition, go back to Run,
select this transition here,
and we can set this up in the exact same way as our Intro,
because we just want to play our full attack animation
and then go back.
So lets set exit time, to 0.9,
remove fixed duration,
and then transitioned for 10% of the animation.
There we go.
That looks pretty smooth.
So we've now set up a symbol AI gameplay loop
for the first stage of our boss.
However, this is currently just playing animations.
We still need to add logic to the different states
in order to make it do something.
Let's start with the Run state.
So to add logic here,
we'll simply select the state and hit Add behaviour.
And this allows us to add a script to this state,
similarly to what we would do with a game object.
Let's call this Boss_Run.
Let's say, create and add
and double click it to open it up in Visious Studio.
And right away, we get a bunch of commanded out code
I'm going to take the two bottom functions here
and remove them completely.
We're not going to need those.
We'll focus on the top three.
I'm also going to go ahead and uncommand these functions.
You could go through like this and do it manually.
Or you could just select all the commands
you want to remove,
and hit control + shift, hold + slash
in order to do that automatically.
That's a pretty neat shortcut.
So as you can see,
this script is structured a bit differently
than a normal MonoBehaviour
we would see on a standard game object.
Essentially, we get three functions here that we can use.
The OnState enter method is called, when this state begins,
the OnState update method is called
each update while the state is active
and the OnState exit method is called when this state ends.
So with this in mind, we can start adding some code.
Now, whenever the boss is running,
I want him to move towards the player.
To do that, we of course need a reference to the player.
So let's go to our OnState, enter here
and let's locate our player in our game.
We can do that in many ways.
I'm going to use GameObject.FindGameObjectWithTag
and search for a game object with the player tag,
of course, to use this, we need to make sure
that inside of Unity,
our player is indeed tagged with player.
Then once we find our player,
we can maybe store him in a variable.
Let's create a variable up here,
I've typed Transform and called it player.
This way we can say,
player equal to what we find here.transform
So now whenever the update is called,
we can actually go ahead and access our player's position.
Simply by going player.position
But in order to move our boss object,
we need a reference to the rigid body sitting on our boss.
And here we can utilise something really handy.
And that is for each one of these functions,
you can see one of the parameters
that get fed are the animator component
that is currently playing the state.
In other words, if we have a look at our boss,
this guy is the one
that's going to be playing these animations.
And so this animated component we actually have access to,
and because that sits on the same object as a rigid body
and everything else, we can just use,
GetComponent to find the rigid body next to it.
So inside of our code here,
we can just go, animator.GetComponent(Rigidbody2D)
and that should find a rigid body.
And again, we can just store that in a variable,
let's called RB and set it equal to whatever we find.
So now we have all our references
and we can just add some movement code.
I'll just add a couple of lines for this, now.
First of I'll find the target position
that we want to move to,
this has the player's position on the X,
because we want it to move horizontally towards our player.
And it just has our composition on the Y
because I don't want to make any changes on the vertical.
Then I'm using this Vector2.MoveTowards function.
And all this is going to do
is move a point from one position, our current position
towards the target position, using a certain speed.
And we can of course create a variable for the speed,
like we would with a normal script.
So at the top here,
let's create a public float called speed
that we default to something like 2.5, there we go.
Also notice that I'm multiplying with
Time.FixedDeltaTime here.
I actually tried out Standard Delta time.
However, I got some weird behaviour with that.
This has been working much better for me,
but I haven't been able to find an official answer to this.
So if one of you guys have some information
on what is best to use inside of OnState update,
definitely leave that in the comments.
So now that we have this new point
where we want to move towards,
let's just store it in a vector2
and call it our new position.
Well then we can simply set that using a rigid body.
So we'll go rb.MovePosition
and we'll move to our new position.
And that should actually work.
You've now saved this, go into Unity and hit play.
As you can see,
as soon as our boss goes to the RunAnimation,
he's going to start moving towards our player.
You'll also notice
that if we move to the wrong side of him here,
he's not always looking at our player.
So to do that, I've gone ahead
and created a boss script that sits on the boss himself.
And in this script is a function that is responsible
for always flipping the boss
so that he points towards the player.
I'll of course, have a link to the script in the description
in case you want to use it,
or you can make something yourself.
But the most important thing is
that we can access a function inside of this script
from a RunState.
So let's go BossRun,
and now in here in our OnState update method,
we can use again, animator.GetComponent(Boss)
in order to find that script
and then just call the LookAtPlayer function.
And of course we can go ahead and cash this in a variable
like we do with the other ones.
So we'll just create a Boss, call him boss,
take this code here and said, boss equal to that code.
And then down here, we can just call boss.lookAtPlayer
That's a lot cleaner and more performant.
And as you can see, he flips to look at the player now.
Awesome, of course currently once he gets near the player,
we want him to start attacking.
Right now he's just running and running and running.
So to do that, let's go inside of our RunScript once more
and let's check our current distance to the player.
We can do that using Vector2.Distance
here we can check the distance between two points.
We'll use player.position and rb.position
or the boss's current position.
And we then check how great this distance is,
and then say that if this distance is less than
or equal to some kind of AttackRange,
again, we'll create a variable for this.
Well then we want to go ahead and attack.
So let's go up here and let's create another public float.
This is going to be our AttackRange.
And let's just default this to three.
And now here in order to start attacking,
we just access our animator and go SetTrigger(Attack)
This is going to access our attack parameter and trigger it.
So we'll transition to the next state.
And you would think that this works now,
but I've found a bit of weirdness happening sometimes
when we set triggers inside of the update method.
And that is that sometimes
it's kind of setting the trigger more than once.
And so once we transitioned back,
it's going to automatically attack again.
We don't want that to happen.
So just to make sure that we clear this trigger,
whenever we exit this state, we'll set,
animator.ResetTrigger on our AttackTrigger.
So whenever we are going to attack,
we're just going to reset the state of this trigger.
This way, nothing junky happens.
And if we save this and play, as we can see,
once our boss gets close enough,
he's going to start attacking.
And as soon as we get out of range,
he's going to start chasing us again, awesome.
Of course our boss currently isn't damaging our player.
So to do that, we need to add
some behaviour to our AttackState.
And we could of course do that
by just adding some behaviour here.
But one of the really cool things
about using the animation system
is that we can actually trigger functions
inside of an animation.
This allows us to perfectly time it
so that we do damage to the player
at the right time of our attack.
Now again I've gone ahead and created a Boss Weapon Script,
and this has a public attack function.
It looks like this.
You can of course create your own attack function
that does whatever you want it to do,
or just copy mine, that's totally fine.
But just note that this function is public
and because of that, we can trigger it
from inside of an animation using an animation event.
So to do that, we'll find our attack animation here.
We'll go to the point in our attack
where we want to trigger the event.
I think somewhere around here looks pretty dramatic
and we'll go over here and add an event.
Now with this event selected,
we can go up here and we can select a function.
And this shows all of the public functions
that are on the same object as our animator.
So in our case, we just want to go ahead and select Attack.
And now without doing anything else, whenever we play,
our boss is going to attack
and damage our player if he hits.
And if he keeps doing this, well, ultimately our player dies
and the scene restarts.
Really, really cool.
So our states are now working,
but it's not really a proper boss fight
unless we add a second stage.
So let's drag in some more animations,
Let's take the enrage, enrage to attack and enraged to run
and drag all of them in here.
And the first thing that I want to do
is transition to our enrage animation.
Whenever we are ready to enter the second stage.
So let's go ahead and create a parameter
that is going to trigger this transition.
So let's hit the plus sign and add a bullion.
We'll call this one IsEnraged,
and by default, it's going to be false.
Then we can create a transition from run to enrage.
Select this transition and ask the condition.
We want to make sure that IsEnraged is true.
We'll also disabled exit time and set the duration 0.1
So now we'll go into our enraged animation
and we could actually add completely different behaviour
up here than in our first stage.
However, just for the sake of simplicity,
I'm going to copy kind of the same setup
that we have down here
just with faster animations and more damage.
So we'll go straight from R enrage to EnragedRun
again, we'll do this with exit time,
let's set to 0.9 duration of 10%.
And we can see that working here.
We'll also go from run to attack
and here we can actually use the same AttackTrigger.
We don't need to create a separate one.
So for the condition, whenever a tag is triggered,
again, we'll change the transition settings here,
and we want to go back the same way as well, there we go.
And just like we are reusing the parameter,
we can actually also reuse behaviour.
We can go to our enragedRun here,
and if we hit add behaviour,
we still have our BossRun Script, so let's just use that.
And we can go into here and maybe pump up the speed
to something like six and maybe even extend the attack range
to something like 3.5
And if we now play this,
we can see that our boss is working just fine and normally
until we hit IsEnraged in which
at which point he is going to play the enrage animation
and then start charging after us
and attacking us very very ferociously.
Of course, it's attack now, isn't doing damage again.
And that's because we need to go
to this EnragedAttack animation as well,
go to the point where we want him to damage the player
somewhere around there add an event.
And I've actually gone ahead
and created an EnragedAttack function for this as well.
And if we have a look at it
is the exact same as our attack function,
but just doing more damage.
You could, of course have a completely different attack
if you want to get creative with this,
but I've just amped the numbers a bit.
I know, lazy me.
And there you go, he now damages to player.
Finally, we of course don't want to check off
this IsEnraged manually inside of the editor every time
we want this to happen automatically
once he reaches a certain amount of health.
So how do we influence this system from the outside?
Well, that is actually really simple.
If we go to our Boss, I have this Boss Health Script,
and this is actually really simple.
It's a completely standard health script .
At the top here, we have a public int with our health.
It's currently set to 500.
Then we have a public function for taking damage.
This is called Whenever a Bullet Hits
and it just attracts the damage from our health,
if we go below zero, our boss dies.
So all we need to do here is just go in and check.
If our health goes below some boundary,
let's say something like 200,
well, then we want to enrage and we can then call it,
GetComponent(animator).setBullion("Isenraged", true)
There we go.
We are now finding the animated component
and setting the IsEnraged Bullion to true.
That's really all we need to do.
If we save that, go in and play,
It'll now damage our boss a bunch, he's going to enrage
and then start really doing some serious,
Oh my God! Damage, awesome.
Now one tiny thing that we might want to change
is that we can currently damage the boss
while he's in ranging and then kill him.
This is not what we want.
So let's go ahead and add a quick behaviour
to our EnragedState as well.
Let's just hit, add behaviour, go Boss_Enrage
lets then create that and open this up,
again, let's removed the bottom functions
and some of the commands
and all we need to do is once we enter this state,
we want to become invulnerable.
And then once we exited,
we want to be able to damage him again.
And inside of my Boss Health Script,
I've created this quick Bullion called IsInvulnerable
and it's false by default.
But all it does is that if we try to damage the boss,
while he's invulnerable,
then we just return out and don't do anything.
And we can of course control this inside of our state.
So OnState enter here, we'll go
animator.GetComponent of type Bosshealth
and we'll set, IsInvulnerable to true.
Then once we exit the state, we'll set it back to false
and that's it.
If we now play our game our boss plays our Intro,
he charged us after us, he tries to attack us,
we can damage him a bunch,
and once he enrages we can damage him until he's done.
Then he becomes kind of ferocious and then we can kill him.
That's pretty much it for this video.
If you enjoyed it, make sure you subscribe
and ring that notification bell,
so you don't miss the next one.
Also, don't forget to check out NordLocker,
get 37% off NordLocker and get an additional 5% off
by clicking the link Nordlocker.net/brackeys
and entered the code "BRACKEYS"
Also, we'll be taking a break next Sunday
for the winter holidays,
but we'll of course be back the week after with a new video.
On that, thanks for watching.
(rock music)
And I'll see you in the next video.
Thanks for the awesome Patreon supporters
who have donated in December
and specially thanks to Lost to Violence, Loved Forever
Samuel, Ville Virtanen,
Chris, Faisal Marafie, Megan Frazier,
Dario Desarius, Leo Lesetre,
Danijel Dusanic, Jacob Sanford,
Marc-Antoine Girard, Gregory Pierce
Naoki Iwasaki, TheMightyZeus,
Alison the Fierce, Yigit Kaya,
and Erasmus, you guys rock.