Transcripts
1. Introduction: Welcome to the full course on two D Game Development
with Unity. We'll start from a blank project and end up with a
fully playable game, both as a native Windows app and as a browser based web game. After creating hundreds of minigames in various
programming languages, I've compiled the most common two D techniques
you'll need, simplify them, and designed
a project around them. We'll go through each
technique step by step and build everything you
see on the screen together. The most challenging part for beginners is not
knowing where to start. So let me guide you through the entire process
from the very basics. Let's build a two D sit
scrolling game together. But this won't be
just any basic game. We'll focus on animations, particle effects, and visuals. We'll also create an
enemy and weapon system that you can keep expanding
even after the class ends. With the skills you learn, you'll be able to design your own unique
enemies and weapons, and I'll provide you with bonus art assets to help you do that. There's much more included, as you can see in the previews. This class is for beginners, but I do expect you've completed at least one or two
simple unity projects before. Let's get started.
2. Project Setup: So I create a new Unity project using Universal to
the render pipeline. I'm working in Unity six, but you can use any
Unity version you want. It doesn't really matter. I start by right clicking
here in hierarchy, and I select Create Empty. This will create a
new game object. Unity is all about game objects. Game Object is a
container with position, rotation and scale
that we can fill with components to make
it do anything we need. You can see that my
game object already has a transform component which determines position,
rotation and scale. Unity has many
different components. For example, we can
give our game object a sprite renderer component
to display an image, rigid body component
to give it physics, or even a custom
script component to control the game
object with code. We will do all of that
and much more today. I will rename it to background. Down here in my Assets folder, I right click and create
a new empty folder. I will call it art.
You can download these background players
in the video description. All art in this project was created or commissioned by me. You can take it, modify it, and use it for your own
projects in any way you want. You can credit me if
you are using my art, but it's not required. You can use this little
handle down here to scale the icons up or down. When I select any
of these images, Inspector tab will give us more information
about the files. If I click this little arrow, you can see that
Unity auto sliced my background image
into individual stars. Sometimes this is useful, but in this case,
we don't want that. I select all four images
at the same time by clicking the first one and then Shift click
on the last one. With all four images selected, I can change their settings for all of them
at the same time. In this case, I don't want
these images to be sliced. Each image is only one
sprite, Sprite mode, single. If you are using large images, you might have to increase
max size value here. For me, this is enough,
so I click Apply. Now if I expand these sprites, you will see they are made
up of only one frame, which is exactly what we need. There is one more
thing we have to do. I want to make
sure I can measure the width of each image
because we will be animating them out
of view and back into the view once they
reach a certain point. To be able to do
that, especially with images that have some
transparent parts, I select all my
images one more time, and I set mesh type
on all four of them to full
rectangle like this. This will set the sprite size
of each of these images to a full rectangle that wraps the entire image. I click Apply.
3. Parallax Backgrounds: We processed the
images and we are ready to import them
into the game scene. I have scene tab open here. If I drag and drop image from my assets folder
into the scene, Unity will automatically
create a game object for it. Unity can see that
this is an image, so it will give it a
transform component that every game object has, and it will also give it
sprite renderer component. If I right click here
and reset transform, position will be set to 000. We want to make sure
everything is centered here. We will be moving the images
horizontally like this by accessing this transform
position X value. Why value can move
the image up or down? I drag Layer two
image into the scene. Unity creates a
game object for it, and again, I reset
its transform to 00. Layer three, reset transform. Layer four reset transform. I select all four layers
here and I parent them under the background
game object like this, mainly just to keep our game
scene clean and organized. I make sure that background and all four layers have
transform position of 00. In your project, you might see something a bit different
in the scene view because we are placing all
of these images on the same default
sorting layer, and Unity decides randomly what order these
objects are drawn in. We need to make sure
that layers are always drawn in this order with layer one all
the way in the back. On any of the layer objects, I go to Sortinayer drop down
and I click at Sartin Layer. Inspector will now show me
tags and layers manager. Here, under certain
layers, I click Plus, and I add custom sorting
layer called background. Certain layers in
unity allow us to set the render order of different sprites in
our two D project. They determine what gets drawn behind and what's in front. In this case,
background layer will be drawn in front
of default layer. I select layer one and I set
sorting layer to background. Notice that order in layer
here is set to zero. Layer two will also have sorting layer set to
the same background, and order within the
background sorting layer will be set to one in
front of layer one. Layer three will also be on
background sorting layer. Order in layer will be two. Layer four, background
sorting layer, audio in layer three on top in front of all
the other layers. Here in game view, you can see that game camera
field is too wide, and we see bits outside
the background images. I set aspect ratio to
full HD, 1920 times 1080. Back in scene view,
this white rectangle is what the game camera can see. Inside my assets folder, I right click and I
create an empty folder. I will call it scripts. In unity, we can write scripts in C Sharp
programming language. Let me show you how
simple it can be to write a small script that can
do really cool things. To create a script,
I right click, create scripting and
mono behavior script. Monobhavior is a special
class in unity that allows us to attach our custom
scripts to game objects. It's very simple.
Let me show you. You will use monobhavior
all the time. It's one of the most
fundamental things when working with Unity. I will call it
parallax background. When you create a
script like this, Unity will automatically
create a class with the same name you give
to the script file, and it will make that script
inherit from monobhavior, which means that we can
take this script and we can attach it as a component
to any game object. So I select layer
one game object. Here and I drag and
drop my script here to attach it as a custom
component like this. If I go to edit preferences
and external tools, here, I can choose my default
external code editor. Whatever you choose in
this drop down will be used when you open
a script in Unity. I'm using Visual Studio code, but you can use any
code editor you prefer. So when I select Layer
one Game Object, I can open Parallax
Background script. I attach to it as a component
by clicking here or here. Both of these will
open the same script. By default, Unity creates syntax for start
and update methods. Start method runs just
once when the object is created before the
first goal of update. Update method runs over and over for every frame as
the game animates. All we have to remember here
is that anything we want to happen just once when we create the object,
we put it here. Anything that needs
to be happening over and over, we put it here. Example, we want
to move the layer one game object this
script is attached to. To declare a
variable in C sharp, I first have to
specify the data type. Float is a floating
point number, a number that can
have decimal points. I will set it to one. Then I take transform
dot position of the object this
script is attached to. Back in unity, layer one
game object selected, this script here and transform
position values are here. Notice that position has X, Y, and Z components. To update position value
of this game object, I need to keep that in mind. Vector three is a
simple data type that has three components, usually for X, Y, and Z axis. If I pass it one for
X and one for Y, because this is a two D game, so we care only about
two dimensions, the third value of Z
will default to zero. I'm saying, take the
transform position, which is vector 300, zero, and for every animation frame, add plus one horizontally and plus one vertically to the
current position value. Or instead, I can use my move X variable as X
component of the vector, and as Y, I add zero, meaning that Y component will remain unchanged
as this code runs. It will stay at
whatever value it is. So as the game runs,
for every frame, I'm adding plus one to this X value and plus
zero to this Y value, making the object this script is attached to
move to the right. I save changes, and I can test this by clicking
the play button. I enter the play mode. That happened
extremely fast because plus one doesn't mean
one pixel per update. It means one of these blocks you see in the background
grid in unity. I can fix that by dramatically reducing this value of one
to make it move slower. But the right thing to
do here is to actually account for real time
that is passing by. First, let me show you this. We are increasing
horizontal position by a very small value for
each co of update. This method will run probably something like 60
times per second. I drag game view somewhere
here because I want to see the scene and game at
the same time side by side. To achieve repeating
endless backgrounds effect, we want to at some point, reset the position back to zero. Let's say when X
reaches plus five, set transform position
back to zero, zero. We are not doing anything with the vertical Y component
of the vector, so I might as well just set it to whatever value
it currently is. It will always stay
at zero anyway. If I save changes
and enter play mode, layer one selected here, you can see that
X is increasing. When it reaches five,
it resets back to zero. So the important thing
to remember here is that if we want
to move something, we can do it by accessing
its transform position, but we have to do it through
this vector three data type. Setting this move step to a
hard coded number will work, but it's not the best practice. We should always account for real time time Delta T. Update
method runs over and over. Delta time is the
amount of time it takes our machine
to call one update. It's the time that
passed between the previous frame and the
current frame of our game. The game will run on
many different devices. Some of them can run
faster, some slower. Always want to account
for delta time to make sure the movement is
the same on any device. On a slower device, Delta time, the time between frames
will be higher and therefore position will be
moving by larger steps. Faster device on the other hand, can call update more often, but less time will be
passing between frames and position will be increasing by smaller value for each update. All you have to remember
here is that we never want to tie any movement
in our game to frame rate. We always have to tie it to real time that is
passing by to make sure that things in our game are happening at the same
speed on any device. Delta time is the
amount of time in seconds that passed since
the previous call of update. This value will need
to be larger now. Instead of hardcoding it here, I can put it in a variable
I call move speed. I want the value
to be visible in Unity editor here so I
can set it as public. But even better practice
would be to keep it private but give it serialized
field attribute like this. When I do this, my
move speed variable is private and not accessible
from other scripts, and at the same time, it will be visible and editable in unity. I use move speed
here on line 16. I save changes. Now we get the
field for move speed here. I give it some value and I play. We move by two of these
blocks per second, and when we reach
five horizontally, we reset it back to zero. I can also go in
the other direction by setting move speed
to a negative value. But when I do that, this will
never be more than five. So position will never
reset back to zero. I exit play mode because
most changes we make to the project while in
play mode will not save. I can delete these comments. So if you want your repeating
backgrounds to work in both directions with positive
and negative move speed, instead of checking if
position X is more than five, we can check if the position
is more than five away from zero in any direction
positive or negative. I do that by wrapping
position X in math absolute, built in function like this. Absolute value of five is five, absolute value of minus
five is plus five. I will disregard the minus. So here I'm saying if the
position moved by five away from zero in any direction
positive or negative, reset position back to zero. I can also write it like this. If the difference between position X and five
is more than zero, if we move by more
than five units away from zero, we reset. Now if I save changes
and I enter play mode, we reset if move
speed is positive, and also when it's negative. So you can use this
repeating background for a game where your character
moves both left and right. I don't want it to reset back to zero when it
reached five units. I want it to reset when it
reached the full width of the image when the layer image completely disappeared
off screen, only then I wanted to
reset back to zero. If I try something around 19, my image is 1,940 pixels wide, and each block in this grid
is 100 times 100 pixels. So I want to say,
when we moved more than 19.4 away from zero, then reset, I would work if I put 19.4 here instead of five. But what if I want
this grip to be more flexible and usable
for any image size? And also, we are doing
multiple layers. What if each layer image
is a different width? So instead of hard
coding this value, we will measure the width
of the image with code, and we reset when the image scrolled away from the
screen by that width. Start method runs only
once when the game object is created before the
first call of update. We are inside Parallax background
script, and from here, I want to access
this Sprite property that holds layer one image. I do it by creating
a helper variable. Type is Sprite. My custom name for the
variable is, for example, Sprite lowercase S, and
it will be equal to get component sprite
renderer dot Sprite. From this script, I'm accessing Sprite Renderer component
on the same game object, and it's Sprite field. Now that I have it, I can create a variable. Type is float. I call it background
image width and in unity. Inside Assets art, for
all my layer images, we are setting pixels
per unit to 100. So the image that is
1,940 pixels wide will take exactly 19.4 of
these units in the grid. So back in my script, after I have a reference
to this field, I want to know how many of
these blocks in the grid, how many units does
the image have to move before it's moved
completely off screen, and we can reset it. What value does
position X have to be before we can reset
it back to zero? It will be the
width of the image. In my case, 1940 pixels, we are taking that
texture width from here. Divided by pixels per unit, in my case, 100. 1940/100 is 19.4. When the image moved 19.4
units away from zero, reset its position back to zero. I use background
image width here. I save changes and I play. When the image moves
all the way out of the screen, it resets. Perfect. The code I wrote
will work in both directions, and for any image
width, you might use. The image should be wider than camera view to get
the right effect. I go to Assets
script and I attach Parallax background script to layer two game object as well, and move speed will be
three, for example. Layer one move
speed will be zero. I also attach the script as a component to layer
three game object, and I set move speed 26. And finally, I attach
it to layer four, and move speed here will be ten. I save and I play. We have four layers, each
moving at a different speed and resetting when fully moved
off screen. I exit play mode. I select all four of them at the same time and I set
draw mode to tiled. This shows me that each layer is 19.4 units wide
and 11 units tall. Just to check, I can
go back to my script and I can deba lock
background image width. Save that and play to run the
code inside start method. And in console, I can
see that image is indeed 19.4 units wide, same as the value we
are getting here. I delete that log. With all
four layer objects selected, I set draw mode to tiled, which allows us to easily create repeating tiled textures. We just have to tell Unity
what size we want to cover. In this case, I want to
have the same copy of each background layer to the left and to the
right of the screen. So 19.4 times three. Now each layer was
duplicated to both sides. And if I wanted, I could
also tile vertically, but we don't need that
for this project. As you can see, as the layers slide and reset that
extra side layer comes in place when
needed to create an illusion of seamlessly
repeating endless background. I think seeing the
game preview here and sneview here shows clearly
what is happening. This technique is
great for any type of endless runner game
for platformers and many other projects
where you want a static game camera and seamless backgrounds
that endlessly repeat. This will work in
both directions. So let's try and give layer two negative move speed value. Layer three is on top of it, so it needs to be
faster to create the parallax effect
and illusion of depth, and layer four will
be the fastest. For example, minus one. This motion is for a game where your character is flying
through the space to the right. This code base will
also work for games where you allow players to
move both left and right. For that, instead of
hard coding move speed, for each layer here, we would tie that value to player movement
direction in our code. If I put scene and game view
side by side like this, I hope it's clear and
easy to understand how the layers sliding and
resetting creates a perfect, seamless, endlessly
scrolling effect. And how set in
different move speed for each layer creates parallax.
4. Player Movement and Animations: C. I go to assets scenes and I rename my
sample scene to level one. You can download player
SpaceShip Sprite Sheet in the resources section below. I made that one.
Feel free to use it for your other projects
as well if you want. I drag and drop the image into Assets art with PlayerSprite
Image selected. Inside the Inspector tab, I click Open Sprite Editor. Unity already auto
sliced this for us, but I want to move the
pivot point to the tip of the spaceship to make the transitions look better
because as you can see, the last frame is
wider than the rest. I go here to slice. I set pivot point to right, and before I confirm that, you can see Pivot is
in the middle now. When I click this, it
moved to right center. I click Apply and I
close Sprite Editor. If I click this little arrow, I can see the individual
frames we just sliced. The images are similar but different things are
happening with the engines. We will do some subtle
lightweight animations that react to player movement. I take frame zero and I drag and drop it
into the game scene. Unity will automatically create a game object for it here. I rename it to player. I click Add Sartin layer plus, and I call it player. It will be on top of the
background and in front of it. I select player
again and I assign it that new sorting
layer we just created. I right click on
transform component and I reset it to 00, zero, and I move the
player to the left. So maybe minus three
units horizontally. If I play, we have our little player spaceship
traveling through space. Let's start by adding
some animations. I always have to exit play moode before making any
changes to my game. I go to window
animation animation. I can dock the animation
panel anywhere I want. I like to keep it here.
If I expand this, you can see that this word changes based on what
object we select here. I go to assets and I create a new folder I call animations. I select the player game object, and I want to create an
animator and animation clip. So I click Create. In this window, I navigate to the animations folder
I just created. I will call this
animation player discoreU and I click safe. I go to Assets Art. I make sure I still have player up animation
selected here, and I drag this frame
on animation timeline. If I click Play here, you can see that when
the player moves up, the spreadsheet will
show the top engine stopping and the bottom
engine engaging more, which is what would
happen if we wanted the spaceship to go in
that direction, I guess. I select the player again. I click here and
create new clip. I make sure I'm inside
Assets animations folder and I create another dot anime
file called player Down. Save. This image will
be player moving down. I played to check. Yes. Create New
clip, play right. It will be just
this first image. Yes, I create new clip, player left, and I find this image where
the engines switch off. Yes. If I go to
Assets Animations, we have four animation
files we just created, and we also have this player
animation controller. I can double click
it or I can go to Window Animation Animator to open the player controller file. So keep in mind that
inside Animations panel, we create timelines for
individual animations. Inside animator, we have a
very simple state machine. Here, we define how those animation states
transition from one to another. Hey, I like to do this is
to use something called a blend tree where we merge
all four of these animations, and based on what directional
inputs are being pressed, Unity will automatically decide
which animation to play. I delete all of these. I right click Create State
from New Blend Tree. In Spector tab here, I rename it to player fly. I double click it and we
get one layer deeper. I will rename this one to fly. I go here two
parameters and I click Plus and I want to create
a float parameter. I call it move X. One more parameter, this
one will be called move Y. With my fly blend tree selected, I set blend type to two
the simple directional. The first parameter
will be move X, the horizontal input, the second parameter will be
move Y, the vertical input. Based on the values
of move X and move Y, we will let the
blend tree determine if the player controller
should play left, right up or down animation. I click this plus,
add motion field, plus, and I add one more. I add two more, so now
I have four in total. Let's set all of
this to zero, zero. I click here and I
select the first motion. Let's start with player
we animation sequence. This one will play when
horizontal motion is plus one and vertical
motion is zero. The next one will
be player left, minus one horizontal,
zero vertical. I can also do it by
drag and drop in these, so I take player up
and I put it here. This one will play
when horizontal inputs are zero and vertical
are minus one, and player down, drag and
dropped here will play when horizontal is zero
and vertical is plus one. If I select the
player game object here and fly blend tree here, I will get this little
preview of player spaceship. If I start changing move X, we can see how the
blend tree is deciding which animation to play based on the
conditions we defined. Move Y should actually
be the other way around. I always confuse the
vertical direction in unity because it's the
opposite of HTML canvas. In unity, positive
plus one is up and negative minus one
is down like this. Now it works as
intended, perfect. So we created a blend tree, this little brain that
will automatically decide what animations to play based on the
conditions we defined. Now we want to connect
move X and move Y parameters to input values
coming from a keyboard. We will do it by
writing a small script. I go to edit project settings. I will use this quick
and easy input manager. If you are making a
multiplayer game, I would recommend using
the other input system, which is also very
easy to implement. I have many videos on it. I will link some of them
in the description. This is a simple
single player game, so I will use the
old input system. Unity has this built
in system that automatically captures
inputs from keyboard, mouse, and game pads. It's extremely easy
and quick to set up and connect to
our game objects. Let me show you. Have this
horizontal block here. I can see by default, horizontal controls
are set up as left arrow or a key on the
keyboard to move to the left, negative on the horizontal axis, and right arrow or D
to move to the right. I will also connect
my Xbox controller to my PC later to test this, and these inputs will already be compatible with all
of that as well. We don't have to do
anything special here. All I have to do is to note
this keyword horizontal. We will use it to
access this Vertical is down and up arrows
or S and W keys. We will also use fire
one or more likely fire two to trigger players
special superspeed ability. Here I can see that fire two input event is triggered
by left out on the keyboard, mouse one, which is the
right mouse button or some keys that are specific
for different controllers. We will go over this later. We don't really have
to do anything here. I will leave it all as is. I just want to show you
where you can go to see all these input
events that we can use. I go to Assets scripts, IRClick create scripting
mono behavior script. I will call it
player controller. I select player Object and I attach this script as a
custom component like this. Now that we have this script sitting on player Game Object, I can open it in my code editor by clicking on it here or here. I remove these comments. Back in Unity, player
selected here, and I add another component
called rigid body two D. Make sure you
use the two D version. This will turn the player into a physics object affected by
gravity and other forces. So if I play, player will
fall off screen like this. I exit play mode. And I set gravity scale
on the player to zero. This is space. We
want to be floating. I want to use unit
is built in physics, so I will make the
player move by setting velocity forces on its
rigid body two d component. We could also move the
player by changing values of its transform
component directly, but that way, we would
bypass Units physics system, and we would have to write
our own collision logic and many other things. So let's do it the right way. I need access to this rigid body two D component from this player controller
script component to do that. I define a private variable. Type is rigid body two D. And I will call
it, for example, RB. Inside start method, I
set RB to get component, rigid body two D like this. When this object gets created, automatically find its rigid
body component and save that reference in
this RB variable for us so that we
can work with it. Start method runs only once when the player game
object is created. Update method will run over
and over for every frame. Inside update, I set
linear velocity on player's rigid body component to some value to make the player
move in that direction. I will set it to vector
two and I want to push it plus one horizontally to the
right and zero vertically. I save this and I play just
to see what this does. We are moving to
the right, one unit per second. I exit play mode. Let's try minus one horizontally
and plus two vertically. I save and play, and this is the
result in motion. We know the values we pass
here will make the player move in a specific direction
horizontally and vertically. Update method runs
as fast as it can, usually something around
60 frames per second, but it can run at different
speeds on different devices. Fixed update method
runs in sync with Units physics updates that happen by default 50
times per second. Everything to do
with physics and rigid body should be
inside fixed update. Inside update method, we will
capture player controls. I define a temporary
helper variable. I call, for example, direction X. I set it equal to input dot Gaxsaw and here I pass horizontal
spelled exactly like this. Where is this coming
from? Back in Unity, I can go to Edit Project
Settings and Input manager. Here, Unity captures
input from all devices, keyboard, mouse, game
pads, and so on. If I expand horizontal, this name is important
because this is the word horizontal we will use
to access these values. Here in our script. Get axis raw means the value
is not smoothed out. This value will only be
minus one, zero or plus one. It will not be any decimal
values in between. Minus one is moving left, zero means no
horizontal movement, plus one is moving right. I do the same thing
for vertical Y axis. Keyword is vertical,
and by default, that's up and down
arrow or W and skis. Up here, I define a variable, private type is vector two, and I call it, for example, player direction,
serialized field so that we can see this
private variable inside Unity editor. As update method runs
for every frame, we capture horizontal
and vertical inputs and we set player
direction to new vector two with horizontal input as the X component and vertical
input as the Y component. Now I can simply use player direction X to push the player's rigid
body horizontally, left or right, and player direction Y to push the player
vertically up or down. Save that and play. When I press arrow keys or WSAD on my keyboard,
player moves around. You can see player
direction here. These values are always
minus one, zero or plus one. The problem we have now is
that with this implementation, we are moving diagonally, one unit up, and one unit
to the right, for example. In the same time,
we travel further. Diagonal movement is faster
with this implementation. We want to make sure that player travels the same distance
in any direction. In unity, we can easily achieve that by putting dot normalized. This is how we normalize a vector and we go
from this to this. I explained the math behind
this in many other classes. Let's just trust a unit is built in methods this
time and move on. Now if we play, player moves the same distance
in any direction. You can see that
diagonal movement values are automatically adjusted here. Okay, so we exit play mode and I can remove
serialized field attribute, which will hide player
directions in Unity Editor. We know it works, so we don't need to see those
values anymore. We also want to be able to scale how fast the player moves. So serialized field,
private type is float, floating point number,
and I call it, for example, move speed. We scale the
horizontal component of the vector by
move speed here, and we do the same for vertical. Save that and in unity. I will move my script component up top, so it's easier to see. And I set move speed
to one or maybe two. Now if I play, I
can move around. I can change the
values of move speed here and the player
will move faster. If I go to Animator Window and I select my fly blend tree, I know that by changing
Move X and Move Y, we switch between different
animation states. I want to access
this from my script. I say private type is animator
and my custom name is, for example, animator
with a lowercase A. To set values of Move X
and move Y from my script, I want to access this
animator component from my custom player
controller script component. We are already accessing
rigid body two D component. Let's do the same
thing for animator. Get component
animator like this. Now I can take that
animator variable and I can call built
in set float method. We called that float
move X and move Y, and when they change values, we know that blend tree will switch player states
between right, left, up or down. So set float called move X
to the value of direction X, the value that goes
between minus one, zero or plus one based
on what keys we press and also set float move Y to direction Y from
line 19 like this. If I save and play, we are moving the player
and we are also switching animation states based on the
current movement direction. Perfect. We also have this frame for sprint
or super speed. Let's set that up as
a separate state. I select player Game
Object, Animation window. I click here, create New clip. I go to Assets Animations, and I call it player
underscore boost. Save that. So we have animation
window here to set up individual animations
and their timelines. And we have Animator
window where we set up how these individual
animation states transition from one to another. With player game object selected here and player boost
animation timeline here, I drop this frame in here. If I press play here, we can see the
sprite gets swapped. Now I want to
create a transition between player fly
and player boost. I go here to parameters
and I create a new one. It will be a Bolin, which
can be true or false. I will call it boosting. I right click player
Fly, make transition, and drag this transition arrow and connect it to player boost. I select the transition, inspect a tab, and I
remove has exit time. I expand settings here, and transition duration
will be 0 seconds. We want it to be instant. I go down here to
conditions and plus. We will transition
from fly to boost state when boosting
is true like this. Now the other way around, I right click player boost, make transition, and I
connect it to player fly. I select that transition arrow and I antique has exit time. Transition duration is zero
and condition down here. Plus, we want to
transition from boost back to fly state when
boosting is false. Okay. And finally, now
I need to be setting this boost in bullying between true and false from my script. I will do it here inside Update. If we press space
bar, so if input, get key down keycode space, or if we press, for example, fire two, which is one of
the preconfigured actions. I'm using Get button down here. We take animator and we call
built in set bool method. Set Boolin we call boosting
and we set it to true. Else, if we release
Spacebar or fire two, I copy and paste
this and I have to be very careful about
brackets when I do this. If Gt key up space or
get button up fire two, I set Bolin, boosting to false. Now if I play
IPress space bar on my keyboard or I press one of the predefined
fire two buttons, which by default is left out on the keyboard,
right mouse button, or B button on my
Xbox controller, pressing any of these will
animate player boost state. Okay, that works perfect. When we are boosting,
we also need the background to
start flowing faster. I can handle that in
many different ways. For example, I create a public float I call
boost and I set it to F. Private float boost
power will be, for example, five F. Down here, I create a function I call private void enter boost and another one I
call exit boost. When we enter boost, we take animator and set boos boosting to true and
when we exit boost, we set it to false. Here I call enter boost instead, and here I call exit boost. I have my boost and
boost power variables. When we enter boost,
we set boost from line ten to boost
power from line 11. We set it to five. Player
will move five times faster. When we exit boost, we
set boost back to one. So now we have this
public variable called boost that is
either one or five. I might refactor this later. But for now, we will go to Parallax Background script file, and from here, I want
to access boost from line ten sitting inside
player controller script. I want to make some
public functions and variables that are sitting on the player available
all over the code base. So I will create a very simple singleton public static
player controller instance. Whenever I want to use any
public properties from here, I can access them
through this instance. Awake method runs
before start method. So here I can simply
say instance from line five is this entire script
we are inside right now. To follow good practices, I will make sure I have only
one copy of this instance. If we set this object up later to persist between
multiple game scenes, we want to make
sure we don't have two instances of
player controller. I say, if instance is not now, meaning that instance
already exists, destroy this other game object. We don't need a duplicate. Else, set instance to this. So I'm creating a
public static instance of player controller to make all public properties such
as this boost from line 12 available from other
scripts in our code base. And here I'm checking if this is the only player controller
instance in the project. Now I can use this player
controller instance to access this boost value, which we know can be one, or it can be five if
the player is boosting. Here I say player controller
instance dot boost. It's either one.
So move speed of this background layer times one is that same
base move speed. But if the player is boosting, this will be set to five, and this layer will
move five times faster. I need to put brackets here. First, we calculate the speed, then we account for Delta time. This script sits on
each layer object, so this change will affect
all four background layers. Now if I play, we can see the background players react
when we enter super speed. This boost state will also affect other game objects
we will add soon, so I might move it to a
different script file and I might refactor
this a little bit. But for now, all of
this is working. We have a spaceship
flying through space with animations that react to
player inputs. Awesome.
5. Energy Management: Right now, player
boost is unlimited. Let's create a simple
energy management system. Let's create an energy bar that depletes when we
are in super speed. I right click UI slider, I rename it to Energy slider. UI elements in Unity can only exist if they are parented
under Canvas element. So Unity automatically
created a Canvas element. I rename it to UI Canvas. By default, Canvas in
unity will be huge. If I select it here and
press F to focus it, you can see how far
the camera zooms out. The slider itself is here
in the bottom left corner, and in game view, we
can see it down here. So we know that even though
the canvas is huge like this, in game view, it will overlay
and fit into the game area. For this project, I will set
Canvas render mode to screen space camera and I drag my main camera here into
render camera field. This will shrink the canvas down and make it
easier to work with. But now I can't see
the slider anymore, so we know that UI Canvas is
now behind the background. Let's add a sorting layer. It will be in front of
everything. I call it UI. If I assign this new
UI sorting layer to my UI Canvas element, we can see the slider again. Select Energy
slider, I set width to 400 and height to 100. I click here to set an anchor. I hold down out to
also set position, and let's do top left corner. X will be 50, Y will be
-25 to move it a bit down. Okay, so if I expand this, I can see the slider is
made up of three elements. I select handle slide area
and I delete it completely. With slider selected, we
have this value slider here. I go to background and I
set the color to black. Fill area can be
expanded even deeper. With fill area selected, I set all these to zero. Then I select fill, and I set everything
here to five like this to give
it a thin border. I click this source image
and I press Delete. Color will be 130, 130, 255. I select the main
energy slider object, anti antique interactable. The value of the slider can
only be changed by code, not by simply clicking on it. If I change the value here, it will show us what
the slider will look like when it
fills with energy. This looks good. I write
Click Energy slider. I go to UI text Text Mesh Pro. First time using it
in this project, so I have to import
TMP essentials. This is Units system
for handling text. I want to put some text over the slider that will
display its current value, how much energy we have. I rename it to Energy text. The default value
will say energy, centre it horizontally
and vertically. I resize this to match
the slider size. I want to use a custom font. I will use this
one, for example, link in the description below. I download the zip
file, I unpack it. In my project folders, I go to assets TexmsProFonts, and I put that TTF file
I just downloaded here. TTF is a common true
type font file format. I right click it and I go to create TextMsproFont asset SDF. It will create a font file
Unity can easily use. On my energy text, I find font asset field, and I will be able to see
that new font file here. I select it, and this is how you can use a custom font in Unity. I go to Asset Scripts, right click Create scripting
Mono behavior script. I will call it UI controller. I attach it as a
custom component on UI Canvas Game Object. I can click it here
or here to open it. I will also open player
controller script. Here I create serialized field, private float, and
I call it energy. This will hold the
current energy value. Another field, this
one for max energy, and I make another one
for energy region. Inside start method,
when the game starts and player game
object is created, we set energy to Max energy. Okay, I think I
might need a flag to determine if we are
using energy or not. Up here, I create a private
booling I call boosting. Initially, it will
be set to false. When we enter boost, we
set boosting to true. Exit boost, we set
boosting to false. Update method runs over and over different speeds
on different devices. So to make sure our
energy levels are handled the same way
on every device, I'll put this logic
inside fixed update. If boosting is true, we do something else block here. So if we are boosting and if energy is more than
let's say 0.2, we reduce energy by 0.2
per frame like this. If we are boosting and
energy drops below 0.2, else, we exit boost
automatically. We don't have any more
energy to continue. This se block that will run
when boosting is false, we want to recharge energy here. If the current energy is
less than Max energy, increase energy by energy regen. Save that player game
object selected here, and I see these
three new fields. I set max energy to 50
and energy region to 0.1. If I enter play mode, I can see energy is set to
max energy immediately, and it's now 50. If I press space bar, we activate boost and we
are depleting energy. If I release, we
start recharging. If I hold it down long enough
to deplete all energy, we automatically exit boost. Okay, let's say we only
allow the player to enter boost if the current
energy is more than ten. Otherwise, we might get
some jitters when we are around zero trying
to enter boost. In UI Controller, I
delete these comments. Here, we want to create a
reference to Energy slider. Type is slider. For this work, we need Unity Engine UI name
space up here on line two. I will name this
variable, for example, Energy slider. Another variable. This time, type is TMP
text spelled like this, and we need TMP
name space up here. This will be the text element
overlaying the slider. Save that, Canvas selected. I move the script
component uptop to make it easier for myself. I drag energy slider
into this field, and I drag energy
text into this field. I delete these methods. Instead, I write
my own custom one, I call Update Energy slider. It will expect two parameters, floating point number type with the current energy value and another float with the
maximum energy value. Every time we call this method, it will take slider from line seven and it will set
its value to current. It will also set its
max value to MAX. And we will set energy text
from line eight dot text, which will point to
this game object and this text input
component on it. I want to set this value to the current energy
plus slash symbol plus Max energy like this. I have a feeling this line has to be declared
before this line. First, we set max value and then the current
value. Let's see. I want this public update
energy slider method to be available all
over the code base. So I will do the same thing
I did for player controller. I will turn UI controller into a public static
instance like this. I can copy this awake method. It will work exactly
the same here, making sure we don't
have a duplicate. Now I have this UI controller
instance and I can access all public properties
and methods sitting here, such as this update Energy
slider from other scripts. I go to player Controller. Basically, whenever we update energy value anywhere
in our code, we want that change to be
reflected in the slider. When the game starts, we
set energy to Max energy. So we want to call UI Controller instance Update Energy slider and pass it the current
value and the max value. I copy this line and
inside fixed update, after we did all these
things with energy, we also update slider to
display that new value. Save that and play, and we have an energy
slider that works. If I press a space bar, we are depleting energy and we are showing many
decimal values. So back in eye controller, let's round the value, we pass to slider to
the nearest integer. Whole number without
decimal points using this built in method. Save that, and now I play. And when I activate boost, we get this perfect
energy slider display. If I deplete energy completely, the code we wrote will
automatically exit boost. We also wrote another piece of code that will prevent me from entering boost mode again if
my energy is less than ten. Well done, now you
know how to work with Unity's game object
based UI system. We can create UI elements and we can control
them with code. This is an extremely
useful technique you can use in so many different
kinds of projects. You create a canvas element
for your UI like this, you need to make
sure it displays correctly on all
different screen sizes. For example, a web version
of this will display wrong if the browser window
is not 1920 times 1080. We can easily make the
UI scale by selecting UI Canvas and we set UI scale mode to scale
with screen size. Reference resolution will
be what we put here. 1920 Ts 1080.
6. Asteroid: So. Space is supposed to be empty, but this is a game, so we need asteroids, robots, and aliens. I created this asteroid
spreadsheet for you. You can download it in the video description and you can drag and drop it inside Assets
art folder like this. I make sure Sprite mode
is set to multiple, and I open it in Sprite Editor. I slice it. Type will
be grid by cell size. 248 times 248 pixels. Actually 250 times 248. I click slice, Okay,
and I click Apply. I choose one of the asteroids and I drag it into
the game scene. I rename it to asteroid. Here I add another
sorting layer. I call it objects, and it will be in between
background and player. Asteroid selected again and
sorting layer set to objects. At component rigid body two D to turn it into
a physics object. Gravity scale is zero. I add another component, circle collider two D, Addit collider geometry, and I make it a little bit
smaller like this. I select the player,
and if I wanted to be able to interact
with other colliders, it needs to have its
own collider as well. Add component, circle
collider two D. Addit collider, and I make
it considerably smaller, making our game a little
bit more forgiving when avoiding swarms of
asteroids. This seems fine. I play. And if I
select Game view here, and I move the player
towards the asteroid, we can actually push
it away, perfect. You can see that player
rotated after the collision, and the asteroid
will now endlessly travel to the right
out of game view. I exit play mode, player
selected, and in constraints, I take freeze rotation zt to prevent player from
rotating after collisions. I will not freeze
it for asteroid. They can rotate when they
collide with something. If I play, I can freely move the player outside
of playable area. So let's do something
about that. The quickest way would be to
create an empty game object, I call level boundaries. I reset its transform to
make sure it's centered. Here I add component
and I search for box collider two D. I will position
it along the left edge, so horizontally -9.5 units. Vertical size here
will be ten units, which is the size
of my visible area. Or maybe I will go 12 to get one extra
block on each side. On this same level
boundaries game object, I add another box
collided two D. This one will be 9.5
units to the right. Vertical size also 12. I add one more box collider D. To move it down, I change its
vertical position to -5.5, and horizontal size
will be 12, 20 units. And finally, I add
fourth box collider. This one as the top boundary, so I move it up by 5.5 units, and it will be 20 units wide. Now if I play while still having level boundaries selected
here, so we can see them. Look what happens when
I push the asteroid. It collides with the boundary and player collides
with boundaries as well because both player and asteroid have a collider
component attached to them. We want level boundaries to interact with player but
ignore the asteroid. How do we disable collisions between two specific
game objects? That's what we can use
this layers drop down for. I click Add Layer, and I create a custom
user layer I call player and another one
called level Boundaries. And one more I will
call obstacle. Keep in mind that these
are not sorting layers. Sorting layers up here define what gets drawn behind
and what's in front. They define draw order. These other layers allow us to associate objects with them and we will use it to
disable collisions between specific layers. I select player game object, and using this drop down, I associate it with my
new custom player layer. Asteroid with obstacle layer and level boundaries with
level boundaries layer. Now I can go to edit
and project settings. And here on physics to the tab, I can edit layer
collision matrix. For example, here, I have collisions between
objects associated with level boundaries layer and objects on obstacle layer. If I untake this checkbox, asteroid will no longer
collide with level boundaries. I test it by playing the game. I push the asteroid and it
flies through the boundary, but player will collide with it. Perfect. So this is how you can associate
objects with layers, and you can be very specific about what collides
with what in your game. Inside assets scripts, I create folder and I will
call it obstacles. Create new mono behavior script. I call it asteroid. I attach this script to
asteroid Game Object. I should clean this up,
create empty game object. I call it managers. I will drag level boundaries
under managers, also event system, main camera, and global light to the. I put managers up here and I collapse it to keep my
scene a bit cleaner. Okay, I have
asteroid game object with asteroid script attached. Asteroid has this sprite
renderer component. I want to access this field from my script to give each
asteroid a random image. If you remember, we have multiple different
asteroid frames. I can delete all of this. I
define a private variable. Type is sprite renderer. Name is sprite renderer. Inside start method,
I take this variable, and it will be equal to get component sprite
renderer like this. From this script, I'm accessing this sprite
renderer component. Another private field, but I want it to be visible
in Unity editor. So serialized field
type will be Sprite, and it will be an array. I call it, for example, sprites. Save that and in unity, we have these five
different asteroid frames. Here is the Sprites array we
just created in our script. So I click plus five times, and I drag five frames in here. A now when asteroid game object gets created and the
start method runs, we want to randomly assign it to one of these five images, sprite renderer dot Sprite. We are setting value
of this field, and it's equal to this sprites
array at some index zero, one, two, three, or four. I want to randomize it, so I say choose one of
these images at random. Index in Sprite array will
be random range between zero and however many elements are there in this
array currently. So sprites dot ngth. If this index value is
zero, we get this frame. If it's three, we
get this frame. I go to assets and I
create a new folder. I will call it prefabs. Prefabs are preconfigured
prefabricated game objects that we can
reuse over and over. I drag asteroid from the
scene into this folder. Notice it will turn
blue indicating that this object comes
from a prefab now. I can delete it here because we have our prefab now as an asset. I can just drag it out like this into the game scene as
many times as I want. All of these are already
preconfigured with our custom script that will randomize their image
when the game starts. So let's start and play. And yes, you can see each one gets a random image
assigned from all of these. All the asteroids are already preconfigured with
colliders and rigid bodies. So I can push them around,
and it will all work. This is the power of prefabs. I want to give asteroids a little push using the
built in physics system, so I will need access to this rigid body two D
component from my script. Private rigid body
two D. I call it RB. RB is equal to get
component rigid body two D. Just one push at the start. Linear velocity will be
minus one horizontally. Or maybe temporary
variable I call push X. It will be a random value
between minus one and zero, and push Y will
be a random value between minus one and
plus one like this. Okay, I use push x
here and push Y here. This push will happen only once. We are inside start method, but it will be enough to create a little bit of movement and maybe even some collisions and some spinning asteroids
after they collide. I play, and yes, that worked. Maybe I don't want them
to move endlessly. Let's introduce some
linear dumping to make them slow down and
eventually stop again. Okay, yes, this is good. We have to keep in mind that this is an endlessly
scrolling game. So as the background floats by, we want all game objects to
float from right to left as well to create an illusion of player
flying through space. Inside Update method, I create a float variable I call move X, and I set it to F. Update
method runs over and over. As the method runs, keep
decreasing transform position of the asteroid by minus move
X minus two horizontally. If I save and play, this will be way too fast. We are inside update
method that can run at different speeds
on different devices. To make sure asteroids move at the same speed on any device, we need to account
for time passing by. Time to Delta time is the
amount of seconds that passed between this call and
the previous call of update. Faster devices will serve
more frames per second. Delta time value will be lower, so they will move
by smaller steps. Slower devices will have more
time between update calls. So objects will move
by larger steps, and result is that
objects move at the same speed regardless
of frame rate. When making games,
it's important that we time everything
based on real time, not based on frame rate. This way, we can make
sure that events in our game happen at the
same speed on any device. Asteroids will just move
endlessly to the left. We don't need them once they are not within the
visible game area. So when asteroids move
off screen all the way, I want to destroy them. So as the game runs and
transform position updates, we check if transform position
X is less than minus ten, and if so, we destroy the asteroid game object that
this script is attached to. When I play, I can see the asteroids being destroyed
when they reach this area. Just to make sure
they are completely off screen when
they get destroyed, I set this value to -11 units. I right click and I
create empty game object. I call it game manager. I reset its transform. I go to Assets scripts, and I create a new
mono behavior script. I call it game manager. I attach this game
manager script to Game Manager game
object as a component. I open it in my code editor, and I delete all of this. I want this game
manager script to be accessible from other
scripts in this project. So I will do the same thing we did here with UI controller. I will turn it into
public static instance. I can copy the Awake method from here because it
will be the same. Check if this is the only
instance of game manager. If not, destroy it. I keep a public float variable
I call world speed here. How fast is the
world scrolling by? Save that and in unity,
I set it to two. Now inside asteroid, I can replace this hard coded value of two with game manager
dot instance WorldSpeed. World speed will affect many other floating objects
and enemies in our game. So we want that value to
come from the same source. Save that and look
what happens when I set it to minus
two and I play. While I play, I can change
this value to five, making them move faster in the opposite direction or minus five to make them
go the other way again. Okay, this works really well. I exit play mode and I set
wall speed two plus two. If I play, asteroids
get randomized sprites, they float to the left and they get destroyed once off screen. One more thing here, if
I enter boost speed, the stars fly by faster, but asteroids don't react. I put world speed in brackets, and I multiply it Tam's player controller dot
instance dot Boost. Maybe I will do this inside
game manager instead later. But for now, let's see, save that and play. And when I boost, asteroids speed up as well and
react to it now. Perfect. Now I need a system to spawn game objects such
as these asteroids. Let me show you a simple but very powerful and
versatile solution that can be reused to also spawn game objects
like obstacles, enemies, and event triggers
in predefined timed wave.
7. Object Spawner: All of these are an
instance of a prefab. Let's delete them and create copies of our prefab
with code instead. I go to assets scripts and I create a new
mono behavior script. I call, for example,
Object spawner. I create an empty game object, and I also call it
Object spawner. I attach Object spawner
script as a component. I delete all of
this and I declare a public variable type
will be Unity game object, and I will call it Bree fab. What game object
we want to spawn? Another variable,
float that I call spawn timer and another
float I call spawn interval. So these variables define what do we want to spawn
and how often. I see the fields in
unity editor here. I go to Assets briefaps and I drag my asteroid briefap
into this field. Spawn timer will count and reset each time it
spawns a new one, and the interval at which I want to spawn the
asteroid will be, for example, 1 second. I reset transform to 000. I create a private method. Return type is void, meaning it returns nothing, and I call it, for
example, spawn object. In here, I want to take this game object prefab,
which in this case, is an asteroid, and
I want to create a new copy of it and place
it in the game scene. We can do that using built
in instantiate method. I pass it the object I
want to create a copy of, in this case, prefab
from line five. Then I need to tell it where in the game scene
I want to put it. I want to place it at the same position
as the game object. This script is attached to. So this position
of 000 right now, and it also needs rotation. So I pass it the
rotation of this object, which is also 000 right now. So instantiate method
will create a copy of a game object and it will
place it in the scene. We have to tell
it what to spawn, where and what is its rotation. Whenever we call this custom
spawn object function, it will create a new asteroid. Inside update, which runs
over and over for each frame, we create some kind of countdown
system to trigger this. I take span timer from line
six and for every frame, I accumulate real time that
is passing by in seconds. If spawn timer is more or equal to span interval
from line seven, which we set to 1 second, we set spun timer back to zero so that it can
count again towards the next object to
spawn and we call spun object to create a duplicate of our
asteroid brief up. Count real time. When
we reach 1 second, reset spawn and count again. Save that and play. We have asteroid coming
out every 1 second. We know that they get starting position and rotation
from the game object. This script is attached
to this object spawner. If I take move tool and
I put it somewhere else, I play, and asteroids will
come from this point, perfect. If I use our super speed move, there will be
bigger gaps between asteroids because they
always spawn every 1 second. But in our game, that doesn't
really make sense because we are trying to
create an illusion of traveling through space. We need to make sure
that if we speed up, the rate at which we spawn asteroids also
adjusts accordingly. On player controller,
we have this boost, which can be either one or five. Inside Object spawner I
use that multiplier here. Either it's one, anything
times one is the same value, and if we are boosting,
this will be five. Delta time will be increasing
five times faster, reaching spawn interval
breakpoint faster, and speeding up the
entire spawning process. I save that and I
play to test this. I fly at normal speed, and if I press a space bar
and enter super speed, asteroids spawn
five times faster. We can use this same
object spawner to spawn enemies or anything else we
might need in our level. That can include a trigger
that when it spawns, when the player
collides with it, it will mark this
level as complete. We can basically spawn
anything that can be a prefab, which makes this simple
technique incredibly powerful. But let's take this step by step to make sure we understand everything and we are able to reuse these skills
in other projects. Asteroids now spawn
from a single point. From wherever in the game scene, we place the parent object
spawner game object. Instead, I want to create an area here on the right
side of the screen, and I want each asteroid to randomly spawn
somewhere in that area. I will create two
more game objects parented under object
spawner like this, minimum position and
maximum position. I reset position of
the parent to 000. I move Min position game
object somewhere here. Maybe 11 units to the right, four units up and
max position will be 11 units to the right as well
and minus five units down. Now I want to basically create an imaginary line between Min pose and Max pose game objects, and I want each
asteroid to spawn from a random position
along this line. So position of Min pose and Max pose will define
the spawn area. I will need
references to them so that I can access
their position values. Serialized field,
private type is transform because I want
their X and Y position, and I call them Min
pos and Max pose. Save that. In unity, I drag Min post game object to the field here and
Max pose here. Now we have these
references in our script to transform position of Min Post
and Max pose game objects. I create a new customer method. It will be private. Return
type will be vector two, and I call it, for example,
random spawn point. I define a temporary
helper variable. Type is vector two, and I call it spawn point. The horizontal component of spawn point will be horizontal position of
Min pose game object. We could also use Max pose game object here because both of them have the same horizontal
position of plus 11 units, and now we will need access to the vertical
position of Min pose and vertical position
of max pose, which is minus five, and we randomize the
value within this range. So the vertical
component will be a random range between
the vertical position of Min pose game object and the vertical position of
max pose game object. Now we make this function
return spawn point, which is in vector two format. We have this random
spawn point function that returns vector two, and here inside span object, this parameter, which determines
where in the game world, we place the new object
also expects vector two. I simply call this
method here like this. Every time we call spawn object, we randomize position based
on where we placed Min pose and Max pose in the game world, and
we use that here. Save that and play. And this is how you can create a larger randomized spawn area. Asteroids now appear
randomly along this line. This is probably the
simplest implementation of object spawning system. We can expand on this
and optimize it later. Let's see. For now, I'm very happy with
what we've got. The most obvious
improvement here would be to turn this into
an object pool. Instead of constantly creating and destroying the asteroids, we create a reusable pool
of asteroid game objects and we activate and
deactivate them when needed, reusing the same
objects over and over. So far, we are spawning
only asteroids. Let's create another
game object and plug that into our
object spawner. You can download my Space whales spreadsheet in the
description down below. I animated and rigged this animation myself
using free software called Dragon Bones and I
exported that in the form of a spreadsheet that can
be easily used in unity. All the art assets
I'm giving you in this episode are copyright free, so feel free to use
them however you want. The image is pretty large
because I wanted slow moving detailed animations
and sharp clean art design. You can see the image
is being shrunk down to 2048 pixel width because
of this max size setting, but I want my image
to be able to expand to its full
original size. I check the width of
the actual PNG image, and here in this dropdown, I choose a value that's more
than that and I click Apply. I make sure Sprite
mode is set to multiple and I open
Sprite Editor. I click slide. Type will
be grid by cell size. And the frames in
this spreadsheet are 420 Toms, 285 pixels. I click slice, and
I click Apply. Notice we have four
different whales here, one on each line. If I expand my frames, I can see it's all
sliced and ready. You can choose a different
whale if you want. I will choose this first frame
and with Scene tab open, I drag it into the scene. Unity will automatically
create a game object for it with sprite renderer
component attached. I set sorting layer two objects. I flip it horizontally to face the player and I rename it
to whale one like this. I open asteroid script that we attached to asteroid
prefab game object, and here we have this update
method that handles how the object flows
through space in our game that has
a static camera. Inside scripts
obstacles, I create a new mono behavior
script I call whale. I attach it to Wal
one as a component. I delete the start method code. I go to asteroid script file. I copy this entire update
method, and I paste it here. This calls for inheritance, but let's keep it
simple for now. If I save that and play, Wal will float by same
as the asteroids do. I go to Assets prefabs and I drag and drop
Wal one in here, turning it into a prefab. I can delete it
from the scene now. I can select Object
spawner and I drag Wal one into the
prefab field here. If I play, we are
spawning space whales. That was easy. I
open Animation tab. If you don't have it open, you can go to Window Animation. I drag the prefab out into the scene again and
with Wal one selected, making sure this says to
begin animating Wal one, I click Create Navigate to
Assets Animations folder here, and I will call this
animation Wal one. Safe. I go to Assets art with Space whales
spreadsheet expanded, I select the first frame. For you, this frame
might be different if you decided to use
a different whale. We have four
different ones here. I scroll down to the last
frame and while holding Shift, I click it, selecting
all frames in between. I drag all these frames
on animation timeline. If I press play, I get the
flying or swimming animation. It's too fast, so I
reduce samples to 30. If you don't see samples field, you can click these three
dots and show sample rate. I think 25 is good. I want it to move
slow because this is supposed to be a
huge space creature. I made this animation
on a copy of a prefab, but I want all the
whales made from this prefab to have
this animation, so I need to apply it
to the prefab itself. I find the animator
component and I can see some change was made here
that is not on the prefab. It is only on this single copy. I go up here to overt and we can see that
change to animator. I click Apply A, making
sure the prefab itself and all its future copies will have this animation
we just created. If I go to Assets prefabs, I can drag a few out and play. All space whales now
animate. Perfect. I can delete this. If
I play the game again, let's try to see when
two whales overlap. As the animation runs and we
are swapping sprite frames, Unity is deciding randomly what is drawn in front
and what is behind. This creates this visual buck where the whales are blinking. This can't be fixed by
sorting layers because all of these whales are on
the same sorting layer. We will fix this
by telling Unity specifically the rules
it should use to decide what is drawn in front
and what is drawn behind for objects on
the same sorting layer. I go to assets settings and I find this renderer
to the asset file. Here, we will set transparency
sort mode to custom axis, and we will sort by
the vertical Y axis. This simply means
that game objects on the same layer will be sorted based on their
vertical position. What is higher vertically
will be drawn behind. What is lower will be
drawn in front of it. This rule is useful for the
top down games as well, where you want characters
to walk around objects. But here we use it as a quick and easy way to
prevent blinking spreadsheets. As you can see, now
there's no more blinking because we
defined a very clear rule, telling unity the order
in which it should draw game objects that are
on the same sorting layer. I have object game object here. Currently, we are spawning
always the same object type depending on what prefab we put here inside the prefab field. I want to create a wave system where we can say, for example, spawn 25 asteroids and then
spawn three whales and so on. We will be able to
use this to define and time the flow of our game. I go inside Object
spawner script and I create another
private variable. It will be an integer number with no decimals and I call it, for example, wave number. One more variable and this
one will be type of list. For list to work, we need system collections,
generic name space. Just bear with me
here and we will explain everything step by step. It will be a list
of wave objects and I will call that list, for example, waves like this. You can see that wave is underlined because
it doesn't exist. I'm not sure you
know we can do this, but I'm here inside
Object spawn class. I can actually
define another class inside of this. I
will call it wave. This will be a list
of these waves. Rather than having just a
single prefab and spun timer, we will have a separate set of these for each
wave individually, each wave can be different. Now, we broke this code, so I will comment it out
for a minute. This is good. Each wave will also need integer that defines the number
of objects per wave. And one more called
spawn Object count. This variable will keep
track of how many of these objects we already spawned and how many
we have yet to go. If I save that and I go back to Unity, we see wave number, but this wave class is not
accessible from the editor, so we don't see
our list of waves from line ten in
the editor at all. I will make the class
visible in Unity editor by adding system
serializable attribute. I save that, and now in unity, I have this waves list. I click Plus and it will add the first element with
an index of zero. If I open this, I see
all the properties. I can click Plus again
and add another wave. I go to prefabs, and let's say that the first
wave will be asteroids. I drag asteroid prefab here. We want to spawn a new asteroid every 1 second until
we reach five. When we spawn five of them, it will automatically move on and start spawning
the next wave. The second wave
will spawn well one prefab every 1.5 seconds, and it will spawn three
of them in total. If I put my script
side by side to this, we can see this
list of waves here. Each time I add a new
wave and it will take this class and create fields based on the
blueprint we defined here. These are fields
for the first wave. These are fields for
the second wave. Down here in update method, depending on the
currently active wave, we will either be accessing this spon timer or
this spon timer. I comment this and
to access spontr, I take waves from line ten, this list, and I access spun timer based on the current
wave number as an index. If wave number is zero, we are accessing this one. If wave number is one, we are accessing waves index one dot SponTr which is this one. We will be accessing
these elements depending on what is the
currently active wave. Waves list at an
index of wave number. So I will do the same thing
for the other spa timer here. Same thing for spawn
interval here, and for another spon timer here. I move this. I remove
this closing command. I have to do another
thing for this prefab we defined on line 14. Waves at the index of wave number dot Prefab is
the object we want to spawn. If wave number is zero, it will be this asteroid. If wave number is one, we will be spawning this whale. If I save and I play, we are spawning asteroids. If I manually change
wave number to one, we will start
spawning the whales. Wave number defines the index in this list from which we
are pulling the wave data. Let's automate this. Every
time we spawn an object, we access spawned
object count of the active wave and we
increase it by one. We call this spawn object here. After that, I check if
span object count of this active wave is more
than total objects per wave. If it is, we increase
wave number by one, which is all we need to do to move from this block zero to this block with an index of one and the game will
start spawning that wave. In our case, the whales. I save that and I
play to test it. We are spawning asteroids
every 1 second, and we are counting them. As soon as spawned
object count is more or equal to objects per wave,
when it reached five, wave number will
increase by one, and that automatically made our code start pulling data
from this other block, wave with an index of one. When we reach three here, wave number increased to two, and we get an error because
we have no more waves here. There's no element with an
index of two inside this list. In my code, every time I
increase wave number by one, I check if wave number is
more than waves count. If this was an array, we would use length here, but this is a list,
so I use count. If wave number is more than the number of elements we
have in the waves list, reset wave number back to zero and we start spawning
the asteroid again. We start from the wave one. So if I play to test it, we are spawning asteroids. We see spawn timer count to
one and reset over and over. When we reach five
spawned objects, wave number increases to
one and we start spawning a whale every 1.5 seconds
until we spawn three. And now we are spawning asteroid and wale at the same
time for some reason. It's because when we go
back to wave zero here, spawn object count is
still more than five, so it immediately jumps to the other wave where spawn objects are also
more than three. So we just get both waves at
the same time over and over. If you look at what
wave number is doing. When spawn object count is
more or equal to objects per wave and we are
ready to increase wave number and jump
to the next wave, before we do that, we reset
span object count to zero so that it's ready for another cycle whenever
this wave comes up again. Save that and play. We spawn asteroids, three, four, five, we spawn whales, two, three, back to wave one, and we spawn asteroids and the cycle will
endlessly repeat. I can add another wave. We will create more
object types later, but for now, let's say two asteroids that
spawn very fast. And another wave with a single whale that takes
5 seconds to spawn. We can use this system to craft
and time object spawning, and this is extremely
flexible and it will allow us to implement many
different game ideas. Basically, we are laying out
the timeline of our game. We are mapping out
the level here. We are spawning only asteroids
and space wells for now, but we can spawn any type of game object that can be
made into a prefab here. I will explore this deeper
later in the class. If you have any questions,
let me know in the comments. I exit play mode for now and
I set this to two waves. I.
8. Player Health and Collisions: Creating player health bar
will be easy because I can simply duplicate the energy
slider we already created. I copy and paste it, making sure it's parented under UI Canvas. I rename it to Health slider. I expand it, and I renamed
this to Health text. This text field will say
health as a placeholder value. Fill area and fill color
will be red 25500. I select this main Health
slider game object, and I click here. And under anchor presets, I hold down out to
also set position, and I select the
top right corner. Position X will be -50 and
position Y will be -25. Now I need a reference to this new health slider
from UI Controller script, same as we did with
Energy slider before. I copy these two lines of code. I rename this to Health slider and this one to HealthText. Down here, I copy Update
Energy slider function. I rename it to Update
Health slider. It will take the current and the max health
of the player. Here I replace energy with
health in these five places. Set the current slide value
to players current health, set slider max value
to players Max health, and healthtext will display the current health
slash the Max Health. We are calling Update
Energy slider. We created earlier in two places here in
the start method, and here in update. It works correctly in update, but it doesn't work inside start because right
now we have to call this function twice until slider displays
the values correctly. It's simple to fix this. I have to make sure I set
the max value of the slider first and only then we
set the current value. And I do the same thing
for the health slider, max value first, then
the current value. Same as we hold
values related to energy here, I copy these. We will have a value for
health and for max health. When Start method runs, we set players current health
to Max health and we update health slider Pacinant health and max health as parameters. We also want to call it every time player takes
damage or heals. Let's implement
collision detection between player and asteroids. In unity, I go to
Assets prefabs. I select asteroid prefab, and in this tag drop
down, I add TAG. I click plus here, and my new custom tag will be
called obstacle. Save that. Inside player controller script, I declare a new function, private void and built in
on collision enter two D. This function gets this automatic parameter
of collision two D type, and I name it, for
example, collision. If player collides
with something, this collision variable will
store a reference to it. So if the game object player collided with has
a tag of obstacle, this has to be spelled exactly
like I spelled it here. If we collide with an obstacle,
we will do something. In unity, prefabs folder, asteroids selected,
and tags drop down. We have some default
predefined tags, and we also have our
custom obstacle tag we created. So I select it. We are on the prefab, so all asteroids will now
be tagged as obstacles. Back in my code, if player
collides with an object, tagged as an obstacle, we call take damage function
and we pass it one damage. We haven't defined
that function yet. I will do it down here.
Private void, take damage. It will take one parameter of integer type, I call damage. When player takes damage, we take player health and we reduce it by the amount
of damage received. And we will also call Update
Health slider method, passing it the updated
health and Max health. And as we just did, if player collides with an asteroid
or some other obstacle, we call take damage and
we pass it one damage. So inside UI Controller, we have references to the
health slider and health text. We created Update
Health slider function, which will update that
slider and that text. We have variables for players current health and
player's Max health. When player gets created,
when the game starts, we set health to
Max health and we update Health slider to
display that change. And every time player
collides with an obstacle, player will take damage, and we also update Health
slider to display that. I save that and I
select UI Canvas. The script needs
these two references, so I pass it Health slider here. I expand this and I pass
it health text from here. If we play, health
slider displays zero, so I exit play mode, player selected, and we have
to give Max health a value. Let's do five for now. We might introduce
some power ups later that will increase
players MAX health. I play and player gets
five out of five lives. We are using on collision
enter function, so every time we
touch an asteroid, we will lose one live. Health slider is correctly
displaying these changes. If we get down to zero health, player is still flying
as normal because we didn't write any logic
to handle that scenario. So in my code,
inside take damage. When player takes damage, we will check if health
is less or equal to zero. If it is, we first set boost to one F in case the collision
happened in super speed, and we take player game
object and we deactivate it. Set active falls, which is the same thing as
unchecking this checkbox. Save that and play. I collide with
asteroids losing lives, and when we lose all lives,
player will disappear. The world is still scrolling by, but player is now great
out here and deactivated. Let's do this step by step. First of all, I want
the player to have some animation when
it gets destroyed. I'm giving you a really nice explosion animation spreadsheet. I called it boom one. You can download it in the
resources section below. I drag and drop it
inside my art folder. I increase MAC size
value here and I apply. Sprite mode is set to multiple. I open Sprite Editor. Slice will be grid by cell size, and frames are 300 times
300 Slice and apply. Unity has a robust built in particle system
we can use here, and we will use it later
for some smooth effects. But in this case, I
want to show you how to animate effects
from a sprite sheet. I expand my Boom
one spread sheet and I drag one of the
frames out into the scene. Unity creates a
game object for it, and I put it on the same
sorting layer as the player. I rename it to boom one. I open animation Window. If you don't have it open, you can open it by going to
Window Animation Animation. With boom one,
gameObject selected, making sure this says to begin animating boom one,
I click Create. Inside Assets Animations,
I will name this boom one. I click the first frame, I go down and I shift
click the last frame, selecting all the
frames in between. With Boom one animation here, I drag all the frames
on the timeline. If I play, we can see
the effect in action. If you don't see samples, you can click these three dots
and tick Show sample rate. I slow the animation down
by setting this to 25. I play it. Yes, this is good. The animation loops.
In this case, we want the animation
to play only once. I go to Animations folder
and Inspector tab here. I select this boom one dot
Nim file i antique Loop time. I go to prefabs folder and
I drag boom one in here, turning it into a prefab. I will ignore this
warning message because it makes no sense. Animations will still work fine, and I can easily
test it by dragging multiple copies of
my prefab like this. And if I enter play mode, all animations will
play and work fine. As you can see, these boom one game
objects are still here, even when the animation
finished playing. I want them to self distract after they
played all the frames. I delete all of these, and I go to Assets scripts and I create
a folder I call effects. Inside, I create a new
mono behavior script. I call it boom. We will share and
reuse this file for other spreadsheets
with explosions if I decide to
implement more later. I delete all of this code. I will create a reference
to the animator component. Inside start method, we call Get component
animator like this. Now that we have a
reference to the animator, we can check if all frames of the current animation
finished playing, and then we can
destroy the object. Actually, before we do that, let's just simply
call destroy here. We want to destroy
the game object. This script is attached
to, but not immediately. So I pass it a
delay of 3 seconds. I want to attach this
script to Boom one prefab here inside
my prefabs folder. It's a bit tricky, so I go
back to boom one prefab and I press this little
lock to make sure Inspector tab stays
on this object. Now I go to scripts effects, and I drag Boom script on
the prefab as a component. I will ignore this error. This animated reference is empty because we
will be getting it automatically at runtime with G component we put
inside start method. We define that logic
here on line nine. I can also hide this
from Unity Editor. It doesn't need to
be visible there. We say destroy this game
object after 3 seconds. Save that. And in Unity, I drag out a few of
these into the scene. We see the objects here. I play, one, two, three, and they get
destroyed after 3 seconds. That works, but we don't really know how long the
animation takes, and it's not exactly 3 seconds. So here as the delay, instead, I will take the
animator component and I call get current
animator state info. We created only one animation
with the explosion frames. So we know there is
only one animator state this object can
have at index zero, and we want its length. Simply said destroy
the game object. This script is attached to after its current animation finished
playing all its frames. Save that and I play. They animate, and immediately after the animation is over, all three game objects
get destroyed. I will reset transform to 000. I want this blue
explosion effect to play when player
gets destroyed. So inside player
controller script, I create another variable type
will be Unity game object, and I call it, for
example, destroy effect. When player takes damage
and loses all health, we deactivate the player and we create a copy of
this destroy effect. We know that to create a copy
of a game object prefab, we use built in
instantiate method, which takes the object. We want to create a copy of its position and its rotation. So I pass it the position of
player when it was destroyed because we are now inside player controller script
attached to player game object. Transform dot position here
refers to player game object, and I pass it rotation
of the player, which will be no
rotation. Save that. I delete all these and I unlock this player
selected here, and as destroy effect, which expects a game object, I pass it this boom one prefab. I set player Max health to two
to make it easier to test. I play, and I will collide
with some asteroids. When player loses all health, we have our boom
animation playing. Actually, when player
gets destroyed, let's set boost to zero, which will stop the
movement completely. I play again to test this I collide with an
asteroid and with another one, and the world will
stop scrolling. And also game objects like
asteroids and whales will stop spawning because the spawn timer is tied to player boost speed, which we just set to zero. So spawn timer is not
increasing anymore. Now will be the time when we
display a game over screen. So let's focus on all
of the screens at once. Main menu screen,
game over screen, and level complete screen. I set player Max health to five.
9. Pause Unpause: I right click UI Canvas
and I go to UI and panel. Panel is a UI element, so it needs to be
a child of canvas. Otherwise, it will
not be visible. I will call it pose panel. Color will be 155-15-5255
with 100 opacity. I want it to be
semi transparent. I give it some margins, 50, 150, 50 and 50. I write click pose panel
and UI text text Mesh Pro. I want this text to
be a child under the pose panel like
this. I call it title. I center the text horizontally as well as vertically
within the box. I expand it to make
it really big. Font asset will be the pixel art font we
installed earlier. Font size will be
auto size 72-250. It will scale
between these sizes based on available space. I want to create a
special font style that will be used for
headers in our game. Let's give it a gradient
by ticking this checkbox. Color Mode is vertical gradient. The first color will
be 155, 155, 255. And the second color, I leave it as white. I go down here to the
material settings, and I set outline
color to white. Thickness of 0.05, I will reuse this same
title for other scenes. So let's just drop it
into prefabs folder, so I have it saved as an asset. I have to remember that it will only be visible in
the scene if it's a child of Canvas element like this one is
under UI Canvas. I have this copy of my
title prefab selected here, and I find its text input field. This one will say post. Move tool, and I drag this
upwards pointing arrow, moving it up only vertically without affecting
the horizontal position. I want another element
inside my pose panel, so I right click it here and
I go to UI button TextMshP. I set it to 400
times 100 pixels. Let's give it some graphics. I will link this casual
game buttons asset pack in the description below. It's free. I download it
and I unpack the zip file. It will give me a folder with
all these button shapes. I choose this long
blue one and I drag and drop it
inside my art folder. With the button image selected
inside Inspector tab, I set Sprite mode to
single and I click Apply. I select my button element, and I drag and drop button image into source
image field here. And now we have a nice
button in our game. This button also has a
text element inside, so I set font asset to our
custom pixel art font. Font size will be 60,
font color white. With the button still selected, I find its button component. Here, we can define what the button looks like in
five different states. When it's normal, highlighted, pressed, selected, and disabled. I select highlighted color, and here I can use color picker. I can use this to pick any color that already
exists in the scene. For pressed color,
I will also use a color picker tool and I choose some bluish color and select it will also be
some shade of blue. If I save and play, I can hover over the button and click it and I get
a color highlight. Our game will have a
few different buttons. So let's go to prefabs and
drag the button game object here so we can reuse this button that's
already set up later. I select my existing button. I rename it to resume button. I expand it and it has a
text object inside here. I find its text input field, and this button will be
to unpause the game, so it will say resume. I want to create another button. I can drag and drop our
preconfigured button prefab into the scene. I need to place it as
a child of Canvas. I will drop it on the
pause panel to make sure it's under the
panel as a child object. I move it vertically,
125 pixels. Okay, -125 pixels. I rename it to main menu button. I expand it here, and
inside is the text object. The text on it will
say main menu. I drag out another button
prefab on the pose panel. Vertical position will be -250. I rename it to quid button. On the text object inside, it will say quid game. So if I play now, I can move mouse over the
buttons and they will animate. Now I need to be able to show and hide the pause
panel with code. I will put that logic inside
UI controller script. Public game object variable, I call pause panel. It will be available
all over the code base through this public static
UI controller instance. I go inside game manager and I create a public
function, I call pause. So I want to handle
showing and hiding of this pause panel from
game manager script. I will create a single
function that will both pause and unpause the game. I check if pose panel
is currently hidden. If UI controller
dot instance dot pose panel dot Active
Self is false. If it is currently hidden, I activate it to make it
visible, set active true. Else, meaning when pose
panel is visible, I hide it. Set active false. Back in Unity, UI Canvas selected here. On UI Controller script, we created a public
field for pose panel. So I drag and drop this post
panel game object here. Set active true and
false in our code is the same thing as checking and unchecking this
checkbox here. So finally, I need to call this pose function
from somewhere. Inside game manager, I
create an update method, which will run over and
over for each frame. Inside, I check if Escape key on the
keyboard was pressed. If input dot Get key
down, keycdeEscape. Or let's also bind this
pause function to P key, get key down keycode P, and I also bind it
to a third button, which will be one
of the predefined input events called fire three. Notice that for
fire three event, instead of G key down, I'm using G button down. So when we press
Escape or P or a range of inputs associated with
fire three input event, we will call our
custom pause function, which will pause or
unpause the game. Save that, and if I go to edit project settings,
input manager. See this fire three event here. I save and I play. If I press escape or Pike, pause screen is visible. I want to unpause when
we click resume here. Right now, I can press
escape again while the screen is active and
it will deactivate it. Pressing Escape repeatedly will show and hide the pause screen. Also, you probably notice that the time doesn't stop
when we pause the game, but that's very easy to fix. When we display
pause panel here, we set time dot
timescale to zero. When we hide pose panel, we set time dot time scale
back to one like this. Save that and play. I press Escape key to pause. I press it again to
unpause. This works. I also want this resume button to unpause the
game when clicked. So down here inside
its button component, I find its on click and I
click plus to add an event. I drag and drop game
manager object here because the pause function
I want to call is attached to game
manager script. Here, I select game manager, and I find the pause function. So when we click RezomPton, take game manager script
and call its pause method, which will unpause the game
because Resume button is only visible when pause screen is visible when the
game is paused. I play, I press Escape to pause. Notice that buttons are still animating even when
the game is paused. I click Resume to pause. I pause again, I press
directional arrows and you can see the player is swapping sprites even when
the game is posed. I want to prevent that. Inside player controller script, I will pass these
values to Animator only if time dot time
scale is more than zero. Save that and play. I can still enter
boost when paused. Okay, so actually, I will put all of this code
inside this block. I have to be careful about
brackets when I do this. Run all of this code only when T dot T scale is more than zero. Now I play, and if I pause while the player is in
superspeed, if I unpause, we will be stuck in superspeed because the keyup
event that would trigger exit boost function happened while pause
screen was active. I fix it by calling exit
Boost every time we unpause. I will assume player released the super speed after
they pause the game. They can enter boost immediately again after they unpause. This is a good
solution that will fix the back and not
affect gameplay. There are other ways I
could have dealt with this. Let's see. Maybe I will
revisit this later. Now, if I enter
Superspeed and I pause, player is frozen with
superspeed animation frame, and when I unpause, we automatically exit boost. I tick this check box to
make pause panel visible. We will create main menu later, but now let's implement
quid Game function. Public, so it can be
called from other scripts, and I call it quid Game. In Unity, we can simply call application dot quid like this. This will not actually
quit the game here when I tested
in Unity editor, but if you create a playable
build of your game, it will close the
application down. I save my changes. I select quid button, find its on click
Settings and plus. I drag Game Manager
Game Object in here. And here I find the quid
game function we just wrote. As we said, we can't actually
test it here in unity. We will test the
quid button later when we build a playable
version of this game.
10. Main Menu Screen: C. And finally, we created a main menu button. We will create main menu in a completely different way than how we did
the pause screen. We will build it as
a separate scene. I go to Assets scenes. So far, we have only this
level one scene here. I right click
Create scene scene. I'll call it main menu. By making the main
menu its own scene, we can keep it separate from the rest of the
gameplay elements. It's more modular and also
easier to deal with because we don't have to worry about any of the gameplay
related logic here. We can get very creative. Let me show you some of the
cool things we can do here. I select this background
game object and I copy it. I double click the scene file to enter the new
main menu scene, and I paste the
background in here. I want to have this seamless parallax
background animation even in our menus. If I play, I will get an error. I can view it in console,
and if I click it, Unity will open my code
editor and I can see that this value is not defined
because in the main menu scene, we don't have access to player game object and it's
player controller script. Again, there are many
ways to deal with it. I make sure I'm
not in play mode. I will go to Layer
one Game Object. I find its parallax
background script component, and I click here to
remove component. I also remove it from Layer two, Layer three, and layer four. Now, if I play,
nothing is moving. I go to Assets scripts and I create a separate
folder I call menus. Inside, I create a new
monobhavior script I call Manu parallax. I open it and delete
all this code. I go to Parallax
Background script file, I copy all of this code, and I based it here. Inside Menu Parallax script, I remove this reference to
player controller like this. Save that, and for now, it will be the only change. I select layer one game object, and I attach menu parallax
script as a component. I leave move speed at zero. I also attach it to layer two, and here, move speed
will be minus one. I attach it to layer three. Move speed is minus two. I attach it to layer four, and speed will be minus three. Now we have a main menu scene
with parallax space effect. I have this title prefab. I want to use it here to
display the name of my game. But if I just drop it
in the scene like this, it will not render
UI elements in Unity need to be
children of Canvas. I right click UI Canvas. I will call it menu
Canvas and I put the title here with Canvas
as its parent game object. Menu Canvas is large, so I set render mode to
screen space camera. I drag main camera into
render camera field. When I set sorting layer
on the canvas to UI, title is finally visible. I set its position to 00. Move tool, and I move it up. I set the text to space. I drag another title out here, parent it under Menu Canvas. This line will say Quest. The first title, Max
font size is 300. The second title font
size will be 310. I expand this a little, 320, 315 font size, and
with Move tool, I position it vertically. This will be the title of
my game in main menu scene. I also take this
button brief app and I drag it into the scene as
a child of menu Canvas. Vertical position will be -100. Text on the button
will say Mu game. I rename it to New Game button. I drag another button prefab. I make sure it's parented
under Menu Canvas. Vertical position will be -230. I rename it to quid Game button. The text will say quid game. If I play, this will be
my animated menu scene. We will add a few
more things later. This event system object here, that Unity created for
us is responsible for processing and handling
events in a Unity scene. All I want to do here
is to make sure that the game supports
controls without a mouse, that the game and the menus can be fully controlled by using only a keyboard or only an
Xbox controller, for example. If we are in the scene
playing the game and all we have is a
controller and no mouse, we need to make sure one of these buttons is already
selected so that we can use Control stick or directional arrow keys to
switch between buttons. It's very simple. I just drag New Game button into first
selected field here. So when we enter the
scene while playing, new game button is
already selected, allowing us to move between
buttons with no mouse. I also want to call
quid game function from main menu scene, but this game manager
script is too involved with objects that only exist
inside Level one scene. To make things simpler, I will create a separate
script to handle menu buttons. I go to scripts, menus, and I create mono
behavior script. I call it menu manager. I remove all of this code. I go to game manager. I copy Quid game function,
and I paste it here. It will work the same. I also create another public
method I call New Game. I go to assets scenes. Basically, when I call
New Game from main menu, we want to load this
level one scene. Lucky for us,
transitioning between scenes with code is
very simple in Unity. I say scene manager like
this, and for this to work, we need Unity Engine scene
management name space up here, and I call load scene. I passed the string with the exact spelling of how I called the scene
we want to load. In this case, level one. It needs to be spelled the same as the name of this
level one scene file. So when we call New Game, we take scene Manager and we load scene called
Level one. Save that. New Game button selected. I find it's on click plus, actually I need my Menu
manager script in the scene. So I create an
empty game object. I rename it to Menu Manager. I go to scripts Menus, and I attach Menu
Manager script as a component on Menu
Manager Game Object. On this script, we have new
game and quid game functions. We will need them now.
New Game button onclick. I already pressed plus before, so I have this field, and I drag and drop Menu Manager
Game Object in here. And function I want to run is here under Menu
Manager New Game. Quid Game button selected
plus to add onclick event, Menu manager here, and here I find Quid Game
function we wrote. Okay, so now if I play
and I click New Game, it should load level one scene. Perfect. That worked. I exit Play Mode. I open Level one scene. I find pose panel
and I deactivate it. It needs to be
invisible at first. Now that we have main
menu scene ready, on pause panel, we
have this menu button. So when clicked, I want
to load main menu scene. I will handle that logic
in game manager script. Public void, go to Main Menu. I call scene manager. I make sure we have Unity Engine scene Management
name space up here, and down here, I call Load scene and pass it the name of the
scene I want to load. In this case, main Menu
spelled exactly the same as the name I gave
to the scene file. Safe and under Post panel main Menu button, onclick event, ICliPlus I drag and
drop game manager, and here I select go
to Main Menu function. Now if I play, Escape
to pause the game, and here I click on
Main Menu button. I will get an error. Scene couldn't be
loaded because it hasn't been added to
the built settings. Whenever we create a new scene, we have to go to build
profiles and find scene list. The scene needs to be here before I can load it
with scene manager. I drag and drop main menu
scene in the scene list. I put it uptop as the first
scene with an index of zero. Now I can play again. I press Escape to
pause the game, and now when I click
Main Menu button, main menu scene loads. But you will notice that the
background is not animated. It's because we entered from the pause screen when time
scale was set to zero. I exit play Mode,
menu manager script. Whenever start method runs, whenever we load
main menu scene, set time dot Time scale 21. Make sure it's not
posed. I play. Escape key to pause, main menu, scene loads, and background is animating. Now I can click New Game and we load Level one scene
from the beginning. So this is how you can load different scenes in unity
and jump between them.
11. Game Over Screen: I will duplicate main menu
scene and I will rename it to GameOver. I open it. Title I will say game. Title two will say O. Title one will be
320 BixelsFont size. I expand this box, 310, and I move it down. Now, when do we load this scene? I save my changes and I
go to level one scene. I open game manager script. Down here, I create public
void GameOver function. Inside, I say scene manager,
load scene GameOver. Inside player controller script, if player loses all health, we do all these things, and then I take game
manager instance, and I call public Game Over
function we just defined. Save that in unity, I set player Max
Health to three. GameOver is a new scene, so I go to file, build profiles and scene list, and I drag GameOver scene here. I enter play mode. I hit asteroids and
we get game overseen. As you can see, it
loads instantly. We can't even see
the animation we created for when the
player gets destroyed, it doesn't have
enough time to play. Inside game manager script, instead of loading scene
instantly like this, I will give it a delay. I will create another function I call Show game over screen. But return type will be
something called I numerator. We need to be using
system collections up here for this work. I numerator is a special type of method we use to
implement co routines. When we call Show
game over screen, I wanted to wait for 3 seconds. I do that by saying
yield, return new, wait for seconds, three F.
Only after 3 seconds passed, we load the game over scene. Like this, coroutine
is a method that can pause its execution and
resume at a later point, making it very useful for situations where you
need to delay an action. So by saying start coroutine, show game over screen, I will call this eye enumerator, which will wait for 3 seconds and only then it will
load game over scene. This is an extremely common
thing to do in unity. You will see it over and over many times in many projects. If you've never seen it before, I'm sure it looks strange, but don't worry
about it too much. It will make more sense
if you use it more often. I save changes and I play. I hit asteroids until
player gets destroyed. 3 seconds count down, so the blue explosion animation will have enough time to play, and only after 3 seconds, we get the game over screen. This is how you can delay
something in unity. There are many more use
cases for this technique. We will explore more of it soon.
12. Particle Smoke: So. No. You can download High
Resolution player image in the resources section below. I drop it into my
assets art folder and I set it's Sprite
mode to single. This is just one frame. I open GameOver screen, and I drop the image in. Sorting layer will be player. I set scale to 0.50 0.5, or maybe a little bit more. 0.70 0.7, like this. I rename it to player Game
Over I position it here. I select Rotate tool, and I tweak the values here
to rotate it to an angle. This is a game over screen, so player spaceship is damaged. Let's add a smoke effect. Unity has a built in particle system that's
quick and easy to use. I right click Effects
Particle system. It will create a
particle system object, and we can see it's
already showing us what the particles look
like when they animate. I rename it to particle smoke. I will tweak just a
few simple things to turn this into
a smoke effect. I reset transform to
center it in the middle. If you want, this
is a robust system. You can create some incredibly impressive things with this. I said start lifetime to two. Inside renderer settings, I set sorting layer ID to player, order in layer minus
one behind the player. You don't have to follow
this part exactly. Feel free to play
with the values and create your own unique
particle system if you want. I will change texture to default particle material or default particle system.
They seem similar. Velocity over lifetime. I want them to flow to the left because the background will
be animating behind it, maybe minus two in this field. I set color to black here. Start size will be five to
make it look more like smoke. Size over lifetime. I click here and I have some predefined options
at the bottom now. I want them to shrink slowly
until they disappear. You can also add some noise, for example, to make
it more chaotic. If you play with these values, you can achieve some
nice motion patterns. I go to shape and
I set scale 20.2. I set shape to sphere, or circle would also work here. With Move tool, I place
it under the player to make it look like smoke is
coming from the crashed ship. I want them to fly
to the left to match the background as if player is floating to the
right through space. Start speed two,
start size three, four, I move it a bit here. Okay, feel free to change this in any way you want.
Make it your own. Once you are happy
with it, we go to Assets Brief Abs and
I drag it in here. We can reuse it
later when we need black smoke coming
out of something. So this will be my game over screen, but I'm not done yet. We will improve this and add more things as we flesh
the game out further. If I press new game, I can play. I hit asteroids,
I crash my ship, and I get game over
screen. Perfect. I save my changes, and
I open main menu scene. I drag large player image
into main menu scene. Scale maybe 0.60 0.6. I make these numbers nicer minus two vertical position plus five units
horizontal position. I rename it to player menu. Sorting layer will be
player Animation tab, which as you know,
can also be opened through Window
animation animation. Player menu game
object selected here, making sure this line says to begin animating player menu, and I click Create. I navigate to Assets
animations folder, and I will call this
player menu Enter. With player Menu
Enter selected here, I move to the end of the
timeline somewhere here. I click this record button, and I want the final
animation keyframe to be where the
player is right now. This will be the end
of the animation. So I right click position, and I click at Key. We can see that
animation keyframe here. I move the timeline
all the way to the beginning and the
starting position for this animation will be minus eight -11 units horizontally. I check here if the keyframe was captured and I press Play. We have a simple animation of player entering the menu screen. I can move the last keyframe until I'm happy with
the movement speed. I go to Animations. I find player Menu Enter, and I want the animation to play only once when menu scene loads. So I antique loop
time checkbox here. We will reuse this player
animation in other places, so I will drop it into
my prefabs folder. I might organize
the prefabs into subfolders later.
It's fine for now.
13. Mission Complete Screen: God. I duplicate game overseen. I rename it to
level one complete. We could create a template
that we can use for every level complete
screen where we just swap values based
on the current data. But I want to make a very
custom screen right now. That reflects the
successful result of a specific mission we will
have in the first level. We are on a rescue
mission to find and repair a damaged
space whale. If we succeed, I want the repaired whale to fly next to us on the
mission complete screen. We could, for example,
in Level two, turn that whale into a special ability
that the player has. Doing a reusable
mission complete screen is a bit more advanced, so let's keep this beginner
friendly and simple for now. I save changes and I open
Level one complete scene. I delete player game over image and I delete the
particle smoke effect. Instead, I will drag player
menu object from prefabs. That will just
animate the player floating into the scene
when this screen appears. Title one will say mission. Title two will say complete. I adjust the font
sizes and positioning. I duplicate new game button. I move it up in the hierarchy. I rename it to
next level button. Text on it will say next level. I select new game button and
position Y will be -230. Quid game button will be -360. Now we can see the
next level button and everything is aligned. For now, I will antique
this interactable checkbox. We have a next
level button here, but I will not do
anything with it for now. If you like this class, I
will make the next level into something cool
like a boss battle, or maybe we are escaping from a monster or we are
collecting a fleet of ships, or I just turn this
into Space Invaders. Not sure. I'm having a lot
of fun with this project. So if I get some good requests for the second and third level, I will record a follow
up class for this. I want the next level to be completely different
to level one. Different gameplay mechanics. We could get very creative here. We will get back to this screen and flesh it out
further in a minute. Before we do that, I save my scene and I go
to level one scene. We need to create some
kind of a condition. What has to happen in level
one scene for the player to successfully
complete the mission and see our new mission
complete screen. We are on a rescue mission. We are trying to find
a damaged space whale that was lost in
an asteroid belt. We will set it up as a trigger, and when we interact with it, it will complete the level and it will load mission
complete screen. Inside prefabs folder,
I duplicate my whale one prefab and I rename
it to lost whale. I double click it to
enter the prefab, and inside sprite renderer
component, I flip it. I want it to float upside down. It's broken and it
needs to be rescued. I also grab particle
smoke effect we created earlier and I drop it here into the hierarchy attached as a child
of lost whale. I reset its transform to 000, and because it's a child object, that will be the
center of the whale. Lost whale is on
objects sorting layer, order in layer zero. So particle smoke down here under renderer
sorting layer will be objects and oder in layer
minus one behind the whale. Okay, lost whale
object selected here, and I add component, capsule collider two D.
Direction will be horizontal. I click here to edit collider, and I change the
size a little bit. I tick this is trigger checkbox. By setting this to trigger, it means the collider will not register collisions
with rigid body, but instead it will send on trigger Enter on trigger exit, and on trigger stay events. We will listen for these
events and we will run some simple custom
code when they fire. Lost whale has this whale
script from before. I will remove this component. I click this arrow to go
back to Level one hierarchy, and I drag and drop Lost
well prefab into the scene. This will be a special type
of an object in our game. When we collide with it, it will trigger mission complete.
14. Win Condition: We need to write a little bit of custom code to handle
this special object. I go to assets scripts, obstacles, and I click
Create Mono Behavior Script. I'll call it Lost
Whale. I open it. First thing is that I want to make it float from
right to left, same as the other
whales and asteroids. So I open whale script. I copy the entire update method, and I paste it inside lost
whale script like this. I could create a
system of inheritance for all these objects
that float in space so that we can have
this update method in one place rather than on
every object separately. Let's keep our code
simple for now. I might refactor this later
in an advanced section. Every object that floats in space in our game
moves from right to left accounting for world speed and for
player boost value, and every object gets destroyed when it moves
off screen to the left. I could also do a reusable pool of objects rather
than destroying them. This is not as urgent as we have only a few
objects per second. But if we decide to create
a bullet hell kind of game, we will need those object
pools for performance reasons. I will use built in
method called on trigger Enter two
D. This method will work because it
trigger check box is checked on lost
whale collider. Argument of collider two D
type called simply collider. If another game object enters
the collision area and if the game object attached to its collider has
a tack of player, we will take scene manager, making sure we are using Unity Engine scene
management up here. We call load scene method, and the scene we want to
load is level one complete. Spelled exactly like we
named the scene file here. If the player finds the
whale and collides with it, whale will be fixed and rescued and Mission
Complete screen will load. Save that player game
object selected, and I give it a player tag. If you don't have the tag here, you can always click
at Tag and create it. Okay. I go to prefabs
and Lost Whale. I click this little log
to keep the inspector on this game object while I
go to scripts obstacles, and I attach lost
whale as a component. I unlock the panel here, back to the main
Level one hierarchy, and I play, and the whale should now float with smoke
coming out of it. Yes, I collide with it
and I get an error, of course, I forgot again. Edit build profiles, scene list, and I drag and drop
level one complete here. Now we can load the scene. So I play and I collide
with lost whale. We get mission complete screen. Awesome. I save
changes to my scene, and I open Level
one complete scene. I go to prefabs and I drop
lost whale into the scene. With the copy of the
prefab selected here, I remove capsule collider component just from
this one copy, not from the prefab itself. I also remove lost whale script. Unity will highlight
these changes as the difference
from the prefab. I also remove particle smoke. I flip it back and
up facing this way. I move it somewhere here, and I want it to be
in front of the text. So sorting layer is UI. We found, rescued and repaired
the damaged space whale, and now it's following us into the next level to help us
in our space adventures. I save the scene,
and the game will start in main menu
scene, so I open it. Actually, I open
Level one scene. We created an object
with a special function. When we find it and
collide with it, it will complete the level
and load the next scene. In our case, level
one complete scene. Earlier, we created this
object spawner where we define what object spawn in the game and the
timeline of the spawns. So what I want for now is to
have a field of asteroids, and at the end, there will be one lost whale that we have
to find as a level objective. I drag lost whale here. We need more asteroids.
Ten for now. After the asteroids
finished spawning, we will set spawn interval to 5 seconds and we will
spawn one lost whale. Let's say player missed
the whale and keeps going. We set this up to
endlessly repeat. So after the whale spawned, if game is still running and mission
hasn't been completed, I click plus to
add another wave. It will just be a
single asteroid with 5 seconds spawn interval. This will create some
space between whale and asteroid belt from both sides and we let the process repeat. Player will go through
the asteroid swarm again and we'll get another opportunity to complete the mission by finding
the lost whale. Now I can delete the
lost whale from here. Let's play the game. Escape
main menu. New game. We need to avoid
these ten asteroids. Final game will have
more than that. And here we find our
mission objective. I collide with the whale and I trigger the next scene,
mission complete. New game. I can also go through the asteroids
and avoid the whale. If the player misses
it for some reason, the whole spawn
cycle will repeat, giving the player
another opportunity. This setup lets you create many different mission
types and objectives. You can reuse the techniques we just learned for many
other situations. I was trying to come up with something simple
and easy to follow. Let me know in the comments,
if you have any questions. We covered so many
fundamental techniques, but there's more that
I want to show you. We have a pretty cool game here, but we can make it
ten times better. I tick this checkbox on post
panel to make it visible. I will change the
background color here actually to
completely transparent. I think this will look better. I deactivated again, UI
Canvas selected here. I already set up Canvas
scaler to make sure that UI displayed correctly
on different devices. I have to do this for
all Canvas elements. I open Level one complete
scene, Menu Canvas selected, Canvas scalar component, and I set UI scale mode to
scale with screen size. Reference resolution will be 1920 times 1080, same as here. I save all changes, and I open main menu scene. Menu kind of selected, UI scale mode set to
scale with screen size, 19 2010 80, save all changes. And I open gameOseen
and one more time, I set UIScale mode here
and reference resolution. While we are in Game Over scene, I created another image
of player spaceship, but a bit more destroyed
after a game over. You can download all
project art assets in the resources section below. I drag the image
into my art folder. I set Sprite mode to
single and apply. Player GameOver selected here, and I replace the sprite
with this new one like this. I play I open Animation tab,
Window animation animation. Player game over selected, and I click Create. Inside Assets animations, my new animation will be
called player Game Over. I click Save player game
over selected here, and I set rotation to 000. Animation tab and I enable
keyframe recording mode. I right click
rotation and add key. I move further
down the timeline, and here I want to rotate
full circle, 360 degrees. I can see the keyframe
was captured here, so I play and the
player is spinning, but there is some easing
in and out going on. I want a linear motion. I right click the first
key, both tangents linear. I do the same on the last
key, both tangents linear. Now, if I play, it's just
continuous linear rotation. I want to slow it down. I can move this keyframe further or I set samples
to a lower value. Just find some value that
looks like player lost the engines and is just
spinning in space. If I play, we also
get the smoke. This rotation is why I chose to have the particle smoke
behind the player, but not as its child
object because I don't want the particle
system itself to rotate. I'm happy with my game
overseen for now, but feel free to get creative and add more
elements if you want. As you can see, we
can add images, animations, particle effects
here, so many possibilities.
15. Music and Sound Effects: C. Music and sound effects can add so much to your project. Let's open Level one scene
and implement some audio. It's very easy as well. I create a new game object, I call Audio Manager. I reset its transform. I find a free sound
effects back. For example, this
one is pretty nice. I link it in the
video description. I download it and inside
my assets folder, I create a new
folder, I call Audio. I unpack the sound effects.
I just download it. They are numbered, so I pick six specific ones,
03 step grass, 04 fire explosion,
13 ice explosion, 39 block, 092, pause, and 098 and pause. You can use any sound
files you want, but I will use these now. To play an audio file
like this in Unity, we can use audio
source component. I can drag and drop all
these into the scene. Unity will detect that
it's sound files, and it will create a game
object for each one, and it will automatically give
it audio source component. With all of these selected, I antique play on awake. I don't want them to
play automatically as soon as the scene loads. I go to Window audio Audiomixer. In unity, Audio Mixer allows us to group sound effects
and for example, control volume separately
for effects and music. So let's do that here. I click plus to create
New Audio Mixer. I'll call it master. Then I go to groups down here
plus to create new group, and I call it effects. Plus to add another group, this one will be for music. I select all of these objects, and with all of them
selected at once, I go to their audio
source component, and here in output field, I will choose an
audio mixer group. All of these are sound effects, so effects group selected. In the mixer here, we can
control the volume of effects separately here without affecting the volume of music. We will add music soon. I will grab all of these, and I parent them under audio Manager Game Object just to keep the scene
hierarchy cleaner. Go to my scripts folder and I create new mono
behavior script. I call it audio manager. I select audio Manager game
object here and I attach audio manager script as a
component. Let's open it. I want this audio manager and all its public properties and methods to be available
from other scripts. I create a public
static instance. We did this before for
the player, for example. I copy this awake
method as always, and I paste it here
to make sure we only have one audio manager
instance in our project. I will create a public
reference for each sound here. Public audio source, I call Ice. I save changes, and on
my audio manager script, I see the field here. It's looking for audio
source component, so this one here. I select audio Manager, and I drag this entire
game object in the field. It will find its audio
source component automatically because I defined
it here as a data type. Now, I create a public
function, I call play sound. It will take the sound we
want to play as a parameter, so type is audio source, and I call it sound. Then I take the sound, we pass to it, and first, I call stop to make sure if we play the same
sound again too fast, before the previous
one finished playing, it plays from the beginning. The stop function stops
audio clip from playing, and the clip plays
from the beginning. Next time we play it. Then we call play like this. Okay, so custom function
I called play sound. I pass it sound I want to play, for example, this
one from line seven. I will first make
sure the sound plays from the beginning and
then it will play it. This function is public, so I call it through the public static instance
of audio manager. When player takes damage
and loses all health, we destroy the player, and we will play the
ICE explosion sound. You can, of course, choose a different sound from
the pack we downloaded. Audiomnager dot
instance dot PlaySD and I play this public
audio source reference. Audio Manager dot
instance dot ICE. I save and play. Let's hit asteroids and when
player loses all lives, ice explosion sound plays. I create more audio
source variables, fire Hit pause pause. I saved changes, and now I
have all these fields here. You can choose your own sounds or you can see the file names and numbers from the sound effect asset pack
I'm using here. Fire will be 04 fire explosion. Hit will be 39 block sound, 092 pause here, and
098 pause here. Inside player controller, I just copy this entire line
that will play a sound. We will play hit like this. When player enters boost, we play fire sound. Save that and we also want to
go to Game Manager script, and when we pause the game, we play pause sound. When we unpause the
game, we play unpause. Let's play and test
pausing and I'm pausing tdsed We are getting hit and player getting
destroyed also has a sound. Adding sounds like
this massively increases atmosphere and
immersion of our game. We are also using audio here for player feedback
and gameplay cues. Sound provides immediate
feedback to our players, and it helps player
to understand what's happening and consequences
of their actions. I also want to play a sound when we interact with buttons. I go to my button brief app and I add a component
called event trigger. I click Add New event type, and I select Pointer Enter. Then I go to resume button here and I find its
event trigger component. And here, inside Pointer
event, I click Plus. I will drag and drop
sound that I want to play when we
hover over a button. I choose 03 step grass sound
from the effects back. You can choose any
sound you want. So I drag it here
and I simply take its audio source component
and I call play. Then I click Add New
event type again, and I want a move event. When we use keyboard or a controller to navigate
between buttons, this event will trigger when we move from button to button. I want the same sound to play. Unity automatically
selected the sound here and it's calling
play on its audio source. Perfect. This is the setup
I want on all my buttons. Main menu button, event
trigger component, pointer event and plus, drag my sound in here and
audio source play here. Add new event type
and move event that already filled its fields
based on the previous event. I do the same thing
for quid button plus drag sound, audiosurce play. Add new event type. Move. Also, I find
my event system object that Unity usually auto
generates in every scene. If you don't have
it in some scenes, you can simply just copy
it from another scene. To make sure we can control
the game without a mouse, I need to have a value here
in first selected field. I want zoom button to be selected in this
scene by default. And that way, we can use
directional Aro keys or gamepad control stick to
move between buttons. If the device we are
playing this game on doesn't have mouse connected. So if I play, you will see
zoom button becomes selected. We get a sound now when we
move from button to button. Adding some basic UI sounds like this makes the game
feel more polished, and at the same time, it gives player confirmation that the interaction is
being recognized. I save everything and
I go to game overseen. I make sure I have a value on event system inside first
selected field here. And on Menu Canvas, New Game button, I want to
add pointer Enter sound. First, I have to make sure the sound is present
in the scene. So I drag it from my
audio folder output, and I choose effects
audio Mixer group. I antique play on
Awake, New Game button. Pointer Enter, event
trigger, and I click Plus. I drag my sound here. We access its audio
source component and call play when
Pointer Enter happens, add new type, move, and I make sure the sound is here and play is being called. Same thing for quid Game Button, plus, drag the sound here. Audio source play here. Add new event type move. Well designed sound track can make our game
more memorable. So let's include some
awesome video game music that will set the
vibe and atmosphere. I recommend this free
nostalgia music pack, Link in the video description. It's got some amazing
retro game music. I download it and I choose
two tracks for this project. Game Boy and Long car
journey and VCR jungle. I drag them into Assets Audio. For menus, I will use this one. I drag it into the scene. Output and I select
music audio mixer group. I keep play on Awake tick
and I also tick Loop. I want this music
to start playing automatically when
we enter the scene, and I want it to loop endlessly. I right click here
and create empty. I will call this object audio. I set it as a parent
of my audio object. I can also reset its
transform just for my OCD. I tested by playing the game. Nice. We get music. It's a bit too loud, so that's why we
created audio mixer, where we can set the volume of music and effects independently. I reduce music volume like this. Now if I play, music volume is much lower and other sound
effects are easier to hear. Okay, so that's game
over scene setup. I save all changes. I copy this audio object. I open Level one complete
scene. And I paste it here. This way, we already have
background music set up here and we have
the sound effect for buttons ready to be used. Event system, and I make sure New Game button is in
first selected field. Menu Canvas, next level button is disabled. Let's
leave it for now. New Game button plus here, I drag the sound in
and audio source play. Add new event type, move, and also on
quid Game Button. Plus here, I drag my
sound in Audiosurce play. Add move event. When I play, Everything animates,
responds and makes sounds. Our game is starting to look
pretty nice and polished. I copy the audio object. I save changes to the scene, and I open main menu scene. I paste the audio with its
sound and music child objects, event system, and I make sure New Game button is
first selected here. Menu Canvas, New Game button, and one last time, I
set up Pointer Enter. And move events here. And I also do it on
quid game button. Now, all buttons in our game give audio feedback
when interacted with. Quick play test again. Audio plays. I save the scene. I open Level one scene. We want pose panel to
be disabled at first, so I antique this checkbox. We will have different
background music in menus and while playing. During gameplay, here
in Level one scene, VCR jungle Track will play. So I drag it into
the scene under Audio Manager Game Object
to keep my hierarchy clean. I make sure play on awake and
loop checkboxes are ticked, output here, and I select music audio mixer
group like this. I play we will actually start the game
from main menu here. Do you think the
atmosphere of the game improved now that we have
sound effects and music? Music does a lot
to set the mood. There is a lot more we can
do with audio in unity, but this is a pretty
good base to build on.
16. Flash on Hit: Cold. Visual feedback like flash animations on hit provides immediate
confirmation of an action, enhancing the
clarity of gameplay and making interactions
more understandable. So let's add flash animation
when player gets hit. There are many tutorials online, some of them using
complex shaders, but let's keep this
beginner friendly. There is actually a
quick and easy way to make object flash
white in unity. I go to assets, resources, and I create a new
folder, I call materials. Inside, I right click
Create material. I call this new material
white for material white. And up here in Shader I go to GY and text Shader. That's it. Now on my game object inside sprite renderer component
and material field here, applying this new
white material we created to this field will make the object
completely white. Now we just need a
little bit of code that will switch
this field between white material and this sprite let default material
to create white flash. I open player controller script, and I need a reference to
sprite renderer component. Same process as we did for rigid body two D and
animator components before. Inside start method, we use Get component like
this as usual. So now this variable
points to this component, and I want to access
this material field. We will need a reference
to the default material. And to our new white
material, we just created. I will make them
private but visible in Unity inspector,
serialized field private. I save that, and now I have these two new
fields on the player. I want this white material, so I select player again, and I drag white into
white material field. Default material will be
this sprite lit default. So as soon as we get a
reference to Sprite render, default material will be
sprite render dot material, the value of this field. We will store that in
default material variable so that we can
switch back to it. Now, if I save and play, this value will be referenced inside default material field. Okay, great. When player
takes any damage, we set material to white
material like this. Save and play. I collide
with an asteroid. And you can see
that material here, and player is completely white. Perfect. I can remove
this serial as field. We don't need to see the
default material field in the inspector anymore
because we know it works. Now I want to wait for a few
seconds and switch player back to its default material
to give it colors again. I will do that with a co
routine I enumerator, and I need system collections
name space up here. I call it reset material, yield return, new,
wait for seconds. 0.2 F. Here I'm saying whenever
we call reset material, we will wait for 0.2 seconds, and then we will set sprite renderer material
back to default material. I call this by saying start
coroutine, reset material. So when player takes damage, turn it white, call
reset material, which will wait for 0.2 seconds, and it will turn it back
to its original colors. Save that and play. I hit asteroids and
player flashes white, visually confirming
the collision. Now that we know how to do this, I can also do it
for the asteroid. Let's make it flash when it
collides with the player, and it will also flash white when we hit it with
a weapon later. I open asteroid script and
we will do the same thing, which is did for the player. We will need a variable for default material
and white material. Inside start method, we save a reference to the
default material. I give asteroid collision
enter two D function. We already know how this works. If the object asteroid collided
with has a tag of player, I will do the same thing we
did in player controller. We set sprite
renderer material to white material and we
start a coroutine, a timer that will eventually reset material back to
default automatically. I grab that reset
material code block here and I just copy it. I might refactor this later. For now, this is good. I numerator here only works if we are using system
collections namespace. Okay, so when asteroid
collides with the player, we turn the asteroid white. We call reset material, which will wait 0.2 seconds, and it will turn it back to its original colors.
I save changes. I make sure player has a tag of player in this field here. I go to prefabs, asteroid, and we have this
white material field. I lock the inspector
to stay on this. I go to resources materials, and I drag and
drop M white here. I unlock the
inspector and I play. When we collide, both player
and asteroid flash white. Small things like this really increase the gameplay clarity. This is the simplest
technique that I know to make objects flash
white on collision. Do you have a different
preferred way to do this? Let me know in the comments.
17. Phaser Weapon: I go to Assets prefabs, and I create a folder
I call effects. I put boom one in there and
also particle smoke effect. Another folder, I call UI. I put button prefab in there, title and displayer
menu animation. I create one more folder
and I call it weapons. You can download
bullet one image in the resources section below. I drop it into my art folder. I set Sprite mode to
single and apply. I will drag and drop bullet
one image into the scene. Unity created a
game object for it, and it recognized it's an image, so it also gave it a
sprite renderer component. Sorting layer will be
player, add component, capsule collider two D.
Direction is horizontal. Now, we write a simple script to make it move from left to
right across the screen. I go to scripts, I create
a folder, I call weapons. Inside, I create a
mono behavior script, I call phaser weapon. And one more script I
call phaser Bullet. I'm building the weapons with the assumption that we might want to add multiple different weapon types to
the player later. So let's build our basic
weapon I call phaser. I right click the
player, create empty, and this game object
called weapons, will be a container that will
hold all player weapons. Player will always carry
all the weapons here, but some might be
deactivated at first. Inside weapons object,
I go one level deeper and I create the first
one called phaser weapon. I attach phaser weapon script to phaser weapon game object. I open the script, and I
want to do a Singleton here. Public static phaser
weapon instance, same as we did for the
other objects before. For example, for the player. I will copy and paste this
wake method as usual, just to make sure we have only one instance
of phaser weapon. This phaser weapon
script will be managing the bullets
and hold the stats. It will need a reference
to phaser bullet. It will be a prefab and we will create a copy whenever we shoot. We will also turn this into
an object pool in a minute. I'll show you a quick and easy
object pool implementation for using bullets to increase the performance
of our game. But first, let's do the
basic simple create bullet destroy bullet pattern. We will hold phaser
weapon stats here. If we decide to implement
weapon leveling system later, we can easily access speed
and damage from here. Save that, and with phaser weapon game
object selected here, I set speed to two
and damage to one. Now, I open phaser
bullet script, which will be attached to every single
projectile we shoot. Inside update method, as the
game runs for every frame, I increase transform position by some value to make
the bullet move. Position has X and Y components, so plus equals new vector three, and we give it some horizontal
and some vertical value. If we don't give it
the third value for Z, it will default to zero. So I want to have a
wrapper for all weapons. Under it, we have phaser weapon
that holds phaser stats, and it will be producing one of these bullets
every time we shoot. I attach phaser bullet
script to bullet one here. So this script will make
it move to the right. But instead of hard
coding one here, I want the speed of the
bullet to be taken from this speed property on
phaser weapon script. So as the horizontal
component of the vector, I pass it phaser weapon
dot instance dot speed. Whatever is the speed value, that's how fast the
bullet will fly now. Also, we have to make sure the speed is the
same on any device, so we account for real time
time dot Delta T. Save that, and if I play, bullet is moving to the right, two units per second. As the bullet collides
with asteroids, strangely, the player
is taking damage, but that is actually
expected because the bullet basically acts as
an extension of the player. I put the bullet as a
child of the player here as if it was a body
part of the player. So if the bullet
collides with something, it's the same as when
player collides with it. We don't want that in
this case, of course. For now, I drag bullet one here to the top
level in hierarchy, making sure it's not
parented under anything. I also want to make sure bullets get destroyed when they
reach the edge of screen. I don't want them to
fly too far outside the visible game area and
destroy objects off screen. If transform position dot X, horizontal position
of the bullet is more than nine units, nine squares in the
grid from the center, which is where the He is. We destroy the game object attached to this script,
the bullet itself. We also want to track collisions
on collision enter two D. If the game object this bullet collides with
is tagged as an obstacle, asteroids are tagged
as obstacles now. We destroy the bullet. Save that, and I go
to prefabs weapons, and I drag and drop
bullet one here. Let's drag a few copies out into the scene and play the
game just to test this. Bulets are flying and
they get destroyed when they collide with an asteroid or when they move
past Unit nine. One, two, 345 6789. Phasor weapon script will manage duplicating and
releasing bullets. I will give it a public
function I call shoot. I need to make sure we assign a value to the prefab
from line seven. Save that and phaser
weapon selected, I drag bullet one prefab
into this prefab field. When we shoot, we
call instantiate. We used it before and we know
it expects the game object. We want to make a copy of, in this case, prefab,
this bullet one. We also have to give it
position and rotation. So let's just pass
it position and rotation of phaser
weapon game object, which is a child of
player game object. So it will be wherever in the scene the player
is at the moment. Okay, we can call this public shoot function
using this public instance. Let's call it from update
method on player controller. I can just copy
this block and make sure I don't make any
syntax errors here. If we press left shift
key or fire one event, which by default is
left mouse button, we take Fas weapon dot Instance, and we call shoot function
from there like this. If I click Mouse, I'm shooting. If I press left shift,
it poses my game. I have to go and rebind this. I use right shift for
shooting instead. Now I play. I can use left mouse button, right shift, and also game
controller to shoot phaser. The bullets get destroyed when they collide with asteroids. I set speed to four. I delete all of these. I want asteroids to flash
white when we hit them. On my bullet one prefab, I add tag plus new tag I call bullet and back to my
bullet one prefab, and I set tag to bullet. Inside asteroid
script, if object we collide with is
player or bullet, we run the code that will
make it flash white. Save that and I play I shoot. And we get visual
feedback every time we hit an asteroid. Awesome.
18. Object Pools: Cold. I go to Assets scripts, and I create a new script
I call Object Pooler. We are creating and destroying
game objects in our game. It's fine if it's just a
few objects per second, but with bullets, it can
get out of hand quickly. Continuously creating
and destroying objects can lead to
performance bottlenecks. Memory allocation and
garbage collection can cause sttering
and frame drops. One of the most
common performance optimization techniques is object pooling. Object pool maintains a set
of pre instantiated objects, which can be reused when needed. Let's optimize our game and
create a pool of bullets. We take them from the pool and activate them when
we need them and we return them to the pool and deactivate them instead
of destroying them. That way we can
reuse them later. Let me show you how to create
a reusable object pooler. We will use it to
pull these bullets, a reset transform here, but we will later use it
for other things as well. Not just the bullets, maybe other weapon types,
maybe enemy projectiles. Let's see. I delete this. First, we will need
the game object, some kind of a prefab
that we want to reuse. For us, it will be this bullet. Public integer, I
call pool size. Initially, we will create
five reusable bullets, but we will also make sure we automatically add
more if we run out. We will hold them in
list of game objects. I call, for example, pool. To use list here, we need system collections, generic name space up here. I create a private function, I call Create Pool. First, we reset the pool, make sure it's just
a new empty list, waiting for game
objects to fill it. Then I run a four
loop five times. And each time it runs, we create a new object. This function doesn't exist
yet, so I declare it here. Private function
that will return a game object called
Create New Object. Inside, I will have this helper variable type is game object. I call it, for example, OBJ and it will be
equal to instantiate. We want to create a duplicate of the prefab from
line six up here. We also know that
instantiate method expects position and rotation. So we just pass it
position and rotation of the object this
script is attached to. We want to create
the game object, but we want to
make sure it's not active, set active falls. And now we want to add this inactive game object to the pool we defined
on line eight. Pool dot at because it's a
list and we add that object. This functions return
type is game object, so we return it like this. I want to call create pool automatically here
inside start method. Okay, create Pool function has a four loop that
will run five times. It will create five
inactive copies of a prefab we specify, and it will add
them to the pool. They will be waiting
there inactive for whenever we need them. So this is how you pre
instantiate your object pool. We will also need a function
that will search the pool, find the first inactive
and available game object, and it will give it to us. Get Pooled Object will
run for each loop. It will iterate over game
objects in the pool. And if it finds object that
is inactive and available, I Obj dot Active self is false, exclamation mark means false. We return that object, and this function will end. It will not search anymore. If we didn't instantiate
enough objects, all of them are currently active in the game
and we need more. We simply create one more. We call create new game object
that returns game object, and we return that
here like this. We set pool size to five, so we will have five
bullets initially. Every time we call
get pulled Object, we search the pool for the first inactive one
and we take it. If there are no inactive
ones, we add one more, and we use that one, increasing the size of the
pool based on our need. I create an empty game object, I call Object pools. We will put all of
the pools in here. The first one will be
phaser bullet Object pool. I attach Object Poller script, and it has this field
waiting for a prefab. What object will this
pull consist of? I go to prefabs weapons, phaser bullet pull
selected here, and I drag and drop bullet one into the prefab field here. Now with this setup, instead of creating
new bullet every time, we call Get pulled
Object instead. Inside Phasor weapon script, instead of referencing prefab of the bullet and instantiating
it every time we shoot, I create a reference to the
entire Object Poller script. I call it, for
example, bullet pool. I save that and I have this new field here that
expects Object Pooler type. So I pass it phaser bullet Pool game object that has Object Pooler
script attached. Now I can use it to call Get pulled Object function
we defined on line 29. So when we shoot, don't create a new copy
of a bullet prefab. I comment this out and I
also comment this out. Instead, we will create a temporary helper variable
of type game object. I call it Bullet.
It will be equal to Bullet pool from line eight
dot Get Pulled Object, and this actually needs to be public so that I can access
it from other scripts. So instead of creating
another copy, we take an existing copy
from the bullet pool. I'm saving it in a
temporary variable here like this because
when we get it, I want to reset its position because if we are
reusing the bullet, it might have flown
somewhere off screen. So before we use
it, we make sure its position is the same as
the position of the weapon, which is attached to the player. After that, I can set it active. Instead of creating a new one, we are using one from
the object pool. Inside Phaser bullet script, instead of destroying it
when it flies off screen, I simply mark it as inactive and ready to be used
again whenever needed. Same here. If the
bullet collides with an obstacle,
don't destroy it. Deactivate it so that
we can use it later. I save that and when I play, I have these five inactive
bullet one copies. Maybe I don't want them to
be here at the top level. I would prefer if
they are parented under Phaser bullet
pool game object, keeping the hierarchy clean. We can do that by going here
where we create new object, and instead of
passing them position and rotation of a
specific game object, I replace this with a single
parent transform parameter, which will instantiate
the object at parents position
and rotation, and it will also make
the new object a child of parent
transform automatically. Now if I save and play, I expand phaser bullet pool, and I have my objects
neatly organized in there. As I shoot, it will automatically
increase the size of the pool if the five I originally
created is not enough. If we need more than five of them to be active
at the same time, as I shoot, you can see they get activated and
deactivated accordingly. Now I can create
some super weapons that release swarms of bullets, and thanks to the object
pool we just created, our game will animate smoother. If you want to turn this
into a bullet hell game, object pool is essential to have and the difference in
performance will be noticeable. But
19. Particle Trails: C. Before we finish this, let's take a small
break and create a blue particle trail whenever
player enters super speed, given the special
move a better feel. I right click player Game Object and effects Particle system. It immediately starts animating. We already created smoke effect. But let me show you how we can also make much prettier
things with this. I call it engine effect. Let's give our player spaceship a proper pop when
we enter boost. Let me show you how to create
Particle system and how to control when it plays
with code. It's simple. On my new particle system, I set start lifetime to 0.5. Down here in renderer, I set sorting layer to player, order in layer minus
one behind the player. Simulation space world. Emission rate over time, much higher, maybe 200. We are in a two D space, so I change shape to circle. I want them to fly to the left to match what's going
on in the game world. Velocity over
lifetime, linear X, maybe -20 or minus ten. Radius from which they come
out will be much smaller so that I can hide it
behind the player's sprite. 0.1 seems good. I move it to the left, so maybe -0.6 -0.5, back to renderer material
will be default particle. Color over lifetime. I leave this as white and this one will be
color picker here. I want to match the bluish
color from player jets. Let's try again. I take
color from somewhere here. Yes, I move this left to
make them turn blue sooner, and the top ones are
for Alpha opacity. I want the particle
to be completely transparent by the time it
reaches the end of lifetime. Size over lifetime. I
click this and down here, I select one of the
predefined curves. I want the particles
to grow gradually, so maybe this curve. Start speed will be two. Start size will also
be two or maybe three. If you ask me, this is a
pretty nice particle trail. You can, of course, edit
it in any way you want. This particle effect we
just created will help us to make the super speed
move feel more powerful. Now I want it to play only for 1 second whenever we trigger it, and I want to disable looping. Okay, maybe just 0.5
seconds will be better. Just release a puff of
fast particles like this. It will just play like
this and it will stop. Now I will just play it from inside player controller script. First, we will need a reference
to the particle system. I will call this one, for
example, engine effect. And when we enter boost, I take engine effect
and I call play. It will play just that 10.5 second loop if we
set it up like we did. Save that and I have this
new field for engine effect. So I drag it from here. I make sure it's a child
of the player game object to make sure particle trail
moves with the player. If I play, this is
what it looks like. I don't want the effect to play immediately only
when we enter boost, so I find play on
awake and I untick it. I said boost power to four F, making the super speed
a little bit slower. I think this will work better. Adding eye catching
visual effects with particle systems like
this has many benefits. They provide instant feedback, making actions like using our special super speed ability more satisfying and impactful. Unit is built in particle
system is pretty performance efficient and has endless
customization options, so I recommend you
use it whenever you want to add flare and
excitement to your game.
20. Destructible Obstacles: We can make the
asteroids destructible. I open audio manager, and I will need a few
more sound effects. Boom, too, for when we
destroy an asteroid, hit rock when we
hit asteroid with a bullet and shoot
when we fire a phaser. Save that. I have
new fields here. I go to my audio folder and I bring more sounds from
the sound effect back, we download it earlier. You can use different files. You can see which ones I'm using here if you want the
same sounds as me. 46 poison, also 08 step
rock and 12 step wood. I drag them into the hierarchy. With all three selected, I set output to effects
AudiomxerGroup, and I disable play on Awake. I put them under
audio Manager Object. I move the music file up Okay, so with audio manager selected, I actually want to change what sound plays when we enter boost. I'm using this fire
for enter boost sound, so I'll drop this poison
sound here instead. Fire explosion we used
there will instead be used for boom to when
we destroy an asteroid. Hit rock sound will be this one, and shoot will be this sound. Feel free to use different ones. If I open phaser weapon
script, when we shoot, I take audio manager, instance, play sound, and I want
to play shoot like this. I intentionally choose a quieter sound
because it will play a lot and I don't
want the players to get annoyed with
too much noise. Playing the same sound so often over and over can
be a bit monotone. I can modify it a little
bit every time we play it. Inside Audio manager script, I copy my play sound function. I rename it to play
modified sound. Everything will stay the same. But before we play
the sound effect, we will randomize pitch. Random range between
0.7 and 1.3. So for sounds that
will play often, we can now use play modified
sound function instead. Now, if I play. You should be able to hear some variety
in the sound effect. Phaser weapon and speed. I increase it to 12.
This will feel better. I open asteroid script and I
want to randomize the size. Each asteroid will be
slightly different. Inside start function, I create variable float
called random scale. It will be a random value
between 0.6 and one. Now I access transform scale, this set of fields, and I set it to new
vector two with random scale for both horizontal and
vertical scale value. You can download
Meca Vale audio file in the resources section below. I drop it into my audio folder. I open Level one complete scene. When we enter the
scene and we won, I want the whale to make long robotic whale call
just for flavor. I can simply drop the
file into the scene. I parent it under Audio
to keep it clean. Output is effects and play
on Awake will stay ticked. If I play, we get that
extra sound. New game. I hear the new sound
when we enter boost, and we have a shoot sound. We need more sounds. I save changes to level
one complete scene, and I open level one scene. I want the asteroid to make
sound when it gets hit. I copy this line of code. I paste it here in
this block when we check if asteroid collided
with player or boulet. I want to play sound
I called hit Rock. I make sure audio manager
hit rock has a value here. We get a sound when we shoot and we also get a sound
when we hit something. This does so much
for the game feel. When making a game,
it's important to identify the
action the player will be doing the most and make sure it feels good to
press that button. In this class, I'm focusing on super speed move and shooting
our pacer gun for now. Make sure the actions pop. You can do that, for example, by including particle effects, sounds, animations, make other objects
react to it, and so on. We did all of that already
and we can do so much more. You can download Boom two spreadsheet in the
resources section below. I drop it into
Assets art folder, and I open Sprite Editor. Slice grid by cell size, 400 times 400 slice and apply. I take one of the frames
and drag it into the scene. We've done this before
for boom one effect. I renamed this one to boom two. Sorting layer will be objects, or in layer one on
top of asteroids. Animation panel, window
animation animation. Boom two selected here, making sure it says
boom two here, and I click Create. I navigate to assets animations
like we have done before, and I create a new animation
called boom two. Save that. Making sure this says boom two, I select the first frame. Go all the way down and I
shift click the last frame, selecting all the
frames in between. I drag them onto the timeline, and if I press play, we can see what the animation
looks like in action. Samples maybe just 20. Yeah, this is good. I go to
Assets animations folder. I find Boom two
animation clip file and I antique loop time to make
sure it only plays once. Inside script effects, we have this boom script that will
destroy Game Object it's attached to after
Animator finished playing all the frames of its currently
active animation state. We are using it for boom one, and I can use the same script
file here for boom two. If I play the game, I check if Boom two object gets destroyed
when animation finishes. Yes, this is working. I exit play mode and I
go to prefabs effects. I drop Boom two in here. This message can
be ignored because animations will work
fine. I can delete this. Let's create a
reference to destroy effect inside Asteroid script. Asteroid briefap and I
can see that field here. I lock the inspector. I go to effects, and I drag Boomt Briefab
into destroy effect field. I unlock the inspector again. We have this reference sorted. I will add another property
integer called lives. Save that and asteroid Briefap. Asteroid will have five lives. Every time asteroid collides
with player or a pullet, we reduce lives by one. If lives are less
or equal to zero, we instantiate destroy
effect prefab. We want it at the position
and rotation of the asteroid. We will also use audio Manager. We will play modified sound. I called boom two. We set that sound up
earlier, if you remember. Then I can destroy the
asteroid like this. Save that and I make sure I have boom to sound assigned here
on Audio Manager. I play I destroy some asteroids. I need to hit each 15
times to destroy it. The artwork is blurry, so I go to Assets
art, boom to image, and I need to make sure this value is more
than the width of the image itself so that the image can render
at full size. I click Apply. I also
want to open Boom script, and I want to make
sure that the effect flows to the left as
the game is moving. So I create an update function. And I can copy this code from asteroid
update, for example, make it move to the left
accounting for world speed, whether or not the player is in superspeed and adjust
for real time, like we did for other
game objects as well. Now if I save, I can reset
this transform to 000. There's no reason just my OCD. I like the values clean. If I play, we get an impact sound every time bullet
hits an asteroid. And if we hit it five times, we get a big juice explosion.
21. Harmless Space Critters: Sold. This next feature can be considered optional, but I'm just having
fun at this point. I want to add some
space creatures, little critters that
will float around. I will take this
opportunity to show you some really cool reusable
two D game def techniques we haven't covered
in this course yet. And I think adding some
asteroid belt wildlife is a good way to do it, and it will also improve
the feel of our game, and we could even use them to hide a secret boss in the game. Maybe let's talk
about this later. You can download rater One spreadsheet in the
resources section below. I also made adult
and both versions of these because I have a lot of
ideas what to do with this. I select the image file
and I open Sprite Editor. Slice grid B cell size, 80 times 80, slice and apply. I drag one of them
out into the scene. Sorting layer will be player
I rename it to Krater one. Inside Scripts folder, I create a new mono behavior
script I call Krater one. I select Greater
one game object, I attach Krater one script
to it as a component, and I open the script. I want to give each
one a random sprite, same as we did with asteroids. I will need a reference to its
sprite renderer component. And also an array of
sprites, I call sprites. Inside start method, we find sprite renderer using
G component as usual. Now I want to put some
values into this array, so I save changes. In unity, I see the array
I call sprites here. I click Plus, I
add four elements. You can see they are
expecting a sprite. So I drag these in Sprite zero, one, two, and three. Now I want to access
Sprite render dot Sprite, this field, and I want to assign it one of these
four sprite images. The image at index of zero
or one or two or three. Let's just do a random range between zero and
sprites dot length. However, many images
we put in this array, it will pick a random
one among all of them. If I save and play, we get a random image out
of these four assigned. I test it again. And I
get a different one. That works. We did the
same thing with asteroids. This technique only works
because I'm not animating the critter and the asteroid by swapping their sprite frames. For example, with
the space whale, we are swapping sprite
frames to animate it. So we would have to use a completely different approach to achieve randomized
skins there. But for this simple critter, this is the go to technique.
Let's make them move. We used the built in physics
system to move asteroids and the player through rigid body to the component and
its linear velocity. For the quitters, let's use
the other technique because they will not be involved in traditional
physics collisions. So I can simply move them
using transform dot position. Position has X and
Y and Z components, so we can update all of them at the same time by
using vector three, move towards built in function. We can use it to move object
from its current position. Towards a target position, it's very handy here. We pass it three parameters, the current position
of the critter, target position, where do we
want the critter to move? And this third argument, max distance delta
is basically a step. Distance to move towards
the target per coal. Formats are vector three, vector three, and float. We keep that in mind
when we define them. I define private
float move speed, private vector three
target position. Inside Start method, I
just give each critter a slightly different
randomized move speed between 0.5 and three. I'm trying to create
a bit of a chaos, so it looks like they are
thinking living creatures. Let me show you a few tricks I like to use to achieve this. I will be periodically
generating random position somewhere
inside the visible game area, and they will be rotating
and moving towards it. Custom function, private void,
generate random position. Inside, I set target position
we defined up on line nine. I set it to new vector two because we care
only about X and Y. We can keep the set at default. Here, we pass it some horizontal and some
vertical position. And inside start method, I will call generate
random position immediately so that the critter has somewhere to move towards. We are inside update, which will run at different
speeds on different devices. We never want to tie
movement to a frame rate. We want to tie it to a real
time that is passing by. So I account for time
dot Delta T here. This argument will determine how fast is the critter moving
towards the target. So if I play, my
critter will move from its current position towards
position two horizontal, one, two, three, vertical. These position values are in units measured from
the center here. Critter moved to that
position because we hard coded it
here, two, three. We want to randomize
target position instead. Random X, the horizontal
component will be a random value between minus five and plus five on the horizontal X axis. Vertical component, random Y will also be between
minus five and plus five, but on the vertical Y axis. Now I can pass random X as
the horizontal component of target position vector and random Y as the vertical
component here. So now, every time we
call random position, it will be somewhere five units from the center of the screen. Random value between
minus five and plus five horizontally and random position between minus five and
plus five vertically. Keep in mind that
the initial position of the critter will be
somewhere off screen, and the game world is
also endlessly moving. So the final movement
will look good. You will see in a
minute. Now if I play, critter will move towards
some random position in this range of five units from the middle
in any direction. Now, I just want to call this generate random
position over and over at a specific interval to make the critter zoom around
from place to place. Move timer and move interval is all we need to achieve that. I can also randomize
the interval, making the movement look more
unpredictable and natural. Move interval will
be a random value between 0.5 and 3 seconds. This determines how often the crater changes
target position. We will count backwards. Move timer will be
set to move interval. Inside update method
for every frame, we check if move
timer is above zero. If it is, keep decreasing
move timer by real time, time dot Delta T. Else, meaning when move timer is
equal or less than zero, we generate random position. We set move interval to a
different random value, and we set move timer back to move interval so that it can count again
towards another loop. Okay, so move timer counts time. Move interval defines
the breakpoint when something happens and
when the cycle resets. Inside start method, which runs only once when the
crater is created, we randomize move interval, and we set move
timer to that value. Inside update that
runs over and over, I move timer is above zero, we keep decreasing
it by Delta time. If it drops below, we
generate new random position. We randomize move
interval again, and we set move timer to that new value so the
cycle can repeat. I save that and I play. You can see the critter is moving from position
to position. We are making a good
progress on this. You can probably guess
what we need to do next. We need to rotate
it to make sure it's looking in the
direction it's moving. In unity, it's also very easy, and you will probably
need this technique for so many other
things in your games. Another trick to your two D game development toolkit
is the following. In unity, quaternions are used to smoothly rotate objects. Quaternion is a data type
that represents rotation. It can be used for both two
D and three D. So same as we move to move from the current position
towards target position, we will rotate from
current rotation towards target rotation. The rotation depends on
relative position between target position of the critter and its current
position like this. We need to know where it
is and where it is trying to move to determine which
rotation to give it. If the relative position
is not vector 30, so zero, zero, zero, we take target rotation, and we use built in quaternion dot Look
rotation function. Look rotation creates
a rotation that points an object in the direction
of a specified vector. Often it's used to make objects face a
target or direction, which is what we are doing here. Look rotation needs two
parameters forward and up. As the first parameter, I pass it vector three forward, which is a shorthand
for vector 3001. The second parameter will
define objects orientation. We want the orientation to be towards the target position, so I use relative position
from line 37 here. This approach will work if the sprites are
facing up like this. You have to tweak the
sprites or this code if your characters are not
facing upwards on the sprite. I recommend playing with
the code for a while, passing it different
values here, and it should become
clear how it works. It's probably not
very intuitive if you see this for the first
time, but that's normal. Don't worry about that. It will make more sense if you
use this more often. Now that we calculated
the rotation, we want to actually apply it to these rotation fields on the
critter game object itself. Transform rotation is
quaternion rotate towards, same as we used move
towards before. I don't want the critter
to rotate instantly. I want it to rotate from its current rotation towards
its target rotation, we calculated on line 39 by
a certain step per frame. Full rotation would
be 360 degrees. So if I multiply that value times three times
time the Delta time, it will rotate by this number
of degrees per second. 360 times three is 1080, so I want it to rotate full circle in a
third of a second, but feel free to put a
different value here if you want them to
rotate faster or slower. So here, same as we used to
move towards target position, we are using rotates to rotate
towards target rotation. These are really useful things to know for many two D games, not just for the project
we are working on today. Save that and let's see if it
works by playing the game. We get a critter with
a randomized skin. It's moving to a random
location at a random interval. And always rotate in to
face where it's going. Well done, let me know if you managed to get the same result. Do you have any questions
about this technique? We can talk about
it in the comments. One more thing, the
critter exists in the game world and we are in
an endlessly scrolling game, so we need to account for that when we are placing
it in the scene. We did the same thing for asteroids and other
floating objects. I calculate move X,
horizontal movement, which will be game manager
dot instance dot World Speed, TAMs player controller
dot instance dot Boost. All of that in brackets
multiplied by real time. Then we take transform
position of the crater, these fields, which
as you can see, expects vector three
format for X, Y, and Z component, and we increase the current position
by a new vector three. Horizontal component is this
move X, we calculate it. Vertical component
will remain what it is unchanged by this
and z component, if we don't define it here, it will also default
to zero here. It's the exact same thing we
did for the asteroid before, making sure it floats
from right to left. So now if I save and I play, the quitter is still
doing its thing, but now it's also floating by as the players spaceship
moves through space. I exit play mode, I add
component to the quitter, circle collider two D. I
click here to edit collider, and I use these four small
handles to make it smaller. I add another component, rigid body two D. I set
gravity scale to zero. I reset transform to zero, 00 and the critter is ready
to be turned into a prefab. I drag it into assets prefabs. To test it, I drag it out
multiple times. And I play. They all get a random sprite and they move around as
if they are alive. Currently, they collide
with each other and they even push
the asteroids around. I don't really want
that. They need a collider because I want them
to interact with bullets, but I don't want them
to interact with the other objects in the game world that
also have colliders. We can implement
that by selecting Critter prefab here
and layer ad layer. I create a new layer called critter and another
layer called bullet. Back to prefabs, Critter prefab selected and I assign
it to Critter Layer. I go to weapons and bullet one prefab will
get bullet layer. Now I go to edit project settings and
on physics to the tab, inside layer collision matrix, we can decide what
interacts with what. This column is for
critter interactions, so I disable Critter
with critter collisions. I also disable
obstacle with Critter, level boundaries with critter and player versus
critter collisions. They will also
ignore each other. Here, we keep this one ticked to make sure critters
and bullets do interact. I delete all of these. Instead, we will spawn them dynamically using our
Object spawner system. I select Object spawner game
object, and I duplicate it. I renamed the copy
to crater spawner. On my new reaper game object, I set number of waves to
zero, deleting all of these. And plus to add a new
wave of critters. Prefab we want to spawn
here is this crater one, spawn interval, and I want a new crater to spawn
every 1 second. We will spawn three of them. After 3 seconds, I will
have another wave. I want a swarm of
craters to spawn. So span interval, something low, like 0.2 seconds, and
I spawn ten of them. Then the cycle will repeat back from the first
wave and over and over. It's using the same
object spawner script, so it will work the
same as Object spawner. So we have our critters,
little harmless aliens. In the final gameplay, I will use less of them. This is just for testing. Critters now ignore collisions
with player and asteroids, but they do get pushed
around by bullets. This is completely optional, but it would be fun if we could destroy the critters
in different ways. For example, our
main phaser weapon is a blue energy blast. So what would happen to the critter if it
got hit by that?
22. Plasma vs Critters: C. I put together a small animation. This is what I
imagine would happen. You can download a sprite
sheet I called grater one Zap in the resources
section below. I drop it into my art folder. I open Sprite Editor,
and I slice it. Type is grid by cell size. Frames are 50 times 50. Slice and apply. So I could do this in a few different ways
as usual in Unity. Let's follow the same pattern we did for destroying asteroids. I drag the first frame
out into the scene. Unity will create a game object. Sorting layer will be objects. Rename it to Greater
one underscore zapped. I open Animation
panel KreterO zapped, selected here, making
sure it's mentioned here, and I click Create. Inside Assets animations, the new animation will
be called KreorO Zap. Save that. I select the first frame and I shift
click the last frame. With Great one zapped here, I drag and drop all of
these on the timeline. I play too fast. Five samples, too slow. 15 samples is good. I go to Assets Animations. We have this new
animation controller file and the animation file. I want the dot Anim file
and inside inspector, I antique loop time. I want this animation
to play only once. I go to scripts effects, and we have this boom script. We attached it to
player explosion and asteroid explosion effects
before boom and boom two. It takes animator
component and it destroys the game object when all animation frames of the current animator
state finished playing. It also moves the
animated effect in the game world as
the scene floats by. The way we created this scriptor zapped effect, it
will work the same. I can actually just simply use
the same script like this. If I play, it will float left and gets destroyed when
animation finished. I go to Prefabs
Effects and I drag and drop Creator One zapped
Game Object in here. This message can be ignored because animations
will work fine. I can delete this. Now I need
to go to my Greater Prefab. I open rater one script. I'll create a reference to
the zapped effect briefap. We just created
private variable, visible in unity editor, type is game object, and I call it zapped effect. Currently, bullets just
push the critters around a little bit as the
automatic physics system in unity does its job. We want to detect collisions
on rater script here and do something more specific on collision Enter two
D, built in function, we will instantiate that
zapped effect brief upp at the current position and the current rotation
of the critter, it's actually very helpful
that we can pass rotation like this to make sure the critter animation will
be rotated correctly, facing the same way the
critter was facing. We will also destroy the
critter game object itself. We will not do this
on any collision, only if the object critter collided with has
a tag of bullet. Instantiate greater
zapped animation, which we build as a
separate game object and destroy the crater
itself at the same time. And in unity, prefabs
weapons bullet one, and I need to make sure
tag Bullet is here. I also go to Greater one prefab, Zap effect field here. I lock the inspector. I drag Greater one zapped
game object in here, and I unlock the inspector. Now, if I play, let's
actually try to hit one. Yes, this works. We are frying the critters
using our blue Pacer weapon. We could create a
different animation for different weapons. Critters can get destroyed in different ways using
this technique. Let's give that effect
a proper squish. I go to the sound effect pack, we download it earlier, and I will use sound 77 flash. You can use any sound you want. It's up to you. I drag it
into my assets audio folder. I then drag that
file into the scene. Unity will automatically give
it audio source component, so I set its output to
effects audio mixer group. I disable play on awake, I put it under audio Manager
to keep my hierarchy clean. I open Audio Manager script. New public audio
source variable I called squished. Save that. Audio manager selected here, and I drag 77 flash sound from here to my
squished field here. And now I can simply play
it like we did before. If Critter gets hit by a bullet, I take Audio Manager Instance. And I call play Modified sound. The sound I want to play is audio Manager Instance
dot squished. You might think it's mean to fry the harmless critters with
our phaser like this, but are they really as
harmless as they seem? We might implement some
more functionality to them. Maybe they can get angry. If you like this class and you let me know in the comments, I will show you
something cool we can do here in a follow up
extended version. But I will only do it if people engage with this and let
me know in the comments. Otherwise, my time is better
spent on other things. I want to make sure bullet gets destroyed when it
hits a critter. Currently, it just flies through multiple critters and
destroys all of them. Inside assets prefabs, I
select critter one prefab. Tag drop down here and add TAG. Plus, I will call this new
tag critter and I save. Inside prefabs
weapons, I have bullet one prefab with phaser
bullet script attached. On collision Enter two D, if bullet collides with obstacle or if it collides with a critter,
deactivate the bullet. I save that. I select my critter prefab again and I give
it a tag of Krater. If I save and play, Each bullet can now
destroy only one quitter. If I play the game, we are
spawning a lot of quitters. If you want to have so many
of them in your final game, it makes sense to
create an object pool and reuse them like
we did with bullets. In my final game, I will have much less of
these critters, so I will not do an
object pool here. Anyway, I'm just noticing we are not destroying
them at all, so we have an endlessly
growing trail of critters behind us. We will do the same thing
we did for asteroid here. I copy this block of code. I open crater script, and down here, I paste it. After we move the
critter horizontally, if it's off screen to the
left, destroy the critter. Now, if I save and play, they get destroyed when they
move past this point rate.
23. Heat vs Critters: So. No. We can destroy the critter in multiple different ways and each way can have a
different animation. I created another spread
sheet when the critter gets burned by the heat
of our ship's engines. Or maybe we can
also use this when critter gets caught up
in asteroid explosions. I select the image and
I open sprite Editor. This is what I imagine would
look like if the critter gets exposed to
heat. I slice it. Type is grid by cell size, 70 times 70, slice and apply. I drag any one of these
frames into the scene. I want to do the
same thing we did here for Krater Zap to brief up. I can see we put it
on objects layer. I change order in layer to
one on top of other objects. Now I select greater
burn object. Sorting layer will be
objects, order in layer one. I rename it to greater one
underscore burn like this. I open animation panel
Window Animation animation. Reter one burn selected here, making sure it says
that here as well, and I click Create. Inside Assets
animations, I create a new Anim file I call
reterO burn. Save that. Inside art folder, I
click the first frame, and I shift click
the last frame, selecting all frames in between. With Greater one burn here, I drag all of the
frames on the timeline. I play the animation
too fast, 25 samples. Still too fast. 15 samples?
Yes, this is good. Inside Assets Animations folder, I have this new animator
controller file, and we also have this
animation clip file. I uncheck loop time. I want the animation
to play only once. Scripts effects folder KriterO
burn object selected here, and I attach boom
script as a component. We already know this
script will auto destroy the object when all animation
frames finished playing, and it will also move it along with the side
scrolling game world. I play and the object
gets destroyed. Got prefabs effects, and I
drag KriterO burn in here, turning it into a prefab. I ignore this message, and I delete this game object. So now we have a different
animation for when the Quitter gets electrified
and when it gets burned. I open KriterO script, and we will do the same thing we did with zapped effect before. First, I create a
reference to the prefab. Game Object, I call, for example, burn effect. Down here on collision
Enter two D, if critter collides
with a bullet, we create the zapped animation. We destroy the critter and
we play squished sound. Else, if the critter
collides with the player and touches the hot engines,
we just do the same thing. But the animation that we
play is burn effect instead. I save that. Assets prefabs, critter prefab selected, and I find the new
burn effect field. I lock the inspector
effects folder, and I drag Critter one burn
into burn effect field. I unlock the inspector. Now we have two different
ways to destroy the critter. Maybe I could add more, maybe even a different
animation for each weapon type. We have a lot of freedom here at this point to do
whatever we want. Do you have any
other creative ways how to destroy the poor cutters? Let me know in the comments. I go to edit project settings, Physics two D tab. I will enable
interactions between player and critter colliders by ticking this checkbox in
layer collision matrix. For this to work,
Kriter needs to be on the Critter layer here and player needs to
be on player layer. I go to Assets Audio and I pick another sound from
the sound effect back, we download it earlier. I'll use, for example, 51 flee. I drag it into the hierarchy. Output will be Effects
AudiomxerGroup, and I uncheck play on Awake. I put it under audio Manager to keep this area
clean and organized. I open audio Manager script, and I create a new audio
source variable I call burn. Save that. I have my
new field here and I drag the sound I want to
play when we burn a Kritter. Now I can go here inside
Kreuter one script, and I play audio Manager
dot instance dot BRN LT. Save that and play. Making things interact like this really brings the
game to another level. We could do a challenge, destroy as many critters as
possible in a time limit, or maybe even a better idea. If we destroy a certain
number of critters, big Boss comes to check
what is happening. Okay, I actually like this idea, so I will quickly go and prepare some files
for animation. I'm using free dragon
bones software to rig and animate
my characters, but you don't have
to worry about that part because I'm
giving them to you, export it as a
standard spreadsheet. As I play, you will notice all these scriurs and asteroids. All the things created by Object spawners are on the
top level in hierarchy. I would prefer if they were parented under
Object spawners and collapsible to keep this area clean and easy to
navigate around. Object spawner and
Greater spawner both use the same script, so I can just open it and
make that change here. I exit play mode first. We are creating the objects like asteroids and critters
using instantiate function, which clones the object, the prefab, we pass to
it as a parameter here. We also pass it position
where we want it, and what rotation
should the object have. I can pass it one more
argument for parent. So here, if I pass it, the transform parent of the object this script
is attached to. Objects created here will be children of that parent object. Save that and if I play, asteroids and critters are no longer cluttering
top level hierarchy. Instead, they are under their respective
spawner parent objects. Apsib like this, much better. Now, back to my
secret boss idea. So players of our game might
not even see this boss. It's optional, and
it will only appear if we destroy a certain
number of critters. How to implement
that? At this point, we did so many things with code. We are slowly turning
into game developers, so it should be easy.
24. Secret Boss: You can download
Bs one Spriadseet in the resources section below. I drop it into my art folder. I open Sprite Editor, and in this case, I prepared
two animations here. One for charge and
one for idle state. Each of these states
has the same number of frames but different
frame width. So I will just let Unity
auto slice each frame. You can see it did a great job. We can use this. If I zoom
in, it's very blurry. It's because of this
max size field. I need to make sure this value is more than the
width of the image of the sprite sheet to
allow the image to be rendered at full
size. I click Apply. Now if I open Sprite Editor, we have large and
sharp frames, perfect. I can see my sliced frames
here, and as usual, I will just drag one
of them out into the scene. Move it here. I will rotate the set by -90
to make it face the player. Sorting layer will be objects. Now you can see
this is a big boy. We have to be more careful now. This is not just a
harmless space critter. I rename it to Boss one. With Boss one selected here, I go to Animation tab
and I click Create. Inside Assets Animation, the first animation will be called Boss one underscore Idol. Idle animation should
start here from frame 39, so I click it and I go all the way down and I shift
click the last frame, selecting all the
frames in between. With Bos one idle here, I drag all of these
frames on the timeline. I play it so you can see what
I did for idle animation. I was trying to
create a bit of a creepy space insect
kind of thing. It's a huge creature, so it will have to move a
lot slower than this. Maybe 25 samples. I click here and
create new clip. The other animation will be
Boss one underscore charge. I click the very
first frame here, and I go all the
way to frame 38. Shift click, selecting all
the frames in between. This animation will play when
the boss is moving fast. It will play when the boss is entering or
exiting the scene. Idle will play when the boss is patrolling around the
visible game area. Okay, so we have these
two animation states. I open Animator tab. I select boss one to make
sure I see its states. Window animator, if you
don't have the tab open. Okay, let's move
this around a bit. I right click Boss
one charge state, and I set it as
layer default state. Boss will start in charging
state when it spawns. We will switch
between charge and idle depending on the parameter. Type will be Bolin, which can be true or false, and I will call it charging. I right click Boss one
charge, and make transition. I drag the transition
arrow to boss one idle with the transition
arrow selected, inspector tab, I anti has exit time and settings transition
duration will be zero. I want an instant transition. I scroll further down, and in conditions, I click Plus. Boss will transition
from charge to idle state if charging
parameter we defined is false. Now I write clickidl
state, make transition. I drag transition
arrow to charge state. With that arrow selected, again, I unchecked has exit time and transition
duration will be zero. Conditions plus, and we will
transition from idle to charge state I charging parameter is true,
both selected here. If I play, we are in idle state because in animator
window, charging is false. I check it, setting it to true, and you will see both will transition into charging state. Charging true or
false determines which one of these
animation states is active. Okay, that works.
Inside assets scripts, I create a new folder for NEMs. Inside, I create a new mono behavior script called boss one. I attach Bos one script as a component on Bos
one Game Object. I open the script. We will need to start and
update functions here. I have an idea how I want this
particular boss to behave. I will just write
it and figure it out as I go. Let's
start by this. I go to Asteroid script and I copy this code block that
will move it to the left, and it will destroy
it when off screen. But the boss will not just
flow by with world speed. It will hover around. It
will stay around longer, keeping up with the player for some time, so I
delete this part. We will adjust this
code some more. But first, let's define
some helper variables, private float speed
X and speed Y. And private bullying,
I call charging. Speed X will play a role
in calculating move X, the horizontal component
of position vector, and we will also have move
Y because the boss will sometimes move up and down
instead of left and right. For now, I will just keep
it simple like this. I make sure I account for
Delta time because I want the movement speed
to be the same on all devices regardless
of frame rate. I use move Y as the vertical component
of the vector here. So with this setup,
let's say at first, the boss will not move at all. Speed X is zero, and all of these values multiplied by zero
will always be zero. As long as speed X is zero, we are not allowing for
any horizontal movement. Speed Y will be, for example, a random value between
minus two and plus two. Positive values here will
make the boss move up. Negative speed Y will
make the boss move down. If I save and play, boss will move
randomly up or down, but not left or right. What if I want the boss
to bounce up and down, change direction when it reaches the edge of
the screen vertically. I can say if transform
position Y is more than three, or if it's less
than minus three, which means if the boss moved more than three units vertically from the
center of the screen, simply invert speed Y
to the opposite value, making the boss move in
the opposite direction, multiplied times minus one. If speed Y is positive, minus times plus is minus, this line will make it negative. If speed y is negative, minus times minus is plus. This line will make it positive. It will always switch it to the opposite of what
it currently is. I can test if it works
by playing the game. And yes, the boss will just
endlessly bounce up and down. Perfect. What else? I want the boss
to switch between patrol state where it
moves up and down, and charge state where it
moves fast from right to left. For patrol state, we want speed X zero and
random vertical speed. For charge state, I will do a static horizontal
speed of five. Maybe I remove minus here, and instead, I do
negative five here. And while charging, there
will be no vertical movement. Okay, so patrol state, both is moving up and down. Charge state, both is moving
to the left at a high speed. We want to start
in charging state. I save and play and Boss is
moving fast to the left. Great. We don't have to care about the actual
sprite animations yet. We will deal with
that in a minute. If I start in patrol
state, save and play. Boss is moving up and down. This works. We will
start in charge state. We will have two
helper variables, switch interval and switch timer to help us randomly cycle
between these two states. When we enter paterl state, we want to stay in
it for 1 second. We set switch timer
to the value of switch interval because we will be counting
down towards zero. When we enter charge state, we will set interval
to some other value. But for now, I also
leave it at 1 second. Again, we reset
switch timer back to the interval value
so that it can count again down for
the next switch. Update function that runs
over and over for each frame. Whenever we switch state, we are setting switch timer to the value of switch interval. Let's say 1 second. As the update function runs, as long as switch timer
is more than zero, we are decreasing it
by real time that is passing by time
dot Delta time. Else, when timer
finally reaches zero, we switch to the other state. If charging from
line seven is true, we are in charging state, so we will switch
to patrol state. Else, meaning we are
in patrol state, we enter charging state. Inside both of these,
we are setting timer back to interval value, so this will become
more than zero, and we are counting down
towards the next switch again. So now we can just
define whatever we want the boss to be doing
in each of these states. Inside patrol state,
charging will be false. Inside charge state,
charging is true. Okay, so if timer
is more than zero, keep decreasing the timer. When it reaches zero,
switch to the other state. Reset timer and start
counting down again. Now we are switching
every 1 second. So I save. I move the
boss more to the right. I play, Bs is charging,
patrolling, charging, patrolling, charging, patrolling,
charging, patrolling. So instead of switching
every 1 second in regular intervals, we will stay in patrol state for a random time interval
5-10 seconds. Charging will last between
two and 2.5 seconds. Also we created a separate
animation for each state, and we are transitioning
between them based on the charging parameter
inside animator. Let's also make sure that
different animation happens. I need access to this animator component from this boss one script component. We are inside right now. As usual, private
animator, I call animator. Start function,
animator is equal to get component animator.
I have to do it before. We call this function because
inside this function, we will use the
animator variable to call built in
set Boole method. The animator parameter
that handles animation state transitions
is called charging, and here I set it to false. Here when we enter charge state, I set it to true. Save that and if I play, we start in charging state. Patrolling will use
the idle animation. And eventually the boss will
charge off screen. Perfect. I select boss one,
add component, circle collider two D. I
click here to edit collider. I make it smaller around
this size looks good. I also add rigid body two
D. Gravity scale is zero. If I play, Boss will get stuck on
level boundaries. I move it inside. I shoot. And unities built in physics
system will do its thing. First of all, we want to prevent the boss from rotating
on collision. Constraints, freeze
rotation, z, prefabs, weapons, I have bullet one tagged as bullet and
on bullet layer. Boss one selected layer, add layer, and I call it boss. I selected here, and
I set layer two boss. Tag, add tag, plus this
tag will be called boss. Boss selected here, tag is boss. Rigid body to the component
on the boss, mass field here, and I will make the
boss much heavier, so it cannot be
pushed by bullets. Okay, at this point,
I need to set up which colliders
interact with each other. Edit project
settings, Physics two D. Bs is on boss layer.
We see the column here. I disable interactions between obstacles and boss for now. Also between level
boundaries and boss, and Critter and boss will also ignore each other when
it comes to collision. Boss versus boss also disabled in case we spawn multiple
bosses at the same time. I go inside phaser
bullet script. If bullet collides
with an obstacle, critter, or a boss,
deactivate the bullet. Boss select it, move tool, and I move it somewhere here. It will now ignore
level boundaries, critters and asteroids. It will interact with
bullets and the player. Bullets do get destroyed
when they hit the boss, but the boss is still moving
a little bit on impact. I increase mass to 50 to make
it a proper heavy weight. Okay, it seems it has no effect. Bullet is still pushing the boss around in
a very subtle way. It looks like I will have
to give bullet prefab, rigid body two D as well. Gravity scale zero. Mass is just a 0.1, and freeze rotation
around Z axis. Okay? Now, finally the bullets collide and get deactivated
when they hit the boss, but the boss is not being pushed around by
the bullets at all. This is starting to
look pretty good. I could reuse the sounds
we already have in game, but I want a different
sound when we hit the boss, so we get an audio confirmation
of what we are hitting. I will grab two more sound
effects from the effects pack, 52 dive and hit. You can use any
sounds you want here. One will be for when we hit
boss' impenetrable armor, another sound for when
the boss starts charging. I drag them into the
hierarchy, and as usual, with both of them selected, I set output to effects, and I uncheck play on awake. I parent them under
audio manager. I open audio Manager script, and I want to create two new
fields for these sounds. Public audio source
I call hit armor. Another audio source
I call boss charge. Inside boss script, when
boss enters charge state, we call Audio manager instance. Play modified sound,
and the sound we want to play is audio manager
instance, boss charge. I will give the boss a public function I
call take damage. It takes one parameter, integer, I call damage. When boss takes damage, we will play hit armor sound. We will also reduce the lives of the boss by the
incoming damage. Up here, I give both private
integer, I call lives. Inside start method, we set
lives to, for example, 100. Private void on
collision enter two D. If the object bus collides
with has a tag of bullet, we call T damage. Our basic bullets can't
penetrate boss' armor, so we pass damage of zero. We have a system
ready for later if we decide to add more
powerful weapons, or maybe our phaser
weapon can level up, and eventually it might be
able to damage the boss. We have a simple
system for that here. For now, the boss is
invulnerable. Save that. On audio Manager, I have
these two new fields, so I drop whatever sound
I want to play when boss gets hit by a bullet and
when boss enters charge. Because we are putting
a different sound here, we know exactly
when we are hitting the boss and when we are
hitting asteroid or a critter, just from the sound effects
that play on that collision. It's one of the things we can do to increase gameplay clarity. I also want asteroids to be completely destroyed when
they collide with the boss, making the boss feel
even more powerful, like an unstoppable
force of nature. Inside asteroid script, we are checking for
collisions already, but I will refactor this a little bit to avoid
code repetition. Same as we just
did for the boss. I create public void function, I call take damage. That takes integer, I call
damage as a parameter. Inside, I simply copy
and paste all this code. Which means I can
delete it here. If asteroid collides
with player or bullet, we call take damage, and the damage will
be one for now. Else, if asteroid
collides with a boss, asteroid will take ten damage, which will take its lives
below zero immediately, and it will trigger
this block of code. I'm missing a bracket
here. That's better. Before, I disabled collisions between boss layer
and obstacle layer, so I need to go to
edit project settings, physics to the tab, and I enable obstacle
versus boss interactions. And one more thing, we created
this take damage function. It takes income
and damage value. I sets material to white to
make the asteroid flash. It plays a sound, and it reduces lives by the
income and damage value. If lives are less
or equal to zero, we destroy the asteroid
and we play animations. Now if I save and play, Asteroids will get destroyed
on collision with boss. In player controller script, if player collides
with obstacle, player takes one damage. Else, if player
collides with boss, player will take five damage. Currently, player has
only three lives, so colliding with the boss
is an instant game over. So when does the
boss get summoned? Let's handle that logic from
inside game manager script. This is a secret boss. Some players might not even
see it because it will only come if we destroy a
certain number of critters. We will have a public integer
called critter count, keeping track of
how many critters got burnt or zapped
by the player. Inside start method, we
set critter count to zero. We will also need a reference
to the boss prefab. Private game object I
call boss one. Save that. I drag Boss Game Object
into Assets Prefabs folder. I can delete it from here,
game manager selected, and I drag and drop Boss one prefab into Boss one field here. So I have this Boss one
reference here and ready. Inside update, we check if greater counter
is more than ten. If it is, we reset
counter to zero so that it can count again
and we spawn a boss. Instantiate. We used
it many times before. We pass it the game object, we want to cloud,
boss one prefab, in this case, we need to pass it position where we
want to spawn it. So I want it to be just
off screen to the right, 15 units on the horizontal
X axis to the right from the center of the scene and exactly in the
middle vertically. So zero here. We
also need to rotate the boss because our spread
sheets are not facing left. We can rotate it by using
quaternion oiler like this. X, Y, and Z rotation. Let's leave it at 000 for now, just so you can
see how it works. Just for testing, we will spawn a new boss every time
we fry two critters, so we will probably get multiple
bosses at the same time. We will make this
value different for the final game, of course. Finally, I also have to
go to Critter Script, and every time
Critter collides with a bullet and gets destroyed, we take game manager
instance dot Critter count, and we increase it by one. We also increase it
when the critter gets destroyed after touching
players hot engines. These critters have no armor, so they get destroyed easily. One, two, and the third
critter is more than two, so boss gets spawned. And if I destroy more
critters, you get more bosses. You can see the
rotation is wrong. So depending on which direction is your boss spread
sheet facing, we will use this
quaternion oiler to rotate it accordingly. In my case, -90 degrees on the Z axis will make the
boss face to the left. Every time we destroy more than ten critters,
we spawn a boss. I save that and I play. I have to destroy a few more
of these and we get a boss, and it's rotated -90 degrees to face the
player to the left. I exit play mode,
critter spawner, and the first wave will be one critter every
second for 3 seconds. And then the next
wave will spawn one every 0.2
seconds three times. Then the cycle will repeat. The boss will spawn when we destroy more than 15 critters. I play to test it to see
if everything works. This boss is very custom. Other bosses in our game might work in a
completely different way. I play for a while to see what exactly I want to do
here to finalize this. First of all, I want to
make sure the boss doesn't go in patrol state when it's
behind the player like this, when it moves past the
player horizontally. I want the boss to just charge off screen if it moves
past the player. Another thing is the way it interacts with our
super speed move. When the boss is
patrolling up and down, it's not affected by player
entering super speed at all. It just keeps the same
horizontal position. And if the player
is in super speed, when the boss is charging, the boss moves unusually fast. It doesn't feel right.
I want to flip it. I want player super
speed boost to have no effect on the boss while
it's charging and have some, but reduced effect on the boss when the boss is
patrolling up and down. I want the boss to feel like it's backtracking a little bit to stay on the screen longer to pose threat for the player. But if player uses super speed, the boss is not able to
back up at the same speed, and our super speed
move should have some reduced effect on
boss' horizontal position. I'm not sure if I'm
explaining it well, but let's just implement it and it will be very
clear what I mean.
25. Cleaning and Refactoring: C. On Game Manager, I set world speed down to one. Let's do some refactoring to
make our code a bit cleaner. We have this world speed
value here that affects how fast the background scrolls and how object
spawners count time. Instead of accessing it directly from the player when
we enter boost, let's make sure this can only be set through a
dedicated function. Public void set world speed. It takes one parameter.
Type is float. I call it speed. Inside,
we set world speed from line nine to speed that was
passed as a parameter here. We can use this public
static instance of game manager to access
set world speed function. I open player controller script. I will delete boost and boost
power variables completely. I will keep this
bullying I called boosting. I will make it public. This broke our code, of course, so I go down here and
when we enter boost, instead of setting
boost to boost power, I will set world speed. Game manager dot
instance dot set Wold Speed maybe
speed seven for now. That way, in our other scripts, instead of using world speed
and player boost value, both of them, we can
use world speed only. Copy that, and when
we exit boost, instead of this line, we set world speed
to one like this. When player takes damage and player health is less
or equal to zero, we call exit boost and I will
set world speed to zero, which will make the
background stop scrolling and object
spawners will stop spawning. Save that I have some more
errors in console because we make objects float by based on player boost value,
we just delete it. So let's fix them one by one. It's easy. Inside
Greater one script, I remove this bracket. I completely remove player
controller instance boost because we deleted
that variable. So the speed at which objects float in space will
depend only on one value on world speed because player boosting is affecting the world speed value already. Save that. I also have the same error in Parallax
background script. So I open it. I
deleted player boost, so I deleted here for now. Save that. We have the
same error in asteroid. This line will be only
world speed times Delta time like this. Save that boss one scraped. I delete this. We will come back here and finalize boss
movement in a minute. Save that Object spawner
will account for world speed to
spawn game objects faster if the game world
is scrolling by faster. Safe. We have the same
error in Wale script. I will actually completely
delete this script. Scripts obstacles swale delete. Okay, we are almost
done with this cleanup. Lost whale script. This line will be only world Speed TAMs Delta And I have one more
Csolog inside boom script. Again, world speed TAMS Delta You probably notice
that we have this code that moves the object to
the left as the world is scrolling by in multiple
different scripts. Code repetition like this
is not the best practice. It would be much cleaner if we just put this
code somewhere in one place and apply it to all objects that we
want to float in space. Before we do that,
I save my changes. I can see there are no
more console errors. I play just to check if
everything is working. I player entering boost affects world speed and if world speed affects
position of asteroids, critters, and so on.
It's all good here. So player controller
inside fixed update, I change this value to 0.5, and I will make players spend
energy a little bit faster, maybe 0.5 per fixed update. Now I play, if I
enter super speed, we are deleting
energy much faster and we actually have to
manage it as a resource now. As we said before, we have this same code here
in asteroid script. We have that exact same code
here in critter script, also here in lost whale script. Similar code here
in Boss one script, and similar code
here in Boom one. That's a lot of unnecessary
code duplication. We will clean this up.
Before we do that, let's finalize the boss
movement and behavior.
26. Boss Behaviors: C. I want boss to be aware of player's
horizontal position so that it can react to it. I will put it in a variable. I call player position. It's equal to player controller, instance, transform position X. Boss is switching between
patrol state and charge state. I only want the boss
to patrol up and down if it's in front of the player to the right of the player. So here we only enter
patrol state if boss is currently charging and if player is to the
left of the boss. And here, where the boss
bounces up and down, I can do else I When boss' horizontal position is less than player's
horizontal position, when boss moves to the
left of the player, in case of this game, that means boss is behind the player. I want the boss to just
charge off screen instantly. I make boss enter charge state. Because of these changes, boss can now enter charge state while
it's already charging, so I don't want the
charging sound effect to play in that case. Only play the sound if
the boss is not charging. Play it only when the boss enters charge from patrol state. Okay, so let's test it. I need to destroy a
few more critters, and here's the boss. If I enter super speed, the boss is keeping its
horizontal position. We kind of want that. We want the boss to backtrack a little bit to
stay around longer, but boss shouldn't be able
to speed up and keep up with the player boosting completely,
maybe only partially. Player has this static
bullying called boosting. When we enter boost,
we set it to true. When we exit boost,
we set it to false. So inside boss one script, we need to know if
it's true or false. I will actually put
that line down here. I want bosses' horizontal
position to be completely unaffected by players boosting
when boss is charging, and I want the boss' position to be only partially affected by players boosting when boss
is patrolling up and down. So let's declare move X here, and its value will
depend on a few things. If player is in super speed, if boosting is true, and if the boss is not charging, Bosses horizontal
position will be affected by world speed
Tim's Delta time, same as other floating objects, but only half of it, making it seem like the
boss is still able to backtrack and keep up
at least partially. Else, if player is not
boosting or boss is charging, MVX will completely
ignore world speed. It will be unaffected. I think this should do
what I wanted to do, but let's play and test it. I destroy critters until
it summons the boss. If player enters super speed, boss' horizontal position is affected, but only partially. If player gets to the boss
and moves to the right so that the boss'
horizontal position is less than players, it will immediately trigger the boss to charge off screen. Okay, I will try again. I move all the way to the boss. And yes, and the other
one a bit further. Yes, it triggered
charge. That works. One thing I notice is
that we aren't getting any sound effect when
boss enters charge. It's because this line needs
to be above this line. If I put this all the way up, it will work as intended. I will make the
boss charge faster, maybe minus ten F. This will make it a bit
more difficult to avoid. It will stay in charge state
between 0.6 and 1.3 seconds. I go to Assets script, and I put rater one script
inside enemy's folder. I go to prefabs and
Bos one prefab. Sort in layer is objects, or layer is two. Crater one prefab,
sorting layer is objects, or inlayer is three. I go to Assets Audio folder and I drop two more
custom sounds in. You can download them in the
resources section below. One will be a bit of a scary horror sound
when the boss spawns and another sound I call Swipe for when the
boss enters charge. As usual, I drop them
here into hierarchy, turning them into game objects with audio source component. With both of them selected, I set output to effects, and I uncheck play on awake. I parent them under
audio Manager. I open audio Manager script, and I add one more
audio source variable for boss spawn. Save that. Audio manager selected, and
I have this new field here. I will drag and drop boss
spawn in this field, and I will drag and
drop swipe to replace the existing sound we
are using for Bs charge. Swipe sound is more subtle
and I think it fits better. Boss script enter charge state. And here I don't
want to use play modified sound, just play sound. Up here inside start method, when the boss spawns, we will play boss spawn
sound effect like this. Note, I'm using play sound, not play modified sound
for this one as well. I play to test this and I should get a new horror
sound when the boss spawns. Yes. We are using a new
sound when the boss charges. Yes, that works.
27. Utility Functions: Inside assets scripts,
I create a new folder. I will call it Utils. We will have our helper
utility scripts in here. Inside the folder, I create
a new mono behavior script. I will call it destroy
when animation finished. I open it. If I go
to effects folder, we have this boom one
script which destroys the game object after Animator
played all its frames, and it also moves the object
along as it floats in space. I want to do this
in a cleaner way. I take all this code, and I copy all of this code, and I paste it here. Destroy when animation
finished is my utility script. I get reference to
animator component, and it will destroy game
object we attach it to after all the frames of the current animator
state finished playing, after the current animation
finished playing, basically. So now I have this simple
script with a single job. I can attach it to
any object I want, and it will do
that simple task B to boom script and
I delete this. If I go to prefabs effects, we have these four
prefabs that all should destroy when they display all their
animation frames. I select Boom one and
down here at component, and I find the script
we just wrote, Destroy when animation finished. Boom two prefab, add component, destroy when animation finished, creater one burn, add component, and I also added here. And finally, creater one zapped, and I add that
component as well. We need to make sure
that objects we add this script to have
animator component on them. Okay, another utility
script like this. Inside script utils, I will
call it float in space. I open it. I go back
to Boom script. Its secondary job was for the
object to float in space, considering the
current world speed, which itself is affected by
player's super speed move. I simply cut this code block
and I paste it in here. The job of this float in space utility script
will be simple. Whatever object I attach it to, it will make it
animate from right to left in regards to the
current world speed. We took code from boom and we split it into two
separate utility scripts. I save that and back in unity, prefabs effects if I play, because of the fact that I removed all the code
from Boom script, these four animations
will now be static. They will not flow along with the side
scrolling game world. I exit play mode,
boom one prefab, add component, and I add float in space script
we just wrote. And I remove boom script. Boom two prefab, I
remove boom script. And I add float in space script. Creature one burn prefab. I remove boom script. I add float in space script. Creature One zapped prefab. I remove boom script. I add component float
in space script. Now each one of these
four prefabs has a separate script with
a very specific job. This script will make it auto destroy when it
finished animating. This script will make
it float in space. If I play, everything
should be back to normal. Objects are auto destroying
and floating. That worked. I go to prefabs folder, well one prefab and I delete it. We are not using
it for anything. I open asteroid script. I want to use this
floating space script on asteroid so we don't
have to declare it here. I can delete this
entire update function. Save that. Prefabs, asteroid, and add component, and I
add float in space script. Now, if I play, asteroids are floating and react to player boost as they
should. That's good. Lost well prefab,
lost weal script. I will delete all
of this code here. Save that and back in unity. Lost Weal prefab add
component, float in space. Creator prefab creator script. This code here is what
I want to delete. Now, if I play, critters are
not floating as they shut, so I exit play mode. And on reterPrefab at
component, float in space. Now if I play, Kriters movement is influenced by
world speed again. So this is how we can take a
piece of code that appears on multiple objects and
turn it into a component. Creating modular, reusable and focused scripts that handle specific tasks makes our project easier to maintain.
So let's do one more. I go to Scripts Utils and I create a new
mono behavior script. I call, for example,
flash white. I opened the script, asteroid and player flash
white on collisions. I want to have a script that
I can attach to anything, and the script will make
it flash like that. Let's see how we did it
inside asteroid script. First, we declare variables for default material
and white material. White material is
serialized field, so it's visible in Unit editor, and here we dropped
the material asset in. I don't really want to have to drop the asset in like that, so we do it differently. I copy these two lines of
code and I paste them up here inside my flash white
utility script like this. I can delete this
entire update method. I can see we won't need it. Also, this field doesn't have to be visible in Unity inspector, so I delete serialized field. I will need a reference to
sprite renderer component. Inside start method, I
get component and I set default material to the value of Sprite render material field. White material will have to be handled differently because
as you can see here, previously, we just drop the material here
into this field. This file itself sits inside
resources materials folder. I want this script
to be reusable. So whatever object
I attach it to, I wanted to find that M white material file
in that folder. One way to do that is to say
resources load material. And the path inside
resources folder. I put it inside
materials subfolder, so I have to say
materials slash M white. So instead of
dragging and dropping that asset into a field
visible in unit inspector, I'm loading it
directly in a script. Now that we have this, I
create a public function. I call, for example, flash. In there, I will use
these two lines of code. And we also need
reset material block that I defined down here. So I copy this as well. When I'm using I Enumerator, I need to make sure I have system collections
name space up here. When we call flash,
we set material to white and we start coroutine
called reset material, which will wait 0.2 seconds, and it will set material back to the original
default material. Here again, we extracted a very specific task into
standalone reusable script. So I can go inside asteroid
script and I delete this. I can also delete this. I delete this line of
code and these two lines. Instead of all of that code, I can now simply
declare a variable that will point to that
flash white script, which we will attach as
a component in a minute. So type is flash white. Variable is flash white with
a lowercase F, for example. Inside start, I just point the variable towards it
using get component, and at this point, I can call
this public flash method. I will call it whenever
asteroid takes damage. Flash white component
dot flash like this. I save that and inside
prefabs folder asteroid prefab and add
component flash white. Now, if I save and play, Asteroids flash white when
they get hit that worked. Let's put flash white
script on player as well. Inside player controller script, first, I delete this. I also delete this and I don't even need sprite
renderer reference here. I delete it up here as well. I delete these two
lines of code, and this entire block, no more code repetition. I save that, and now when I play and player collides
with an asteroid, player will not flash white. I exit play mode, player game object selected, add component, flash white. After we add this flash white
component on an object, we just have to create a
variable that points towards it. I want to access flash
white script component, which sits here from player controller
script component we are inside of right now. So once I declare this variable, I use it component as usual. And now I simply call this public flash method
to make it do its thing. Now here, when the
player takes damage, take its flash white component and call the public
flash function. Save that and play. And now, when player
collides with asteroid, player flashes white. I would follow the same
process if I wanted the boss or the quitter to flash white when it collides
with something. Inside assets, prefabs, I
create a folder I call enemies. I put quitter one
and boss one inside. I go to script effects, and we removed everything from the boom script so I can delete the entire
effects folder here. Inside scripts Utils, we have our three new utility scripts. Whenever I want
to make object or to destroy when it
finished animating, or I want to make object
flash white on collision or float in space and react
to the current world speed, I can simply attach one or
more of these scripts to it. Each of these scripts has
only one clearly defined job, making our code easier
to read and understand. We are also reducing code repetition
throughout the code base by declaring that functionality only once and then reusing it. I go to assets scenes. I open Level one complete scene. For this animated whale, we are actually reusing
the lost Wale prefab. We just flipped it and
remove the smoke effect. We added floating
space on the prefab, so now it's on here as well, but we don't want it here. So with just this copy
of a prefab selected, I will remove floating
space script, just from this copy, not
from the prefab itself. We can see that these
changes were done here and that's how this object
differs from its prefab. I enter play mode just to make sure the whale doesn't
float off screen. Okay, good. Save
changes to the scene, and I go back to
level one scene.
28. Code Consistency: C. If I expand player game object, it holds this weapon
object where we can place all different kinds
of weapons layer can use. So far, we built one. I called it phaser weapon. It produces phaser bullets. In assets prefabs, we have a
prefab for that bullet here. So phaser weapon has
phaser weapon script, which holds the current
stats of the weapon. So far, we have only
speed and damage, but we can add many more. Phasor bullet script sits
on the bullet prefab, and we are accessing that phaser weapon speed value through the static
instance we created. Accessing the speed value like this has an
advantage because if we implement weapon
leveling system and weapon speed increased, that increased speed value
can make the bullets move faster immediately because
of this connection here. We are not really
doing the same thing with phaser weapon damage value. When it comes to collisions and taking damage across
our code base, I wasn't really following a consistent pattern as I
was developing the project. So now it's time
to clean this up. Let's have a look at how
I implemented it here. We have asteroid
that can take damage from bullets and it can
also damage the player. Notice that when asteroid
collides with a bullet, it's not looking at what
damage the bullet is dealing. It just takes a hard
coded damage of one. This value on phaser
weapon script doesn't play any role at
all in that interaction. That's a problem
because if we have a weapon leveling system and
this damage value increases, as we are leveling up
our phaser weapon, it would have no effect on how
fast we destroy asteroids. This speed value, for example, is directly connected through this phasor weapon instance
to the speed value. So if speed increases, bullets would move faster. We need to make sure that damage is connected in the same way. Here on Phasor bullet script, when bullet collides with
object tagged as obstacle, which is the asteroid, for example, or with
critter or boss, bullet gets deactivated and put back to the pool of available
ready to use objects. I want to handle this
a bit differently. Inside this on collision
enter function, if bullet collides
with something, we will pass the weapon
damage to that object. I delete this and I say, I collision game object, compare tag, is obstacle. When bullet collides
with an obstacle, this asteroid is tagged
as obstacle here, I might have to change
this tag later. For now, we will just remember
that the only thing in our game we tagged as
obstacle is this asteroid. So when bullet collides
with asteroid, we want to access the
asteroid script on the one it collided with and pass that script the damage
value to handle. So helper variable
type is asteroid, and I call it asteroid. We want asteroid
script component on the obstacle asteroid, this bullet collided with. We get it by taking the
collision dot game object, the object tagged as obstacle, bullet just collided with, and we want this specific
asteroid script component. So get component
asteroid like this. So on collision with asteroid, we find its asteroid script
component. This one. The reason we need it
is when I open it, it has this public
take damage function. I want to be able to call
that function so that I can pass it phaser
weapon damage value. So if we are able to
find that component, I take that asteroid
script component and I call its public
take damage function. It expects an
integer, for example, too, but I don't want
to hardcode this value. I want to pass it whatever value Phasor weapon is
currently dealing. So this damage
value from line 11, I can access it by saying phaser weapon,
Instance dot damage. Same as we are accessing
weapon speed here. Okay, so when bullet
collides with something, we check, does it have
a tack of obstacle? So far, we have only one
obstacle, this asteroid. We get a reference to its asteroid script component and we take its public
take damage method. We pass it the current
phaser weapon damage value, and we let T damage
function handle the rest. Doing it this way is
better because if our phaser weapon levels up and its damage value increases, we will be immediately dealing
more damage to asteroids. Right. So now that we
created kind of a template, how we will handle this,
let's make sure it's consistently applied for
other objects as well. Another object that collides with a bullet is this critter. I open Critter One
script on collision, enter to the function here, and we are destroying the
critter in two different ways depending on if it collides
with bullet or with a player. Critter is a bit of a special
case because taking any, even the smallest
damage will destroy it. So I will leave this code as is. I could refactor it, but this code here is serving its purpose well
enough at this point. We might come back
to it if we need critters with more
lives, for example. Another game object
involved in collisions, dealing and taking damage
is our Bos one brief app. I open Bos one script to
see what we did here. Here we will follow the same pattern we did
for Phasor bullet script. On collision entry function
is for dealing damage. Here, we will pass boss' damage along to
objects it collides with. We will not use it for
take in damage at all, so I delete this code block. Bos has a public take
damage function, so we should use that if we want the boss to actually receive
damage from somewhere. Boss will receive damage
from Phasor bullet. So inside phaser bullet script, we will do the same thing
we did for asteroid. Else, if the object bullet collided with has
attack of boss, we want to access this
boss one script component. Type is boss one, and I call it, for
example, boss one. It's equal to the object
bullet collided with that has a tag of boss dot get
component, and boss one. We want this component here. If we found that component, we call its public take
damage function and we pass it the current
face weapon damage value. One more thing, we want to deactivate the bullet
if it collides with obstacle and we also want to deactivate it if
it collides with boss. After we pass the
damage value here, we then let take
damage function inside Boss one script to handle the
rest of that interaction. I see that function
here on line 77. As you can see, it will play hit armor sound and it
will reduce bosses' lives, but there is no logic
to destroy the boss. So far, we don't have a weapon
that is able to do that, but maybe we will implement boss destroying mechanics later, either by giving player a super weapon or by allowing
Phasor weapon to level up. For now, I will leave this
code block like this. The most important
thing here is that we connected this
damage value here. Which has been passed along here from the damage
value on the weapon. So if this damage
value increases, boss will be taking more damage. Here inside asteroid script, you can see that we are
using on collision enter two D for asteroid to receive damage, but
we don't want that. Asteroid has a public take damage function through which it can receive incoming damage. On collision enter two D
is for dealing damage. That way, differently
sized asteroids can deal different amount
of damage, for example. Also notice the problem here that when asteroid
collides with Bs, it takes ten damage. But when I open player
controller script, when player collides with boss, player takes only five damage. That's why we want
to clean this up. We don't want some
hard coded values that I came up with randomly when
I was writing this code. We want to look at boss object, check how much
damage it's dealing and apply that damage
value consistently, no matter what it collides with. I go to asteroid Script. As we said, to follow
our pattern on collision enter two D
is for dealing damage, and this public take
damage function is for receiving damage
from other scripts. We know asteroid can deal
damage to the player. If asteroid collides
with a player, we want to get access to this player controller
script and pass a damage value to its
take damage method. We defined here on
line hundred oh eight. I need to be accessing
this function from other scripts to deal
damage to the player, so it needs to be public. Also here, as we said before, this is for dealing damage, not for taking damage from other objects, so I delete this. It's not a hard rule. It's just a pattern
that I decided to follow in this project to
keep our code more uniform. Back to asteroid here, asteroid deals damage
to other objects. If asteroid collides
with player, I need access to this player
controller script component. I will call it player. It's equal to the
player game object we collided with dot get component, and we find that player
controller component. Now that this helper
variable we called player holds a reference
to this component. If we actually found
this component, we can use it to call its
public take damage function, and we want to pass
it some damage value. Player controller,
take damage function, we receive that
damage value here, and it will handle the rest of that interaction based on
whatever code we put here. Instead of this hard
coded value of one, we want to pass a
value from variable. Up here, I can remove serialized field from lives to hide it from Unity inspector, and I create another private
integer I call damage, defining how much damage will this asteroid deal
to other objects. Inside start function,
I set lives to five and damage to one for now. Or maybe for testing,
I set it to two. So down here, when asteroid
collides with player, we will pass to damage
to the player or whatever value the damage
variable we just defined is. I play to test it.
If everything works, players should lose
lives on collision with asteroid because we set
asteroid damage value to two. Yes, single collision
takes two lives. Second collision takes
player lives from plus one to minus
one. This works. Back to asteroid script, I will set this
damage value to one, but we could, for example, connect it to the mass field on asteroids rigid body
component and make them deal damage based on
their size or something. For now, I leave it like this. So as we said, to keep
our code consistent, we try to follow a pattern
where we use on collision enter to the function to deal
damage to other objects, and we will have a public take damage function to
receive damage. Phasor bullet script is
following the same pattern. Bullet doesn't need
take damage function. It doesn't receive
damage from anywhere. But here in this block, it's dealing damage to
obstacles and both. I just do a quick play test to make sure it's all working. We did a lot of refactoring, so just a quick test if we can still destroy asteroids
with bullets. Okay, all good here. If player
hits asteroid like this, player is receiving damage, but you can see that
asteroid is not flashing. Asteroid is not receiving
damage from the player. I do that here inside
player controller script. Inside on collision Enter two D, I will follow the
pattern we established. It will be similar to what we did inside phaser bullet script. So I copy this entire
block of code. I go back to player
controller script and I paste that code in here. We have to be careful
about brackets. I make sure I'm not missing any. I remove this line of code. So when player collides
with an obstacle, only obstacle type
we have is asteroid. We find its asteroid
script component. If we can't find it,
we call asteroids public T damage
function and we pass it some damage value
like one, for example. In this case, I can leave
the hard coded value of one because it's just player
body slamming the asteroid. It will be just some
minimum damage like this. I save that and if I play, if player touches the asteroid, asteroid is receiving damage and it's flashing
white again. Perfect. I go to my Boss prefab and
I open Boss One script. Boss has lives and it
will also have damage. This value will determine
what damage will the boss deal when it touches
or charges into something. Inside start method,
we are setting lives to 100 and we will
set damage to, for example, just for testing. Again, to follow our pattern,
I move this up here. Bos has a public function
called take damage where it can receive damage from
sources outside this script. And here in collision
entry to the function, boss will deal damage to
objects it collides with. Boss will deal
damage to asteroids, so I go here to
player controller, and I copy this
entire block of code. I paste it in here.
Instead of hard coded one, we pass it this damage
value from line 19. Currently, we set
that value to two. So if boss collides
with obstacle, find asteroid script component. I found, take its public take damage function and pass it the damage the
boss is dealing. Boss will also
damage the player. So else if the object boss collided with has
attack of player, try to find its player
controller script component. I found, take public take
damage function that sits on that script and
pass it the damage the boss is dealing
from line 19, where we set it to two. I want to test if this works. I save and play. And when boss comes, I expect it to make asteroids flash and if the player
touches the boss, player should lose two lives. Yes, and yes. And now that we test did that, back in Boss one script, I said the damage boss is dealing to a higher
value, maybe 20, which means in case
of a collision, it will destroy player
and asteroids instantly. With this code,
after our refactor, Bs is dealing consistent damage. After this refactor, we have
a more consistent code base, and mainly the
damage objects are dealing is coming from a
property on that object, which allows us to level up our weapons or make
our boss grow in size and deal more
damage or make asteroids deal damage
based on how big they are. For example, we can get
really creative with this.
29. Debugging and Game Balance: So. No. As I play the game, I notice three small
easy to fix bugs, few things we had before and we somehow lost
during the refactor. Bullets are not
being destroyed when they hit objects. They
just slide alone. Objects are not being destroyed when they move off screen, and parallax background
movement speed doesn't react to players
superspeed move. I exit play mode and I will
deal with the bullet first. Pas the bullet script. When bullet collides
with an obstacle, I set active to force
to deactivate it. Also set active to force
when it collides with boss. I copy this code block I
remove all of this code, I have to be careful about
brackets when I do this. When bullet collides with a critter, deactivate
the bullet. I don't want single
bullet flying through and destroying multiple critters.
That was an easy fix. Next, float in space script. After we move the
object from right to left in regards to the
current world speed, if the object move off
screen to the left, let's say 11 units from
the middle of the screen, which should cover even the largest game objects
we have in this game, we destroy that game object. Done, final Bfix
include word speed in this calculation that sets position of each parallax
background layer. One more small thing I noticed
inside greater one script. We have a game that
has a static camera, but we are creating
an illusion of floating through space
from left to right. Camera is not moving. It's static, so we are
moving all the game objects from right to left
to create that visual. Graters generate random
position somewhere in the visible game
area to move towards every 0.1 to 2 seconds. Sometimes that random
position is very close to the critters
current position. So it arrives to the target
position a bit earlier before this code runs and picks a new random target position to move towards, which is fine. But let's move interval to higher value to make the critters set target
positions less often, so we can see what
happens when they hover in place and
what it looks like. Critter's target
position is not floating by with the rest of the
game world objects. Critter is hovering in
space, but at the same time, it appears to be moving
to the right because of the fact that this
game has a static camera. For example, look at this one. It's thinking where to go next, so it should be
floating to the left. It shouldn't be able to keep the same horizontal
position like this. Also, as I play, I can see bullets get destroyed
when they hit objects. Game objects are being destroyed when they
move off screen, and parallax backgrounds react to players super speed move, so we fixed all of that. Critter is moving from its current position
towards target position. I will make sure
that target position floats from right to left, depending on the
current world speed, same as all other game objects. Now, if I play, Look
at this critter. It's thinking where to go and it is floating to the
left as it should. Perfect. Back in my script, I make critters pick random
position more often, something between
0.3 and 2 seconds. We learned a lot about making side scrolling games
with a static camera. If you are interested, we can
take this so much further. Let's implement more
poses, special events, different enemy types, environmental hazards,
two player co op. Let's turn this into space
invaders or vampire survivors. I can do all of that in a follow up class if
you are interested. Let me know by
leaving a comment. Oh. For my final build, I adjusted object spawner waves. You can add waves by
right clicking one of these and duplicate
array element, or you can go all the way down to the last wave and click Plus. The first wave I have here
with an index of zero will spawn 20 asteroids in
total, one every second. The next wave will
spawn asteroid every 0.6 seconds until it spawned 40. Next wave will spawn 100
of them even faster, one every 0.4 seconds, which should create a
thick asteroid field that we have to shoot our way
through unless we get a boss, and it will help us to break through some
of the asteroids. The next wave is ten
asteroids in total, one every 2 seconds creating more empty space and the
final wave is the lost whale, our mission objective that we can collide with
to end the level. We miss it, the level
will continue and waves will repeat from
the first one again. Critter spawner uses the
same object spawner script, and it will produce three waves of critters
over and over. The first wave is three
critters every 1 second. Then they will start
coming a bit slower, 20 critters in total. Each will come
every 1.2 seconds, and the third wave will spawn one critter every 5
seconds, three in total. Then it will repeat
from wave one. Earlier in Game Manager, we defined that every time
we destroy 15 critters, it will spawn a boss. This type of boss is more
of an environmental hazard. We can't kill this boss
with our basic weapon, so we just have to make
sure we understand its movement pattern
and we manage to avoid it to
continue our mission. Object spawner is using
Object Spawner script, and the same script is
used by Critter Spawner. So let's open it. Can
make it more precise. Here, every time we reset
spun timer back to zero, we are potentially losing
a few milliseconds because spun timer might be
jumping by certain steps. So by setting it
here to hard zero, we are losing that little
difference completely, even if it's just a
fraction of a millisecond. If we run this on
different devices, in theory, this difference
can build up over time. We can change this code
just a little bit to make sure we are not discarding
that left over time. One way to do it is that we will make spun timer count backwards. Let's just do it, and then
I explain it. I cut this. I put it here. And we do plus equals to keep the
original value, add more to it. And this line will be less
or equal to zero like this. So now spawn timer is counting
backwards towards zero. If span timer is less
or equal to zero, we set it back to
the interval value, but we don't set the value. We just add to whatever
that value is, accounting for those little
differences that can happen. Let's say interval is 1 second
and we spawn an object. So span timer will become something around 1
second here again, and it will be counting down
from 1 second towards zero. When it is less
or equal to zero, we add spawn interval value to whatever its
current value is here, and the cycle
repeats. Save that. Object spawner and
Min position object. I set its position to 15 moving it a bit
further off screen. I also set it to 15
on MaxPosGame object. That way, asteroids spawn a
bit further along this line. And the reason I do it
is that when I play, asteroids might push each other around a bit when they spawn. I want that pop in
into existence and chaotic pushing to happen well off screen outside
the visible area. I select player and I slow down energy region to half
of its current value. We regenerate 0.05
energy per fixed update. If I play, and we get to the part where
we spawn many asteroids, you can see how they kind of pop in and push
each other around. I want that pushing and
shoving to happen far outside the visible game area
so that the user doesn't see any jerking
or snapping asteroids. This is base Game complete. Let me know if you
managed to get this far by saying I
did it in commans. Now, let's create
some playable builds for multiple platforms.
30. Export and Deploy your Game: C. Unity is excellent at exporting our games to
various platforms like Windows and web because of its powerful cross
device compatibility. It allows us to create a single project like we
just did and then deploy it across a wide range
of devices and operating systems with
minimal adjustments, whether it's for desktop, mobile or web browsers, but also Playstation,
for example. Let's create a native
Windows App first, and then I will show you
how to turn this into a playable web game
and how we publish it online so that other
people can play it. I will also give you a
link to the project I create so you can try
to play my build. I go to Edit Build Profiles. Here, we can see
all the platforms we can create a
playable build for. We will focus on
Windows and web today. If you want to publish for
devices like Android and IOS, you can do it but make sure you add touch controls
to your game first. For any build, we
have to make sure we add all of our scenes
into Senist here. You can edit Scenlist
on this tab. As you can see, my
active Build profile is set to Windows. If I want to activate
another one, I select it and I go
to switch platform. These platforms are great
out for me because I don't have that specific module
installed in my Unity Hub. If you want some of these in your Unity Hub, when
installing Unity, make sure you include Build support module during installation. Select
the platform. You want to create a Built four by clicking Switch
platform here, which will mark it as active. You can see I already have
Windows built set as active, so I select it, and I
click here on Build. On my local computer, I create a folder somewhere. I called my folder build 01. The folder name
is not important, and I select folder. I forgot to save
changes to my project, so I click Save here. Unit two will now take some time to create a playable
Windows Build. It puts all of these files in that folder and you can
just find dot X file, and that's how you play
your game on Windows. Create a web Build. If you have the web option
here grade out, you have to install
WebGL Build Module. Depending on the Unity
version you are using, there might be different
options on how to do it. Usually, you can go to
Unity Hub, go to Installs, locate your installed version of Unity and find option
to add modules. Find a module called WebGL Build Support or
something similar to that if you are watching
it in the future and they made small changes
to this process. I have my WebGL
module installed, which means I can click this web option and
switch platforms here. This might take some
time, so I speed this up. Now, web is marked
as active for. Same as I did before,
I click Build. I create a different folder
somewhere on my computer. I called mine Web Build 01. I go inside and I
click Select Folder. Unity will compile everything and it will create a
playable web Build. On my computer, this process took much longer
than Windows build, so I fast forward 9 minutes. Unity created these files and folders inside my
web Build 01 folder. This is a HTML file, but when you click it, you will probably get
something like this. The web Build needs to be
run through a web server. If you are a programmer, you probably know many
ways how to handle this. For beginners, one of the
things you can do here is to go to some site that allows people to publish
their web games, maybe hdtiOO in my case, I will use Unity Play. I pack all of these folders
and files into zip archive. I will call it
Spacequest or something. Once you have your
web build zipped, you can go to play.unity.com
to publish your game. Notice that I'm logged in here. It's the same login details I use in my actual UNIT editor. Once I'm logged in, I can
click on the button that says Upload your game.
Now it's very simple. I drag and drop my WebGL build Zip file and it will
process my game. And your game Build is
successfully loaded. Awesome. I give my game
some title and description. You can set your game to
public or private here. I will also upload thumbnail and short game play
video here later, but it's just optional,
and I click Save. Now I have a playable
version of my game and I can share it with people
by using this link up here. I will leave this link
in the description down below if you want to
play test my build. I can also go here and my games to see all my games I
published on Unity Play. Let's give it a quick test and you can click
this for full screen. You can download source
code on my Github. Don't forget that when you get
Unity Project from Github, Unity will open an
empty untitled scene, so it looks like the
project is empty. Make sure you go
to Assets Scenes folder and open one of the actual project
scenes from there. So this is how you build a two D space game completely
from scratch, all the way to playable
Windows and web Build.
31. Get Github Source Code: Cold. So this will be
the base project, and I created a Github
repository for the game at this stage of development.
This is the URL. I will also link it somewhere in the resources down below. It's a complete Unity project with all the
scripts, art assets, and it might be
useful if you get stuck to compare
your code to mine. If you want to open
this project in Unity, you can download
a ZIP file here. The whole game is
only 12.5 megabytes. I have my zip archive here. I unpack it somewhere on my computer where I
keep my Unity project. I open Unity Hub, I click Add and Add
Project from Disk. In a pop up window, I find the place where I unzipped
the Github files, and when I add it,
it will appear here. When you open it,
Unity will take some time to install all
the necessary packages, and one confusing thing is
that it might open the project like this where all you see
is just this untitled scene. If you see just
the untitled scene like this, let me
know in the comments. I'm not sure if this happens every time or only
for some people. Anyway, all I have to
do here is to go to Assets scenes and I open
one of the game scenes, for example, level
one. And that's it. You have the same
Unity project as me. We made sure our projects
are identical at this stage, and this will conclude the beginning version
of this class. Let's move on to some a little bit more
advanced techniques, and let's make this
game a lot more fun. I have an idea that I will
branch this Github repository out from this stage into a few completely
different game genres. I'm thinking Spacequest mixed with plans versus
zombies mechanics. Or space quest, but it's got rogue like and card
game elements. But how about we just
do a bullet hell? But we create multiple weapons, each with their own
unique upgrade tracks, and we designed some more
interesting enemy types that come in increasingly
difficult waves. That would be mixing
our space game with something like
vampire survivors. Hordes of enemies,
screen full of weapon projectiles
and animated effects. To be able to do
something like that, we need to optimize this
code base a little bit more to make sure it runs
well even on older devices. When making bullet hell game, the most obvious performance improvement technique
is object pool. Let's optimize the game
for better performance.
32. Performance Optimizations: C. In this game, as we fly through space, we are constantly
creating new asteroids here and destroying them when they move
across the screen. Object pooling in unity
improves performance by reusing objects instead of constantly instantiating and
destroying them. I select Object spawner. Here in waves list, we map out the objects that
spawn as the time goes by. We already created
Object pool for bullets. I reset transform here just
to keep the numbers nicer. The logic is handled here
inside Object Pooler script. Here we have a prefab for
the object we want to pull. For example, here we
used bullet one prefab. The initial pool size is
five bullets that we will pre instantiate and make sure are ready when
they are needed. We have a list
where we will store those five game objects. As soon as start method runs, we will call Create Pool, which will run five times, and it will create new
object five times. You can see we are instantiating whatever prefab is up here. This time, it's bullet. For the next one we are about to do, this will be asteroid. We are setting its
parent to make sure they are collapsible to keep
unity hierarchy clean. Also have a public function
called get pulled object, which will search
the pool for the first inactive and
available one, and it will give it to us. If we run out of
available objects because they are all in use
and we still need more, this logic will just
create one more to make sure we always have
as many as we need. It's helpful to understand
this code because we are about to turn everything in
our game into object pool. No more destroying objects. We are going to learn how to
make object spawners work with object poolers and we
will be reusing our objects. It's actually easy if we
take it step by step. So we have an object
pool for phaser bullets. Let's also turn asteroids
into reusable pooled objects. I write click, create empty, and I call it asteroid pool. I parented under object puls
to keep hierarchy clean. Asteroid pool selected here, and I drag and drop Object
Pooler script on it. I want it as a
component like this. Here, we need to select
the prefab that we want to pull and we select the
initial pool size. How many do we think
we might need? If we need more, it's fine, our code will create
more if we run out. I leave it at five for now. Because of the logic inside Object Poller script
that we just went over, if I play, asteroid
pool will already automatically fill with five
inactive asteroid objects. They are grade out, inactive, so they don't appear
anywhere in game. We need to adjust our
object spawner code a little bit so that it can
work with these object pools. Object spawner
selected waves list, and I set this to one. Let's set up only the
first asteroid wave. I open Object Spawner script. Here we have our wave class, the blueprint that will be
reused for each enemy wave, defining what prefab
to spawn in that wave, how often and how
many of them to spawn before we move
on to the next wave. Instead of instantiating
new prefabs and then destroying them when
they move off screen, we will activate reusable
objects from the pool, and instead of
destroying them later, we will just deactivate them, making them available
later again when needed. We will leave most
of this as is, but instead of
using this prefab, we will use the
object pooler here. Public variable type is Object Pooler and I call
it pull, for example. Object Pooler type
means I'm looking for that custom object Poller
script component we attached on asteroid pool
game object earlier. So now if I save, I have this new pull field here plus to add one
more wave block, this pull field is looking
for object puller component, so I drag asteroid pool
game object in here and it will find that object
pooler component on it automatically. We will be also spawning asteroids in the
second wave for now. I can delete this
prefab completely. We will not be instantiating any new prefabs in this script. We will be just
pulling existing ones from our object pools. Down here, inside spawn
Object, custom method. I comment this out. Instead, we create a helper variable
type is game object. I call it, for example,
spawned object, and here I just want to pull an inactive available object
from the asteroid pool. So I say pull which
is a variable here from line 14 that points to object pooler component
because we dragged it in here in the inspector on
that script component, we have this public get pulled object function that will cycle over
objects in the pool. We have five inactive
asteroids in there at first. It cycles over all of them, but as soon as it finds the
first one that is inactive, which means it's available, it will give it to us and
it will stop looking. If it doesn't find one
because the pool is too small or because all of them are currently
active in the game, it will just create
a new one for us. So when I want to spawn object, I will take my object pool
and I get pulled object. This code doesn't work because we don't have just
a single pool here. Each wave has its
own pool so that each wave can pull
different objects from a different object pool. So here, I have to specify which one of these pools I want
to get pulled object from. I do that by saying Waves list at index wave number dot pool. If wave number is zero, we go inside Waveslist at index zero and call Get Pulled
Object from here. If wave number is one, we go to this index and G
Pulled object from this pool. Let me know the comments if this makes sense or if you
need more information. I can make a special video on this topic and go slower
and in more detail. We know that when
this line runs, we will always get spawn object. Either we get one from
the inactive ones or this function creates one
if there are none available. So I take that spawn object
and I need to position it. Where do I want that
game object to spawn? Keep in mind that we might
be reusing this game object, so it can be anywhere
depending on what happened during its
previous active cycle. So I reset its position
back to random spawn point, logic we wrote here before. I also set its rotation. I'm basically doing
the same thing we did here inside instantiate. We set position,
rotation, and parent. We don't have to deal with
setting up the parent here because we handled that earlier inside Object
Pooler script. When we wrote the logic that pre instantiates objects
and fills the pool, it sets the pool
itself as the parent. I can delete this so we get pulled object and reset
its position and rotation. Object pools store
inactive objects. So at this point, we need to set active to true, and that's it. Instead of instantiating
brand new object, we pre instantiate five
asteroids in our pool, and we are pulling them and activating them
when we need them. I save Object spawner
selected here. First wave will spawn asteroid every 2 seconds until
it spawns three. Then it moves onto the second wave where we spawn
asteroid every 3 seconds, but here we only spawn one. Then I will go back to the first wave again
and repeat the process. We also have grater spawner that uses the same
object spanner script. For now, I will deactivate it because we don't have
object pool for graters. Now if I play, I have
five inactive asteroids, but you can see they light up as they get activated
and they appear here. If we run out of inactive
ones and we need more, Object spawner will
create more as needed. If I destroy one, I
will get an error. Unity game object
has been destroyed, but you are still
trying to access it. To fix this, I go to my asteroid brief ab and
I open asteroid script. Down here, where asteroid takes damage and
loses all lives, we don't want to destroy it. We want to set active to falls. Active true means it's doing
something in the game. Active false means that the
object is sitting in the pool inactive and available
when it's needed. I save and I play. Basically, now, these
asteroids are pooled objects. We never want to destroy them. We only want to deactivate them, which means they will
get grade out here. When they reset, sometimes their sprite renderer is
stuck on white material. We are handling that logic here inside Scripts
utils flash white. The issue is that the
last hit sets material to white and asteroid
gets deactivated. So this core routine that will reset the material
doesn't actually run. I can handle that in
many different ways. For example, I create a
public function I call reset, which will set material back to default material immediately. When asteroid loses all lives, we call flash white
reset to make sure the material is reset before
the asteroid is deactivated. I save and play. I shoot to deactivate
some asteroids. They flash on it,
but when they reset, you can see they are
not white anymore. That's great. I get
one more error, and it's because we destroyed an asteroid that
flew off screen. Inside float in space script. We don't want to
call destroy here. We want to just say set
active falls like this. Now if I play, it
all works fine. I see more asteroids
being added to the pool, getting deactivated and
activated again as needed. And when they move off screen, there are no more
errors anymore. All good here. This is
how we turned asteroids from a single use object into
a reusable pooled object. In our game, there will be a
lot happening projectiles, robots, swarms of aliens,
and animated explosions. So far, we created these four animated
effects as prefabs. In game, we create a
copy of one of these. We play the animation once
and we destroy that copy. We are using this one, boom two when we deactivate an asteroid. We are using it as an
asteroid explosion effect. Let's turn boom two from a single use object into a reusable pulled
object as well. I right click Create
Empty boom to pool. To keep this simple, I
will make sure the name of the pool is always the same
as the name of the prefab. I parent it under object pools. I go to prefabs asteroid, and here we are using boom
to prefab as destroy effect. When we destroy an asteroid, boom to prefab will be created,
animated, and destroyed. Let's use our pulled reusable
objects here instead. I open asteroid script instead
of referencing the prefab, we will reference Object Pooler. Down here, when asteroid
gets destroyed, instead of creating
a new instance of destroy effect from a prefab, we will get one from
an object pool. Variable type is game object
because it's a prefab. I call it destroy effect, but I'm not creating it here. I'm taking destroy effect
pool from line ten, and I'm calling its public
get pulled object function. I take that game object and its transform position and also its transform rotation
will be the same as the asteroid we are about
to destroy or deactivate. Objects in the
pool are inactive, so I set active to true to make the animation
visible in game. I delete this line. We will have an object pool that
stores boom two prefabs. When needed, we take one, position it over the asteroid, and we activate it. Then we can deactivate the
asteroid itself. Save that. On my boom to pool, I add component object pooler
Prefabs effects, boom, two, and I drag it into
the field here, telling the script
that we want to pre instantiate an object
pool of five boom, two prefabs and store them for later whenever
they might be needed. Now when I play, I have five
asteroids in asteroid pool, and I have five boom, two objects in boom two pool. I exit play mode. Asteroid prefab. This
is the tricky part. Asteroid needs a reference
to destroy effect pool. I want to put this boom
to pool there so it can pull one of those prefabs
from there when needed. If I try to do that, you can
see Unity is not letting me. There are some
limitations when it comes to prefabs because, for example, this asteroid
prefab is an asset. It exists in the project, and it can be used
in multiple scenes. This boom to pool game object exists only
in level one scene. So if I drag it in here and we use asteroid
in a different scene, asteroid wouldn't be able to access this boom to pool from that other scene because that object only
exists in this scene. As with everything, there are
many ways to approach this. I'll go with the simplest
possible solution. But if you are a more
advanced unit user, feel free to do it your way. I need access from asteroid prefab to this boom to
pool so that we can pull objects from there and play explosion animations when
asteroids get destroyed. I have this destroy effect
pool defined up here. Inside start method kind of in the same way as
we are looking for sprite renderer and
other components that all sit on the
same asteroid object. We will look for Object
Pooler component, but that one sits on a
different game object. So first, I have to find that game object,
game object, find, and here I need to
call it exactly how I called it here in hierarchy. If you later rename
the object here, you need to adjust your code. That's why some people prefer other methods of doing this. Once we have that
game object from it, we call Get component
and we find that object Pooler
component that contains our pre
instantiated prefabs, as well as the public Get pulled object method
that we need. This is the simplest solution to this issue, in my opinion. There are many other
ways to do this. If you are a more
advanced developer, you can let me know
in the comments how you would do this. From asteroid prefab, we
are getting reference to this game object and its
Object Pooler component. I can remove this now. I save and I play. I have my boom to pool expanded, and I see five inactive
pre instantiated prefabs. If I destroy an asteroid, I can see one of the boom
to prefabs gets activated. Animation plays, and
then I get an error. Game object has been destroyed, but you are still
trying to access it. The reason for that
is here inside, Utils destroy when
animation finished script. We don't want to
destroy the game object when it displayed all
its animation frames. We just want to deactivate
it so that it returns to the object pool of available
ready to use objects. I will start coroutine,
I call deactivate. Think of this simply
as something I want to happen,
but after a delay. Outside start function,
I declare I numerator. For this work, we need system dot collections
name space up here. I call it deactivate because
that's what I call it here. And inside, I say yield, return, New, wait for seconds. This syntax might
look a bit unusual, but you will use it a lot
when working in unity. But how many seconds we want to wait before we deactivate it? We have that time here after
the length of the animation, after the animation
finished playing. I can simply copy and
paste it here like this. Then we deactivate the object. For example, the boom to prefab that has this script
attached as a component, and I can delete this. Okay, so we get a reference to the animator first
because we will need it. Then we call a special
type of function, a co routine that allows us to pause execution and
resume it later. Inside, we wait for however
many seconds it takes our animation to finish playing
all its animation frames, and only then we
deactivate the object. Let's save and play. I see my five inactive boom to objects here and let's
destroy some asteroids. I can see how they are being
activated and deactivated. But if I continue for a while, this one is not being
deactivated anymore. How can that be? If you know, pause the video and
leave a comment below. So the reason it's not
being deactivated is because we are using the same five game objects
over and over. And if I open this script, we have to think about what
runs when and how often. Start method is
called only once. Typically when the
object this script is attached to becomes
active in the scene. We have to keep in mind that we are working with an object pool. We are using the same set of five game objects
over and over. Start method doesn't run when an object is
toggled between set active true and false after it's already
been initialized, which means this co
routine will not run to deactivate it when we reuse the same game object
for the second time. Lucky for us, Unity has a different lifecycle
method called Enable. And this one is
called every time the game object or the
script is activated. I could have left this
animator inside start because we only need this start co
routine line inside on Enable, but it's fine like this. I might refactor
this a bit later. Important thing we
learned is that start method runs only once
when the object is created, but on Enable method runs every time object
is set to active, which is very useful in our situation here
with object pools. I can see how my
boom two objects get activated and deactivated
as we play the game. One more thing we
have to keep in mind when working
with object pools, sometimes our objects have stats that get modified
during gameplay, like the asteroids have five lives at first,
but as we hit them, lives get reduced, and then when we reuse that same
asteroid game object, it still has no lives left. So now I can destroy
them in one hit. Exit play mode and
an asteroid script. I define lives and
damage up here, and I set it to some values
here inside start method. This would be fine for
a single use object, but not for reusable
pooled object. I define private integer
called Max lives. I set MAX lives to five, and I set lives to
Max lives like this. Lucky for us, we
just learned about Enable method that runs every time object
is set to active. So anything I want to reset to its original value,
I can do that here. I set lives to Max lives. This can be cleaned
up a bit more, but let's see if it works. On Object spawner, I increase spawn interval to 5 seconds for the first three asteroids. They will come a bit slower. I play. I hit it five times. It has five lives. So this one also has five lives. This one also lasts
for five hits, and now we are using. So this one still has five
lives. Five lives as well. Perfect. We have a working
object pool for asteroids and Boomto what we just did is great for
performance optimization. We are reducing memory
allocation because we are not creating and destroying
objects frequently. We are just activating
and deactivating them, and we are also reducing something called
garbage collection, which is an automatic
process that frees up memory by removing objects
that are no longer in use. We are not destroying
our objects, so we will get no frame drops caused by garbage collection. Now that we understand the concept before
I can add swarms of enemies or different
crazy weapons that produce
thousands of bullets, will make sure that
everything in our game is an object pool because we want this game to run
fast and smooth.
33. Everything is an Object Pool: Go. Same as we create boom two pool
for this briefap, let's also turn boom one briefap into a reusable pulled object. I right click Create
Empty boom one pool. I parent it under Object pools. At component Object Pooler. I drag boom one here, and let's just create one in
the beginning because we are using this boom one effect only when player gets
destroyed so far, but we can use it for many
other things as well. Player select it,
I have that boom one prefab here as
destroy effect. It's here on this line. I don't need to see it in the inspector, so I delete this. It will be Object Pooler that
I call destroy effect pool. Save that. I open Asteroid script to see
how we did it there. Let's just follow
the same pattern. So I copy this line to find the component inside
start method. This will be helpful
if we decide to turn the player
into prefab later, but don't worry about
it at this point. Are simply repeating
how we handled destroy effect for the asteroid. I copy and paste this
line of code here. Destroy effect
pool we defined on line 22 is game object, find, and we want boom one
pool dot G component, and we want that object Poller script component
that sits on it. We need Object Pooler
script because it has a public get
pulled object method, which we will use here.
I comment this out. Instead of creating
a new object, we will get one from the pool. Game Object, I call
destroy effect, for example, I can actually go to Asteroids
script down here. I copy these four lines of
code and I paste them in here. Get pulled object from
Destroy effect pool. We are pointing this
variable to Boom one pool with the blue
explosion effect, set its position and rotation over the
player object that just got destroyed and activate the object to play
the animation. I can delete this
line, save that. Boom one pool selected. It has one copy of
Boom one prefab, deactivated and waiting
when it's needed. I collide and when
player gets destroyed, use this object from the pool, and when the animation
finishes playing, we deactivate it and the object is ready
to be reused again. I could go to asteroid and I could use that boom
one pool here instead. Save that. On Object spawner, I make the first three
asteroids spawn much faster, one every 0.5 seconds. Now if I play, the
blue explosions play when asteroids
get destroyed. Our system is pretty
flexible at this stage. You will add more of these
animated explosions later, so we will be able to choose
from multiple ones as we add new destructible objects
and enemies to the game. I go to prefabs EMs. Let's also turn critters into
reusable polled objects. At this point, we
did it four times, so it should be quick and easy. I create empty game object. I will call it KriterOPool. I parent it under Object pulls. I add component object puller, I drag KreterOPrefab
into this prefab field, and let's initially create five. I select Objectspwner,
and the first wave, let's drag this new
KreterOPool here. I adjust these values.
Okay, that's fine. So what will happen
now if I play? Critters are spawning,
but the problem is that when I hit one,
it gets destroyed. We remember from when
we did the other ones that we never want to destroy
object from the pool. We just want to deactivate
it so that it can be reused. Also notice that
these critter zap and burn objects are being deactivated and they are staying here because they don't have
a pool to return to yet. We will solve all of this now. First, I go to
Critter One Script. And I never want
to call Destroy. I want to replace destroy with set active face in both
places here and here. Save that and play. Now we are spawning critters, and as I'm destroying them, they are being activated and deactivated inside
my critter one pool. Perfect. That works. I will completely
delete Critter spawner. Let's just handle critters from Object spawner along with
all the other game objects. I play again. Let's destroy
a few more critters. And if I scroll down here, you can see that the
prefabs that play animation when critters get destroyed are stacking up here, deactivated, but we are
creating more and more. So I exit play mode. Prefabs effects. Let's turn this creater one burn prefab into an object pool. Step one, create a new
pool, critter burn pool. Step two, give it object
puller script component, drag the prefab in and define
the initial pull size. One will be enough at first. Let's do the same thing
for crater one Zap prefab. I create crater one zapped pull. I give it object
poller component. I drag the prefab in, and the initial
pull size is one. I drag both of them under object puls to keep
the hierarchy clean. Inside Creator one script, instead of instantiating
a new copy of a prefab here and here, we want to get a
reusable object from those two object pools
we just created. Up here, I remove
serialized field. Type will be Object
Pooler and let's call them Zap effect pool
and burn effect pool. Then let's follow exactly
what we did with asteroid. Inside start method, we have
this line, so I copy it. And I paste it inside start
method on Greater one script. I call this zapped effect pool, and it's equal to game object, find crater one
underscore zapped pool. The spelling is important here
and it needs to be exactly the same as this as the name of the object
we are looking for. This one will be
Burn Effect Pool. Again, here I make sure I spell this exactly
as the name of the object we are trying to find and I get its object
pool or component. Scroll down here. I commend
both of these lines out. Instead, we will do the
usual four lines of code, same as we did here
on the asteroid. Game Object, zapped effect is zapped effect pull
get pulled Object. Once we got one of these
reusable pulled objects, we set its position over
the critter that got hit. And importantly, we will
also rotate it so it faces the same direction as the critter that
just got destroyed. Otherwise, it will
not look good. And then we set active to true to actually
play the animation. I copy these four lines
and I paste them here. Instead of zapped effect, I will say burn effect
in these five places. I delete this line
and also this one. Now if I play, both of these pools have
just one inactive, pre instantiated,
reusable game object. Look how they light up
as I destroy critters. We created only one, but our
coach will make more if we need to play more than one of these animations
at the same time. If only one at the time plays, the first game object will
be reused over and over. Additional ones get created only if multiple need to
play at the same time. After playing for a while and seeing how many
we are creating, it actually gives
me a better idea what this initial pool
size value should be. We turned all four of these
effects into object pools. This goes a long way towards performance optimization
for this game. We can start creating
a bit more chaos now and it will run smoothly. We'll also turn Boss one prefab into a reusable
pooled object. I open Boss one script, and I declare private integer. I call it Maxfs. I set it to five at
first just for testing. I set damage to 20 here. And I can delete this. Inside start method, I
set lives to Max lives. Currently, boss takes damage, but it cannot be destroyed. So let's write that logic. We will implement more
powerful weapons soon, so if we level them up, we might actually be able
to destroy the boss. If boss' lives are
less or equal to zero, we deactivate it, set
active falls. Save that. I go inside game manager script. We have this prefab field. Instead, I want to create an
object pool, create empty. I call it boss one pool. As usual, I add
component Object Pooler. I drag Bos one prefab
into this field here and the initial pool
size can stay at one. Inside game manager script, I remove this part,
private Object Pooler, and I name it Boss one pool. Inside start method, I set
Boss one pool to game object, find Boss one pool. I make sure this spelling
is the same as this. And when we find
this game object, I want to access this component. So get component, Object
Pooler. That's good. I comment this out. Instead of creating a new copy every
time we summon a boss, we will take Boss one pool
and we get pulled Object. Then I take that Boss
one game object. I set its position to
what we did here before, just off the screen
to the right. I set its rotation to -90 on Zet axis because I want the
boss to be facing left. Then we call set active True
and we can delete this line. I changed this to more or equal, so when we destroy the fifth
critter, boss will come. When we destroy the boss, it will be deactivated. For testing purposes, we
give boss only five lives. Save that and play. I destroy five graters
and boss spawns. I hit the bus five
times to deactivate it. Here, if I expand Bs one pool, we can see that the object is active and it's off screen here. I wait for it to charge. I destroy it. Five critters
and another boss spawns. Still, we're using that
same first head object. The problem is that the boss doesn't start in charging state. It starts in patrolling
state, apparently. Also, there is no
destroy effect, so let's start with that. I copy this line from
asteroid script, and I paste it up here. Inside start function
on asteroid, I copy this line, and I paste it inside start
method on the boss. If we use Boom one Pool here, we will get those blue
explosion animations when we destroy the boss. I will also copy
these four lines from asteroid and I paste them here. When boss loses all lives, get destroy effect
from object pool, set its position
and rotation over the boss and
activate it. I play. And when I destroy the boss, we get the same blue
animation from Boom wamp, the same animation that plays
when we destroy asteroids. I exit play Mode. I will also copy this line from asteroid to play some
sound, and I paste it here. Save and play. I
destroy the boss, and we get animation and sound. The next boss at spawns will not charge into the
visible game area. It's because this code only runs once when we
create the object, but it doesn't run when we
activate it again later. We know from before that
we can use enable method. Every time boss gets
reset and reactivated, I want to set lives
back to Max lives, and I want to call
Intercharge state. I also want to play boss
spawn sound every time it's activated and reactivated.
Save and play. I get the sound now
because we created the boss as we pre
instantiate the object pool. I also get an error. It's because Intercharge
state method needs animator reference. But that first time on
Enable runs first before start method has a chance to run and actually
get that component. It's good to know the
execution order in which these built in
lifecycle methods run. I can do a few
different things here. For example, I define weak here, which runs before on enable, and I get animator
reference here. So when it comes to
these three methods, weak always runs first, then enable and only then start. Now if I play, I don't
get animator error. I still got boss spawn sound when there was no boss spawning. Every time Bs spawns, the sound plays and boss
charges, so that's good. To make sure Boss spawn sound doesn't play when we create
Boss one Object pool, but only when we actually
summon the boss, I can go on Boss one
prefab and I can deactivate it by unchecking
this checkbox here. But that will also
hide the prefab image, which I think is a bit ugly. So instead, I activate it again. And in my code, I call set active falls inside awake this will run before this, so no sound will play
initially when we are creating the pool and adding inactive boss
object into it. The sound will play only when we actually
summon the boss. I play. I can see my inactive boss one
object here, waiting. I destroy five critters. This object gets activated. Sound plays, and boss charges
immediately. I destroy it. If we need another boss before we deactivated
the first one, Object Pooler script will create another
reusable object here. It all works great. I go to assets Art and I drag and drop Boom
three spreadsheet. You can download all art assets
in the description below. I need to increase Mac size. We want the image at full size, sharp and not blurry. Apply. I open Sprite
Editor, slice. This spreadsheet
has larger frames than boom one and boom two. So we will use it when
we need a bigger effect. For example, when the
boss gets destroyed. Type is grid by cell size, and frames here
are 600 times 600. I drag one of these
frames into the scene. I rename the object
to boom three. Sort in layer is objects. Order in layer is three on top of most things within
Objects layer. I go to Window
Animation Animation. I will dock the animation
panel somewhere. I like to keep mine here. Boom three object selected, making sure this says to
begin animating boom three, and I click Create. I navigate to assets animations and I will call it
boom three anime. Left click, the first frame, and I shift click
the last frame, selecting all the
frames in between, making sure that this
says boom three. I drag and drop all the
frames on animation timeline. I play the animation. That's too fast. I think
20 samples is better. Inside Assets animations. Inspector tab open here. This is the new
animation controller. I'm looking for this
other boom three file, the animation clip, and I
will uncheck loop time. I want the animation
to play only once. Boom three selected here, add component, float in space, which will make it
float from right to left as the world scrolls by, and I add one more component. Destroy when animation finished, which will deactivate the
object and return it back to the object pool
when the animation finished playing when all
the frames displayed. Okay, assets prefabs, and I drag Boom three
game object in here, turning it into a prefab. Now we have three different destruction animations
we can choose from. I delete this, right click
Create Empty boom three pull. I parent it under Object pulls at component
Object Pooler. I drag boom three here, and pool size maybe
two at first. Inside boss one script, we already created
all the logic. All I have to do is to go
inside start method and point destroy effect
pool variable towards boom three
pool like this. And inside asteroid script, let's use boom two pool here. We have asteroids
of different sizes. So if I want, I could also scale the destroy effect based on the asteroid that got destroyed. Transform local
scale is equal to transform local scale of
the asteroid like this. In unity, scale is defined in these fields on
every game object. Actually, let's test this
with boom three pool first. I save and I play. We have two inactive
objects here at first. Nice. I'm getting
different sizes based on the size
of the asteroid. Bos also uses boom three effect. Small asteroid. Small effect? Large asteroid. Large effect. Okay, all of that works. I exit play Mode. Inside Boss one script, I set Mxives to 100. On asteroid, I set destroy
effect back to boom two. Now, the boss has 100 lives, so it can't really
be destroyed with the basic weapon,
but don't worry. In the next lesson, we will
learn how to implement weapon level ups and we'll make our face weapon
much more powerful. I get an error. It's because
inside Boss one script, we destroy the boss that
charges off screen. Boss is a reusable object now. So instead of destroying it, we just set active to falls. We have this boss
that is summoned when a certain number of critters
is destroyed by the player. I could also just plug the boss object pool into the spawner to summon the boss
directly the same way it's spun in
asteroids and critters. Object spawner selected, plus to add one
more spawner wave, and I drag it up top so that
we start with that wave. I expand object pools, and I drag boss
one pool in here. The first wave will
spawn three bosses, one every 2 seconds. If I play, I notice an issue. They are not rotated correctly. Even though here on the prefab, we rotate it to face left, this rotation gets overridden by the logic inside
object spawner. Because here we spawn the
object and we give it rotation of the spawner and
spawner is not rotated. It's always zero, zero, zero. I could remove this line. And now this -90 rotation on the prefab will
actually persist, and the bosses are spawning
face and left as they should. Now we have an option. We can
make our game more diverse by sometimes spawning a bunch
of these bosses directly, or we can just leave
them only spawning every time a certain number
of critters gets destroyed. It's up to you how you
want to design your level. This is pretty dangerous
for the player. They charge unexpectedly,
and they hit for 20 damage. Player will be able
to level up and might eventually have
more than 20 lives, but at this point, bo hitting
the player is a one shot. Let's check what happened to asteroids after we commented
out that line inside Object spawner that
resets rotation to 000 every time a
new object spawns. I changed the first
wave into asteroids, spawn one every 0.5 seconds, ten in total. I play. Let's destroy some asteroids, and as they get reused, they will keep the rotation from their previous life cycle. So we gradually get
new asteroids spawn and rotate it in every
possible direction. It might be fine for you, but the art I'm using for asteroids has this
light and dark side. So I would prefer if they reset rotation every
time they respawn. I open asteroid script, and I know that whatever
I want to happen every time an object from
Object pool responds, I can put it inside
on enable method. So to reset rotation
to 000 is easy. I do that by setting transform rotation to
quaternion identity like this. No matter how many
times I hit the boss, I will have a pretty
hard time hitting it 100 times before it
charges off screen, so I can't really destroy the
boss with my basic weapon. We have bullet pool. We
optimize this code phase, so I can spawn many more
bullets at the same time and still keep my game running smoothly.
So let's do that. Let's implement leveling system
for player phaser weapon. This will allow the
player to shoot more faster and
bigger projectiles and actually be able
to destroy the boss. And then we can get creative and implement even more
dangerous enemy types.
34. Weapon Leveling System: I go to Assets scripts, weapons, I right click Create Empty, and I want to create
one more script. I will call it weapon. This script is where we put all the code shared
for all weapon types. We have only phaser weapon now, but we might add many
more if we want. I will write this code with
the expectation that we will implement 20 different
weapon types for the player. Let's organize our
code in a way that would make it easy to
manage something like that. Also, this code is a
public Github repository linked in a description
down below. So maybe some of you
will fork it and add more custom weapons when
you finish this class. But let's take it step by step. I open weapon script, phaser weapon script, and
phaser bullet script. Weapon script will have code that is shared for
all weapon types. Each weapon type will have the main script that
manages the main logic, like producing bullets or
whatever it is the weapon does, and we will have another
script that sits on the prefabs that the
weapon produces. In this case, phaser bullet. This script is where we handle collisions of each
individual projectile. So shared script,
weapon specific script and script that sits
on projectiles, or if it's a completely
different type of weapon, whatever is the prefab
that the weapon produces. We will follow
this structure for every possible weapon type
we will have in this game. On the projectile, I'm using phaser weapon instance speed, and phaser weapon
instance damage. Those properties sit here inside phasor weapon script,
speed and damage. I will actually cut
them from here. And I paste them in here. I want this weapon script to
contain all the code that every weapon type needs so that we don't have to repeat
it in multiple scripts. Notice that weapon
extends mono behavior, and phaser weapon extends
weapon inheritance. By doing this, we can treat any property that sits here on the weapon as if it was sitting directly here in
phaser weapon script. I will show you exactly how it works when we write the code. I save that, and if I play, you can see everything
still works. We restructured our code, but we didn't change
any functionality. It all works exactly
the same as before. Placing any code in here is the same as if we
declared it directly inside phaser weapon
script because phaser weapon class
extends weapon class. Weapon is custom based class, and we will create one
subclass for each weapon type. So far, we only have
phaser weapon subclass. Doing this allows us to
have a lot of flexibility. We can define very specific
unique weapon behaviors on each subclass, while
at the same time, our code is reusable and easy to maintain because
we are putting all the code that is shared for all weapon types on
the base weapon class. What happens if I add
a few more properties, public float size,
public float amount, and public float range. I'm defining them on
the base weapon class, not on phaser weapon subclass, but because this
class extends weapon, these properties will
work the same as if they were sitting here
inside phaser weapon script. I can delete this and also this. I move this uptop and I
put space in between. I see this script
in the inspector. If I save, I will see all these properties on
phaser weapon script. They are here. I
hope it makes sense. I set size to two, amount also two, and
range will be three. What will these properties do? Well, they can do
different things for different weapon types. It really depends what that
particular weapon does. We will define what exactly
they mean for phase weapon. Let's start with the most
interesting property. I called it amount.
Currently, amount is two, so I want to shoot two
bullets at a time. I do that by wrapping this
in a single four loop. I is zero, as long as
I is less than amount, I plus plus, and I put all this code
inside the four loop. Notice how I'm using amount. I'm simply saying amount. I'm not saying
weaponscript dot amount. Save that, and let's
see if this will work. I have my phaser bullet
pool here and if I play, I shoot two bullets
at the same time, and in this pool, we
can see that they activate and
deactivate in pairs. While still in play mode, I can increase amount to three. Nice. How about ten? Now we have ten bullets. Can I destroy the boss now? Yes, we are strong.
That was easy. I get an error that
says co routine on asteroid could not be started because the
object is inactive. Once we are hitting objects by so many bullets
at the same time, we can get strange
errors like this. I can prevent this one by going here inside asteroid script and we will only call the
flash white coroutine if lives are more than zero. Else if lives are less
or equal to zero, we do all the other things we normally do when
asteroid gets destroyed. I can remove this part and
it will still work the same. Save and play. Phasor
weapon selected. Let's do ten bullets per shot. I destroy asteroids very fast
and I get no errors grade. Our weapon will also
have size property. In case of phaser weapon type, size will affect the
scale of each bullte. So here I say bullet, transform local scale if
I go to assets prefabs, weapons, and bullet one, I'm accessing these
scale fields here. We want to set X and Y. We don't have to worry
about Z in this case, so new vector two, and I pass it size as both vertical and
horizontal component. I reference it simple as size, even though it sits on
the base class here. Save that, and let's
see if it works. Size is set to two, so I expect the bullets
to be double in size. 0.5 is half the size, three times the size, five times the size. Amount is five. We are
making so much progress. Notice how the
bullets collide with level boundaries object,
but not only that, the reason they are spread out
like this above each other is because they collide with
each other and spread out, they push each other away. They also push the player
back a bit when we should. For my game, I want to disable
all of these interactions. I go to edit project settings, Physics two D and layer
collision matrix tab. We can see bullet
versus bullet collide. I disable that interaction. I want critter versus Bullet,
obstacle versus Bullet, but I will disable level
boundaries versus bullet, and I will also disable
collisions between bullet and player. Okay, save that. And if I play, Notice that
even if I set amount to ten, it looks like one bullet, even though here
we can see ten are activated at the
same time per shot. Because we disabled bullet
versus bullet interaction, they no longer push each
other aside and they overlap. We have ten bullets on top of each other, perfectly
overlapping. We are going to write
our own better logic to spread the bullets around. We will make use of
range weapon property. It will define how spread
out the bullets will be. We will take the
amount of bullets and we will spread them
evenly within the range, whatever the amount
or the range is. This will make our
phaser very flexible, and we will have all
these properties we can tweak and customize it. To spread bullets evenly
within a specific range, we need to calculate
what will be the spacing between the bullets. Spacing will be the range divided by the amount
of bolets minus one. I'll explain y minus
one in a minute. Let's just write the logic down, and then we will explain it in a little visual once
we have all of it. Vertical Y position of each bullet will be the
vertical center point, so the vertical position
of the phaser weapon, which sits directly on the
players spaceship object. So position of phasor weapon
is always the same as position of player object
as the player moves around. Plus index from the four
loop times spacing. Keep in mind, we are
inside a four loop. So as we create one
bullet after another, depending on what the amount is, Y is increasing and placing
bullet after bullet, and their spread depends
on the spacing variable. To see visually what this
value actually does, let's apply it to the
bullets position. Bullet transform
position is new vector two because we want to set
just X and Y component. I keep the horizontal
position as the current position
of the phaser weapon. But for vertical position, we will use Y pose, which should spread the
bullets within range we specified here. Save and play. We have two bullets spread
within a range of three units. Units are visible in
scene view as this grid. One cell in the
grid is one unit. So range of three is
three of these units. Three bullets now, they spread evenly within
the same range. That part works, but I want the spaceship to be in
the center vertically. Look what happens if I
change the range value. Whatever amount we have here is spread within a
range we define. For example, if I stop this now, we have four bullets evenly spread out in a
range of one unit. Four bullets spread out
within a range of four units. I can see that our spacing
formula is working well. I center the range around the spaceship
vertically by moving the vertical position of all the bullets down
by half of the range. So this will happen. I save and play. Did that work? Two bullets. Three
bullets. Four bullets. Five bullets. Five bullets
in range of one unit. I think we can actually
destroy the boss now. Amount of seven spread across
the range of two units. I can play with these
values to check, but I can already see
our formula is correct. It will always spread
whatever amount of bullets we have here, across whatever range
we define here. There is one issue
with this formula. Look what happens when
I set amount to one. Maybe at first, we want to
start with just one bullet, but that will actually
give me an error. I can see the
vertical component of the position vector
is non, not a number. So when amount is one, this formula breaks because
one minus one is zero, so we are dividing range by
zero, and we can't do that. We need to say amount minus one here to spread
them evenly within a range because we want to divide the range by
the number of gaps, not by the number of bullets. We need the size of
the gap, basically. There will always
be one less gap than the total
number of bullets. That's why amount minus one because two bullets
have one gap. The bullets is two gaps, four bullets is three gaps, for example, spacing value is the size of these
gaps, basically. Then we center the range
around the player spaceship, and the first bullet
is zero times spacing. Second bullet is
one times spacing. The third bullet is two
times spacing, for example. To be honest, you
don't really have to understand these two lines. It's just a standard formula that's always used for
this kind of a thing. You can easily Google
it when you need it. Your skills as a game
developer are completely unrelated to being able to
write math formulas like this, especially if you
are a beginner, so don't worry about
this code too much. This is the only part of the class where I
allow you to just copy my code and don't worry about actually
understanding it. We know it works and it spreads the bullets
within a specific range, and we just need to make sure this code doesn't break
when amount is one. I will go with the
simplest solution. If amount is more than one, I put this code here. When I do that, we can't
access Y position here. It's outside its scope. So I define it one
level up here, and inside, I just
give it a value, and I also need to
give it default value. Amount is one, vertical position will be zero just
on top of player. If amount is more than one, we do our vertical
spacing formula. And actually, I
don't want the zero here because the player
is moving around. I want the default
vertical position when amount is one to be wherever the player is vertically at the
moment like this. We always position
it over the player, but if amount is more than one, we override that
Y position value. If amount is one, this code
block will be skipped. If you have any questions or
improvements to this code, let me know and let's talk
about it in the comments. I save that and let's test it. Amount is one that works. Amount two, amount four, range three, five bullets
spread within three units. Ten bullets spread in a
range of three units, 20 bullets, and we have
an unstoppable wall. I can also increase the
range and the bullets will still evenly spread in
whatever range we define. I could store these values
in a scriptable object, for example, but I will go with a simple object
oriented approach here. This is a beginner
friendly class. Every weapon will have
one more property. Integer that defines
weapon level, and for each weapon level, I want to define values for
each one of these stats. I will do it in a way that's
easy to understand and read. We will have a helper class, public class weapon stats. Notice I'm defining a
class within a class. I copy these properties
inside the class like this. And every weapon will
also have a list. I need system collections
generic to use a list, and it will be a list
where each element is an instance of
weapon stats class. I will call that list,
for example, starts. In a minute, I will show you
visually what we are doing. It will be easier to understand
when you actually see it. I know from before that because phaser weapon
extends weapon, I can treat all these
properties as if they were declared on phaser
weapon script directly. And if I save on phaser
weapon game object, I can see this weapon
level property, even though I didn't define
it on phaser weapon script. It's defined on the
base weapon class. But how come I see this, but I don't see
my list of stats? It's because this
weapon stats class, even though it's public to
see it in Unity Editor, I need to serialize it system
dot SerializB like this. Now if I save, I have
the stats list here. This is the list, and each element in that list will be an instance
of this class. What does that look
like? I expanded. And plus to add element. You can see it will use
weapon stats class as a blueprint to add stats
for each weapon level. It will be replacing these, but if I remove them right
now, I will break the code. So let's do it step by step. Stats list, the
first element with index of zero will
define the breakdown of stats for each
weapon property when phase weapon is
on weapon level zero. Five speed, one damage, size is one, amount is one, and range is one. Plus to add one more element. This will be weapon starts when we get to weapon level one, speed of ten, two damage, size of two, amount of two spread within a
range of two units, plus to add one more block. When weapon level is two, speed will be 15,
damage will be three, size of each bullet will be 2.5, amount is five and bullets will be spread across a
range of three units. So we have this
weapon stats class that defines a blueprint. We have a list of weapon starts. Each element in this list will represent one weapon level. So as weapon level increases, we move from block to block, and we apply all these starts to our weapon based on its
current weapon level. When weapon level
starts at zero, phaser weapon will be taking values from this block of stats. When we level up and get
to weapon level one, phaser weapon will change
based on values in this block. Weapon level two will be
values from this block. So instead of accessing this single amount value
that I'm about to delete, I want to access one of these amount values
from my starts list and which one of them I will be accessing depends on the
value of weapon level. I take stats list at the index of weapon
level dot amount. This is the stats list. This is weapon level. So for example, when
weapon level is zero, we take stats list
at index zero, and we access this amount value. We will produce only one Bolt. When weapon level
increases to one, we will access
stats at index one, this block, and this amount
value, which is two. So we will produce two Blites. I also have to do
the same thing for amount here and also here. Depending on whatever the
current weapon level is, we are accessing different
amount values in stats list. The same thing will apply
for all other stats. This block is for
weapon level zero. This is weapon level one, and this is weapon level two. We can add as many weapon
levels as we want, and we will add more soon. At this point, we replaced this amount with this
value from line 20, so I can actually delete this and the code
will still work. I save and I play to test it. Weapon level is zero. We are using this block. Amount of bullets is one. If I manually increase
weapon level to one here, we are accessing this block, this amount value of two. If I set weapon level to two, we are accessing
this amount value of five. Our code works. I want to replace all these
remaining starts with these values that change
based on weapon level. So let's do it. What else do I have in phase
weapon script? I want this range value to
dynamically change as well, based on the current
weapon level. It's in two places. So I say starts at index
of weapon level dot Range. Weapon level zero or one or two. We also have this size value that will make bullets
larger as we level up. Starts weapon level dot size for X and Y component
of local scale vector. Weapon level one is
just the base size because size value is
a multiplier here. Weapon level one is double size. Weapon level two will
be size times 2.5. Let's save and play just
to check if range and size respond correctly to
changes in weapon level value. Weapon level one, two bullets, double size, range of two units. Weapon level two, five bullets, 2.5 times the base size, spread across the
range of three units. Weapon level zero is just a single bullet
at the base size. We have two more properties
that we want to connect to these stats blocks,
damage and speed. These properties
are not used here. We are using them inside
Phasor Bullock script. Here is the damage.
Here is the speed. Accessing the values that
sit on weapon script or phaser weapon script from here takes a little bit more
code, as you can see. We need to get phaser
weapon instance, so I actually have to
access phasor weapon, and from it, I want
this stats list. Phasor weapon instance
starts at index of phasor weapon
instance weapon level. It will work like this,
but it's a bit long, and we will be writing phasor
weapon instance a lot. So let's put it in a variable
and give it a shorter name. Up here, I define a variable. Type is phaser weapon, and I call it, for
example, weapon. Start method. And I set weapon to phaser
weapon dot Instance. Now, instead of writing
pas weapon dot Instance, I can simply write weapon, weapon dot stats at the index of weapon dot
weaponlevel dot speed. And now I'm accessing these speed values from
phaser Bullet script. I just have to use this
prefix in front of every stat name that I
want to access from here. I do that in both of these places to access
the current damage value. So we are accessing
damage here and here, and we are accessing speed. We connected all the stats, and now I can delete this. We have speed value here. It can be five or ten or 15. So as we level up, we should see bullets moving
significantly faster. I save, and I play to test this. Weapon level zero, bullets
are relatively slow. Weapon level one should
double the speed 5-10. Yes, they move faster, and weapon level two should be speed of 15, so even faster. Awesome, our code works. I exit play mode. I set the speed value to eight. This will be 11 plus to add another block
for weapon level three, speed will be 14, damage is one, two, three, four, size is one, 1.4, 1.8, 2.2. Amount is one, two, three, four, range will be
one, one, one, one. We will tweak this a bit more later as we develop the project. Because of the fact we have
speed, damage, size, amount, and range that all do something with how
the weapon works, you can create a different and unique progression
track for your weapon. It doesn't have to
be the same as mine. We built a pretty robust
and flexible system here. Right now, my weapon is at Max level producing
four projectiles, but we will make
changes to it when we finalize the game during the
difficulty tuning section. While in play mode, I
can set weapon level three to ten bullets spread across the
range of five units. It will revert back to what
it was when I exit play mode, but maybe our final
upgraded weapon will look something
closer to this, or maybe even more, even
larger, even faster, depending on what enemies and what bosses we come up with. I did promise some
reverse bullletl action, and as you can see,
we are getting there, but we are missing one
fundamental piece. Right now, we can only
increase weapon level by manually changing the
value in Unity Editor. We need an in game
leveling system. I can do a classic system
where enemies drop power apps or I could
do an experience bar. I haven't decided where
I will take this next, but I guess we will find
out in the next lesson. So if you want to see what
I do, I'll see you there.
35. Experience Bar: Right now, the front layer of parallax background floats at the same speed as asteroids. That's not good
because asteroids should be closer to the camera. They should flow faster
than the background. We have move speed
for each layer here. I will set layer three to -0.4 and layer four to -0.6.
This will look better. We have phaser weapon and it can level up to
become more powerful. We defined stats for each
individual weapon level here. But right now, the
only way to move to a higher weapon level is to manually change
the number here. Let's implement a simple
experience system and connect it to weapon levels. I open player controller script. Player will need private
integer for experience. Private integer
for current level. And private integer
for max level. Now I want to define a list. To use list, I need system collections,
generic name space. This will be a simple list that contains a series of integers, numbers with no decimal points. I will call that list, for
example, player levels. Each integer in this list
will define a breakpoint, experience requirement
for player to advance to the next level. I save this and in unity, I see experience, current
player level, and max level. I will set max player
level to, for example, 30. Here we have player levels list. Currently, it's empty, so
I click plus three times. This list can contain
only integers. I will put, for example, ten, 20, and 30. This means that player will need ten experience to go from
level zero to level one. 20 experience to get from level one to level
two, and so on. We will add more level
breakpoints soon. But first, how do we display the current experience and experience required to
reach the next level? One way to do that is a
classic experience bar. I expand UI Canvas game object, and inside, we already created Energy slider and health slider. I will duplicate Health
slider and I will follow the same structure to
create an experience bar. If I press F on my keyboard, it will focus it like this. I rename it to
experience slider. And I expand it fill area, and fill color here, and I will do full
blue, zero, 0255. I rename this to
experience text. Default value will
say experience. I collapse it, select
the main game object, and I click here
on Anchor Presets. I hold down out to
also set position, and I want to do
this bottom center. I set width to 800 pixels. I select experience text Width
will also be 800 pixels. Experience slider selected here, and I need to center it again. I click here, I
hold down Alt key, and I select bottom center. Okay, now I have
an experience bar positioned in the bottom
middle of the screen. I set height to 50 pixels. I move it vertically,
maybe 80 here. I click this label for
full screen preview. Yes, this looks better. So we created experience
Slider Game Object. Let's open UI controller script. I already have
code that controls Energy slider and health slider. I will just follow
the same pattern to control experience
slider with code. First of all, we will need
a private slider variable. I call it experience slider and we will also need
private TMP text. I call experience text. Down here, I just duplicate this entire function
and I will adjust it. It will be called Update
Experience Slider. I rename it Experience
slider in all these places. I have to be careful not to create an error when doing this. This function takes
the current experience and MAX experience
as parameters. First, we set max
value of the slider, then we set the value of the slider to the current
experience amount, and we set text to show the current experience
MAX experience that we will need to
get to the next level. These current experience and MAX experience values
will be coming from here. Current experience will be this and MAX experience
will be value from player levels list
and which value it is will depend on
player's current level. So inside start method
on player Controller, initially, we set
experience to zero. Then I call UI controller dot instance dot Update
experience Slider. We know that function
expects current and max, so I pass it experience
as the current value. And here on player game object, we have player levels
list with three elements. So if player is on
current level zero, MAX experience will be ten. If level is one, experience needed to level up will be 20. When current level of
the player is two, it will need 30 experience
points to level up. So as max value, we pass it player levels list at the index of
player's current level. So when current level is zero, MX experience needed
for player to level up is player levels list at index zero, which
means it's ten. As current level
value increases, we are moving along this list. They will need a way
to get experience. Let's create a public
because it needs to be accessible from other scripts,
void, get experience. It will expect
integer, I call EXP. Insight, I increment
players experience by the experience that was passed
here, received experience. And we know that whenever
experience value changes, we need to update
experience slider, so UI controller, instance,
update experience slider. I pass it experience as
the current value and player levels at index of
current level as the max value. I save that and in unity. On UI Canvas, we
have two new fields. I drug experience slider here. And experience text in here. If I play, experience
slider updates, and you can see I have zero
out of ten experience. I exit play mode. Let's open asteroid script. Destroying asteroids will give
player experience points. It will have a property. I call, for example,
experience to give. Let's set it to one here. Down here, when asteroid takes
damage and is destroyed, I want to call this public get experience method we wrote
on player controller. Player controller dot
Instant dot GET Experience. I pass it experience
to GIF from line 14. I play. When I
destroy an asteroid, G experience function
will receive that one experience
point per asteroid, and it will update slider
to reflect that new value. Each asteroid gives me
one experience point. Perfect. When I get
to ten out of ten, you can see that it stops. So how do we handle that? At this point, I want
player to level up. Public function, I call, for example, level up like this. Let's select player object so I can see this list
of player levels. So when player levels up, I want to reset the
current experience back to zero so that we can start filling experience bar again towards the next level up. But doing this
means I could lose some experience
that was left over. So a better way to
do this is to say minus equals the experience
needed to level up. Like this, Let's say I'm on level one and I need 20
experience to get to level two. Currently, I have
18 experience and I destroy an enemy that gives
five experience points. 18 plus five is 23. Then I minus the 20 needed, and I am Level two with three experience points coming
from the previous level. The leftover experience wasn't discarded, we have it here. After that's done, we want to increase the
current level by one, but only if the current level
is less than Max level. So only if current level is less than max level minus one, we increase current level
of the player by one. By doing this, we will also start looking at the next value inside player levels list as the max experience value
needed for level up. Every time we change
experience value, we need to update
experience slider to display that change. Okay, and when do we call
this level up function. Every time we get
any experience, we check if the
current experience is more than experience
needed to level up. Let's say I'm on level zero, I have ten experience points, and this function runs again. My experience total is 11,
which is more than ten. So we call LevelUp, which will reset current
experience value. It will increase
current level by one, and it will update
experience slider. I save that and I play. I get one experience point
for each asteroid I destroy. If you look now, my
current level is zero. Max experience is ten. I destroy one more asteroid
and level up function runs. Current level increases to one, which means we are
taking MAX value of 20 from play levels
list at index one, and experience slide
reset so we can collect experience again
towards the next level up. If I keep going, eventually, I level up again and
now the current level is two and Max experience is 30. If I keep going, gaining
experience and I fill the bar all the way
to 30, I get an error. If I pause the game, I can see the error says index
was out of range. It's because we increased
the current level to three, but this code is trying to pull a value from player
levels at index three, and we didn't add
that many elements. We only have index
zero, one or two. Index three is out of range. I exit play mode. We say here that there
will be 30 player levels, but we only added three. I could keep adding
manually here until I have 30 elements for each
of players 30 levels. But if we want this to be
more dynamic and flexible, we can calculate them with code. Let's do it inside
player controller up here in start method. Start method runs only once when the script
is first enabled, and it runs before
update method. Inside, I create a four loop. It will run once for
each player level. We set max level to 30 for now. Each time it runs, we
take this player levels list and we will add some
value. Let's start simple. Just add a value of ten
for each player level. If I save and play, this list will fill
with elements. I can see I have
32 elements here. I exit play mode. I'm adding the first
three elements manually here, ten, 20, 30. I want the total number of elements in this array to match max level value to make sure we have one value for
each player level. So instead of starting the
four loop from index zero, I will first check how many values we
already added manually here and I run four loop
that value from that index, all the way to max level. And instead of adding a
hard coded value of ten, we will add the previous value. I can access them like this. Player levels at index
two, for example. So to make this dynamic, I take player levels list at index of player
levels dot count. So the last index. And as I'm adding one
new value after another, I want each consecutive one to be 10% more than
the previous one, so times 1.1 float. We can't add a float value to player levels list because
it expects integers, so I convert it to an integer. Use built in math function
called seal two int round the value up to the
nearest integer number with no decimal points. Okay, so I start
from this element, and I will be adding more until we have one for
each player level. Each new element I add will be 10% higher value
than the one before. If I save and I play, I get an error that
says out of range. It's because here I want to
look at the previous element, count minus one like this. Now I have my ten, 20, 30, and then every consecutive
value goes up by 10%. Keep in mind that every time player reaches the next level, experience will
reset back to zero, and these values represent how many experience points player needs to level up again. I want to space the values
out a little bit more, especially in the beginning where the difference
between level two, three, and four is very small. So I add a 10% increase
plus let's say 15. Save and play, I might tweak this later when
I'm balancing the game, but the idea is that this
formula will generate experience breakpoints
for each level so that we don't have
to do it manually. 48 68, 90. This seems like a
reasonable set of values. If I manually set
current level 25, now I have to
destroy an asteroid to trigger update
experience bar function. You can see the max
value on the bar is now showing 90 this element.
The code works. So we have an experienced system that increases player level. Here on phaser weapon, we have some weapon levels. When a player levels up, I want the weapon to
level up as well. Public function, I call, for example, level up. Depending on the
value of weapon here, we will be using different
block from this stats list. As we progress from
block to block, phaser weapon gets more
and more projectiles and other stats we have
here improve as well. For example, size and speed. So whenever level up is
called on phaser weapon, we check if weapon
level is less than however many elements we add
it to stats list minus one. We are checking, can
we still increase it? Is there somewhere
to move up to? If so, we increase the value
of weapon level by one, and we move from one
block to another. We don't have stats for that
many weapon levels here, considering player can
get up to level 30, we only have four
weapon levels for now, but I will deal with that later. On player controller,
when player levels up, we will also level up phaser weapon using
the other public level up function we just wrote inside phaser weapon
script like this. I can do other things
when player levels up, for example, I can increase Max health of the player by one. I can heal the player
to full health, and whenever health
value changes, we know that we need to
update health slider to actually show that new
updated value like this. Okay, so player
starts at level zero, three MAX health and
single projectile. If I destroy ten
asteroids, we level up, and player now has
four MAX health, and the weapon fires two
projectiles. Awesome. We have a pretty good
progression system here already. As we level up, at some point, we are strong enough
to destroy the boss. So let's open Boss One Script, and let's make sure Boss also rewards some
experience points. Up here, I define
experience to give. And I set it to let's say 20, considering boss has 100 lives. I go to Asteroid Script
and I copy this line. Back to Boss one script. Down here, when boss takes
damage and is destroyed, we take player
controller instance and we call public get
experience method, and we pass it
experience to give. Okay, safe and play. My weapon leveled up. Let's try to see if I
can destroy the boss. Yes. And I received a nice
chunk of experience for it. Our system works really well. Let's do one more boss. I have two experience. Boom,
I received 20 for that. Weapon level is three right now, and because we didn't add
any more elements here, when player levels up, it will
not increase any further. We stay on weapon level three. I can add many more blocks here to make the weapon more
and more powerful, or I can add more weapon
types and let the player choose which one to upgrade
every time level up happens. So many options. Where
should we take this next? On phaser weapon, I will
reduce damage to one in all of these places because we are already getting more
and more projectiles. I feel like the power increase should be a bit more gradual. These are not the final numbers. I will have another look at the numbers and balance later. I will also add one
more weapon level. Element three will
have range of 1.5. Element four will have
amount of five, range 1.8. I add one more element. Element five will have
speed 14, damage one, size 2.5, amount of seven, spread in a range of two units. We focused on weapons
for a while now, and I feel that there isn't much of a challenge in the game. When I level up my weapons, it's pretty easy to get
through everything. So let's have a look at enemies. We need more of them, much more. As I play, I notice that when Boss destroys asteroid,
we get experience. Why we wrote the code, asteroid getting destroyed by anything will give
player experience, so we have to fix that. Down here inside
asteroid script, asteroid takes damage, gets destroyed, gives
player experience. Asteroid is a bit of a
special type of object. It's not a traditional enemy. It's an obstacle. With enemies, they don't get destroyed by anything else than
by the player, so we can follow this code
structure for all of them. But if we want a special
interactions between objects, like we have between
boss and asteroids, we have to make a small
adjustment to this code. There are many ways
I can do this. For example, I will make
this take damage function, expect a second parameter, Bolin true or false. I call it give experience. And only if give
experience is true, we award player
experience points. By doing this, I
will get an error in all three places where
we call this take damage method on
asteroid because now that method needs
an additional argument. Inside player
controller script here, when player hits asteroid, but not by a weapon, this is when spaceship crashes into an
asteroid accidentally. I can decide, do I want to give experience if we destroy
asteroid like that? I will say no. So give experience parameter will
be false. Save that. Unity console will guide me to the next file that
I need to edit. Phase bullet also calls
take damage on asteroid, and in this case, we do want to give
player experience. So as the second parameter
here, I pass it through. Safe and close. Okay, good. The final third
script is boss one. When boss destroys asteroid, we don't want to give
player experience, so I pass it falls here. Now the errors are gone. Back in asteroid script. Notice here, I'm
giving each asteroid a little horizontal and
vertical push using rigid body so that there
is a little bit of motion. I do that inside start method, which runs only once,
but at this point, we turned asteroid into a
pulled reusable object, which means that this
code doesn't run when we repeatedly reuse the
same asteroid object. I can do a tiny factor
here to fix that. I cut this line of code
that actually applies the forces to asteroids rigid
body component to push it. Now, push x and push Y are not accessible here because we define them in a
different scope. So let's cut this and
paste them up here. Okay, like this, I save
and back in unity, I get an error that
says range is not allowed to be called from a
mono behavior constructor. Back in my script, up
here in constructor, I can give my variables
values like this, but I can't do randomized
values directly in here. So instead, I copy
and paste this here. It needs to go
before this line is called because we will
need these values there. Up here, we will just
define the variables to make them available anywhere
inside asteroid class. And here I just take them and randomize their
values like this. We define them here, and every time we activate
the asteroid, we randomize them again and we give rigid body a
little physics push. I save that and back
in unity, I play. I get one more error. This time it's because here, we know that enable
runs before start. So in the beginning,
when I'm filling my object pool with
asteroids, as I prepare them, this code runs and
looks for rigid body RB before I actually found
that component on line 31. We define a variable here. We assign it here, but just
on that very first load, this runs before start
method found that component. I'm going to say,
only give rigid body a push if we have
rigid body like this. I suspect this will not
do the push at first, but only when we reuse the asteroid. But
let's check that. To clean up, I can also
delete these two lines and I can simply
give them values up here since we are not changing
them dynamically anyway. We only set lives
to Max lives every time asteroid activates
and reactivates. Save that and let's see. When I play, we don't get
that physics push at first, only as they respawn
because that's when Enable actually finds a
rigid body component. Also, Boss is coming and we don't get any experience when Boss
is destroying asteroids. We only get experience when we destroy asteroids with bullets. Perfect. I copy these
three lines of code, and just for that first
time we are using them, I put that code in here. I might actually not need to randomize push x
and Bourgeois here. Let's see. I save
that and I play. And yes, they're moving
as soon as they spawn. We have that extra bit of dynamic motion to make the
world feel a bit more alive. I exit play mode. I go to Assets art, and we have a lot
of images here. Let's organize them in folders. I create a new folder
I call enemies. I put Greater one in
boss one as well. I create another folder,
I call environment. I put all four background
layers in here. In prefabs, we have greater
Burn and Zap effect here, inside effects folder because all of these work in
a very similar way. But for art, I will put
these spreadsheets inside enemies folder because this
art is very enemy related. It doesn't really matter. Asteroid can actually go inside enemies or even inside
environment folder. I create one more folder, I call, for example, weapons. So far, we have just
this bullet one. And another folder, I call UI. I put this button
image in there. Also, these two
larger player images we use in main menu
and game over screen. Finally, one more folder, I call effects, and I put these three boom
spreadsheets in there. Space well is not
really an enemy, but let's put it inside enemies folder for
now. Nice and clean.
36. Swarms of Enemies: Let's open enemies folder, and I prepared another
spread sheet for you. You can download it in video
description down below. I drag it in here, I select
it and open sprite Editor. Slice grid by cell size, and frames are 120
times 120 pixels. Slice and apply. This is all the same enemy type, but I wanted some variety, so we will have some
randomized skins. I drag one of the
frames into the scene. Sorting layer at sorting layer, we are creating
multiple enemy types, so let's give them
their own layer. It will be on top of objects, but under the player like this. Okay, prefabs enemies. Boss is large. It will be on enemies layer all
the way in the pack. So order in layer zero. Beatle Morph is a
standard enemy size, so it will be on enemy layer, order in layer one on top
of the boss in front of it. Critter is the smallest, so I want it to be
in front of boss and regular enemies,
so it's visible. Enemy layer, order in layer two. I renamed this enemy to
Beetle Morph like this. We will have multiple
enemy types. Each one will have
a little gimmick, something unique about it, and we will learn how to
implement it step by step. This will be a basic enemy type. We will practice some
motion patterns on it. We have all three evolutions
of this enemy now, but I can prepare
more enemy types. If you like this
project, let me know and I will create some
additional lessons. I will rotate it by 90 degrees
like this, just for now. We will make it face movement
direction later anyway, which will override this. I add component,
circle collider two D. I can click this button
to edit collider geometry. I get these four handles and I can scale and move the
collider that way. But more often than not, that will put it slightly off center. So I could also just
set this offset to 00 to perfectly center the
collider over the enemy, and then I can edit
this radius value, which might be a
better way to do this. I will go with 0.5. I need to give it a tag, but I can't really use
critter or boss tag because those are
special enemy types that have some
unique interactions. I will need one more tag
for general enemies. This way, we can
add more critters, boss and enemy types and keep those interactions
separate if needed, depending on what
ideas we come up with. I select Beetle
Morph game object, and I give it enemy tag. It will also need a layer
because I need to set up physics and define which
layers collide with which. Let's add layer for
enemies like this. I select Beatle Morph, and layer will be NEMA. I go to prefabs enemies, and I drag Beatlemorf
game object in here, turning it into a prefab. Now I can delete
it from the scene. I right click Create Empty. I will call it Beetle Pool. We want an object pool for
these new enemies so that we can reuse them as we are doing with many
other game objects. We have our systems in place, so creating object
pool takes seconds. I add component Object Pooler. I drag Beetlemorf
prefab in this field, and the initial pool
size will be five. I parent it under
object pools here. Now I can already plug
it into Object spawner. Waves list here, I take Beetle Pool and I drag and
drop it into the pool field. These numbers are fine for now. I enter play mode to
see what happens. They are spawning, but
they are not moving. I need to add component. Floating space. I play again. This time they are coming. You can see bolets have
no idea what to do. They just slide around them. You will need a script. Inside assets scripts, NEMs, I create new mono
behavior script, I will call NM. Our enemies will need
update and start methods. But first, let's
declare some variables. Serialized field private
because I want them to be visible in the
inspector. We will need lives. Max lives, damage, and
experience to give. All of them will
be integer type. Few things to do now, which one to start with? Maybe let's use this damage and make the enemy deal
damage to the player. Private void on
collision Enter two D. We use this many
times in this class. It will be the same here. If the object this enemy
collided with has a tag of player we need to access the player controller
script so that we can pass it that damage
value to handle. I'm looking for player
controller script component. I will call it player for short, and I find it by checking
the game object we collided with that I know
has a tag of player, and on it, I find player
controller component. If we were actually able to find that player controller component that I'm calling
player here for short, we know that it has a
public take damage method, so I call it and I pass it whatever damage this enemy
is dealing like this. Save that and in unity, I will actually not attach
this enemy script to anything. Instead, I will
create another script for Beetle Morph enemy type. I open it. I delete
all the code inside. Enemy script will be code that's shared for
all enemy types. Notice that it
extends monobhavior, giving us the ability to attach it to game objects
as a component. Betlemrph enemy type
will extend enemy class. It will become a subclass, a derived class that will be
inheriting everything from enemy class and by
extension from monobhavior. I can prove that relationship by saving and if I go to
Beatlemorf prefab, I lock the inspector to make sure it stays on
this game object, and I go to scripts Nemes, and I will not attach
the NEM script, but instead, I will attach Beetle Morf script as
a component like this. This is the Beetle Morph script, which is completely
empty right now, but because this bit of code, it extends enemy class. Beetle Morph is a subclass. Enemy is a base class,
the parent class, and for that reason, I can see all these fields here
on Beetlemorf script. We did the same thing for weapon script earlier
in the class. Structuring our code
this way allows us to put the code shared among
all enemy types here inside the enemy script and
code that's unique and only specific for Beetle
MorfEemy type will sit here on
Beetle Morf script. This will make our
de much cleaner and easier to read
and navigate in. We worked like this
before when we created a base weapon class
and phaser weapon as one of the subclasses. With enemies, we will go a bit
deeper and I will show you more tips and tricks with
subclasses and inheritance. Max lives will be five. It will deal one damage
if it touches the player, and it will reward one experience
when destroyed. I play. I crash into the enemy three
times. Yes, this works. So this code is shared
for all enemy types. And here we will put code specific only for
this enemy type. For example, let's say I want to randomize the appearance
of this enemy. We have four different visuals. To do that, I need to access
sprite renderer component. I will do it on the
shared enemy class because all enemies will
have sprite renderer. Inside start method, we find it using Get
component as always. On the subclass, I create
an array of sprites. I call it sprites like this. We did this for critters
and asteroids before. Save that and now on Beetle MorfPrefab I have
this sprites array, so I click Plus to
add four elements. I go to art enemies, and we have four frames here. So I drag the first sprite here, the second one here,
third and the fourth. Now I want to randomly assign each enemy one
of these four sprites. So let's do it
inside start method. I take sprite renderer, which we defined here on
the base enemy class. So I want to access that Sprite render component
and it's sprite field. But for some reason, I'm
getting a warning and it says Sprite render is inaccessible due to its protection level. The field is set to private, which means it can
only be accessed within the class
where it's declared, but not from the classes
that inherit from it. I could do public, which
would get rid of the warning, but we also have
another keyword, protected, which is
similar to private. But now this variable can also be accessed from
derived classes, from classes that
inherit from this class. Now it will work and I
can set the value of this field to one of the
elements from Sprites array. I just pass it index of zero
or one or two or three. I can also randomize
that index by, say a random range between
zero and sprites length. Here on the base enemy class, I define sprite render
as a protected property. Inside start method, we pointed towards this
sprite renderer component, and here on the derived
class on the subclass, we assign this field a random
sprite from these four. If I save and play, I will get an error. It says that this sprite
renderer reference is not set to an instance of an object. How
is that possible? I clearly do that here where I point the variable
towards the component. We are learning how
to extend classes, and there is one thing I didn't explain
yet. Look at this. I have a start method here
on the base enemy class, and I'm also declaring start
method on the derived class. The way inheritance works, we are accessing
everything through this Beatle Morph script that inherits from
enemy base class. When we call something from this script and it
can't find it on here, it will follow the
chain of inheritance, and it will go up to enemy
class and look for it there. But because I have a start
method here, it will find it. I will just run it, and it will ignore the other start
method on enemy class. Sometimes you want to
override a class like that, but in our case, I want
to run both of them. I want the code from both
start methods to run. To do that is simple. On the base parent class, I use public virtual here. It means it's accessible
from anywhere and it can be overridden
by a derived class. On the derived class here, I'll use public
override like this. Inside, I say base dot SAR to first call all the code inside the start method here
on the base class. This code that is shared
for all enemy types, and then all the other
code here will run. This code is unique only to
this particular enemy type. If I save and I play, now all of it will work. We are randomly assigning each enemy one of
the four sprites. You can see that the bullets don't really know
what to do here. They just slide along. It might be an
interesting behavior for one of our future weapons, but here we don't want that. So this is a derived class, subclass, also called
a child class. It inherits from the
parent base class. Here, we put things that
all enemies need so that we don't have to write
them over and over for each enemy
type separately. One thing that all
enemies need is a public take damage
method so that the bullets can actually pass the weapon damage along
to this enemy to handle. I will follow exactly
the same code structure we did for this thing before, for example, for asteroids. I create a public method, I call take damage. It takes a parameter for damage. Inside, we reduce
lives by damage. If lives are more than zero, we will do the
flash in a minute. Else, meaning that lives
are less or equal to zero, we will deactivate it, returning it back to the pool of available reusable objects. Okay, this is fine
for now. Save that. And on prefabs, bullet one, I need to unlock
my inspector here. I want to open Phaser
bullet Script. Down here where the bullet
checks what it collided with, I copy this block and
I paste it in here. I have to be careful
about syntax and brackets when I do this. We add one more block. If the object, Phasor bullet collided with has
a tag of enemy, we want to find its
enemy script component. If we found that component, we take it and we call public take damage
method we just wrote. We pass it the current
weapon damage that changes as the weapon levels up and we deactivate the bullet, returning it back to
the object pool and basically marking it as
available to be reused. So this is that method
we are calling. It takes damage, reduces lives, and when the lives are
less or equal to zero, we deactivate the enemy object. I save and play to
see if it works. Something is wrong because
I gave enemies five lives, but I can see they get
deactivated after one hit. We define lives and
MAX lives here, and we set those values
up in Unity inspector. I already did that, but one
more thing we have to do. Every time we activate and
reactivate this object, we need to set
lives to Max lives. Now they have five
lives that works. I want them to flash
white on impact. I go to prefabs enemies, Beatlemorf prefab, and I
add component flash white. You can ignore this warning
message if you get it. Up here, I define my components, private flash white
type that I call, for example, flash white. We did this before
for asteroids. Inside start method, I get that component and now I can call its public
methods when I need them. So when we hit the enemy and
the lives are not yet zero, we want the enemy
to flash like this. When we upgrade our weapons
and we have many bullets, sometimes it gets hit by multiple at the same time
or really close together. So the reactivated enemies could still have
the white material. To prevent that, I call
flash white reset, and I will run this before I deactivate the
enemy game object. If I save and play now, They flash on hit. It would be good if it also flashed white when
player crashes into it to better indicate exactly which enemy caused the
player to lose a hit point. I open player controller and
to make the enemy flash, I will go down here on
collision Enter two D. I copy this code block, and I say, if player spaceship
collides with enemy, so this is player's body hitting the enemy,
not player's weapon. We try to find enemy
script component on it. If we find it, we call it T damage function and
we pass it one damage. Enemies Ta damage function will handle the flashing because we just wrote that logic there. I save and play. Now, player crashing
into an enemy will make the player flash and the
enemy will flash as well, making it more clear
what exactly we collided with and why
we lost the hit point. We want enemies to also
play some destroy effects. Private object pooler I
call destroy effect pool. I will actually define
it on enemy base class because all enemy types
will have a destroy effect. But which pool we will
point that variable two will be different
for each enemy type. So I do that here inside
Beatlemorf subclass. We don't have access to private property on
the parent class, so we already know I can just change private to
protect it like this. This will make this
property available on this enemy class and on all
classes that inherit from it, such as this Betelmrf class. Here, I will point
it towards one of the object pools that
holds a destroy effect. Let's start with Boom one pool, just to see if it works. We've done this before
many times in this class. So I'm defining destroy
effect pool variable on the shared enemy class, but I'm deciding which destroy effect we will use individually
for each enemy type. Doing it this way,
I can also write all that logic that handles
destroy effect pool only once here in enemy class so that I don't have to repeat that code for
each enemy type. I'm following the same
pattern we used before. If enemy lives are
less or equal to zero, we get a destroy
effect from whichever destroy effect pool we pointed
this variable towards. We position that effect over the enemy that is
just being destroyed. And we will also rotate the effect the same as
the enemy to make sure the more specialized
destroy effect animations are facing in the
correct direction. If we are using this
as the destroy effect, rotation doesn't
matter that much, but we will use
something like this. So I want this animation
to be facing in the direction the enemy that is being
destroyed was facing. Finally, we activate destroy effect that will
play the animation and we deactivate the
actual enemy game object that just got destroyed. I save that and I play. We are using Boom
one pool for now. And yes, the effect plays. This blue explosion
animation is nice, but I'm saving it for
mechanical enemies. This creature will need
a custom destroy effect. Notice what is
happening to the boss. It's getting stuck
behind enemies. You need to set up
interactions and define which layers will interact and which layers will
ignore each other. I exit play mode. Notice that on
Beatlemorf prefab, I set layer to anime. This is important for
collider interactions. I go to edit project settings. Physics two D layer
collision matrix. This intersection enables and disables collisions
between objects on the same enemy layer. I disable that. I also disable collisions between enemy
layer and boss layer. I want to keep enemy versus
bullet collision detection. I disable enemy versus
critter collisions, also enemy obstacle and
enemy level boundaries. I will keep enemy versus
player collisions checked. So basically, I want the
objects I put on enemy layer to register collisions
with objects on player layer and
bullet layer only. We are using Boom one Pool
when we destroy Beetle Morph. I want to give it a
custom animation so I go to assets art effects. Actually, I'll put it
inside enemies folder here. You can download all art assets in the resources section below. I called this file Beetle
Pop because it will animate the enemy's shell
being inflated and popping. I open Sprite Editor, slice grid by cell size, 110 20 times 120,
slice and apply. I expand the sprites and I will drag one of
them into the scene. I rename it to Beatle Pop. I put it on enemy layer
and order in layer, maybe ten on top of
all other enemies, so we can see this happening. I go to prefabs effects, and I'll also set
Kriter Burn to nemayer, order in layer ten, and KriterZap to enemy layer,
order in layer ten. I go back to my Beatle
Pop Game Object and I open Animation tab. If you don't have it open, you can go to Window
Animation Animation. I make sure Beatle
Pop is selected, and in my animation window, it says to begin animating
Beatle Pop and I click Create. I will navigate to
Assets animations and I call it Betlepop dot Nim. I click Save. With
Beatlepop selected, I make sure this drop down on my animation window
says Beatle Pop. I expand the spreadsheet. I left click the first frame, and I shift click the last frame to select
all the frames in between. I drag and drop them
on animation timeline. Play too fast. 20 samples. Yes. I go to Assets Animation. I have my new animation
controller here. I need this other
animation clip file, I antique group time. I want this animation
to play only once. I go to prefabs effects. It will work exactly the same as these
creater animations. I select Beatle Pop, I reset transform just to
keep these numbers nicer, and I add component. We will need this float in space and also destroy
when animation finished. It will all work already. We set this up
earlier in the class. Let's create an object pool so we can use them
and reuse them. I create empty game object. I call it Beetle Pop pool. I drag this into
prefabs effects folder, turning it into a prefab. I can delete it here.
Beetle Pop pool, selected, I reset transform, at component, Object Pooler. Initially, we can
create just two. I drag the new Beatle Pop prefab into this
field, and that's all. I put it under Object pools and inside Beatle Morph
script, I want to use it. I make sure I use
the right spelling Beetle Pop pool like this. Now if I save and I play, We have two inactive
objects here, and I can see they activate and deactivate to play the
animation as needed. I'm using the same animation for all four variations
of this anime. I think it works fine, but I could have also created a custom one for each
variation if I wanted to. We also need some
sounds to play. I could use something from Assets Audio folder
we used before, but I will go with some custom
audio files I prepared. You can download them in the resources section
below as usual. I drop them into
Assets Audio folder. I name them Beetle
Destroy and Beetle hit. Same as we did before. I drop
them into the hierarchy. With both of these selected, I uncheck play on Awake. Output will be effects
audio mixer group. I put them here
under Audio Manager. I open Audio Manager script, and I create an audio
source property for Beetle hit and one for Beetle Destroy. We have these public
play sound and play modified sound methods on
here that we will use. Save that and back in Unity. Audio Manager selected. I have these two
new fields here. I drag Beetle hit here
and Beetle Destroy here. Inside enemy, we want to play the hit sound whenever
enemy gets hit. Audio Manager, instance,
play modified sound to have some randomized pitch because this sound might
play over and over. But what sound will I play? I want to define
that separately here so that we can have a unique
sound for each enemy type, similar to how we have unique destroy effect
for each enemy type. I will give it a
generic name here, shared for all enemies. For example, hit sound. And what sound
will this variable point to will differ
based on enemy type. I copy this and I paste it here. When we destroy an enemy, I want to play destroy sound. Now I have to define
these properties, so I go up here
inside enemy script. I will use protected
to keep this property limited to this class and
classes that inherit from it. Type is audio source, and I call it hit sound. One more protected audiosurce
I call destroy sound. So we will play this,
but I will point them to actual sounds here
inside Beetle Morph script. Hit sound will be audio Manager dot instance
dot Beetle Hit. Destroy sound will be audio manager, instance
Beetle Destroy. Doing it this way, I can have completely different
unique sounds when I introduce the next
enemy type later. Okay, so here on the subclass, I define the actual
sounds I want to play. These variables are
defined here on the parent enemy class
under some generic names, and we play them from here. Save and play. And we get
sounds on hit and on destroy. Perfect. I wanted kind of a
sound of soft cracking shell. What else can we do? Right now, the enemies just
passively float in space, same as the asteroids. They don't really move relative to the site scrolling
game world. You will have
different enemy types with different
movement patterns. So on the shared enemy class, I will define protected
float speed X for horizontal speed and protected float speed Y for
the vertical speed. I will give them default
values of zero here. Again, as we do, we can give each enemy type a different
speed value here. Let's say for this one, I want the horizontal speed
to be a random range between -0.8 and -1.5 to make each enemy move from right to left at a slightly
different speed. On the parent enemy class, inside update method, I will
take transform position. And I say plus equals, whatever its position is, add to it new vector three, where its horizontal component
is speed X times Delta time to make sure it runs the same speed regardless
of frame rate. And the vertical component
is speed Y, Ts Delta time. If speed X or speed Y, for that particular
enemy type is zero, this line will not do anything to its position, basically. For this enemy type, we will define only speed X in this way. Speed y will stay at zero. Now if I play, each enemy is moving to the left at a
slightly different speed. And when asteroids come, you can see these are just
passively suspended in space. The enemies are actually flying and moving horizontally
in comparison. We have a static camera, but we managed to achieve a pretty nice and consistent
side scrolling effect. Also, when we destroy an enemy, I needed to reward
experience points. We decided that this enemy
type will give one experience. Player controller, instance, and I call it public get
experience method. And I pass it experience to give from this enemy that
was just destroyed. I save and play. We are defeating enemies,
sounds are playing. We are gaining experience. Enemies inflight and pop. They move at a randomized
horizontal speed. Okay, this game is
really coming together. It might be a bit
too easy to defeat this enemy type
if they just come directly towards us
from right to left. Maybe we can make them
move up and down or follow some movement pattern so that the player
actually has to aim. Let's do that in
the next lesson. He.
37. Enemies and Sine Waves: Sold. I reset transform on the pref app just to
make the numbers nicer. In this file, we write code that is specific only to
this enemy type. I want to write some code that needs to run
for every frame, so update method, but we also have update on the
parent enemy class here. I want this line to run first and then the other
update method after. So public virtual void update on the base class and public override void
update on the subclass. And we call base dot update. Now I can write some
special logic that will apply only to this
particular enemy type. I want a sine wave motion. Sine wave is a geometric
wave form that oscillates. It moves up and down or left
and right periodically. I want enemies to follow a
sine wave motion pattern, which is extremely
easy to implement. I create a helper
variable I call sine. It's equal to a built in
math dot sine function. It expects one parameter, an input angle in radiance. Basically, we can put in any value that gradually
changes over time, and the speed at which it changes will determine the
shape of the sine wave. Let me show you exactly
what that looks like. In a game like this, the
most common thing is to simply put the horizontal
position of the enemy. As the enemy flies
from right to left, this position value
changes and sine function automatically maps that change in value along a sine wave path. So it expects an angle
value in radiance, but it will just take position
and it will all work. Now I want to apply that wave
to enemy's actual position. It will be new vector three. The horizontal
component will just stay whatever it is at the
moment when this runs. But the vertical
component will be taking those updated
sine values, meaning that the motion
we will get will be up and down, vertical
oscillation. If you have a simple game,
this is all you have to do. Look what happens
if I save and play. That looks pretty good
already, doesn't it? Beatlemorf prefab should be
rotated 90 degrees here. Object spawner. I sent
this to 00 here as well. And here, 0.2 seconds and
thousand objects per wave. I play I pose the game, and on player controller, just for now, I increase
energy regeneration to five. I play, and we are getting
a really nice sine wave. Notice how it by default centers around the
zero vertical position, and it oscillates,
goes up and down between plus one and minus
one unit in the scene grid. Also notice that they speed up when the player uses
the superspeed move, which is something we
don't really want. If we had a simple game with a static scroll speed,
we could be done here, but because our player
can speed up and because we use static
camera and we move objects from right
to left to create an illusion of a player that
is flying through space, we have a few more
things to do here. As much as I like how they follow this clean,
regular sine wave, the fact that they speed up when the player speeds up
doesn't really look right. So let's change this to
make it look better. As update method runs, enemies transform
position X is changing, and sine function is mapping those changing values
on a sine wave path. We are then using that
oscillating value as vertical position
for each enemy, and the speed at which they
move between these two values depends on how fast the value
we pass to it is changing. So basically how fast the enemy
moves from right to left. When the player uses
a super speed move, this position X value
starts changing faster and it causes the enemies to speed up in a way
that we don't want. Let's try something else. We learned that math
sine function expects an angle value in radiance and it maps that
position on a sine wave. We also know that we can pass it any floating point number and it will interpret that as an angle value for
its own purposes. So what if instead of using enemy's horizontal
position here, we will use real time
that is passing by. I declare a float variable. I call it, for example, timer. As update method runs, I will increase or decrease
timer by Delta time. I need this value
to update to get that fluctuating wavy movement between plus one and minus one. Then I pass that
changing time value here as an angle to
math sine function. I can also do minus equals
here. It will still work. We are using these objects, so I don't want an endlessly increasing or
decreasing time value. I want to reset it
back to zero every time we activate or
reactivate the object. I will need on enable
method for that, but I also want the code inside on Enable on the
base enemy class to run. So as usual, I will say public virtual void
on enable here. And on the subclass, I will say public
overt void on enable. Inside, I first call on
enable from the base class, and then I can put
some custom code that I want to run only for this
particular enemy type. All I need here is to
reset timer back to zero. Okay, so ever changing value pass as angle to
math sine function. This function will map
those updating values on a sine wave oscillating by default between minus
one and plus one. We then use those values as
enemies vertical position. If I save and play now, I increase energy region on the player just so I can stay in super speed a bit longer and observe how it affects
the sine wave shape. Tir is changing much slower than position
that we used before, so as a result, we have a sine wave with a
lower frequency. We can change the shape of the resulting sine wave by changing its frequency
and amplitude. Frequency value describes
how quickly the wave cycles, how many cycles it
does per second. Amplitude refers to the height from the center
point of the wave. It defines what is the
maximum displacement from its resting point. Later, I will randomize
them for each enemy, so let's already put
them inside on enable. Just to see how it works, I set frequency to two
and amplitude to three. In the formula, we multiply
the angle by frequency. And amplitude will affect
the entire resulting value, so it's outside the
brackets like this. Don't worry about this too much. It's a very standard formula. You can just tweak the values, see how it affects the
resulting wave shape, and after a while,
it will make sense. If I play, not
getting a clean wave, so I will comment this
line out for a while. I want each enemy to have the same identical
horizontal speed for a while so that we can
see the wave clearly. Now, if I play, this is
what happens when we set frequency to two
and amplitude to three. Amplitude is pretty
straightforward. The wave always
goes three units in both directions away
from the resting point, which for us is at
vertical coordinate zero. The wave will look like this. This is when player is
not using super speed. If we enter super speed, it will change the
wave shape like this. They are selected and I
increase energy region. Now, if I boost for longer, you can see the other
wave shape better, the beauty of mathematics. I made this a little bit more challenging for us
because I gave the player this super speed move and also because this is a game
with a static camera. So when we use superspeed, the custom float in space script is moving
enemies to the left faster, depending on the
current world speed, which causes the change
in the wave shape. Strangely enough,
this will already look consistent if we separate the enemies as
individuals when they are not grouped in a snake
like formation like this. While in play mode,
I can also give player a high energy
value just so I can sustain the boosted
speed for longer and fully see the
other sine wave shape. I think this motion is
pretty interesting, and we will explore
deeper if we decide to do more traditional
space game where enemies come in
oscillating waves. But first, let's
just send them out as individuals because
that's easier. I exit play mode, and player energy and
region automatically resets back to default
values because changes we make here in
play mode don't save. I will set frequency to a random value
between 0.3 and one. Amplitude, the displacement from the center point of
the wave will be a random value between
0.8 and 1.5 units. I also uncomment this
randomized speed. Right now, the wave
forces all enemies to oscilate around vertical
coordinate zero. I want them to
oscilate up and down around their original
randomized vertical position, so I will save it in
a helper variable. I call, for example, center Y, the vertical center point, the vertical resting
point of the sine wave. Every time enemy spawns and response reactivates
from the object pool, it will get some random vertical
position assigned to it, so I will save it in
this centra Y variable. Then instead of oscillating
around the coordinate zero, I will make it go up and down in a sine wave motion around that randomized
vertical position. Object spawner. The first wave will be 20 beetle
morphs in total, one every 0.8 seconds.
38. Enemy Motion Patterns: I also want to offset
the initial pace where on the sine wave the enemies are placed at first when they spawn. I can set the starting timer to anything that is
different for each enemy. For example, their vertical position will do that job here. As they move up and down
along the oscillating path, it would be nice if
the enemies also rotated to face in the
direction they are moving. I will write that
functionality as a reusable utility
script so that we can attach it
as a component to other enemy types in the
future if we want to. I go to scripts Utils and I create a new
mono behavior script. I call it face
movement direction. If you remember, we already
did this with critters. They always face the
direction they are moving. Let's use the same
technique here. All we have to do
basically is to take the current
position of the object, its previous position from
the previous animation frame, compare them and rotate
the object accordingly. I will need a private
variable that will hold a reference to
the previous position. Another one I call
move direction, which will be the
difference between the current and the
previous position. And quaternion for
target rotation. Unity uses quaternions
to represent rotation. It's basically the same thing
as vector three construct, but quaternion has
four values X, Y, Z, and W. W is a special value representing
the amount of rotation. We don't really need to
understand that for this project, so don't worry about it now. Also, let's define float
for rotation speed. Initially, I set it to 30. Start method. We set previous position to the object's current
position just at first. Every time update runs, we set move direction, which is a vector three
to the difference between objects current position and
objects previous position. And after we calculated that, we do some rotations, and then we set
previous position to objects current position so that it can be used as the previous position in the next upcoming
call of update. As update runs over and over, previous position
is always object current position from
the previous update. Once we calculate
move direction by comparing the current
and previous positions, we can set target
rotation by using built in quaternion
look rotation method. We are in a two D
space, and this method, as far as I know, is
built for three D games, but it will work for us here. All we have to understand
is that we define which way is up and where
do we want to rotate. I have a little typo
here. That's better. There is another way to
do this with atan two, which I prefer
because I made a lot of web games in
JavaScript before. But let's try this
approach with quaternions. All we have to
understand here is that we calculate what is the target rotation
that we want to achieve to make the object
face where it's moving, based on its current
and previous position. This method is
handling that for us. Once we know what is the
target rotation we need, we will use built in
rotate towards method. It takes objects
current rotation, target rotation, and how fast we want to animate
towards that target, the speed of that rotation. This will give us smooth
animation because instead of snapping directly
to the target rotation, we just calculate smaller step towards that target rotation. And for this update, we
do just that one step. I want this to run at the
same speed on all devices, so I will scale rotation
speed by Delta time. Okay, so check where
the object moved. The difference between
the current position and the position from
the previous update. From that, calculate
target rotation we need to make the object
face in that direction. Then animate that rotation gradually towards
that target rotation. Don't snap directly
to that new rotation, and then set the object's
current position as its previous position so that we can use it in the
next upcoming update. Save that and let's
see if it works. I go to prefabs enemies,
and on BeatlemorPrefab, I add component face
movement direction. I play the C, and yes, enemies no longer face
directly to the left. They rotate in the
direction of movement. I want to really
test it. So what if we have larger waves? And by that, I mean, I will
change the amplitude range, and I will also
increase frequency to higher values just for
testing now. I play. I can see they don't
rotate all the way to face where they are moving if they
move much faster like this, which means my rotation
speed is too slow. Well, this makes them more
dangerous and hard to avoid. Wow, they just
slapped me around. Maybe we can use this motion for some more difficult
enemies later in the game. I increase rotation speed to 50. Still too slow. Maybe 100 then? I want to say we
are getting there, but this still
feels pretty slow. It really depends on what is the final effect you
are going for, I guess. I doubled the rotation
speed to 200. This kind of feels like
playing a space Frogger. Well, that's an idea for a game. Maybe a special Frogger
level later, we can do that. Now player superspeed move is actually helpful
because it allows us to quickly push through some of these dangerous paths
and move out of the way. Notice that when they don't
move in a single formation, it actually makes
visual sense that the sine wave path gets longer when we speed up, so
we are good on that. There is one small
issue that comes from the fact that we have a game
with a stationary camera. And we create an illusion
of movement through space by moving everything
from right to left. When we speed up, the
relative difference between the current and
previous location of the enemy makes
them rotate towards a different angle because
when we speed up, we are stretching that sine
wave as we saw before. We want that stretching when it comes to positioning
of enemies' bodies, but we want to negate it when it comes to the rotation
they are facing. It probably doesn't
make much sense now when I say it like
this, so let's just do it. And when you can see it,
hopefully it will be more clear. In Beatlemorf script,
I set frequency of the sine wave to a random
range between 0.3 and one, and amplitude will
be between 0.8 and 1.5 units from
the center point. Now, the final issue I mentioned will
become more obvious. Look where the
enemies are facing as they move up and down, and look how they change, where they face in reaction
to player speeding up. I don't want them to react
to that in this way. I want the rotation
of the enemies to ignore whether or not
player is in super speed. But player entering
super speed stretches that sine wave as we
demonstrated before, and that causes the rotation to change when the
world speed changes. I have to have a little
think about this, and basically what we need to do is this to achieve the rotation, what we are already doing, we determine how much we rotate
the enemies by measuring the distance between their current and their
previous positions, and we make them face rotate
towards that direction. If I speed up, world
speed increases, and the distance between previous and current
position of the enemy also increases because the
sine wave stretches and it's causing this rotation on
enemies that we don't want. I will fix it by checking what is the current world speed, and I negate its effect on
enemies previous position. So here I want to adjust previous position minus
equals to negate the effect, and it has to be
vector three because position values are in
vector three format. And when it comes
to world speed, we are dealing with the
horizontal component only. It will be game manager dot
instance dot World speed, Tams ta dot Delta But to
calculate that world speed, TAMs T Delta T here
might be a little bit inefficient because
we would be doing that calculation too many
times on each enemy. What if I do it differently? I open game manager script. Here we have that public
world speed variable, and it can be changed by this public set
world speed method. Player is calling that when
it enters and exits boost. So let's calculate that
adjusted world speed here inside game
manager directly. Up here, I create
a public float, adjusted world speed,
and in update, I make that calculation
world speed Tams ta dot Delta T. We pre calculate it once per update
here inside game manager, and we will access this
adjusted world speed value directly in all
places we need it. So we start here in phase
movement direction where I'm negating the previous position by whatever that
adjusted world speed is. I'm not sure if I explain this bit in a way that's
completely clear, but by doing this, I'm
negating the effect of players super speed
boost on enemies rotation. I want enemy's rotation
where it's facing to completely ignore how
fast the world is scrolling. Before I test it, I also go
to float in space script, and instead of
calculating world speed, Tams Delta time for
every update on each object that has
this script separately. I will pull that pre
calculated value from game manager directly. Adjusted world speed like this. Save that and we also do
that same calculation on Object sponer here because to create our illusion
of moving through space, we are speeding up
the rate at which Object spawner activates objects based on the current
world speed. Again, instead of multiplying
Delta time T's world speed, I will use that pre
calculated value here by using adjusted
world speed directly. Now, let's save
and play to see if this little trick
with offsetting enemy's previous
position worked. Notice now that enemy rotation doesn't react at all
to player entering and exiting super speed
because we are moving previous position of the enemy in relation to the
current world speed. And now, finally, enemies following sine
wave motion pattern look natural and consistent with the illusion of player
moving through space. I give myself some extra
work by giving player this superspeed ability that affects how fast the world scrolls by. If we had just a constant at
speed that never changes, this lesson would
be much shorter. But we learned a few new tricks, which is always useful. We have a basic enemy type, and we know that if we want to make it more dangerous later, we can increase the frequency and amplitude of the
sine wave motion, turning this into a
hardcore space frog. Maybe we will do
that, but before, let's implement another
completely different enemy type with different interactions, different sounds, and
different visuals. It will be much quicker
because we already have our base enemy
class that handles all the basic
functionality for enemies, and we can focus only on the
fun part on implementing unique enemies that add different aspects to the
gameplay loop. Let's go. For a while now, we
had no way to win this game and actually see
mission complete screen. So let's reinstate
that functionality. It's really quick and easy. At this point. We have
all the systems in place. We have this lost whale prefab, a broken space whale that the player has to
find and rescue. We set it up as a trigger
that will complete the level. At this point,
everything we spawn dynamically in the game
is an object pool. So let's turn the space whale into a pooled object as well. We have all the
systems in place, so it only takes a few clicks. I create empty game object
I call lost Wale pool. I reset its transform to keep these numbers
nice and clean. Add component Object Pooler, and I drag lost Well prefab into this field. One will be enough. I parent it under
object pools as usual, and now we are ready to plug
it into Object spawner. I made some changes to my object spawner waves to
kind of design a simple level. First wave is Beetle Pool, two of them, one
every 0.8 seconds. Then I will have five critters. You don't have to follow
exactly what I'm doing here. Feel free to create your
own level structure by spawning different
objects in different waves. In my case, I will follow
up with 70 asteroids, one coming every 0.5 seconds
until we spawn all of them. Keep in mind that it
will probably not take 35 seconds to get through this wave because the
spawning speed is increased whenever player
uses super speed move. So if player uses that move, this wave will
happen much quicker. Then I have critter pool, ten of them in total,
one every 0.1 seconds, then 40 beetles, spawn
interval 0.8 seconds, 70 asteroids, one
every 0.5 seconds. Seven critters coming in
0.1 second intervals. 30 asteroids, seven
critters again. Then I will have the lost
whale spawn interval one. Objects per wave will also be
one and after 30 asteroids. The whale will be basically in the middle of an asteroid belt. So the whale is basically a
mission complete objective. Player needs to collide with it to trigger mission
complete screen. We designed this game
in a way that if player misses the
whale and keeps going, this waves list will
start from the beginning again from the first wave,
repeating the cycle. This is fine for me now. This is not the
final level layout because now it's time to add another unique enemy type and also a different type of
boss right after that, and we will slot them to spawn
somewhere in here as well.
39. Charging Enemy Type: I created another spreadsheet
with a new enemy type. As always, you can download all the project resources in
the description down below. I drag and drop it into
Assets art Enemies folder. I open Sprite Editor and
slice grade by sell size, 120 times 120. Slice and apply. If you look at the
spreadsheet I prepared, this enemy type is a little bit different than
the first one. This one has double
the amount of frames. We still have four
variants, one, two, three, four, but each one has another frame that will be
used for charging animation. We already have our
enemy parent class, so all the basic functionality
will come from there, and we can focus on unique
features of this anime. Want this enemy type
to float around and kind of wait for an opportunity
to strike the player. And when it gets hit
by players weapons, when this enemy's health
reaches a certain breakpoint, it will charge forwards
at high speed. It will introduce a new
dynamic to the gameplay. Let's implement it,
and then we will do a boss version of
this enemy as well. I expand the spreadsheet and I drag and drop the first frame. Sorting layer will be enemy, and ON layer is one above the boss and under the
critters, basically. Tag is enemy, and I
put it on enemy layer. I reset transform and I
rotate it to face the player. I move it a bit to
the right here. Okay, I rename it
to Locust Morph. I lock the inspector and
I go to scripts enemies, and I attach this enemy
script as a component. Max Life is ten, damage is two, and
experience to give is two. Add component, float in space. Now if I save and play, we have the enemy
moving to the left. I add component
capsule collider two D. I rotated the
enemy by 90 degrees, so these values
and direction can be a little bit unintuitive
because of that. What I want is vertical
direction for the collider. Horizontal size is 0.7. Vertical size is 1.2. This is pretty good.
Now, if I play, bullets are colliding
and sliding around. I add component, flash white. I move capsule collider
component up like this, just to keep all these
utility components together at the bottom. Inside scripts EMs, I create
a new mono behavior script. I will call it locus morph. I delete all this
code. Save that. Instead of attaching the
parent enemy script, I actually want to
remove that component, and instead, I attach
Locust Morph script. If I make that script, extend enemy class, we will
get the fields here again. Max lives ten, damage two, experience to give two. Now, this enemy is inheriting all the basic functionality
from the parent enemy class, and on locus morph subclass, we will define only the things specific for this enemy type. It will work the same like
we did it for Beetle Morph. I copy this entire code block
and I paste it in here. I will replace all these
values one by one with things unique for this enemy
type. I delete this line. Horizontal speed
will be positive, a random range
between 0.5 and 0.8, which will make the enemy
float slightly backwards. The enemy is also
influenced by float in space script that
makes it move to the left in relation to
the current world speed. But this line will make it
hover around a bit longer, waiting for a perfect opportunity
to strike the player. I will actually increase this speed just to make
it obvious what I mean. If I save and play, you can see the enemy
is moving backwards. It's looking at the
player and backing up. It also flashes on it. Great. So I decrease
this range of values, but I still keep them
in positive direction, but much more subtle. I also want the enemy
slowly bounce up and down between the top and
the bottom of the screen, similar to what Bos does
when it's in patrol state. I give it random vertical
speed between negative -0.5, which will make it move down and positive 0.5 to make it move up. To do this, we will need
an update method here with some custom code specific
only to this enemy type. So public override void update. Inside, first, I call
pace dot update. This function that sits
on the parent enemy class that contains code shared
for all enemy types, and then we will bounce the enemy between the top
and bottom of the screen. But to get the right values, we have to check
the vertical range where the enemy can spawn. Because if we set the
bounce breakpoints to range inside of that, the enemy can get stuck
there if the spawn range is larger and it spawns outside
of those bound breakpoints. We spawn enemies here
on object spawner. It has the minimum and
maximum position objects. I unlock the inspector. Min position object is here. Max position object is here. I will actually move it
to minus four like this. Okay, so we know that
where we placed, these objects in the game
world will define the range. Enemy spawned by Object spawner will appear randomly
somewhere on this imaginary line connecting these two objects from plus four to minus four vertically. Okay, now that I know
the spawning range, let's try to use five. I have to make sure enemy
doesn't spawn past this point. I say, I transform
position Y of the enemy is more than five or if
it's less than minus five, invert speed Y by multiplying
it times minus one. We already did this for both
in patrol state before. Let's say enemy is moving up, speed Y is positive. When it moves past plus five, positive speed multiplied
by minus one will turn the speed negative and
enemy will start moving down. I grease speed Y
just for testing. So we can clearly
see if it works. It moves down, it
bounces, it moves up. It bounces once it reaches the fifth
unit from the center, vertical position
five. Yeah, it works. Just to remind you, this
is vertical position zero. This grid represents units
plus five units is here, minus five units is here. We go from the middle
where zero is. Now that I know it works, I return speed wi to a
random range between, let's say, -0.9 and plus 0.9. Now I want to replace
these sounds with some unique sounds that I created specifically
for this enemy type. This is a special enemy type. It will have hit sound, destroy sound, and it will
also have a charge sound. You can download them in the
resources section below. I drag and drop them
into Assets audio. Then I drop them into hierarchy. With all three selected, I antique play on Awake, I set output to
effects as usual. I put them under
audio controller to keep hierarchy clean. On Audio Manager Script, I create three new properties. Audio Source, Locust
hit, Locust Destroy. And Locust charge. Save that. And now I drag Locust
hit into its field here, Locust charge here, and
locust destroy here. Okay, nice. On
Locust Morph script, I can now set hit
sound to locust hit and destroy sound
to locust destroy. I play to test it. And, new sounds. This is a squishy space
slug, as you can hear. It still uses destroy effect
animation from Beetle Morph, so let's give it its own
unique animation sequence. I created something a bit
more dynamic for this one. You can download
Locus Pop Sprid Sheet in the resources section below. I open Sprite Editor, slice grid Bell size, 120 times 120 slice and apply. I expand the spreadsheet, and I drag and drop one of
the frames into the scene. Sort in layer will be anime. Auto inlayer is ten. I rename it to Locust pop. It will work the
same as this Beatle Pop prefab we created earlier. So we will need Animation panel. If you don't have it open, it's under Window
Animation Animation. Locust Pop selected. This has to say to begin
animating Locust Pop. I click Create Inside
Assets Animations folder. I create a new file,
locuspop dot Nim. Save that. With Locus Pop
Object selected here, this drop down says Locust Pop. I go to Assets art. I expand this spreadsheet. I left click the first frame, and I shift click
the last frame, selecting all the
frames in between. I drag and drop all of them
onto the animation timeline. I play. Now you can
see what I meant when I said I created a
bit more dynamic effect. 20 samples. It's much
more cartoony this time. I'm just having fun and experimenting with how
I do these animations. Okay, I go to Assets Animations. I find Locus boop animation
clip and I antique Loop Time. With L ocusPop game
object selected, I add component, destroy
when animation finished. Add component, float in space. We always do this for this kind of an object that
plays animation. I go to Assets,
prefabs, effects, and I drag and drop
Locus pop in here, turning it into a prefab. We can ignore the
message in console. I delete the object from here. I create an empty game object
I call Locust Pop pool. I reset transform, at
component Object Pooler, I drag Locus Pop prefab here. The initial pool
size will be two. I create one more game object, I name it Locust pool. Reset transform, at
component Object Pooler. I go to prefabs NEMs. I drag Locus Morph
game object in here, turning it into a prefab. Now I can delete it
from the hierarchy. Locus pool selected, I drag Locust morph prefab into
the prefab field here. I keep the initial
pool size as five. I put both of them
under object pools. Object spawner waves list. I right click the header of the first element and
duplicate array element. I move this one up here. Object spawner selected again. I drag and drop locus
pull into this field. We will do ten objects per wave. Spawn interval 0.8 seconds. I want to use this
locus pop pull as destroy effect
on this enemy type. So here I say locus
pop pull with exactly this spelling
to make sure it works. Object spawner. I right click the
header of element three, duplicate array element. I take element three by this
corner and I drag it up. So now I have locus morphs spawning and then a
wave of 70 asteroids. I do this so that when we play, we can see the difference
between asteroids that are just passively suspended in space
and flowed to the left. And in comparison, we
can see that Locus morph enemy type is backtracking a little bit so that it
can stay around longer, waiting for a perfect
opportunity to charge at player. They flash on hit, they play
hit and destroy sounds, and they animate the new
destroy effect. I'll get here. Okay. Let's go inside
Locus Morph script again. I want to randomize
enemy visuals. We have four random
sprite sheets. Each variant has an
idle and charge frame. We also had four variants
of Beetle Morph enemy, but that was just a simple
sprite sheet with four frames. We just added them to Sprites
array and we assigned a random one to
this sprite field on sprite renderer component. With Locust Morph, we want
to create a data structure where each element represents
one of these four variants, and each one of those
variants will hold its own idle and charge frame so that we
can switch between. I create a private list. We need system collections
generic name space. This list will hold frames, which we will
define in a minute. And I call the list
frames like this. So this frames is
undefined. I go down here. Still inside Locus Morph class, I define a private
class, I call frames. Now we have a class frames nested inside Locus Morph class. This class will be very simple. The blueprint is that
every object created using this class will
have public array of sprites, I call sprites. That's it. It's a
very simple class. We will give this
array two elements, one sprite for idle and
one sprite for charge. I need to use system
serializable here so that I can work with this
class inside Unity inspector. We are using that class up
here. Don't worry about this. Let me just show you in the
inspector why we did this. It will be easier to
understand when you see it. On Locus Morph Prefab, I have this new frames list. So I click Plus to
add a new element. I expand it, and inside, it has sprites array. I click plus twice to give
that array two elements. I go up one level, and this plus will add another
element to frames list. I open its Sprites array, and this one already
has two sprite elements inherited from the previous one. Plus to add another
element to frames list. Again, we have Sprites array with two elements
inside already. And finally, plus to add
one more to frames list, and inside again, it has sprites array with
two elements. Okay. Now if I compare the script and what we did in the
inspector side by side, we added four elements
into this list. One, two, three, four. This list expects that
each element in it is an instance of our
custom frames class. Frames class has only one
property, array of sprites. We see this here,
here, here and here. We gave each sprites array
two elements because we have two frames for each
enemy, idle and charge. I log the inspector, I go to art enemies, and with Locust morph
spread sheet expanded. If I look at the spread sheet, this is the first variant, idle charge, second variant, idle charge, and so on. This first element represents the variant one of the enemy, so I drag and drop
this first frame as idle and this second
frame as charge. Then the second enemy variant, Idle frame here,
charge frame here. The third enemy variant, idle frame Charge frame. And the fourth enemy variant, idle frame here, charge
frame goes here. So the way we designed this
simple data structure, we have a list we call frames. It has four elements, one element for
each enemy variant. Each one of these variants has one property, sprites array, where the first element in that array element with the index of zero
is the idle frame. And the second element,
the element with index of one is
the charge frame. Now I will create a
custom private method I call Enter idle. And another one I
call Enter charge. Let's start by just swapping these frames to index zero when the enemy enters idle and to index one when the
enemy enters charge. So Sprite render dot Sprite, the image we are
displaying for the enemy is this frames list
at index zero. We will only deal with the
first enemy variant at first. And we want the idle frame. So Sprites array index zero. When we enter charge, we
want frames at index zero. It's Sprites array at index
one, the charge sprite. Now we can call Enter idle or Entercharge from
anywhere in this class, and it will swap the sprite
frames automatically. For example, I will make
enemy Entercharge when it takes a certain amount
of damage from the player. We will need to overwrite take damage method
from the base class. First, we call the method
from the base class, and then we pass this
damage value along to it so that the method can
do what it needs to do. And that method on
the parent class will be public
virtual like this. So once we call that method
from the base class, we will add some
extra code specific only to this enemy type. I say, if lives are less
than Max lives times 0.5, I need to make these
properties protected, which means available on this enemy class and on all
classes that inherit from it. And here I say, as the
enemy is losing lives, when it lost a half of its
maximum lives, enter charge. Save that I enter play mode. Locus Morph has ten lives. When I reduce its
lives to five or less, it will enter charge
and its sprite will change to the charging
image. Yes, that works. I also want to call a
sound effect from here. Audio manager,
instance play sound. The sound I want to play is audio manager instance,
Locus charge. We already prepared that sound and that property
earlier, so it will work. I go up here, I cut
these two lines of code. I paste them in here
and also in here. When enemy enters charge, it will speed up to the left. So vertical movement
will be zero. Horizontal movement will be a random range between
minus four and minus six, moving very fast to the left, charging at the player. The values for idle state
can stay like this. Save that and play. I hit enemies when
their lives get down to 50% of Mac's
lives or less, they charge to the
left. Perfect. This introduces
some new mechanics for the player to look
for and deal with. We have to focus this enemy to make sure we
destroy it before it reaches us or we have to make sure we avoid it
when it's charging. We are only showing the first enemy variant for all of them. This one for idle and this frame for when
it starts charging. Let's display all four of them. I create a private integer
called enemy variant. I will need on
Enable method here, so public override
void on Enable, and we call pace on Enable at first to run the code from
the parent enemy class. In unity on Locust Morph prefab, I unlock the inspector. I go down here to frames list so that we can see how
it's structured here. Every time we activate
or reactivate the enemy, I want to randomly assign
enemy variant, a number 0-3. So enemy variant will hold the
index inside frames array. If enemy variant
variable is zero, it will take sprites from
frames at index zero. These two. Index one, Index two will be these sprites and index three
is these sprites. So we use that
enemy variant here. And here as the index
in frames list, let's say, for example,
enemy variant is two. So for idle, we go
to frames list at index two dot Sprites
array at index zero, and for charge Sprite, we go to frames at index two dot Sprites
array at index one. Enemy variant value will
determine which one of these four elements in frames list we are
pulling the sprites from. If I save and play now, it will still show
only the base frames. It's because I need
to call Enter Idle somewhere to actually assign that value to sprite
renderer component. I will call Enter idle
here after we randomized that enemy variant
value while we are activating or reactivating
Locusmrp object. If I do that, I will
get an error that says object reference is not set
to an instance of an object. It's because I'm
calling Enter idle too early before we actually get component for
sprite renderer. When I enter idle, I need
sprite renderer component, but that very first
time we are creating this object and putting
them into the object pool, this enable runs first, and Enter Idle needs a
sprite renderer component. Before start method
that runs after has a chance to actually
get that component. We get that component inside start method by calling
the base class start. And here on line 26, we actually get sprite
renderer component. We need to get it
before on Enable runs. So if I check unit
documentation, I can see that awake method
runs before on enable. I will put this line here. Awake method will now get
that component before enabled triggers
enter idle function where we will need
that sprite renderer. I save and play, and we get randomized
skins and we manage to associate them together in pairs for idle and
charge states. I have a feeling that we are triggering Enter charge
method over and over. I create a property
I call charging. It's a booling. It
can be true or false. Right now, when enemy takes damage and its lives
are below 0.5, every time it gets hit, it runs this code again. So this sound plays
after every hit, resetting the audio
from the beginning. I only want entercharge method to run when charging is false, when the enemy is not
charging currently. I say, I charging is false, exclamation mark is false, only then run all this code. And inside, I set
charging to true. So once we entered this method, we can't call it
again over and over. Let's say charging is false currently and enter
charge gets called. This check will pass
because charging is false and all
this code will run, setting charging to true until something in our code sets
charging back to false again, this code will not run again and this sound will
not play again. I will set charge into false whenever we call
Enter Idle again. Right now, we only
call Enter Idle at the beginning when we activate
and reactivate the enemy. Now if I play and I hit
enemy over and over, charge sound plays only once. Perfect. I want to create
a new boss now because this enemy type will also have a special boss
version. Let's do it. Will.
40. More Boss Types: C. I open boss one script. We have Beatle Morph enemy type that extends the
base enemy class. We have Locust morph
that also extends the same base enemy class and we have this Boss one that
extends mono behavior. We are not taking advantage of inheritance here and we are redeclaring some basic
functionality that is shared for all enemies
and for this boss. Things like speed X and speed Y destroy effect pool lives, Max lives damage and
experience to give. All enemies have that. We don't really have to declare
all of that here. We can just extend
enemy class and inherit that from
here. Let's do it. Boss one extends enemy. Now I get warnings
for everything that is already declared
on the parent class. I delete destroy
effect pool property. I delete speed x and speedY
from Boss one script here. Here we have lives and Max lives and if I go to the
parent enemy class, I said damage and experience
to give to protect it. We get all four of these
highlighted as duplicates, so I can delete them. We will leave here
things only specific to how this specific boss
functions and behaves. Let's refactor this code a bit
and then we will implement a completely different boss type with some unique interactions. On the parent enemy class, I set awake method
to public virtue. Here on boss one, I set
it to public override. I also do public
overt void on Enable, public overt void start, public overt void update, and public override
void take damage. Now, as we always do, first, I want to run the code
from the parent method, the one that sits on
the base enemy class. Base awake, base dot on Enable base start, base update. And base dig damage. Actually, looking at the
code inside take damage, it's almost the same on the enemy base class except for the flash
white functionality. Let's go on Boss one script and completely delete
take damage method. Now, this take
damage method from the base class will be automatically inherited
for boss one. I just have to make sure we have flash white script component
on Boss one prefab. I save changes to all my
files and on Boss one prefab, I go down and I add
component flash white. On Object spawner, I right click element zero
in waves list, and I duplicate array element. I drag reterOPool inside and let's say 20
objects per wave. I save and play and I destroy critters
until it spawns a boss. Boss will just
zoom by like this. Clearly not
everything works yet. I'm getting errors about
destroy sound and hit sound, which we are trying
to play here, but we didn't actually point them to any audio source yet. I will do the same thing we
did on Beatlemorf subclass. I go inside start method
on Boss One script, and I define what audio source we want to play
when Bs gets hit. Hit Sound is audio manager
dot instance dot HIT Armor, the same sound we used
for this until now. And destroy sound will be audio manager Instance boom two. When Boss enters charge state, we set speed X two minus five. I save that. On Boss prefab, we made this script inherit
from the base enemy class, so I need to re
declare these values. Max lives is ten
just for testing. Damage is 20. Experience
to give is 20. I save and I play. Yes, that works. I'll get here. We have boss one inheriting from the base enemy
class, and that way, we don't have to re declare the shared functionality
for every single boss type. Inside boss one script, we declare only the
things that are specific to how this
particular boss functions. When boss is in patrol
state going up and down, I want the vertical
speed Y to be a random value between
minus one and plus one. Up here, inside on Enable,
we have this line, but we already have that line
of code on the base class. Yes, so I can delete it here. Let's have a look down
here on collision enter two D and on
base enemy class, we already check for enemy
versus player collisions. So I do public virtual void
here, and on boss one, I do public overwrite void on
collision Enter D. Inside, I first want to call the shared
code from the base class, passing this collision
reference along, and I can delete this bit. We are handling that one
on the base enemy class. We only need this
part because boss is the only enemy type that actually causes
damage to asteroids. A enemies damage player. Boss damages the player and
also damages asteroids. I save and plays charges, B patrols. It gives player experience.
All is good here.
41. Boss that Cruises Along: I created a new boss type
large version of Locust Morph. As always, you can download all project resources in
the description down below. I drag and drop the spreadsheet inside art Enemies folder. It's fully animated and similar
to the Beetle Morph boss. It will have two
distinct animations, one for idol and one for charge. With Bs two PNG Art
Asset selected, I go to Max size, and I do the bottommost value, the largest one possible so that the spreadsheet can
render at full size. I save changes and I
open Sprite Editor. As usual, I go to slice, grid by cell size. These sprites are big,
360 times 360 pixels. Slice and apply. These are big sharp frames. I hope they look good in a game. Let's see. We've done this part so many
times in this class. I expand the sprite sheet, I drag and drop one of the
frames into the scene, which will prompt
unity to create a new game object with
sprite renderer component. Sort in layer is anime
or the in layer is zero. I rename it to boss two. I want to rotate this boss
but not to face the player. I want the boss to
face away like this. It will cruise along with
us moving from left to right in the opposite direction
to all other enemies. Animation tab, Window
animation animation. Boss two selected here, and I click Create Inside
Assets Animations. I create a new file, I call boss two underscore Idle. Save that. I expand the spreadsheet. I left click the first frame, and I shift click frame 38, the last frame of
idle animation. It will select all the
frames in between 0-38, and I drag and drop all of
them on animation timeline, making sure this
says both too idle. This plays too fast. I try 20. Maybe 30. Yes, that's good. I click this drop down and
create new clip. I will call this one Bs two underscore charge, and I save. I make sure I see
Bs two charge here. I left click frame 39 and I shift click
the very last frame. I drag and drop all those
frames on animation timeline. I don't see the
frames here because I don't have Bs two
game object selected. Now I can see them.
60 is too fast. Let's do 30 again. Okay, so I have boss
two selected here. I scroll down and add
component float in space. I go to assets scripts enemies. I create a new mono behavior
script I call boss two. I open it. I remove
all this code inside, and I make sure it inherits
from the base enemy class. Save that boss to
beam object selected, and I add boss two
script as a component. Boss two script is
completely empty right now, but because it extends
the base enemy class, it already covers most of
the basic functionality that's shared for all different
enemy types and bosses. Just for testing, Max
lives will be ten. Damage dealt by this
boss will be 20, and experience to
give will be 40. On boos one prefab, I increase Max lives to 100. Boss two selected, and I
have boos two script here. Inside Bs one script, I copy this entire code
block with start method, and I paste it inside
boos two script. Here we have an opportunity to give this boss a
unique destroy effect, hit sound, and destroy sound. For now, I will leave
them like this. We will also need public
override void update, where we first call
the update method from base enemy class, and then we will write
some unique logic to make this boss move
in a specific way. I will make the boss
bounce up and down between the top and bottom
of the screen at first. So if transform position
Y is more than four, if boss is more than
four units above the vertical center of the screen or if it's
less than minus four, in both cases, flip speed
Y to the opposite value, making the boss move in
the opposite direction. We did this for boss one in patrol state and for
locust morph as well. It will have a private
method I call enter idle state and another one
called Intercharge state. Private booling,
I call charging. By default, it will
be set to true. When boss enters idle state, we check if charging is true. Only if boss is
currently charging, we will switch to idle and we set horizontal speed
to slightly positive, making the boss move backwards, but only very slowly. And vertical speed will
be a random value between -1.2 moving down and
plus 1.2 moving up. We will also set charging
to false because boss is now in idle state.
It is not charging. I copy this code block in here, and I change a few things. Inside Enter charge state, we only run this code if boss
is currently not charging. Boss can only enter charge
state if charging is false. Inside, we set charging to true. When boss is charging, it is moving fast but
not from right to left. It is moving from left to right, in this case, in the same
direction player is moving. So when charging, vertical
speed will be zero. Horizontal speed will be a
random range between plus 3.5 and plus four
positive values, so boss will move to the right. Okay, we set these
boundaries to plus four and minus four. Let's save this. And if I check Object
spawner mean position, it's plus four here. Max Boss game object
is minus four. The area where the boss can potentially spawn
is in this range, so it will work fine. I need to make sure
that spawn range is the same or smaller than bounce range to prevent
the boss from spawning outside the bounds range because it would
get stuck there. These values are fine
because they are the same as maximum vertical and
horizontal spawn point. I will make this boss
automatically try to keep up with the player in moving along with the player
as we play the game. If the boss reaches the
right edge of the screen, if its horizontal position
is more than 7.5, it will enter idle state. It will stop charging, and it will wait around for
the player to catch up. Else, if boss's position
is less than minus five, if it's almost touching the
left edge of the screen, it will enter charge state and starts moving to the right. If I save, and let's check. Plus 7.5 units
horizontally is here, minus five units is here. Seems good to me. Let's set the initial position
to 40 for now. It doesn't really matter.
Boss is in idle state, just hovering in space. It's affected by the game world
scrolling by world speed, same as all other objects
and creatures in our game. Okay, so only if charging
is true, we can enter idle. Only if charging is false, we can enter charge. Initially, charging is set to true here as a default value. I will need on
enable method here, something that will
run every time we activate and reactivate boss two game object because it will be a pooled
reusable object, same as everything else. Public override void on Enable. First, we call base on Enable to run all the code shared
for all enemy types, and then we make this
boss enter idle state. Every time we activate
or reactivate this boss, it will start in idle state. If I save and play, Bs is in idle, it moves very slowly up
and down and bounces from the top and bottom edge of the screen if
it reaches there. When its vertical position
gets to minus five, it will enter charge state. It will start moving
forward at a higher speed. It moves forward
until it reaches plus 7.5 units horizontally, and it will switch back to
idle again. That works. I go to Window
animation and animator. It will open animator Window, which is basically a
simple state machine. Here we define conditions when object transitions
between different states. Earlier, we created animation
for boss to idle and boss to charge and you need to turn these into
animator states. I want to switch between
these two states dynamically to play these
animations when we need them. I go to parameters
here, I click Plus, and I want to add a booling I will call that
bullying charging. So this value can
be true or false. I click Idle animator
state and make transition. I drag the transition arrow over the charge state like this. I click the arrow itself, and inside the inspector tab, I antique has exit time. I also expand settings and I set transition
duration to 0 seconds. I want instant transitions. I go down here and I
find conditions block. I click Plus, and the
condition to transition from idle to charge state will be when charging
parameter is true. I right click the charge
state, make transition, and I drag this transition
arrow from charge to idle. I select the arrow itself. I anti has exit time. Transition duration is zero. Condition to transition
from charge to idle state is when charging
parameter is false. So these are animator states. They play the two
different animations we set up from Bos two
spreadsheet earlier. Now I can simply target this animator parameter
we called charging, and by setting it
to true or false, Animator will be switching between idle and
charge animations. Base Anim class doesn't
have a reference to animator component because basic anime types
don't use animator, so I will do it inside awake
method on Bs two subclass. We are doing that here
in Bos one script. I will copy this
animator property first and I put it here. And I will copy this
entire Awake method from Bos one script, and I paste it here.
I delete this. We don't need this
line because boss two doesn't play a
sound when it spawns. Okay, so we define
animator and we pointed to the animator
component inside Awake, which runs before on Enable. Because here inside on Enable, we will already
need that animator because in Inter idle state, we will be setting that animator parameter we just prepared. Whenever Boss two
enters idle state, we take its animator
reference and we call built in set Bool
method, set booing. It will set that
animator parameter we called charging to false. Keep in mind, these are
two different things. This is animator property. This is a separate variable, a flag we use to
prevent the boss from entering idle or charge while
it's already in that state. When Boss two enters
charge state, we call animator, set Bool, charging true like this. If I save and play, Boss is animating idle. When it enters charge state and starts moving
fast to the right, it will animate charge. It will switch between
the animations as it switches between
idle and charge. I also want the boss to react to player's
position and movement. Let's keep track of player's horizontal position
and save it in a variable. It will be equal to
player controller dot instance dot transform dot position dot X. I do this so that I don't have to write this long line multiple times. I use or operator here, and if boss' horizontal position is less than minus four units, if it's almost touching the
left edge of the screen, or if boss' horizontal position is less than player's
horizontal position, in both of these cases,
entercharge state. Okay. So now if the player moves
to the right of the boss, boss will react to
it by speeding up. I exit play mode, boss two game object selected. Layer will be boss, which will give it
interactions with asteroids because earlier we set up that objects on boss layer collide with objects
on obstacle layer. Tag here doesn't have
to be boss anymore. After the refactor,
we use this tag to identify that bullet
collided with boss or enemy. I will access its
take damage method and it will pass its
weapon damage along. This boss now inherits
from enemy base class. Let's give it a tag of enemy. And actually on Bos one
prefab, I can do the same. I keep the layer as boss
for asteroid interactions, but tag can be enemy like this. So after making this change, I go to Phaser Bullet Script, which is currently
our only weapon. And down here inside on
collision Enter two D, we check what this
bullet collided with and we pass the
weapon damage to it. I can now completely delete this boss block because for
the purposes of this code, boss now falls under enemy. Bosses have enemy
component and take damage method on there will
pass weapon damage to Bs one, and I'm careful about brackets
and syntax when I do this. I save that Bos two will
need a collider component. I set rotation to
zero for a while. This will make the collider
setup more intuitive. At the bottom, I add component, capsule collider two
D. We have to make sure we always choose
the two D version here. I leave the direction
as vertical. Horizontal size
will be two units, vertical size will
be three units, and I move the collider
down a little bit like this. I'm
happy with it now. I can rotate the boss
by 90 degrees to the right again. Object spawner. I grab this little list item by a corner and I drag
asteroids wave up here. I set spawn interval
to 0.3 seconds. If I save and play, I try not to hit many creatures. I don't want Bs one
to spawn right now. I just want to
check if Bs two is interacting with the asteroids and if it's pushing them around. Boos one charges in and it also interacts with asteroids and
it destroys them instantly. When I try to hit Bus two with
bullets, they slide along. Something in our code
still needs attention. Usually, console is a good place to check if you don't
know what's wrong. I can see it's coming from T damage method on
enemy class and it says object reference is not set to an instance
of an object. It's looking for flash, white component, but
it can't find it. I exit play mode. Boss two game objects selected, at component flash white. Now, if I save and play, it flashes on hit. Great. I exit play Mode. I right click Create
Empty boss to pull. I reset transform, at
component, Object puller. I go to prefabs NEMs and I drag and drop boss two
game object in here, turning it into a prefab
and I can delete this bit. With boss two pool selected, I drag and drop Bs two into
the prefab field here. Initial pool size will be one. I drag it under Object pool
to keep hierarchy clean. Object spawner, and the first
wave will be Bs two pool. Spawn interval one,
objects per wave one. Now when I start the game, I have Bs to pushing through the asteroid belt cruising
along with the player. If I hit it, currently, it's very easy to destroy. Let's increase
both lives to 400.
42. Enemy that Follows the Player: In this lesson, we will put together
everything we learned so far to create a boss
that shoots a small enemy, that shoots a small criticized
enemy at the player. This can make the game much more difficult depending on how fast and sturdy we make
these new enemies. It will give the player
a proper positioning and aiming challenge, and it will further increase the gameplay variety and make the game more
interesting in general. The art assets and
sounds we will need are available to download in the video description
down below. I created a spreadsheet
I call Squid Critter. I called this enemy type
squid Morph because it's soft and squishy and there are some tentacles and
teeth involved. We already have an enemy
type with critter, basic and boss evolutions
for Beetle Morph, and this enemy type will
have the same three levels, boss, regular sized
enemies, and critters. I added an extra fifth option
for each just for fun. If you want to mix and match these body parts and
create your own enemies, you can sign up
for my newsletter, where I share bonus
content packs that you can download for free and use
in the games we make. I drag and drop Squid
Critter Spreadsheet into Assets art enemies, and I open Sprite Editor. As always, slice
grid by sel size, 80 times 80 pixels,
slice and apply. I drag one of the
frames into the scene. Certain layer is enemy, or the in layer is two. I adjust position and rotation. Although, in this case,
it doesn't matter. We will move and
rotate this with code. I rename it to squid grater. At component circle
collider two D. I will go a little bit faster
because we have done this so many
times in this class. So I'm sure you can
navigate your way around these menus if you
follow it all the way here. Radius of the collider
will be 0.4 or 0.35. Add component flash white. Whenever I add this
component, I get an error. It doesn't actually break
anything when we play the game, but to make sure this error
doesn't appear like this, let's see what it's
complaining about. Something inside reset
method we wrote before. Okay, only set this back
to default material, which if you remember,
is this field. If we already captured the reference to the default
material in a variable, this should fix
the error message. I add another component,
float in space. Tag for the previous
critter was critter, but that critter type works
in a bit different way. It has different animations
when we destroy it and it has no lives and it doesn't
damage the player as well. For this critter, I wanted
to behave more like a regular enemy so
that we can give it multiple hit points
and things like that. Even though I call it
a critter and it's the smallest of these
three evolutions, I will tag it as enemy. Layer will also be enemy here. Enemy layer will make it ignore other enemies and asteroids
in terms of collisions, but it will make sure
it collides with the player and projectiles,
which is what we want here. Assets scripts folder. I create a new mono behavior
script I call squid grater. I attach it to Squid
Critter game object as a component and I
open it like this. This critter will
have multiple lives. It will damage the player and
it will reward experience. That's why I make
sure it inherits all the basic features
from the enemy base class. I need this critter to do
everything we define here, so I make enemy the parent
class of squid gritter. It will be its subclass, derived class, also
called a child class. We will basically handle
it in the same way like we handled Beetle
Morf enemy type. So squid critter extends enemy. That's why I want to do
the usual thing where I said start method
to public override, where I first call the start method on
the base enemy class, on the parent class for
all the shared code, and then we will add
some code unique only to this Squid
critter enemy type. Same for update method, public overt void update. And inside, first, call the update method on the parent enemy class on the base class. If I save that, Squid
critter is already inheriting all these fields
from the enemy class. So let's set Max lives to three. Damage it will deal if
it touches the player to one and experience
to give also to one. There will be lots
of these critters everywhere if you
let them multiply. This enemy type can
completely overwhelm the player and fill
the entire screen if not kept in check. We optimized our code
base with object pools. Let's see if it can handle
this or if I need to implement some further optimizations
like grid based partitioning with quad trees to only check nearby objects, not all objects on the screen. I want to start by taking all five frames and assign
each enemy a random one. We did this before, so you
might remember that we start by defining an array
of sprites, I call sprites, and inside start method, we access sprite
renderer component, which we don't have to
get here because we are already doing that on the
parent enemy class here. Defined here, get
component here. So here we already
have a reference, and I can simply access
its sprite field, which I can see here. And I want to assign it one of the five sprites we will
put in this sprites array. For example, the first one
with the index of zero. So this sprites array is
here plus to add elements. I put all five of these
sprite frames in there. Now, to randomly
take one of these five and assign it
to the sprite field, I say that the index is random range between zero
and sprites dot length. I save and play and this
field should have one of these five sprites assigned and anime will look
different every time. Yes. I try again.
Yes, that works. On collision, we
get an error just because we didn't define
some pieces of animal logic. We need hit sound and
destroy sound as usual. I sampled some frock sounds
and rubber noises and I created a custom set of sounds we will need
for this enemy. You can download all
of them below or feel free to use different
sound effects if you want. I drag and drop all seven files
into Assets audio folder. We have so many files
because we will use them for all three evolutions
of this enemy type, and we want some
variation there. We will have to destroy sound two hit sounds and
three different shoot sounds because these enemies
will be shooting each other at the player from
their cannon like trunks. So let's prepare all seven of these sounds so we have
them ready for use later. I drag and drop all seven
into the hierarchy. With all of them selected, I uncheck play on awake, and I set output to effects. I open audio Manager script, and I create a public
audio source property for each new sound effect. Squid hit, squid hit two, squid Squid destroy two, squid shoot, squid shoot
two and squid shoot three. Okay, now if I save, I have all these new fields, and I drag each one of
these from hierarchy into its associated field on
audio manager component. So we have two sounds for hit, two for destroy, and three
sound effects for shoot. I take all of these
and I put them under audio manager to keep
hierarchy clean. I open Squid Critter script. It will work the same as other enemy types
we created before. For example, how we
did Beetle Morph. We are repeating
the same pattern to keep the code
easier to understand. Every enemy type needs
these four lines of code. What animated effect will
play when it gets destroyed? What sound will
play when it gets hit and when it gets destroyed? So I copy this and I paste it inside start method
on Squid Kriter. I will leave the destroy
effect pool for now. We prepared sounds already, so I will use Squid hit here and Squid
destroy sound here. I go inside prefabs
enemies folder. I drag and drop Squid
Krater game object inside, turning it into a prefab. Create Empty, squid crater pool, reset transform, add
component Object Pooler. I drag and drop
Squid crater from prefabs folder into this field. The initial pool
size will be five. Now I can delete this
one from hierarchy. Object spawner waves list here, and the first wave will
be squid grater pool. Spawn interval is 0.5 seconds and objects
per wave will be 50. I drag squid critter pool and the object pools.
Let's try and play. We have critters coming
slowly from the right. They get randomized skins. They play sounds when
hit and destroyed, and I actually want to use the other destroy
sounds for these. I exit Laode here I
say squid destroy too. Yes, now the right sound plays
when they get destroyed, and we can see they play destroy animation effect
from Beetle Morph. So let's create a quick
custom destroy effect. I put together a spread sheet
I called Squid Critter Pop. As always, you can download
all our task sits below. I open Sprite Editor, slice grid by cell size, 70 times 70 pixels. Slice and apply. As usual, I drag one of
the frames into the scene. Sort in layer is enemy, audio in layer is ten. I want the animation
to be on top of other enemies so that the
player knows what's happening. Okay, right? I rename
it to squid Crater Pop. I add component, float in space, and destroy when
animation finished. Window animation animation as usual because I
need Animation tab. With Squid Critter Pop
gameObject selected, I click Create
Animations folder. New animation file
will be called squidretorpop dot m. Save. I left click the first frame, and I shift click
the last frame, selecting all the
frames in between. I drag and drop all the
frames on the timeline. I said speed 220. Okay, this is a bit different. I'm experimenting with
these animations. I go to Assets Animations, Squid Creator Pop
animation clip file. I wanted to play only once, so I uncheck loop time. I drag and drop Squid
Creator Pop game object into Prefabs effects folder. I create empty game object, squid Creator Pop pool. Reset transform, at
component Object Pooler, pull size just one
to start with, and I drag Squid Creator
Pop prefab into this field. I delete this game object
from hierarchy and I put Squid Crater Pop
pool under Object pools. In Squid Krater script, I set destroy effect pool
to Squid Krater Pop pool. I save and play. We have sounds, we
have animations. We get experience
when destroying them. Perfect. Next step, make the crater rotate
to face the player because these critters
will be projectiles that are shot at the player
by larger enemies. We did the rotation before for the other crater type that other critter rotates in the
direction it's moving. To make object rotate
towards a specific position, in this case, towards
the player position, we will need private vector
three target position, and we will also need
target rotation. Inside update method, I set target position to the
position of the player. This will update as the
player moves around. To make object rotate to face towards a specific
target position, first, we need the relative position, the difference between
the target position and objects current position. All these are in
vector three format. If the difference is not zero, we will calculate
target rotation. We did this before using
quaternion look rotation. We pass it two parameters. The first parameter,
which direction the object will look in
after applying rotation. I wanted to look forward. This depends on where is your creature facing on
the spreadsheet as well. The second parameter
is optional. It's a vector which defines which direction
is considered up. If you give this second optional parameter a custom value, unity will adjust the final
rotation based on that. We pass it the relative
position between player and this object to make it
rotate towards the player. You can try to pass
it different values to really understand
how it works, but we used it before. We know it works,
so let's move on. This is not vector
three but quaternion. We have the rotation
that will result in critter rotating to
face the player, but we don't want to set
that rotation instantly, making the critter
snap very fast. We want to slowly animate
the rotation towards the target rotation step by step for a nice fluent effect. Unity, it's very easy using built in rotate
towards method. We passive the current
rotation of the crater, the target rotation,
and maximum angle. How much towards the target
we wanted to rotate, for the care of update for
this one animation frame. Let's say I want
the rotation speed to be 90 degrees per second, 90 times time Delta
time. Save that. I have my crater prefab
here. Let's play and see. As you can see, they are keeping an eye on the player
always watching, rotating towards the player as it moves around the screen. Now in mpire survivors style, we want them also to always move towards the
player's position, which is also super easy. We save players position
as target position, so the position of
the critter will be vector three, move towards, and we pass it the
current position of the critter the current
position of the player as the target to
move towards and the maximum distance to move towards that target
per function call. I will have a variable, I call
move speed, and as always, I multiplied Times
Delta time to make sure the speed is based on real
time and not on frame rate, which can vary from
device to device. I have to define move speed, private float move speed. I want move speed to be dynamic. I will explain what
I mean in a minute, and I need move
speed to reset to the default value
every time we use and reuse the quitter
from Object pool. So as usual, public override
void on enable method, where I first call on Enable
from the base enemy class, and then I add some
custom code specific only to this enemy type. In this case, whenever this object is enabled
or re enabled, we set move speed back to two. Let's play and test to
see what we have so far. They are coming for the player, and there is no
way to avoid them. They are everywhere. Perfect. I want the critters to move
in a non linear speed because I want the bigger enemy to be shooting these smaller
enemies at the player. I want them to start moving
very fast but gradually slow down to a speed that the player can
actually avoid them. So we will have the
current move speed, and we will also need
target move speed. Initially, they will
move very fast. Move speed will start at eight. Target move speed, the speed
I want the critter to end up moving will be a random
value between 0.1 and 1.2. So each critter will move at
a slightly different speed. As Update method
runs over and over, we will be checking if the current move speed is not
equal to target move speed, and we will be
gradually reducing the current move speed until it reaches target move speed. We will use math arp
built in method. Linear interpolation
eases the transition between two values over time. We pass it the start value, the end value, and the interpolation value
between two floats. I want to go from the
current move speed to target move speed, and I want the interpolation between these two values
to happen at this speed. This is the step in between those two values
per function call. I include Delta time to
make sure this runs at the same speed on
different devices, regardless of framerate. I save that And when I
play in the scene view, we see they start at the high speed and
gradually slow down. My plan is to have these
little critters being shot as projectiles
by larger enemies, so the initial high
speed will make more visual sense once
we implement that. Okay, let's create
that bigger enemy.
43. Enemy that Shoots: I called this enemy
type squid morph. I drag and drop the spreadsheet
into assets art enemies. You can download it in the
resources section below. I made five frames here
to get more variants. We will follow the same
steps we always do. I open Sprite Editor, slice grid by sal size, 120 times 120 pixels,
slice and apply. I drag one of the
frames into the scene. Sorting layer is enemy, Audi in layer is one in front of the boss
behind the crater. Position and rotation like this, it doesn't really matter. We'll move it and
rotate it with code. Tag will be enemy so that it
can give and take damage. Layer will be enemy so that it registers collisions with
projectiles and player. I add component
circle collider two D. I set collide
radius here to 0.6. I rename it to squid morph. I I create a new mono
behavior script, I call Squid Morph.
Let's open it. I will follow the same pattern
we did for Beetle Morph, but it will have its
own unique thing spitting critters at the player. Squid morph will extend enemy, inheriting the
basic functionality that all enemies share. I will also override start
as we usually do here, and I will override
update as well. Let's start by giving each squid morph one of the
five random sprites. We just did it for
Squid Critter. I copy this line that
creates an array of sprites. What else we did here?
I copy this line. Then inside start method, we first call the code on the base class and I
paste this line here, randomizing the
sprite we display. I also call the code inside base update method
as usual. Save that. I attach Squid Morph script
to this game object. Squid Morph now has these fields been inherited from
the base enemy class. Max lives will be
ten, damage is three. Experience to give is three. Sprites array, I
add five elements, assets enemies with Squid
Morph spread sheet expanded, I drag five sprites in here. Like this. Add
Component flash white. Add component float in space. I play, and I get a
random sprite assigned. That works. In Squid
Critter script, I copy these lines of code.
I paste them in here. Hit sound will be squid hit two, which we prepared earlier. Destroy sound will
be squid destroy. I keep this as the animated
destroy effect for now. I play. It flashes on hit, sound effects are playing. I drag it inside
assets, prefabs, enemies turning it into prefab. I create empty game
object, Squid pool. Now I can delete this one from hierarchy, Reset transform, add component Object Pooler, squid morph prefab
here, Object spawner. And the first wave
will be squid pool. I drag and drop it
under Object pools. Save that and play. Okay. We have squid morphs
coming from the right. It's using destroy
effect from the critter, so let's give it its own
unique destroy animation. I drag and drop squid
pop spreadsheet into Assets art enemies. You can download it in the
resources section below. I open Sprite Editor, slice grid by Sal size, 120 times 120 pixels,
slice and apply. I drag any one of these
frames into the scene, sort in layer me,
or in layer ten. Position and rotation
doesn't matter. We will control that with code. Animation tab, I rename it to Squid Pop. Create button here. Inside Assets animations folder, I create Squid Pop
animation clip File. Save that. I left
click the first frame. I shift click the last frame, selecting all the
frames in between. 60 is too fast. 20
should be better. Assets animations folder,
Squid Pop animation clip file, Inspector tab, and I
uncheck loop time. I drag and drop Squid Pop
inside prefabs effects folder, turning it into prefab. I can delete it here. Create
Empty Squid Pop pool, reset transform at component, Object puller, pull
size just one. I drag Squid Pop prefab
into this field. I drag and drop Squid Pop
pool under Object pools. Inside Squid Morph script file, I want to use Squid
Pop pool here. Spelling needs to be the same as the name of the game object. On the prefab, I add component, float in space,
and add component, destroy when animation finished. If I play, we have enemies
that flash on hit. With a unique hit sound, destroy sound, and
destroy animation. They just float
passively in space. Let's say that this enemy is
a blank slate at this point. Let's give it dynamic motion
and make it react to player. Then we will implement
its unique ability where it shoots
critters at the player. To rotate the anime
towards the player, we will need quaternion. I call, for example,
target rotation. We just did it for the critter. Inside update method, we will
create a helper variable, vector three type called
relative position. It will be the difference
between the current position of the player and the
position of this anime. If the difference
is more than zero, we will calculate
target rotation. What is the rotation
we need if we want the enemy to
face the player? We used rotation before in
this class a few times. When we know where
we want to rotate, we will rotate
towards the target, but not all the way
there instantly. We will take a step towards
the target rotation. Quaternion rotate
towards built in method. I pass it the
current rotation of this enemy, target rotation, which is where we want
it to end up eventually, and the third parameter will be how fast we want
to rotate there, accounting for Delta
time as always. I set objects per wave to
200 in the first wave. If I play now, they will rotate towards the player
wherever the player moves. They are still very
easy to avoid. We want a challenging enemy
that forces the player to be aware of it and to
avoid its danger zone. And we want the
danger zone to be dynamic, animated,
and interesting. As they reset, if we reuse the same enemy and
they spawn here, they will still hold
their old rotation value. We want to reset that every time we activate and reactivate them. Public override void on Enable. I call base on Enable
first as usual, and I can just set
transform rotation of the enemy to 0090 like this, making the enemy face to the left as soon as it activates. I also want the enemy to slowly bounce between the top and
the bottom of the screen, let's give it a random speed Y. This will already move the enemy vertically because on
the base enemy class, we are defining speedY
here on line 18, and we are using it to influence enemies position down
here inside Update. I want it to simply bounce up and down. We did this before. If it's vertical position is more than plus five and
less than minus five, Switch speedY to
its opposite value, making it bounce and move
in the opposite direction. I need dot Y here. Just for testing purposes, I will increase these values, so they move up and down faster, and I can see if they
actually bounce off the edges. Save and play. Well, we already have a bit
more interesting enemy type when we combine the
rotations and bouncing, but we can take this
so much further. We know that the bouncing
works, so I slow it down. Speed Y will be a random value
between -0.3 and plus 0.3. Same as with the critter, they will start moving very fast at first and
gradually slow down. Inside on Unable, whenever we activate or reactivate
this enemy, its speed X will be set to -15, moving very fast to the left. I'm not declaring this speed
X and speed Y. I'm just assigning a value to a variable defined on
the base enemy class. And line 35 will use these values to actually
move the enemy around. With squid critter, we have this helper move speed property for this because critters are rotating and also moving
towards the player. With Squid Morph, we are just
using speed X and speed Y to handle this movement because this enemy rotates
towards the player, but it doesn't move towards it, so we can just keep
this a bit simpler. We will also have
target speed X, which is the value we want
the enemy to slow down too. Every time we
activate the enemy, we randomize the target
horizontal speed to random range between -0.1 and -0.6 to make each enemy move at a slightly different
speed slowly to the left. Speed X is not the same
as target speed X. We will slowly
change speed X value using ARP again,
linear interpolation, which will float between
two float values, the current speed and
the target speed. And the third argument
is what portion of their difference we want to
travel in this single call. I will just use some value
here multiplied by Delta time, and I can increase or decrease
it if I want the slowdown to happen faster or slower once I see the
resulting animation. Because of this third
argument that always defines a portion of the
difference between speed X and target speed X, speed X will never be
equal to target speed X. They will basically
just be getting infinitely closer and
closer to each other, but never exactly match. I will say if they
move close enough, snap the values together. I math absolute of the
difference between the current speed and target
speed is less than one. If they are close enough, stop interpolating and just set them both to the same value, so this code block
will no longer run. Mas absolute will return the
absolute distance from zero, which means it will
disregard plus or minus, minus two and plus
two, for example, are the same distance from zero, so they return the same value. I'm just doing this to treat negative and positive
numbers in the same way. We start at a very high
speed and gradually we will slow down to the target speed
using linear interpolation. I save and plate to
see if it worked. You can see they are moving very fast, but they slow down. This is exactly what we want. But I don't want them to rotate to face the player all the time, only when they slow down
to the target speed. I want them to look around
and find the player. To do that is simple. I just put s here. When speed X is equal
to target speed X, only then look where the player is and rotate towards
it like this. If I save, my prefab
is here and I play. But they move fast facing left. Only when they slow
down to target speed, they start facing the player. This will make even more
sense in the next step. When they slow down
and locate the player, they rotate towards
the player and they start shooting critters
in that direction. I already created the critters, so this part is
relatively simple. This enemy will shoot critters in player's direction
over and over. We will need to count time so shoot timer and shoot interval. Every time we
activate the enemy, we give it a random
shoot interval. Let's say shoot a critter at the player every
one to 6 seconds. Every enemy will have
a specific interval that's somewhere in this range. I enemy is moving fast in
this block, we don't shoot. When it slows down to the
target speed in this block, it will rotate towards the player and it
will start shooting. We will have a shoot timer that starts at the value
of the interval. It will be one to 6
seconds randomly, and it will count
backwards towards zero, accounting for real time
that is passing by. When shoot timer is
less or equal to zero, we set shoot timer back to the interval value so
that it can count again. We do plus equals. We add to whatever the timer is to account for
each millisecond, and then we call shoot method, which we haven't written yet. So let's define it
outside update method. But before this closing bracket, private void shoot, we will use our crater object
pool as the projectiles. So private object pooler and
I call it projectile pool. Inside start method, we do the same thing we did
for Destroy Effect Pool. Projectile pool is equal
to game object find, squid crater pool, and on it, we find Object Pooler component. That component has G put
Object public method, so I can define a
helper variable. Game Object type,
I call projectile. It will be equal to projectile pool dot Get Pooled Object. We set position of
the projectile, which will be the squid critter to whatever position
the enemy is. We will also rotate
the critter in the same direction
enemy is facing. Finally, we set it active. We will also play audio. We prepared these sounds before, and I called this sound
squid shoot. Like this. Okay, so each anime has a
random interval assigned to it. Tir is counting between that interval value and
zero over and over. And every time it resets, it shoots a projectile. Save that Okay, this will be very
challenging, I think. There are so many. You can hear the new shoot
sound playing, and there are spitting
critters at the player. I exit play mode.
Object spawner. Let's set spawn
interval to 3 seconds. Now they spawn a bit slower and we can better see
what is happening. The shoot sound is long and it ends with a little
squeak from the critter. I sampled a frog sound for this. But because we are playing
the same sound file from the beginning every
time an enemy shoots, it resets and we rarely get to hear the
full sound effect. Let's deal with the sound. Let's play a random
sound when enemy shoots. If random value
is less than 0.3, which will be in
roughly 30% of cases, we will play this longer
sound called squid shoot. Else, in the other 70% of cases, we play squid shoot too. I save that and
see if it worked. We get one of two random sounds, giving the shooting
a bit more variety. What is how the critters move fast at first, and
then they slow down. Critters always
follow the player. Squid morphs only rotate
towards the player, but they don't move towards it. In front of the squid morph, there is a danger zone. Players should
avoid it because it shoots the critter at a
very high speed at first. I sampled some frog noises
and some scratching rubber to achieve the sound
effect for this enemy type. We have an enemy that shoots a projectile towards the player. What if we take this
one step further? What if we have a big boss
that shoots mid size enemies, and those enemies will
shoot the small critters? At this point, we
can just quickly repeat all the techniques
we learned to achieve that.
44. Boss that Shoots: I created a special
animated spreadsheet for Squid Boss enemy type. You can download it in
the resources below. I drag and drop it into
Assets art enemies. There are a lot of frames and this boss is
relatively large, so the spreadsheet
is larger as well. With Bos three,
spreadsheet selected, I go to Max size and I
select a larger value here to make sure it renders
at full size. I click Apply. Open Spread Editor. Slice, grad bicell
size, 400 times 400. These are large frames. Slice and apply. I drag and drop one of the
frames into the scene. Sorting layer is enemy, or the in layer is zero. Around the position numbers
to something nicer, but mainly I will rotate it to the left to
face the player. I was having fun with
designing this alien. It's a squishy space
octopus that multiplies very fast and shoots its
little clones at the player. Do you have any other ideas for an enemy type that would be
fun to design and implement? Let me know in a comment and I might do an extra
lesson and do it. I add component, flash white, add component float in space. At component circle
collider two D, collider radius will be 1.4. Tag will be enemy, layer will be boss
to make sure it interacts with
colliders on asteroids. I rename it to boss three. Before we animate these frames, let's program some
logic because I want this boss to move around
in a very specific way. In assets scripts, enemies, I create a new mono
behavior script. I call it boss three. I attach it as a component here. I open the script,
and boss three will inherit from the base
enemy class like this. As always, we will need
public override void start, where we call base dot start. And we will need public
overt void update, and base update like this. I save that and now I
see some new fields. For testing, I set
Max lives to 20, damage to 20, and experience
to give also to 20. If I play, I get an error
as expected because we know each enemy type needs to have three things inside
its start method. Destroy effect pool, hit
sound, and destroy sound. I copy this line
from Squid Morph script and I paste it here. I won't boom three effect to animate when boss gets
destroyed like this. I copy these two lines. Squid hit two sound here, and boom to sound here. Now if I save and play, all the basic functionality of the boss is already in place. Now we need to give it
some unique behaviors. We have a game where a player
can use a super speed move and it affects how fast
the game is scrolling by. So for this boss, I will
define an imaginary baseline. I want the boss to always try to move along a path
somewhere around here, and if the boss moves to the
left because the player used super speed or if the boss spawns to the right
from the baseline, it will always try to move
back to that baseline. I want the boss to
always try to be around this area and shoot its clones at the player
from the distance. Let's say plus seven
horizontal unit will be where I want the baseline
to sit horizontally. And if boss' horizontal
position is something else, it will try to move towards it. So I define private
float base position. And private float charge speed, the speed at which
the boss will move if it's not at its
base position. We will need public overt
void on enable that runs every time we activate and
reactivate this game object. We will slightly
randomize base position. It will be Unit
seven, so along here. But if we have multiple
of these bosses spawned, I want some random offset plus random range between
minus one and plus one from the base position, just to make sure if we
have multiple bosses, they don't stack on
top of each other. Charge speed will
also be random for each boss between 1.2
and 2.5, for example, Inside update, we first call all the shared code
from the base class, and then we will define
some logic for how the boss will move along
horizontal X axis. Also how the boss
will move vertically, and then boss' special
shooting behavior. It's actually pretty
simple at this point, so let's quickly do
it step by step. I need to save boss' current horizontal position
in a helper variable. Current X is boss'
transform position X value. If the boss is not at its base position
horizontally, actually, if the boss is further away
from its base position than 0.1 unit to the left
or to the right from it, math absolute to
disregard plus or minus. Base position is at unit seven. If the boss' position is
not at unit plus seven horizontally with a tolerance of 0.1 unit in both directions, we will make the boss move
towards its base position. Helper variable,
position X will be linear interpolation
between where the boss is right
now horizontally, its current X, and
its base position X, which is the target
position basically. Bos will cover a
certain proportion of that distance
per call of update. It will depend on charge speed
accounting for Delta time. Then we set boss' position to
this new horizontal value, leaving the vertical value
to whatever it currently is. We don't modify it here. Okay, so if boss is not
at its base position, make it move towards it. I save let's move the boss far away from the
base position to the left. I play. Okay, that's
too much damage. I move the boss away from the player, and if I play again, Boss will move towards its
horizontal base position, and it works visually, even when the player
uses super speed move. Perfect. What about when the boss spawns
somewhere around here? Will our logic work in the
other direction as well? Yes. Boss will
always move towards its baseline from
both directions. This works really well. Vertical movement
will be bouncing up and down. We did that before. If boss's center point is more than plus five
units from the middle, vertically moving up or less than minus five
units moving down, we flip speedY, making it move
in the opposite direction. I want the boss to always
have some vertical motion so that when it shoots the
enemy clones at the player, it covers the entire game area. Speed wy will be this. Let's write it and
then explain it. We have expression to evaluate. If true, return this,
else return this. If random value
is less than 0.5, in 50% of cases, make the boss move down
one unit per second. Else, in the other 50% of
cases, make the boss move up. This is so called
terniary operator. I'm using it here as a simple
one line if else statement. If this is true, do
this, else, do this. Cos is moving up, and when it moved more than five
units from the middle, its speedy flips to the
opposite and Cs is moving down. Perfect. We have
the movement logic. Et's do the shooting. It's very simple because we did it
before for Squid morph. We know we'll need
object puller, I call projectile pool, pull of something that this
boss will be shooting. We will also need shoot
timer and shoot interval. Shoot interval will be randomized between
two or 3 seconds. Down here, inside update, we will be reducing shoot
timer by real time Delta time. If shoot timer is less
or equal to zero, we increase it by
shoot interval, and we call shoot method, which we haven't defined yet. So I define it here,
outside update, but still inside this
class, private void shoot. I can copy these four
lines of code from Squid morph because
they will be identical. Get a projectile, position
it where the boss is, give it the same rotation as
the boss and activate it. What this projectile
will be will depend on which object pool we will point projectile pool to up here. We can make it shoot
anything we want with this code just by swapping
the name of the object pool. Try and make it shoot other
enemy types if you want, or make it shoot space
whales or something or other bosses or make it
shoot more copies of itself. I will make it
shoot squid morphs. So squid pool here, and we already programmed Squid morph to shoot
squid critters. So this should result in a fun cascade of
spawning enemies. We also want to play a sound. I copy this line,
I paste it here, and I want to play Squid shoot three sound effect like this. If I save and play, Boss will shoot instantly
because shoot timer starts at zero and immediately
triggers shoot method. I moved the boss up here,
so let's play again. Okay, big enemy,
shooting smaller enemy, shooting even smaller enemy. We almost got it. Just
a few things to tweak. It already looks
pretty interesting. When we first activate the boss, I wanted to wait for 1
second before it shoots. And squid morphs will have
random range, which is fine. But that first shot
will also happen after 1 second to make it a
bit more predictable. And also, I want to show off the cascading spawn
effect we created. Let's make squid morph a bit slower when it's being
shot by the boss. Maybe minus seven speed X here. If I save and play, boss will wait 1 second. Okay. Squid morphs will move fast when they slow down
and locate the player, then 1 second countdown
and they shoot a crater. Object spawner. I don't want to be spawning
this right now, so let's just spawn asteroids. All squid morphs that appear
are produced by the boss. Yes, this is good. We have a lot of animation
frames for the boss, so let's actually use them and make this more
visually interesting. Boss three Game Object selected, Animation panel,
and I click Create. Inside Assets Animations folder, I create a new file I call boos three underscore idol dot m. Save that. I go to art Enemies. I expand Bos three spreadsheet. I left click Frame zero, and I shift click frame 38, selecting all the
frames in between. I drag and drop them on animation
timeline. Okay, slower. 25 frames should be good. I click here, create Clip. In the same folder,
I create boos three underscore shoot
dot Nim. Save that. I left click frame 39 and I shift click
the last frame 77, selecting frames in that range. I drag and drop them here. If I select Bus
three game object here, I will see the frames. I play the shoot animation.
Okay, that's fine. I open Animator Window,
Windows Animation Animator. You can also open it here by clicking on the Animation
Controller file. I want the other file that was created the animation clip
for boss three shoot, and in Inspector tab, I uncheck Loop time. This is important for what
we're about to do next. I want this animation
to play only once, and then automatically
transition back to idle. Let me show you exactly
how to do that. So the first step to achieve that is to uncheck
loop time here. In animator, I have these
two states Idle and shoot, parameters tab plus here, and I want to create
a bully baometer. I will call it shooting. I we click Idle,
make transition, and I drag the transition
arrow over to shoot. Arrow selected and conditions
here, I click Plus. We want to transition
from idle to shoot when shooting
parameter is true, and now this will
be a bit different. I want to shoot animation
to play only once, and then automatically
switch back to idle. Animator can handle that automatically without us
having to write any code. I right click Shoot
state, make transition. I drag the transition
arrow back to idle state, and I select the arrow. If I want this animation
to play once and then automatically
transition back to idle, I keep has exit time checked, and I set exit time to one. On the transition
from idle to shoot, I disable has exit time. So when in idle, if shooting is true, we transition to shoot state. This will play only once, and it will automatically
transition back to idle. Now, all I have to do is to control with code this
shooting parameter, set it to true when I want the shooting
animation to play. On the base enemy class, we have a reference to
sprite renderer component, but classic enemies are
not using animator, so I will create that
reference here on boss three. Inside start method, animator is equal to get
component animator. And once we have that
reference, whenever we shoot, we take animator
component and we set that bully parameter we
called shooting to true. This will trigger transition
from idle to shoot state. Shooting animation
will play once, and then it will automatically
transition back to idle. But if shooting parameter is still set to
true at that point, it will transition
back to shoot state, and it will play shoot
animation again. So I want to set
shooting to true here. I want to wait for one frame and then set it back to false. I can do it with a
simple co routine. We used it in this class
before. I numerator. For that, I need system
collections name space up here. I will call it reset shoot. Let's just write it,
and then I will explain it one more time to make
it clear what's happening. Inside reset shoot, if I
say yield return null, it will wait for one frame and then it will run the next line where I take animator
component and I set bullying we call
shooting back to false. Here, when we shoot, we
set shooting to true, and we want to start
this countdown. Start coroutine reset
shoot like this. This will wait just
for one frame, and it will set
shooting back to false. I made a small typo here. Line 26 has to say animator is equal to get component
animator like this. Now if I save and play, Notice when the boss shoots, it will play its shoot
animation once and it will transition back to idle
animation state automatically. This works really well. What
else could we do with it? We could add a timer on each small enemy
and make it evolve to a larger version if there doesn't destroy
them fast enough. I could spend a lot of time designing different enemy types. We have a simple and begin friendly but still solid
enemy system that's easy to extend with new very
different types of enemies. I create an empty game object, boss three pool, I
reset transform. At component object puller, the initial pull
size will be one. BriefasEems, and I drag Bs
three game object in here. I delete this one from the
scene, boss three pull, and I drag boss three briefap into this field. Object spawner. And for testing, the first
wave will be boss three pool. So let's say span interval 3 seconds and two
objects per wave. I put boss three pull
under object pulls here. Let's play and see what happens when we have two bosses
at the same time. Okay, all seems to be working. This is pretty hard to manage. Especially since if
I'm not careful, they will start coming
from behind me, and I don't have a weapon that can reach behind the player yet. Maybe we need a
different weapon type that can help us in a
situation like this. Ir lit.
45. What's Next?: Well done on
completing the class. This was a big project. I hope you had as
much fun as I did, let me know if you'd like
me to create another one. What game genre are you
interested in learning next? Check out the resources section for some bonus art assets, and if you found
value in this class, feel free to join
me in another one.