Transcripts
1. Project 1 Introduction: Let's travel back in time into the golden age of
arcade video games. In this class, we
will use HTML, CSS, and Javascript to build a game
inspired by the classics, But we will give it
our own art style and custom features. We will control a spaceship that has to fight
its way through increasingly larger waves of enemies with an occasional
bust in between. As we progress deeper
into the Unknown, we unlock more weapons and we encounter even more
dangerous alien species. Class is not for
complete beginners, but if you already understand the basics like
variables, arrays, and for loops, come explore game development and chat
script animation with me. Students of this class
will get a ton of premium quality two game
art completely for free. You can use it for your
own personal projects later as well and build
your own games with it. You can also download the
full source code from multiple stages
of the project as we expand it and add
more and more features.
2. Project 1 features: First we will implement
three weapon types, a basic attack, a
long range laser, and a super laser. We will need it because
we will have to fight our way through five
different enemy types. At first we encounter just
a regular space back. One hit from our basic
weapon is enough. As we explore further, we run into armored enemies. We have to hit them five times. We will be able to see
the gradual damage to their thick exoskeleton
after each hit. If we are not careful,
we might find a massive sized enemy with a
dangerous set of claws. Every time a new one appears, it will have more
lives than the one before slowly increasing the
chal***ge for the player. As the game goes on,
some alien species are up for revenge.
Look at this one. Every time we hit
it, it sacrifices one body segment and
it will throw it back at us as a mutated projectile made out of ice,
slime, and tentacles. We will also have to fight
an enemy that absorbs our projectiles and
inflates until it pops. With that one, I will give you a regular sized and
both sized version of the spread sheets
to make your game even more diverse
and fun to explore. We will code everything
from scratch with no frameworks and no
libraries as usual. Hope you have fun on this
Java Script space adventure. Let's go.
3. Project setup: In index HTML, we create
a simple blank webpage. I give it some title. I link CSS style sheet, I create HTML five canvas element with an
ID of canvas one. And I link script the
GS file in style CSS. I target my canvas element
and I give it a border. I want to center it
in the middle of the web page vertically
and horizontally. One way to do that is to use
these four lines of CSS. We can use transform translate, or we can also just use the
translate property like this. Be careful about the syntax, there is no comma
between the values. Everything else in
this class will be written in plain
vanilla Javascript. No frameworks and no libraries. In modern programming, we can structure our code in
many different ways. There are a few common and established
programming paradigms from something very simple, such as procedural programming, all the way to functional and object oriented programming. Object oriented programming is probably the most
popular paradigm. The core concept is that we
organize our data in objects. It usually relies heavily on classes that give us a way of creating many similar objects based on a shared blueprint. In today's class, we will
keep our code clean and organized by making
everything into an object. And we will make those
objects talk to each other. For example, when projectile
object hits an enemy object, enemy object will be deleted. One of the main principles of object oriented programming
is encapsulation, Which means we data
and methods that operate on that data into
separate bundles into objects. We can also restrict access to that data from outside
the bundle us closures, private fields and so on. We will encapsulate
our data into four separate classes that communicate and work together
to create the final game. Player class will handle
movement and animation of the main player character,
the robotic spaceship. This class will only
have one instance. Classes are used as blueprints to create many similar objects. In this case, player
doesn't have to be a class, we can just declare it
as a one simple object. I will make it into
a class to keep the code structure same
across the code base. I want this to be easy
to read for beginners. Projectile class will handle lasers that the player
will be shooting. We will learn how to
make this into a re, usable object pool to
massively improve performance. Because creating and
deleting thousands of projectile objects would
waste a lot of memory. Enemy class will draw and
animate Space Invaders. We will learn how to
organize them into grids and how to make them come
in larger and larger waves. As the game goes on, Game class will contain the main
logic of the code base. This is the main
brain that sends commands everywhere and it
holds all of this together. We will be using polished
professional game art that I'm giving you
completely for free. And we need to make sure
Javascript runs only after all our art assets
are loaded and available. I will only start and
set my project up after load event on
window object fires when the whole page has loaded, including all dependent
resources such as style sheets, scripts, images and so on. Inside the callback function
on load event listener, we can set up our
canvas element. As usual, I use four
simple lines of code. First, I point Java
script towards my canvas element using its ID. I save that reference
in a variable, CTX context canvas variable dot get context and
I pass it to the. This will create an instance of built in to the canvas API, giving us access to all
drawing methods and canvas properties such as
fill style or line width. Because we want
to make sure that canvas drawings
are not distorted. We are not setting
the size of canvas with CSS because that would only set the element size if I set canvas width
and height like this. With Javascript, we are setting both element size and
drawing surface size to the same values. Html canvas has two sizes, they need to be set to the same value to
prevent distortions.
4. Game and player objects: Game object will need to
be aware of canvas size. So constructor will
expect a reference pointing towards canvas
element as an argument. Inside we convert it
to a class property. Take canvas passed as an
argument and convert it to this dot canvas property on this instance of game class. From there, we can
extract canvas width of 600 pixels and canvas
height of 800 pixels. This is very important. And these values will be
needed all over our code base. Because for example, player
and projectile need to be aware if they are inside or outside the visible canvas area. Game object will have
a custom method. I call for example, render. This method will run
60 times per second, drawing and updating everything. First, let's just put
a consolo in there. I create an instance
of game class using the new
keyword on line 14. I can see it expects
canvas as an argument. I pass it canvas from line 25. If I consolo this
new game variable, I can see an instance
of my game class. If I open it, I can see
these properties perfect. I can also call that custom render method we
wrote on line 19. And now I can see it's
correctly consolo in width of 600 pixels and height
of 800 pixels. Our game is set up
ready and working. I want to draw and animated The player player class
constructor will expect a reference to the
main game object from line 13 as an argument. Because we need access
to width and height and many other things that will be sitting on the main game class, I need access to all
these properties from inside the player class. Keep in mind that by doing this, I'm not creating a copy
of the main game object. I'm just pointing to the
space in memory where the game object sits from
inside the player class. I'm saving that reference as this dot game property width of the player will
be 100 pixels. Height will also be 100 pixels. For now, horizontal
x coordinate will be 200 and vertical Y coordinate
will also be 200 pixels. Let's give it a
custom draw method. It will expect a
reference to context to specify which canvas
we want to draw on. I do it like this to keep each
class as self contained as possible inside we will
take that context from it. We call built in fill
rectangle and we give it x, y, width, and height. The current player
position and size. I will structure my code in a way where everything sits on the main game object so I can draw and update
everything from there. Inside the game
class constructor, I create a custom
dist player property and I set it equal to an
instance of player class. Using the new keyword, player expects a reference
to the main game object. I passed this keyword because here we are inside
that game class. Now that we created the player, we can draw it by calling
draw method from line nine. This player from line 27 draw. I can see that draw method
expects context and I will be calling this Phil
rectangle method from context. I also know that Phil
rectangle method is sitting on my CT X
variable down here. I pass CTX to render, render will expect that value as an argument and it will save it as a variable
called context. I will pass it along
to Player Draw like this method already
expects context. And that's how this fill
rectangle can be called. Here on line ten, nice, we are drawing a black square
representing the player. I can change x and y coordinates to move
the player around. What if I want the player to be in the middle horizontally? This game dot width times 0.5 Then we need to offset that value by a half of
the width of the player. Now it's perfectly
centered vertically. I want the player to sit at
the bottom of the game area. The height of the game minus
the height of the player. Perfect. We have a
working game object and a player object. I want to make the player
move left and right. As we press arrow
keys on the keyboard, I give the player this
dot speed property. I want it to move, for example, by five pixels per
animation frame. I give it an update
method that will update horizontal position of
the player by its speed. To run this code, I have
to call it from down here. Nothing really happens because
this code runs only once. To create some movement, I need to draw the player
update its position, draw it again, update
it again, and so on. Let's create animation loop. Custom function. I call animate. I put game dot render inside. Then we call request
animation frame sits on the main window
object and I pass it animate the name of its parent
function to create a loop. If I delete window dot
here, it will still work. Javascript knows where to find this method. We call animate. We draw and update the game, and we trigger animate again. This will repeat over and over. I just need to
actually call animate to trigger the first
loop like this. We see old paint. I only want to see the current
animation frame. Between every loop, I will clear the entire canvas
from coordinate 002. Canvas with canvas height. Nice, the player is
moving to the right, five pixels per animation frame.
5. Keyboard controls: If I set speed to minus one, the player moves to the left one pixel per animation frame. If speed is zero,
there is no movement. Let's add keyboard controls. All the code inside a class constructor
gets automatically executed at the
point when we create an instance of the class
using the new keyword. Normally, we only
use the space to give our object properties, but we can actually put any
code in there that we want to run at this point when this
particular object is created. For example, if I do this event listeners will be
automatically applied. When a new instance of
game object is created, I create an event listener
for key down event. Callback function has access to autogenerated event
object that contains all different properties
and information about the key down
event that just fired. To use it, we just
have to give it a variable name here and
Javascript will know what to do. I will call it for event inside. I will consolo. I
save my changes, I select Canvas element, and I press something
on my keyboard. Keydown event fires and autogenerated event object is displayed in the
browser console. If I inspect it, I can see
all kinds of information. Today, we care only
about this key property. Right here you can see I
pressed letter on my keyboard, in this case if I consolo key. Now when I press random keys, the name of the key that was
pressed will be displayed. Make note of these values. Notice how it spells arrow keys, because we will
need these values. Now we need to make sure we use the exactly
same spelling, including lower
and upper casing. On my main game object, I will have an array
called this dot keys. When a key is pressed
down on key down event, I will add the name of the key that was just
pressed into this array. When the key is
released, we remove it. I take this dot keys here
and I call push method. I will push dot key the name of the key that was
pressed into the array. I save it and I test it. When I start typing
on my keyboard, I will get a console error. It is because we are placing
event listener that sits on a window object inside
our custom game object. In here, I'm trying to access this dot keys property that
sits on the game object. But this callback
function doesn't actually remember where in the code
it was originally declared, it doesn't remember
its lexical scope. We need to bind the
keyword so that it points towards the game object from inside this callback function. Or alternatively, we can use ES six arrow function syntax. Arrow functions
automatically inherit this keyword from
the parent scope. Now this dot used here correctly points
to the game object, and my code will work. Scope enclosures are
advanced programming topics. If you are a beginner,
you don't have to fully understand
it at this point. It will make more
sense eventually. Just keep writing code. I'm cons logging this dokey
and we can see that it grows larger and larger as
I press more and more keys. Also I reload and notice that if I keep
pressing arrow up, it will add it over
and over again. I only want to add one
entry per each key. If arrow up is already in the
array, don't add it again. In Java Script, index off method can be called on an array. It returns the first index at which a given element can
be found in the array. Important thing to remember
is that it returns minus one if the
element is not present. Knowing that, we can say if this key index of dokey
is equal to minus one, if the key that was pressed is not present in this
dot keys array. Only then push the
key into the array. Now I can press arrow
down over and over, and it will only be added
to the array once perfect. I also want to remove the key from the array when
the key is released. When key up event fires, I copy this code block. To make it cleaner,
I create a block scoped constant variable,
here called index. And it will be index of the
key that was released inside this dot keys array if index
is more than minus one. Meaning that if the key that was released is inside
this dot keys array, which it always should be. By the way, we will use splice method to
remove that entry, that key, from this
dot keys array. Built in Java script splice
method can be used to replace or remove existing
elements from an array. If key is found in the
array I call splice, I find the index of that element in the
array and I remove it. Splice can be used to
remove multiple elements. We have to pass it one
here because we want to remove only that one
element we found in there. Okay, so I press Enter
Key on my keyboard. Entry is not present in
this dot keys array. We push it in there,
I release Enter. We find the index
of that entry in this dot keys array and we remove it using
the splice method. I press arrow up, It's added, I release it. It's removed. I type a
bit more. It works well. Great. It's a good practice to always remove consolo when
we don't need them anymore. Now on the main game object, we have this dot keys
array that keeps track of keys that are
currently being pressed down. Have access to everything on the game object from inside
the player object because of this dot game property here inside update method
on player class. I can say if this game dokey index of arrow left is more
than minus one, meaning that arrow
left was pressed, keep producing
horizontal position of the player by distort
speed from line eight, making it move to the left. Another if statement
arrow right and we say plus did speed making
it move to the right. Now I need to set
the distort speed to something other than zero. Here, for example,
five pixels per frame. Now I can press left
and right arrows, and player is moving
left and right. The advantage of
this code structure is that we can easily
boost player speed. Or we can slow the
player down by changing this speed
property on line eight. The problem is that
the player can move all the way outside
the game area. I will set speed to
ten pixels per frame. For now, this is where we
handle horizontal movement. I will introduce some
horizontal boundaries here. If this x the horizontal
position of the player, the top left corner of the player rectangle is
less than zero, x is zero, this will create a hard
left boundary L. If because the player can't be
touching left and right the game area in
the same animation frame, we can use L if here, if x is more than the width of the game minus
the width of the player, x is equal to the width of the game minus the
width of the player. This will create a hard
boundary on the right side. I test it, the player can't move outside the game area
anymore on both sides. Nice.
6. Object pool: Now that we have
keyboard controls, I want the player to shoot lasers when we press something. Projectile class
will handle that. In a game like this, we will shoot a large number
of projectiles. We could create a new
projectile object each time and discard it when it collides with something or when it
flies off screen. But as I explained in
the previous lesson, creating and discarding
large amounts of objects creates memory
related performance issues. In this game, we will have only ten projectile
objects in total. And we will re, use
those same ten objects over and over, massively
improving performance. The performance benefit
will become especially apparent in more heavily
animated parts of the game. Believe me, this game
can get very busy and animated when waves
of enemies start coming. We need to make sure we optimize performance of our games. We will achieve that by using
object pool design pattern. Each projectile will have width, height, x, y, and
speed properties. Because we want to
turn this into a re, usable object pool member, we also have to give it
this dot free property. When this dot free is true, projectile is sitting in
the pool ready to be used. It's currently not playing an active role in the
game. It's not visible. When this dot free is false. It means we pulled it from
the pool and we are using it. It's no longer free, it's no longer available. The object pool design pattern is a creational design pattern. It allows us to avoid
performance issues related to automatic
memory allocation and garbage collection processes
that trigger when we create and destroy large
amounts of Javascript objects. We will instead create a small pool of ten
projectile objects. And we will pull them from
the pool when we need them. When the projectiles
collide with enemies or when they
fly off screen, we just return them
back to the pool. We reset their properties
and we make them available to be pulled out
again, they are needed. This will completely remove
the slowdown caused by memory allocation when we
create objects one by one. Also garbage collection
process that automatically and
periodically clears objects that are no longer referenced in our
game from memory will not trigger
at all because we will not be discarding
those projectiles. There is more to object pool
design pattern than this, but this class is for beginners. Today I will show you how to do a very simple but
powerful implementation in a real working project. If you want to read
more about it, I'll link some good articles
in the description below. We give it a draw method, but we only want to draw active projectiles only if the projectile is
currently not free. Same goes for update. Any code inside
update method will run only for the active
object pool members. Only if free is false, exclamation mark means false if projectiles are not active, if they are just sitting in
the pool waiting to be used, we will not draw them and
we will not update them. If projectile object
was pulled from the object pool and
it is not free. Right now, we will draw a rectangle representing
the projectile, the update method for
all active projectiles. We will make them
move upwards in the negative direction
on the vertical y axis. I want them to move
relatively fast. Let's try 20 pixels per animation frame and see
what that looks like. Each object pool member needs
to have two helper methods. A method that will run when the object is taken
from the object pool. I will call it start. Another method that will run
when the object is no longer needed and it's returned back to the pool of
available objects. When we start using the
object we set free to face, the object is being used, it's not available
in the pool anymore. Reset method will
return it back to the pool by setting its
free property to true. Which means because
of these two checks, the object will stop being drawn and updated after
reset method runs. I hope it makes sense
leave a comment if you understand it or if you need
more in depth explanation. You can just leave
a comment with a simple thumbs up emoji. If you understood it well, I will know what you
mean and everyone else will be confused
by that comment. This is the projectile class. It's an object pool member
and it has everything it needs to create object pool design pattern
in our code base. Now we just need a way to create that object pool using
this projectile class. We will do it inside the
main brain of our code base. Here in the game class, I give it a property called
this Projectiles pool, and I set it to an empty array. Number of projectiles
will be ten. I want to use only a
very small number here. It will work well for gameplay
in this case as well. I will show you why I want only ten projectiles when we
have some animation going. Object pool design pattern
needs a method that will fill the pool with ten inactive
projectile objects at first. This way we pre allocate the memory for old
objects in one go, which is good for performance. It's faster to do it
this way and to create all of the objects in
the pool at once rather than if we make Java
script look for free space in memory for
each projectile separately, one by one as and
when we need them. This is a very basic way
of explaining how and why object pool design
pattern solves memory allocation issues by reusing them instead
of deleting them, we also completely avoid triggering garbage
collection process. Another performance
benefit custom method I call create Projectiles. It will run ten times. Each time it runs, it takes this projectiles pull
and it will push one new projectile object
inside created us. In the class we
wrote on line 23, we need one last helper method. A method that will
allow us to take one free projectile object
from the pool when we need it. I call the method, for example,
get projectile inside. We will cycle through the
projectile pool one by one. As soon as we find one element with three property set to true, we return that element
and we stop searching. The return keyword will stop
execution of this four loop. I want to create
projectiles automatically. Here, when an instance of
the game class is created, Projectiles pool is created
and automatically filled with ten inactive and ready to
be used projectile objects. Let's check if it
worked by consolo. In this projectiles pool, I can see it's an array
with ten elements. I just open one and I check
to make sure I don't see any undefined or non values
which would indicate a problem inside my projectile
class. All is good here. Projectile has a start
method that will run when we get the
projectile from the pool. And we want to start using it in the game because projectile will be flying out
of the player. Start method will expect X and Y coordinates of the
player as an argument, it will set this
dot and this y of the projectile to the current x and y coordinates of the player. Finally, to complete
this big logic loop, we give the player a
custom method called shoot Inside I create a block scoped constant variable
called projectile. And it will be equal to get projectile method
we just wrote on the main game class
here on line 92. It will search through
the projectile pool if any of the ten projectiles
is free and available, it will give it to us if all ten projectiles are
currently being used in game, it will not find any free one. So we would get an error here. Only if we found the projectile
and it's not undefined, we will call start method on that inactive projectile in
the pool to activate it. We know that start method expects x and y
position of the player. I pass it dot x dot y. We are passing the current
x and y position of the player wherever the player
is when start method runs, start method expects it. It sets player x and player Y coordinates to x and y
coordinate of the projectile. And it activates it by setting its free
property to falls, meaning that the
code inside draw and update methods
will pass this check. It will start running drawing and updating the projectile. How do we trigger this custom
shoot method we just wrote? I could for example
come down here and inside key down
event listener. I say if the key that
was pressed is enter, be careful about the spelling. Lower and upper case letters
here are very important. You can consolo key from here to see how any particular
key value is spelled. Maybe I want multiple
different attacks. Basic shot will
be triggered when we press number one
on the keyboard. In this case, numbers are in number in this context
is a string data type. When number one is
pressed on the keyboard, player shoot method runs. To actually see it, I
need to cycle through all ten projectile
objects inside projectile spool array
and draw and update them. I do it from down here. This projectile spool for each projectile
object in the array, we call its update and
we call its draw method. We know that the draw
expects context. We just pass it along the
context value from line 84. That context value
is expected here. If projectile is activated, its free property
is set to false. Context will be used to call phil rectangle method that will draw the projectile for us. I save my changes and I
press one on the keyboard. Shoot method runs. We get a free
projectile object from the object pool and we
trigger its start method. Projectiles will
always start from players current x
and y position, even when the player
moves around. Because we are passing
those current player coordinates to the start method. Here I can only shoot ten times. And then we used up
all the objects in the object pool and I
can't shoot anymore. I want projectiles
to reset and become available in the pool again
when they fly off screen. If the vertical y coordinate
of the projectile is less than zero minus the
height of the projectile, if the entire projectile has moved behind the top
edge of the screen, we call reset method
from line 52. Reset method will
set free property on that projectile to true
returning it back to the pool, making it available again. Now I can shoot nonstop. If I shoot very fast, it will give me ten
projectiles in a short burst. As they leave the screen, they reset and they will
be able to come out again. Perfect rectangles on canvas are drawn from the
top left corner. Player X and Y is
this top left corner. Actually, I want them to come out from the middle of
the player horizontally. We pass it x of the player
plus half of player width. It's still not
perfectly centered, which becomes even more obvious if I make the projectiles wider. Maybe my secondary attack will
look something like this. I need this to be
centered over the player, no matter what shape
the projectile is. I will do it from here, inside the projectile class. Because here we have access to the width of the
projectile itself. We can offset the
horizontal position coming from the player by a half of
projectile width like this. Now, no matter how narrow or wide the current
projectiles are, they are always coming exactly from the middle of the
player horizontally. Let's do eight pixels for now. Amazing work. We
have the player that can move around and
shoot projectiles. The techniques we
just learned are extremely useful and
powerful and you can use them to make hundreds of different games if the
player we have is very wide, for some reason, if I stop player when it touches
the edges like this, we will struggle to
shoot enemies that are closer to the edges
of the game area. We will not be able to reach
them with our basic attack. This can create all kinds of chal***ges in how
we design our game. I will fix it here in
horizontal boundaries area. I will actually allow the
player to hide behind the edge of the screen
until half is hidden. Only then we will
introduce stop boundary. Now I can reach enemies
all the way to the left. I will also do this for the
right edge of the game area. Now no matter how narrow
or wide the player is, our basic simple laser
attack can reach everywhere and enemies have
nowhere to hide. Perfect.
7. Enemy Waves: Speaking of enemies, this
will be interesting. Let's create enemies that come
at us in a grid formation. And every time we
defeat one wave, a larger wave of
enemies will come. Before I do that, let's give
our game some a background. I'm giving you a complete
polished and professional set of high quality to the art
assets for this project. You can download everything in the resources section below. Take the background
PNG image you can find there and put it
inside your project folder, Then use it as a
background property. We can actually do
this part with CSS. Css is GPU accelerated. Doing it this way is faster
performance wise then redrawing the same
image over and over on Navas as
our game animates. Now my canvas
drawings are too dark against that new
background down here. When everything is loaded
and navas is set up, I set fill style for everything
drawn on navas too wide. For now this is fine. I will create individual enemies using our custom enemy class. They will need access
to some methods and properties that I put
on the main game class. We will have a reference to the game class
from here as well. Like this. There are a few challenges we
have to solve here. Because we want
enemies to move in a grid and we want them to
move in the same direction. I want the entire wave of enemies to follow the
same movement pattern. We will have to stop and think about how to organize our code. To achieve that each enemy, actually all objects in our
game need to have width, height, x, and y properties, mainly for collision
detection purposes. Custom draw method will
take context as an argument and we stroke rectangle
representing each enemy. For now, I was going to make enemies into a reusable
object pool as well. But I want to keep this class beginner
friendly and combine an object pool with
the grid logic we are about to write could
become hard to follow. For now, enemies will be
very simple basic objects, no object pool
functionality here. For now, update method
will move the enemy. But if I want all enemies to
move in a grid formation, in the same movement pattern, we can't have individual speed here and update each
enemy separately. Position value of the
entire wave of enemies will have to be stored somewhere in one place in our code. And each enemy will know where it sits in relation
to that value. As the wave moves, all
enemies will move with it. Let me show you
exactly what I mean. Create a wrapper class
called Wave Constructor. Again, will need access to properties and methods
on the main game object. We know that all objects in
our game need to have width, height, x, and y position. Let's set position to
00 top left corner of the game area for now. Here inside the game
class constructor, I remove this consolo. Each wave will have a set of
enemies organized in a grid. The first wave will be
a grid of nine enemies organized in three
columns and three rows. I also need the enemy size, the size of each
individual enemy to be globally available, all over my code base. Because we need to be aware of it in multiple
places in the code. For now, each enemy will be a square of 60 times 60 pixels. I want the entire
grid of enemies to bounce between the left and
right edges of the game area. Each time the wave of
enemies touches the edge, it will jump one step down,
closer to the player. The width of the entire wave
of nine enemies organized in three rows and three
columns will be this dot game. Dot columns from line 95 times
this game enemy size from line 97 height will be this dot game do
rose times enemy size. Like this render method will draw and update that
entire wave of enemies. It will need context as usual, then we will use that context. And for now we will stroke rectangle representing
the entire wave area. Just bear with me, it will
make more sense in a minute. Each wave will contain a
certain number of enemies depending on didot rose
and did columns value. We are starting with
three times three. The first wave will
have nine enemies. Each time we destroy all
enemies in one wave. Next wave will be
automatically triggered. We will keep all active waves inside didot waves array
on the main game class. Immediately as we
create game class, the first wave of three
times 39 enemies will be automatically created and pushed inside this dot waves array. I passed this
keyword to represent the main game object we
are inside of right now. It is expected here and converted to this dot
game class property. From here we have
access to columns, rows and enemy size. Now I can take
didot waves array, we pushed one wave inside. Already I call for each Wave object inside
this waves array. Currently there's
only one inside. We will call it
surrender method. We pass it context. We can see the wave
being drawn here, D stroke style to wide, so it's easier to
see it line with, for example, 35 pixels. Size of the wave will adjust to fit all enemies it contains. If we have five columns
and eight rows of enemies, 40 enemies in total, they will be arranged
inside this area. The entire wave of enemies
will move as one unit. The speed will be
on the wave object. For every animation frame, we increase horizontal
position by speed if x is less than zero or if x is more
than the width of the game minus the
width of the wave. Meaning that whenever
the wave rectangle touches the left or right
edge of the game area, flip its horizontal speed
to its opposite value. This will make the wave bounce
endlessly left and right. That worked. Let's make
it three times three. Vertical speed will be zero whenever it touches
one of the edges. I want it to jump by enemy size down vertically
towards the player. I cut this line and I
paste it down here. And I also increase vertical
y position by speed y. When we touch the H like this, speed y stays high and the
wave keeps moving down. I only want it to
jump by enemy size in one animation frame and then
return back to speed y zero. Yes, this is the movement
pattern I was looking for. We have a wave that
moves left and right and down over
the game area. It will contain a
grid of enemies. Each enemy will
move with the wave using x and y position
of that wave, plus relative horizontal
and vertical position of the individual enemy
within that grid. Within the wave, I will call those values this position
x and this dot position Y. Position X and position Y is the position of the
enemy within the wave. Each enemy has to know exactly where it sits
inside the grid. Width of the enemy
will be coming from this value from line
hundred and 11, I set it to 60 pixels. This game dot enemy size. For width and height as well. Enemies will be square shaped. X and y will be 00. Initially, when we
create an enemy, we pass it as an argument
relative position between the enemy
and the entire wave. Position x and position y
values will determine where in the wave relative to the top left corner of
the wave rectangle. This particular enemy sits
position x and position Y past as arguments will be converted into class properties. Here update method will take x and y position of
the wave as arguments. And it will set x
and y position of the enemy relative to
that current value. Exposition of the enemy
is position of the wave plus relative
horizontal position of the enemy in that wave. Y position of the enemy
will be vertical, Y position of the wave plus relative vertical position of this particular
enemy in the wave. There might be a better way
to structure this code. I just came up with this.
Let's see how it works for us. Each wave will have
its own enemies array. We give it a custom
create method inside, we will have a four loop. We want to organize
something in a grid, which means we will need two nested four loops loop
inside of another loop. Let's write it first and then
we explain how it works. The outer four loop will handle rows as we go down row by row. Vertical y coordinate
will be increasing. The inner for loop will handle columns as we go
from left to right. Inside each row, horizontal
x coordinate is increasing. Let's say we are here. This is the current enemy square 60 times 60 pixels row is zero. In the outer loop, we enter
the inner loop and we cycle through all horizontal
positions Within that row, we reach the last column. We exit the inner loop, we enter the outer loop, we increase vertical Y position and we jump to the next row. Again, we enter the in loop, We cycle through
horizontal positions through the columns
from left to right. We exit the inner loop, enter the outer
loop, we increase y and jump to the next row. We enter the inter loop cycle through all
horizontal positions. There we repeat this
over and over until we created the entire grid
of the entire wave. I need to account
for enemy size when creating enemy X and
enemy Y properties. I could have also alternatively jumped by enemy size
value here and here. The result will be same. Each time we jump to a
new cell in the grid. We take this enemies wave and we push one new enemy
object inside. We can see that enemy class
constructor expects game and relative X and Y position
of that enemy in the grid. I pass it this
game from line 78. I pass it enemy X and enemy Y. Okay, When we create a new wave, we have a look inside game class constructor to see the current value of
rows and columns. Based on that we create
a grid of enemies. I want create method to automatically trigger when
we create a new wave. I call it here, that means that as soon as we create
an instance of wave class, this enemy's array will be automatically filled
with enemy objects. And their X and Y
coordinates will be organized in a grid
inside render. I can now take that array and for each enemy
object inside, I call its update method
and it's a draw method. I pass draw method
context as usual. Here we can see that
the update method expects X and Y position of the wave so that I can use it to position enemies in a
grid like formation. Whenever I call enemy update, I have to pass it X
and Y position of its wrapper wave object to make sure all enemies move
around the screen. With it we are drawing
three times three enemies. Remove this rectangle that
strokes the entire wave. That was just to help us
visualize what was happening. We are still drawing
the enemies. We have a wave of three times three enemies and they move around the screen in
a synchronized way. Perfect. I want the enemies
to come wave by wave. And I need each wave to start up off screen and float
into the game area. I said the initial y
coordinate of the wave to be zero minus the
height of the wave, meaning that
initially it will be hidden behind the top
edge of the game area. The wave will be
created off screen, and I want it to quickly float down until
it's fully visible. Then it will start its
left right motion pattern. If this Y is less than zero, increase Y by five pixels
per animation frame. It will float in
like this and starts bouncing left and right when all rows and columns of enemies, the entire wave is fully
visible within the game area. I can test it by changing the
number of rows and columns. You can see it still works. It still floats down until
all enemies in that wave are visible and can be
targeted by player lasers. I try five columns
and seven rows, which will give me a
wave of 35 enemies.
8. Collision detection: This is going really well. We have the player,
projectiles and enemies. I want the projectiles
and enemies to interact. When an enemy gets
hit by a projectile, enemy is destroyed,
projectile object is returned back to
the object pool. Every object in our game has x, y, width, and height properties. Let's create a re, usable
collision detection method, which will compare any
two rectangular objects we pass to it as arguments. And it will return true or false depending on whether or not
these two rectangles collide. For the purposes of two de, collision detection, rectangles
need to be axis aligned. If they are rotated in any way, we would have to treat
them as polygons. Collision detection
would need to be a more complex technique such as separating axis
theorem formula. Today, all the
rectangles will be axis aligned, non rotated. So we can use the simple and the most straightforward
collision detection formula between two axis aligned, meaning non rotated rectangles reusable custom check
collision method. We'll take two arguments, rectangle A and rectangle B. There are many articles
online about this technique. I can reference them. I will leave some links in
the resources section below. The formula always checks
if rectangle A is within the boundaries of rectangle B horizontally and
also vertically. For that, we need to
perform four checks comparing all four edges
of the rectangle on. If all four of these
checks are true, have a collision else, even if one of them is false, there is no collision. Let me show you how that works. By the way, this formula is
very cheap when it comes to performance because the
calculations are so simple. If our game design allows it, we should try to use it. Some games, of course, demand more complex collision
detection techniques. It's not always suitable
for our project. Today it's perfect. We
need to check four things. First, we check if the
horizontal position of rectangle we
pass as argument a here is less than
horizontal position of rectangle B plus the
width of rectangle. We are checking if this
is to the left of this. At the same time, double
percent operator. We also need to check if the horizontal position
of rectangle a plus the width of rectangle a is more than horizontal
position of rectangle. We are checking if this
is to the right of this. If both of these
checks are true, we know that rectangles
collide horizontally, but they can still be far away from each other vertically. We need to do the
same two checks, but this time for
the vertical y axis. I check if the vertical
position of rectangle A is less than vertical position of rectangle B plus its height. If this is above
this one last check, We check if the vertical
position of rectangle A plus its height is more than vertical
position of rectangle B. If this is below this. Only if all four of these checks are true,
we have a collision. If all are false or at
least one of them is false, we know there is no collision. We know that the two rectangles are far away from each other, at least in one direction. I can actually simplify this and I can make this
method automatically evaluate this expression and return true or false directly. This way I can just call check collision passed
two rectangular objects, for example projectile and enemy and it will directly
return true or false. For me this is our re usable
collision detection method. Keep in mind that any
objects we pass as rectangle A or rectangle
need to have x, y, w and hyde properties. For this code to work, I want to use a custom
check collision method. Now we hold all ten re, usable projectile objects Here inside projectile pool array, enemies are stored inside
enemy's array separately. For each wave here,
those enemies are created using the class, the blueprint we wrote
here on line 57. So it makes sense to put that
collision check in here. Every time update method
on each enemy object runs, we will compare x, y within height of that particular enemy with
all ten projectile objects. We will only check against the active projectiles
projectile pool. For each, for each projectile we call our custom
check collision method. We know it expects
rectangular object a and rectangular
object as arguments. We know both of these objects
need to have x, y, width, and height properties for check collision method to
be able to work with them. I pass it this enemy object
as rectangle A and I pass it all ten projectiles one by one as we cycle through
projectile pool. For each method we wrote check collision method to
directly return true or false. So I can just say if and wrap
it in brackets like this. If this enemy and
projectile collide, this code block will run side. We delete the enemy. I said it's marked for
deletion property to true. Also I need to declare that
property in enemy class constructor Up here inside
render method on wave object, we will filter enemies that were destroyed out of enemy's array. Built in array filter method
filter method will create a copy of a given array and that filtered
copy will contain just the elements that passed a certain condition here
I'm basically saying create a copy of this dot enemies
array and only allow inside elements that have marked for deletion
property set to false. Then override this
original dis dot enemies array with that
new filtered array. This way I'm removing all enemies that collided
with projectiles because those
enemies have marked for deletion property
set to true. I'm getting a console error. It is because I misspelled
projectile pull here. Yes, it's supposed to be spelled
projectiles pull with an S. When a new wave
of enemies comes, you can see that the bottom left enemy is already destroyed. It's because all my
inactive projectiles are initially sitting in that
corner at coordinate 00. We are not drawing them
and updating them, but we are still calculating
collision between those inactive
projectiles and enemies. I fix it by only checking collision if projectile
free is false. If the projectile is
currently active, we want to ignore
inactive projectiles when it comes to
collision detection, I also want to reset that projectile and return
it back to the pool. It collides with
an enemy so that each one projectile can
destroy only one enemy. We have a reset method here for that which will set
free property to true returning
projectile back to the pool of unused
available objects. When collision between enemy
and projectile happens, I call reset method on that
projectile to deactivate it. Yes, this is the gameplay
I was looking for. When collision between enemy
and projectile happens, enemy gets deleted and projectile object gets returned
back to the object pool.
9. Score and status text: I want to draw status texts such as score wave counter player lives or how much energy we currently have for lasers
and special attacks. I will do all of that inside
draw status text method, I call built in fill text method which takes
at least three arguments, the text we want to draw
and x and y coordinates of the anchor point in relation to which the
text will be positioned. I call it here inside Render, and I want to draw
it behind enemies. So I draw it first.
It's just my choice. You can draw it on
top if you want. I passed context
along, and here we go. I set font to, for example, 30 pixels impact for nice, big and easy to read letters. We will keep the
current score as a variable on the
main game object. It will start at zero. Inside field text, I will add that dynamic variable value
next to the text like this. Now I want the score
value to increase by one when we destroy
an enemy here. When projectile
and enemy collide, we take this game score and we increase it
by one. Like this. Nice, I'm shooting enemies
and I'm increasing my score. Perfect. The game will be lost if any enemy touches
the bottom of the screen. For each enemy, I
will check if its vertical Y position
plus its height. The bottom boundary of enemy rectangle is more than
this dot game dot height, the bottom boundary
of visible game area. If that happens, I set this
game game over to true and I destroy that enemy by setting its marked for deletion
property to true. For now, I need to make sure I declare that game
over property here. Initially it will be false. If game over is true, I want to draw some text. As you can see by default
canvas text is left aligned. I want this game over message
to be center aligned. Font will be 100 pixels. Impact text will say game
over in capital letters. And it will be exactly in
the middle of the screen. Width of the game times
0.5 and height of the game times 0.5 Nice, we have big game over text
in the middle of the screen. As soon as enemy touches the
bottom of the game area. The problem is that
this text line and font properties change
the entire canvas state. And now the same text style applies to score fill
text on line 194. I will wrap this
entire code block between safe and restore
built in canvas methods. The safe method
saves canvas state, including fill style
text line font and all other canvas properties. It creates a safe point, like in a video game. It remembers what values
all canvas properties have. At this point, then we can alter canvas settings
in any way we want. Then when I call restore, it will return all canvas
properties to what they were when safe method was
called. What is happening? Now we save canvas state, we draw score at 30 pixel
size and left aligned, we set text 200 pixels
center aligned, we draw game over and we
reset those settings. Back in the next animation loop, when score is redrawn, it is back to 30
pixels left align. It's an endless animation
loop we have here. I hope it makes sense, we
can take it even further and since we wrapped
all the status text between safe and restore, we separated it from the rest of the shapes drawn on canvas. I can also give it
some shadows here. Shadows will only be
applied to the text, but not to enemies, projectiles, and player shapes. To display canvas shadows, we need to define at least three out of four available
properties. Horizontal distance between
the shape and the shadow. Vertical distance,
color of the shadow. And we can also apply blur using shadow blur property.
But it's optional. Blur is performance expensive. It involves calculating
opacity and sub pixel values. I will avoid it today. We are counting score and we are drawing game
over message nice. When I destroy an
entire wave of enemies, I want a new larger wave
to come automatically. We will count them and
players can chal***ge themselves to see how many waves of enemies they can defeat. Wave count will start
with a wave number one. Like this, we will draw the
wave number here, like this. Inside our draw status
text method vertically, I set the text to 60 pixels. No, 80 pixels from the top. Yes, the first wave will be a small grid of
two times two enemies. When I destroy
these four enemies, I want a new larger
wave to appear. We will handle that logic
inside a customer method. I call for example, new wave. You can think about
this technique as a very simple level mechanics. When you defeat a wave, larger, more difficult wave of enemies
will come a new level. We can add any conditions and properties of that
new wave here. First, I can increase
the size of the wave by one column and by one
row. Let's try that. I will increase columns
and row values by one and I will push new wave object
into this waves array. I pass it a reference to the main game object
as an argument because it expects it as we
can see here on line 91. Because we increase
columns and rows value, these lines of code will ensure that the next
wave is larger. I also need to make sure that each wave triggers the
next wave only once. So I give it a flag
that indicates whether or not it already
triggered the next wave. Initially, I said it to false. Each wave contains
enemy's array that holds all currently active enemy
objects in that wave. As we run for each method on all wave objects in
this waves array, I check if the ***gth of enemy's array on that
wave is less than one. At that same time if
the flag I called, next wave trigger hasn't
been activated yet. And only if game over is false. Because when game over happens, I want new waves to stop coming. If this entire expression
evaluates as true, we entered the if
statement block inside. I call new wave
method we just wrote. We will also increase
wave count by one. Finally I sat next
wave trigger to true, preventing this particular wave from triggering any
more new waves. I test it, we have a wave
of two times two enemies. If I defeat all of them, we immediately get next wave of three times three enemies. Perfect it is because
here in new wave method, we always add one
more column and one more row each time new
wave of enemies is created. If I want the waves
to grow a bit slower, I want to randomly in 50% of the cases to
add a new column, or in the other 50% of cases, I want to add a new row. We can do that for example. This way math at random
called on its own, like this, will give us a random value
0-10 included, one excluded. I can say if math at random called on its
own is less than 0.5 roughly in 50% of the cases increase the
enemy grid by one column, else add one row instead. This way the waves will
still grow but a bit slower. It will take more time
before they fill big part of the screen and the game
becomes more difficult later. I will also add boss
battles, features randomly. Instead of a new wave, we could just get a big
boss with a lot of health. When we defeat the boss, waves will start
coming as usual. I will also have to
make sure we are only adding new columns
to a certain point. Let's say until the width of the entire wave is less than
80% of the screen width. If we allow it to
go more than that, the game can quickly become too fast and completely unplayable. You can try to play the game
at this stage where we have no limitation on columns
and rows. See what I mean. I will also only add
rows if the height of the entire enemy wave is less
than 60% of game height. I tested by setting
starting grid to five times five
enemies new wave. And I can see it
added a new column. Let me quickly deal with them. I need to add some more
powerful weapons later. Now it added a new row. We have a grid of six times six. Now it added one more row. We have a grid of
six times seven. When we get to this stage, I want enemies to somehow react when they
touch the player. The first step in that
interaction will be to detect a collision between
enemies and the player. If you check collision between
this enemy and player is true set marked for deletion on that enemy
to true to destroy it. Inside that if statement, we do another check if
game over is false. If score is more than zero, we reduce score by one. We will punish the
player for not being able to shoot
the enemy at distance. I will also reduce
player lives by one. Then I check if player lives
variable is less than one. If it is, we set the
game over to true. Nice player will start with
let's say three lives. To check if it works,
we will visualize the lives inside draw
status text method. But let me show you a different
way how to display them. Rather than just to draw
a number that says three, I will create a
four loop that will run once for each player life. Each time it runs, it will
fill rectangle at coordinates 1010 within height of the rectangle will also
be ten times ten pixels. Now I'm drawing three squares
on top of each other. I want this to be a loading bar. I just multiply horizontal position Tams index
of the four loop. Now the three
squares representing player lives are drawn
next to each other. This value is effectively
spacing in between. If the width of the rectangle
is less than the spacing, we will see them drawn
as separate shapes like this height,
maybe 20 pixels. I can also add left margin
like this vertically. I position them 80
pixels from the top, 100 pixels from the top. If I let enemies hit the player, we can see we lose lives and then we get
game over message. Each time we successfully
defeat a wave of enemies, a new wave is created. We will give the player
one more bonus life. I test it, I have three lives. I defeat the wave, yes, now I have four lives. I set the grid to five
columns and 11 rows. I need to test something. When game over happens, I want to prevent
the player from being able to get any
more score points. We are giving these
score points here. I will only do it if
game over is false. Now if I get game over and
I destroy more enemies, game score stays the same. Down here, I want to add
one more line of text. I copy these two lines of code. Font will be 20 pixels. Text will say press
R to restart. I move a bit down under
the game over message. Now when game over
message appears, we get another message that
says press R to restart. Let's actually
implement that feature.
10. Restart method: Restarting a game can be
handled in many different ways. I usually do it when I finalize my basic code structure
like we did here. At this point we understand
our code base pretty well, and we understand
what variables, properties all across our
code base needs to be reset to a default value
when the game restarts. Yes, I can also restart the game by simply reloading
the browser window, but let's do it the
proper way this time. First, I need to
restart the player. And I can do it here
from the player class. I will give the player a
custom restart method here. I know that when we restart, I want the player to move back to the middle
of the screen. I want to reset player
lives back to three. There might be more things
here to restart later after we actually animate
the player spaceship. But for now, this is all we
need to do in this area. On the main game class, we create the main
custom restart method. From here, I will call
the restart method we just defined on the player. I also need to reset
game properties. I go up here inside game class
constructor and I have to think which properties from here change values
as we play the game. Therefore, there will need to be reset to default when
we restart the game. I copy all of this code
and I paste it down here. Enemy size will stay the same. I delete that. When
the game restarts, I want to go back to a
small grid of enemies, maybe two columns and two rows. I will delete all
wave objects from this waves array in case
there are still any. We will automatically push one new wave of two times
two enemies inside. We reset wave
counter back to one, reset score back to zero, and we set game over to falls. I should make sure
that columns and row values are the
same here as well. Because as I play the game, the number of columns and rows increases as I defeat enemies. Wave after wave. We
need to make sure that the restart method resets the enemy grid back
to this small size. Down here we have key down
event listener inside I say if the key that was
pressed is lower case R, we call the custom
restart method. We just wrote the game. I destroy some enemies. We press R and the first
wave comes out again. I play, the enemy grid
is getting larger. I press R and we
reset the game and enemies are back to the
basic grid of two times two. Yes, this clearly works. You can use or operator
here and you can have multiple keys triggering
the restart method. For example, lower and upper
case R. If you want to, I don't want the
player to be able to restart the game unless we
see the game over screen. We will only run
the restart method if lower R was pressed. At the same time if
game over is true, like this, now I'm pressing
R and it's not restarting. I increase roles to ten just
to get a quick game over, when enemies touch the bottom of the game area or when
player lives get depleted. I see game over screen now
When I press R on my keyboard, we actually reset
the game Perfect. When I hold down
number one to shoot, player will shoot all
ten projectiles over and over very quickly
in a single sequence. And it actually makes the
game too easy and trivial. Maybe I want to prevent the
player from doing that. And I want to make
sure that each time we want to shoot the
basic laser attack, basic projectile, we have
to press the button again. If we hold it down, only one laser
projectile comes out. We will add more
powerful attacks later, which will shoot a
massive laser ray as long as we hold a key down. But that strong attack will have energy cost attached to it. We can't have a Spam Mable
super attack like this, otherwise the game is
too easy and point less. I will create a simple
flag here called fired. It means that attack
button was pressed and one laser projectile was
fired inside Key down event. I take this, I put it here. After that, I set this
dot fired to true. And we will only
call player shoot. If fired is false, it means it will only shoot
one pair. Key down event. Then in key up I said this
dot fired to false again. Okay, now one key press
means one projectile. We have to release
the key and press it again to shoot another
projectile. Perfect. Well done forgetting so far. If you are watching
the extended version, there is a code from this
stage available to download. In the resources section, we have a very solid
game skeleton. Our game has enemies coming
in larger and larger waves. We use object pool
to reuse projectiles and improve performance and memory management of our game. Have keyboard controlled
player that gets a bonus extra life for
each enemy wave we defeat. And we already implemented
many other smaller features. It's time we talk about
graphics and animation. In this project, we are focusing on optimizing
performance. Let me show you how to
make the game look great, but at the same time, how to use very lightweight
animation system that will make sure our game runs smooth even on the
oldest machines.
11. Beetlemorph enemy class: The first enemy type I want to implement is a basic space bug. A simple enemy with one life that explodes
when we hit it. With a projectile,
you can download the spreadsheet in the
resources section below, Place the image into
your project folder and include it inside
index HTML file. Like this. Source
is beetle morph, Png ID is Beetle moorphscriptGs. We declare our enemy class, I want to have
multiple enemy types. I will put properties
and methods that are shared between all enemy types
on the parent enemy class. Here I will create a smaller subclass for each
enemy type separately. There we will declare
properties and methods that are specific only
to that enemy type. We will do it using the
extends keyword class. Beetle morph, extends enemy. The extense keyword
used here makes beetle morph a child class of
enemy parent class. We can also call this
a subclass and enemy is a superclass. I will give this subclass
a constructor as well when I create an
instance of this class. First, I want to run the
superclass constructor. First, I want to
trigger all the code inside the parent enemy
class constructor here. To call this constructor, I can see I need to pass
it game position x and position y. Subclass constructor will expect the same arguments. And we will call superclass
constructor triggering the code inside the constructor
on the enemy class. We know we need to
pass it these values along for that code
to actually work. Now I triggered
superclass constructor and once all the code
inside here is executed, the code that contains properties shared
between all enemy types, I can finally use this keyword
inside here and give this subclass some
properties that are specific only to this
particular enemy type. Each enemy type will have a
different sprite sheet image. This dot image here will be document dot get element by ID. We gave it an ID of beetle
morph back in index HTML file. If your browser window
is larger than mine, you can also see the
sprite sheet here. I want to hide that IMG element. I give it display none with CSS, we only want to draw it
with Java Script on canvas. We will draw that image here inside the draw method
on enemy class. Whatever drawing code we
put inside this method will determine what each
individual enemy looks like. I will use built in
draw image method which expects at least
three arguments. The image we want to draw dot image and x and y
coordinates where to draw it. Now when I save the changes, I will get an error. This error message means that Java script can't find the
image I'm trying to draw. It's because I'm creating an instance of the
parent enemy class. This class doesn't have
didot image property. I need to instantiate the subclass and enemy
class constructor will also be
triggered from there. When we extend
classes like this, Javascript will also create the usual prototype based
inheritance behind the scenes. If I for example, call
draw and update method from an instance of Beetle
Morph class from the subclass, Javascript will not find that method on this
child class and it will automatically follow
the prototype chain up to the parent enemy class. It will trigger that draw and
update methods from there. For Javascript to actually find this image property sitting
on the child class here, I need to create an
instance of Beetle Morph. Instead, as we just explained, Beetle morph child
class has access to all properties and methods on the parent enemy
class as well. We pass it game and
the relative x and y position of that
particular enemy within the wave within the grid. These properties are expected here they are passed along
to enemy class constructor. All the code inside enemy class constructor
will be executed. On top of that, we will
add whatever code we have here that is specific only
to the Beetle Morph class. Beetle morph is a
subclass child class, enemy is a superclass
parent class.
12. Sprite animation explained: We get this strange situation. It is because we are passing a draw image only
three arguments. So it is drawing
the entire image, the entire spread
sheet we passed to it here at its original size. At these x and y coordinates, we can pass draw image optional fourth and fifth
argument for width and height. And when we do that
it will squeeze or stretch that image to
fit that specified area. Now I'm squeezing
all 12 frames of the sprite sheet into an area
of one enemy, of one frame. We want to crop out individual frames from
the sprite sheet. For that, we will need
the longest version of draw image method which gives us the most control over the
image we are drawing. We will pass draw image
method nine arguments. The image we want to
draw source source Y, source width, and source
height of an area. I want to crop out
from the source image, destination X, destination
Y, destination width, and destination height to
define where on destination canvas we want to place that crop out piece
of image on to, let's say I want to
crop out the area from coordinates 002,
coordinates 2050. I want to stretch
this cropped out piece of source image
over a space of 60 times 60 pixels on the destination canvas.
This is what we get. What if we crop out an
area 00-5050 pixels? Ideally, we want to use the
same size, so I will use, with height currently
that's 60 times 60 pixels. I created the
spreadsheet frames at a size of 80 times 80 pixels. Let's draw the images at
the same size on canvas. Enemy size will be 80 pixels. We are setting
width and height of the enemy to enemy size here. And then we are
using that width and height in side draw
image method here. Now we are cropping out exactly one frame from
the source image. And we are drawing it
at the same size of 80 times 80 pixels on canvas instead of hard
coding coordinate 00. Here we will convert
these into variables. I will put them on the subclass, because each enemy type might have a different
number of frames. In the spreadsheet frame X will handle horizontal navigation
around the sprite sheet. In this case, because of how I structured this
particular spry sheet, we can see that this will give us distortion and explosion animation that will play
when we hit the enemy frame. Y will handle vertical navigation around
the sprite sheet. In this case, that will give us different variations
of the beetle morph. You can see I created
some variety. I created four versions
with different wings, different armor, and
different claws. It's just to make the game a bit more visually interesting. You can see that each enemy will distort slightly
differently. And we will show
different explosion frame when we hit them
with the projectile. I could have made the
explosion animation smooth out of many animation frames as I always do in my other classes. But today I want to
show you how to improve performance with this
sprite animation technique, where we use only
a limited number of frames in each sprite sheet. It will give us a bit
of a retro effect. And it will look good
because animations will react to key presses
and to game events. Let me show you what I mean. These are source X and
source Y arguments passed to draw image method. They determine X and Y
coordinates of the cropping area. Currently, we are cropping a rectangle of 80
times 80 pixels. Starting from coordinates 00, I can jump around
the sprite sheet by w. And height like this, zero times width,
zero times height is still this frame
from coordinate 00. If I say one times height, here we are dropping
from coordinate 080. Here we can see
the second row in the sprite sheet with a
different enemy variation, two times height
will crop from here. Seeing another beetle morph, three times height will
give us the last one. I can replace this
value with frame Y. Source X will move around the
sprite sheet horizontally. One times width will give
us this cropping area. Two times width will
give us this frame. If I increase it to three, we are outside the spread sheet and we are looking
at a blank frame. It will still work and
nothing will break, but we just don't see anything. I will use frame
x variable here. Now we know that
source y argument, this code determines a role. Well, if I want
each enemy object to be randomly assigned, one of the four
available visuals, one of the four available
petal morph variations. I can do that simply by setting
frame y to random value 0-4 There is no Road
3.5 for example, so I will need integers here. I wrap it in mask floor
to round the number down. Now this line of code gives us either zero or one,
or two, or three. We can see we are getting randomized beetle
morphs in the wave. Perfect, That's
vertical navigation. Now I want to also use horizontal navigation
around the spreadsheet. And I want to play
the frames like this. When enemy gets hit
by a projectile, we go up to the
parent enemy class. Because this behavior will be shared among all enemy types. We will give it a custom method. I call for example hit that will take damage
value as an argument. I do it like this
because maybe we will have weapons that
hit for more damage. Later, we will have enemy
types with more than just one life that need
to be hit multiple times. Inside I decrease lives of
the enemy by the damage. Each enemy type will have a
different number of lives. Beetle Morph is a simple, basic, squishy enemy with
only one life. I put that property
here on the subclass. We also need to
define Max Frame. In this case we have frames
01.2 Max Frame here is two. I will also create a
property I call Max Lives and I set it to the
initial distant lives value. We do that because even
if the current enemy lives might be zero and
the enemy was destroyed, I want the enemy
object to remember its original maximum
number of live, so we can reward
the same amount of score points once we
defeated that enemy. We will write that
code in a minute. Up here, when active projectile
collides with an enemy, we set marked for deletion
on that enemy to true. I want to change this logic. Instead of immediately
deleting the enemy, we will call this dot hit
method we just defined. I pass it a damage of
one like this. For now. Inside hit method,
we are reducing lives of this enemy
by this damage value. If you remember,
after that I create a separate if statement and I say if this lives
is less than one, once we hit enemy enough times
to deplete all its lives, which in this case was very easy because each beetle
morph has only one life. I want to trigger
horizontal navigation around the sprite sheet
to play this animation. This dot frame X plus plus. Every time we increase
frame X by one, we check if the current frame
X is more than max frame. We check if we played all the horizontal frames
in the sprite sheet, only after we hit the enemy, we depleted all its lives. And after we played all horizontal frames of
the sprite sheet, only then we are ready to set marked for deletion to true. Which will filter the enemy
out of enemy's array. Because of the logic
we wrote earlier, automatic garbage
collection process will eventually delete it as it does with all variables that are no longer referenced
anywhere in the code. I will also cut this line of code only once we
destroyed the enemy. If game over is false and
the game is still running, we increase score by the
original amount of enemy lives. By this max lives, enemies that had one life
will give one score point. We will have another enemy
type with three lives later, that one will give us three
score points when destroyed. Down here we can see we
give Beetle Morph one life, so it will reward
only one score point. The code, I can see that
horizontal frames are playing, but they are playing very fast. We can barely notice
what's happening. We are animating probably at 60 frames per second for
every animation frame. We are increasing
frame X by one, and we only have three frames. This whole animation plays out
in a fraction of a second. We need a way to control the
speed of sprite animation. I can comment out the rectangles around enemies,
This looks better. I want the waves to move slower, maybe one pixel per
animation frame. I also want the waves to start from the
middle horizontally. The initial x coordinate of the wave will be the
width of the game times 0.5 minus a half of
the width of the wave. I want each wave to
randomly move to the left or to the right
from that middle position. I will do a little trick here. Horizontal speed is
equal to an expression, so called ternary operator, the only Java Script operator
with three operands. We will use it here as a
simple one line L statement. If method random
is less than 0.5 roughly in 50% of the
cases set speed x to minus one L set speed
x two plus one ternary operator used as an L
statement goes like this expression to evaluate
if true question mark, do this L colon, do that. We could have also used a regular FL syntax here,
but this is faster. Now if I reload the
project a few times, I can see that waves
start from the middle, and randomly they will
move to the left or to the right at a speed of one
pixel per animation frame. When we hit an enemy, horizontal frames play,
but they play very fast. How can we control that animation
speed? Let me show you.
13. Animation timing: Built in request.
Animation frame method we are using here has
two special features. The first one is that
it automatically adjusts the speed at which it serves the next
animation frame. It adjusts it to
the screen refresh rate because there is
no point calculating and drawing more frames
than the refresh rate of your computer monitor
allows you to display. Drawing those extra frames
would be a waste of performance because we wouldn't be able to see them anyway. The second special
feature is that it automatically generates
a time stamp which simply is a value of
milliseconds that passed since the first request
animation frame in the sequence was called. We will use those auto
generated time stamps to trigger periodic
events in our game. For example, I can update
horizontal sprite frame in enemy sprite sheet only once
every 500 milliseconds, which would be two
frames per second. We will be able to set
any frame rate we want. Let me show you how to do that. We will need a help er let
variable I call last time. This variable will
hold a value of a time stamp from the
previous animation loop. So that we can compare it with the time stamp from
this animation loop. And we can calculate how many milliseconds passed between the two
animation frames. We usually call this
value Delta Time request. Animation frame auto generates a time stamp and it passes that value to
animate function here. All I need to do to use that value is to assign
it a variable name. I will call it timestamp here. Now if you consolog this time
stamp from inside Animate, you will see how it counts milliseconds from when
the first loop started. 1 second is 1,000 milliseconds. Delta time is the number
of milliseconds it takes our computer to
serve one animation frame. It is the difference
between the time stamp from this animation loop minus the time stamp from the
previous animation loop. Once we calculated delta time, we will set last time
to the time stamp from this animation loop so
that it can be used as the old time stamp in
the next animation loop. As the animation loop
runs and request animation frame is
generating new time stamps, we are always comparing the current time stamp
and the old time stamp from the previous loop
and that difference between them giving
us delta time. We will need that delta time
value inside render method. I pass it as an argument here on the main game
class inside render, I make sure that the
value is expected. I will consulate delta time from here to make
sure it worked. If I scroll all the way up, I can see that the
first two values of delta time are
non, not a number. This might be a problem
depending on how we plan to use the
delta time value. This non here could
break our code. Potentially, we are getting none because the first animation
loop is triggered here, but that one doesn't have
autogenerated timestamp value. Time stamp only
gets generated when the second loop is triggered here by request animation frame. For the first loop, the
time stamp is undefined and I'm calculating delta
time undefined minus one, which is giving me none, not a number value. I can fix that simply by passing zero as the
first time stamp. From the next loop,
we will start getting proper
autogenerated time stamps. Now when I scroll
all the way up here, we get numbers all get here. As you can see, my delta time is around 16.6 milliseconds. It takes my computer around 16.6 milliseconds to update and
draw one animation frame. 1,000 milliseconds divided
by 16.6 is around 60. I can see that I'm getting animation speed of 60
frames per second. If you have some high
refresh rate gaming screen, you might be getting
much lower delta time, which also means that
your entire game is running faster than mine. Because as we said before, request animation frame
method automatically adjusts to the refresh rate
capabilities of your screen. We can control the FPS of the entire game
now here by simply saying only call render to update and draw the
next game frame. If, for example 50
milliseconds have passed, which would give
us 1000/50 is 20, it would give us animation
speed of 20 FPS. Basically what I'm saying
here is that we can now use this delta time value to trigger any periodic
events in our code. Delta time is milliseconds. So we can say do something
every ten milliseconds, do something every
100 milliseconds. We can do whatever we want. It could be updating
the entire game every 50 milliseconds
or something else. We can add a boss, a power up, or a special enemy
every 15 seconds. That would also work. I will now show you how
to use Delta Time to periodically update spread
sheet frames in our code base. Because we want to slow
down the animation that plays after we hit
enemy with a projectile. We will also use this later to animate other things
in our game in this low frame rate retro
style that allows us to have spreadsheets with
just a few frames to get a nice performance boost. It also allows us to
time things separately. In our code base, I
have player movement, projectiles, and enemies
animating at full 60 FPS. But the speed at which the spreadsheet itself swaps frames will operate on its
own separate speed. Let me show you how
to implement that. I go up to the main
game class here, inside the render method, I delete this consolo. Even a consolo like
this in our game would affect
performance negatively. It's good to use it
for the bugging, but don't forget to always
delete your consolo, especially the ones that run for every animation frame
like this one does. I want to have a flag variable here on my main game class. This sprite update flag
will always be false. It will only switch
to true once every, let's say 500 milliseconds. It will switch to true
for one animation frame. It will allow all sprite
sheets in our game to update, then it will switch
back to fall again until the next 500
milliseconds pass. I want to update
all sprite frames in our game periodically. Let's say every
500 milliseconds, we can easily change that millisecond value
to something else. Later we will need
two helper variables. Sprite timer will count between
zero and sprite interval. Every time it reaches
sprite interval, it will set sprite
update to true, and the timer will reset
back to zero so that it can count again towards
the next periodic trigger. I will handle sprite
timing logic. Here I say if sprite
timer is more than sprite interval,
do something else. Do something else. If
sprite timer accumulated enough delta time that it is now more than 500 milliseconds, we set sprite update the true. We reset sprite timer back to zero so that it
can count again. Sprite update will remain true only for a single
animation frame, because in the next loop, when the L statement runs, it will immediately
set it back to false. This L statement means that sprite timer is less
than sprite interval. In that case, we want
timer to keep accumulating delta time until we accumulated
more than interval Again, because delta time is the
actual amount of milliseconds, these periodic events will
trigger in the same interval. On slow and on fast refreshed screens it
will be almost the same. It will not be exactly
the same because when I reset sprite timer
back to zero here, in most cases there
will be a little bit of extra delta time that I'm
not accounting for here, which will create
some difference. I'm okay with that. If you
want complete precision, take delta time that goes
over the interval value of 500 here and assign that leftover value to
the starting time here. Instead of setting
timer back to zero, that should make it
even more accurate. I don't think I
need that level of accuracy for my game right now. I want to avoid unnecessary
calculations for now. For maximum performance,
now we have this sprite update property that once every 500 milliseconds, just for one animation
frame, is set to true. The rest of the time
it's set to false. We can use it to trigger
sprite animation when we hit enemy with a projectile and when enemy lives
are less than zero. Instead of increasing frame X in the spreadsheet
immediately. We will only do it if
sprite update is true. Let's test it. I shoot
an enemy and every 500 milliseconds it plays the next animation
frame perfect. Now we have a problem that
the enemy is stopping projectiles until the
animation finished playing. Once we hit the enemy and its explosion animation
starts slowly playing, I want the next projectile
to ignore that enemy, fly through it and to
hit the enemy behind it. I do it by only checking collision between
projectiles and enemies. If that enemy has
more than zero lives. Like this, we have a complete control over the
spread animation speed. Now I can change it down here, Let's say I want to make
them animate a bit faster. Now they animate fast, but it's still slow enough for us to see the individual frames, which is what I was looking for. Normally, when I create
my sprite sheets, I create complex animation loops with 20 or 30 animation frames. But sometimes if you have many enemies animating on
screen at the same time, it can slow your game down using sprite sheets with less
frames and controlling how fast we swap from frame to frame when animating
these sprite sheets is a simple and
performance friendly way how to deal with that chal***ge.
14. Player animation: I wanted to change the color of projectile lasers to,
for example, gold. If I set Phil style like that, it changes the
entire canvas state. Now all shapes on canvas that use Phil will use
that gold color. I changed the width to four
pixels, maybe just three. Yeah, that looks better. I want to restrict this Phil style only to
this Phil rectangle. Cole, I will wrap this code block between
safe and restore methods. That way any code
that sits outside this area will not be affected by this fill style
lasers are gold, everything else stays white. If I go back here to the beetle morph class
and I set lives to three, I have to hit each enemy
three times to destroy it. Each one rewards us with
three score points. Everything is
working as intended. I will set it back to one. I have a special plan for enemies that have
more than one life. Because I want to draw and animate that damage
step by step, each time we hit them
to reduce their lives. We will get to that part later. It's time to draw and
animate the player. What I want to do is
for the player to react when it shoots
a laser projectile. Later I might do also
advanced attacks. I'm thinking something
like this. And this. Notice how the player spaceship animates differently for
each different attack type. At the same time, I also
want to animate player jets, the propulsion rockets,
when we move left, right? And when we stand, the
propulsion rockets will animate to
reflect that motion. I want the shooting animation to react when we press
a hot button. And I want the jets to react completely
independently of that. When we press the movement keys, how do we make the player
animation react to two different independent
sets of inputs? Let me show you,
you can download all art assets in the
resources section. Below we will bring player PNG and player
underscore jets, PN G to the project
and give them IDs of player and player jets. Like this, I hide them
with CSS using their ID's. I save changes I just
made in all my files. Here we will bring in the main player sprite sheet
as this image property. And we point Java script towards that image
element using its ID. Inside our custom draw method, I can use built in
draw image method. Again, to draw the
spaceship on canvas. We already know that we need to pass it at least
three arguments. The image we want to draw and x and y coordinates.
Where to draw it? This will draw the entire
sprite sheet, all four frames. I adjust player width 240 pixels and height 220
to match the frame size. I adjust player speed to five
pixels per animation frame. We will need frame X property to navigate around the sprite
sheet horizontally. We don't need frame Y
for vertical navigation this time because this sprite
sheet has only one row. We also already learned that we can pass
draw image method, optional width and height. And it will squeeze the
entire sprite sheet, in this case all four frames
to the area of one frame. We want to crop out just
that one frame at the time. So we will need to use the longest version
of draw image method. I add source x, source Y, source width,
and source height, and the values we pass to draw image method
as these arguments will determine which area we want to crop out from
the source image. This frame would
be from coordinate 00 on the image to width
and height of the player, which corresponds
to the width and height of a single frame
in my sprite sheet. I like to create
my art assets at the same size at which they
will be displayed in game. Doing this, make sure your images are sharp
and detailed enough, but they are not larger
than they need to be. I want to navigate
around the spread sheet. I will again use source
x argument to define the horizontal crop in coordinate zero times the
width of a single frame. Is this frame one times
width is this area, two is this, and three is this. We will use some of these later, probably for super weapons
I want to implement. I will comment out
the white rectangle. We don't need it anymore
unless we want to display the exact boundaries of
player collision, hit box. I replace this with
frame X property. And now I can change which frame in players sprite sheet
gets displayed here. In this area, I will handle
sprite frames logic inside. I check if attack
button was pressed. In our case, we check if this
dot game dot keys array, which we are accessing through
didot game reference here. And it sits on the
main game object here. When a key is pressed, we add it to the array. When it's released, we
remove it from the array. If keys array
contains number one, which means we are holding down number one key
on the keyboard, frame X will be one. Frame X is zero, we are jumping between
these two frames as we press the basic attack
button. Let's test it. Actually, I will draw
the projectiles behind the player by swapping
these lines of code here. Now if I shoot, we get a
subtle movement and animation of the laser cannon on top of our intergalactic spaceship. I will use frame two for another special
attack a bit later, but we can test the
animation here. I made the last frame
for super laser attack. It doesn't really work well
with the basic projectile. We will probably make use
of all of these frames in a bonus section of the class
if you want me to do it. For now, we have just a simple
basic laser projectile. And player spreadsheet
reacts and animates. When we as a user press
the attack button, I will animate player jets,
the propulsion rockets. I want them to react to a
different set of inputs. Will animate depending on
the direction of movement, depending on which
movement key is pressed. The easiest way to do it
is to draw those jets as a separate image and we can layer it together
with the player. Let me show you what I mean. We already included
player jets image in index HTML and we
hit it with CSS. I'll reference it
here with Javascript. As this jets underscore image, I copy this draw image code and I'll also draw jets
image like this. It looks like Javascript
can't find the image. Let's check in index HTML. Oh yes, I gave it an idea
of player underscore jets. Now I'm drawing this frame. I will create a property. I will call for
example, Jets frame. It will serve the
same purpose as frame X does for the
players spread sheet. We will use Jets frame for horizontal navigation
around the spread sheet. I will use it here inside
source x argument. Now jets frame is zero
and we see this frame. When jets frame is one,
we see this frame. And when it's two,
we see this frame. We will start from jets
frame one because that's the middle basic state
inside the update method. We are already keeping
track of arrow left and arrow
right key presses. We expand this code block here. When we move left, jets frame
will be zero. I test it. Yes, L. If we move right, jets frame will be two
left, right, left, right. Yes, I will also add L statement if arrow left or
arrow right are not pressed. We set jets frame to one. Nice, this is one way
how you can animate a character to react to multiple independent
sets of inputs. Just split it into multiple
layers that react separately. What we did here is also
great for performance. We only have a few
animation frames, but it feels very reactive and animated because it
responds instantly. If you are coding along, you can test the gameplay
yourself and see how it feels. Do you prefer when I do complex animation
sequences like I usually do for my
other games with 30 animation frames per loop? Or did I convince
you to sometimes try this more performance
friendly animation technique that uses only a minimal
number of frames? I will create a larger wave
two times eight enemies. And I will let it trigger a game over by depleting player lives. Notice that when enemies hit the player and they
are destroyed. They don't play the explosion
animation as they should. We handle collisions between
enemies and the player here, I don't want to immediately mark that colliding
enemy for deletion. I delete that line of code
and I will also scrap this. I don't want to reduce score when player collides
with an enemy. I will instead just set enemy lives to zero when
they hit the player. This will trigger the code
block in the following loop. Now I have another problem. Because hitting one enemy
takes all three player lives, because we are colliding with that enemy that
already has zero. And it reduces one player
life per animation frame. That enemy has zero lives, but it is still in there. It's animating the
explosion sequence and we are colliding with that. I only want to check
collision between player and enemies if that enemy object
has more than zero lives. Now enemies are animated and each enemy collision just
takes away one player life. I can press R to
restart the game. I will increase the number of projectiles in the
object pool to 15, so we don't run out So often I will slow down
the sprite animation, maybe serve the next
animation frame. Once every 150 milliseconds here where we handle
loose condition I use or operator and I
say if this game dot player dot lives is less than one trigger
game over from here. And in that case we
don't need this code. I also don't want
the enemies to be deleted when they touch the
bottom edge of the game area. I also delete this. I said the initial enemy wave
to one times one. Up here on the player class, I will create max
lives property. It will be, for example, ten. Inside the draw
status text method. I copy this for loop that draws lives and I will draw
max lives first. Maximum potential lives will be only empty stroke
rectangles like this. I make them larger,
maybe ten times 15 pixels here as well. 20 pixels spacing
here. And here. I want the line width
to be only one pixel, which is also the
default value on canvas. I don't need to
declare it at all, and this is the visual I wanted. When we defeat a wave, we get a bonus life and stroke rectangle turns
into a field rectangle. We can easily see
we currently have five out of potential
maximum ten lives. I also need to go
here to line 247. When a new wave is triggered, we will only give the
player one more life if players current lives are
less than players max lives. We implemented many features and learned a lot
of new techniques. We have a fully
playable game with increasing difficulty as
large and large waves come. As we progress through the game, what should I implement next? Boss battles, super
weapons, enemy types. How about enemies that have multiple lives and
multiple damaged states? Let me know in the comments if you code it all the way here, and what bonus features
you would like me to implement next from
the ones I suggested, or you can give me
your original ideas. Are there any features in the original Space
Invaders game that I forgot about and you would
like to see in our version? Click you like if you
found some value. And check out the video
description if you want to make more
games with me. A.
15. Extra features: Armored enemies: What makes enemies interesting in a two day game like this, It can be things like
their behavior properties and also their visuals. Different combinations of enemy
types in our games should create different situations and chal***ges for the
player to deal with. Today, we will
design and implement a unique enemy type and we will make it a
part of our game. Okay, it looks like we are
somewhere deep in space, exploring the interstellar void similar to the Kiper Belt
in our solar system. This Unknown part of space
has a massive asteroid belt. These asteroids are mostly just solid chunks
of ice and dust. It seems these alien life forms are well equipped to survive in the vacuum space and they have a thick excess
skeleton to protect them in case of collision
with an asteroid. Interesting, how are we
going to get past them? I will calibrate our lasers
to a higher frequency. It should be able to
crack their shells, but it might need
multiple well aimed hits. Perfect. Let's do it.
16. Rhinomorph enemy class: I created four visual variations for the new rhinomorph
enemy class. If you take a close look
at the sprite sheet, you notice they all get
damaged in different ways. My goal is to basically connect their sprite
frame to their health. Let's say this column in the sprite sheet is the
enemy on full health. It will have four lives. When we hit it once, it
will display this frame. This is the enemy on two lives, one life, and the final hit
will play this animation. As usual, you can download all art assets in the
description down below. If you are just joining me
for this project right now, part one is linked in
the description as well. I put rhinomorphPNG file
into my project folder. I include it here in
my index HTML file. And I give it an
ID of rhinomorph. I use its ID to hide it with CSS because we only want to draw it with Javascript on canvas. Later we already learned about subclassing
in Java Script. We know that enemy
is the parent class, also called a super class. This class contains
all properties and methods shared among all
different enemy types. Then we use the special
extense keyword create Beetle Morph child class, also called a sub class. This class contains
only properties and methods specific only to
a particular enemy type. For example, if we hit Beetle morph enemy
with a projectile, we are triggering a hit method, but the Javascript will not find that method
on this subclass. The extends keyword sets up the standard
Javascript prototype chain behind the scenes. Javascript knows that if it can't find a method
on the subclass, it will go automatically look
for it on the superclass, on the parent class, it will follow the
prototype chain until it finds the method. That particular
hit method will be found here We have
all the logic in place and we understand how parent and child classes
in Java script work. Let's use that
knowledge to implement another enemy type with unique
properties and visuals. I copy this code block and I will rename it to Rhinomorph. We point it's this
dot image property to that new
rhinomorphspriadsheet we just included in
index HTML file. This spreadsheet has
012345 horizontal frames, max frame will be five, frame Y will stay
the same because this spreadsheet
also has four rows, same as beetle morph. Rhino morph will
have four lives. We will need to hit it
multiple times to destroy it. Important thing here is
this max lives property. It is created when we create this enemy object
and it always holds. And it remembers the initial
value of this dot lives even as the game is running and the enemy Rhinomorph
has currently, for example, only two lives because we
already hit it before. This dot max lives
property will always hold. And remember that
initial value of four, that maximum value, we will
need all of this in a minute. So try to remember this please. Okay, so we have a new subclass. How do we add this new
enemy type into the game? I go down here to our
custom wave class. This class creates a group of enemies and it organizes them in a grid like formation down here inside create
method on wave class. First I will just
check if everything works by replacing beetle
morph with rhinomorph. Nice, we can see Rhinomorphow
what happens if I hit it? We gave it four lives, so I need to hit
it multiple times. And when we deplete
all its lives, its remaining animation
frames will play. Currently, this
enemy type is using the same logic as the
first simple enemy. We hit it over and over
until we deplete its lives. And then the rest of the
spread sheet will play. I would like the spreadsheet to swap frames every
time we hit it, to show the gradual
damage we are doing to it by our high
frequency lasers. To do that, we need to learn one more thing about
extending Javascript classes. These are my subclasses
and they both extend this enemy superclass
when we hit an enemy, this method from line
hundred and 27 runs. I want the things
to stay the same as they are for the
beetle morph class, but I want to add
some more logic here for the rhinomorph class. One thing I can do is to copy this method and to add
it here on rhinomorph. That way when it method is
triggered on rhinomorph, Javascript will find it
method directly on this class and it will not go looking for it up on the enemy
class anymore. Basically, I'm overriding
that method for beetle morph. It will still stay the
same as it was before. There is no hit method
on this subclass. Javascript will still
be referring to that original hit method
on the parent enemy class. For Rhinomorph, we
are overriding. Original hit method
here on line 154. Now I can just put any additional logic inside hit method on rhinomorph class. This logic will only apply to
this particular enemy type. I want to somehow attach and
connect the frame number on Rhinomorphspridesheet to the current value
of enemy lives. When we hit Rhinomorph, we will set frame to max lives
minus its current lives. Max lives is four. As we know, we hit it once. Four minus three is one. Frame X is one. We hit it again. Its current lives are 24, minus two is two. Frame X is two. We hit it again. Current lives are 14, minus one is three. Frame X is three. I hope it makes
sense. I test it. I hit it once. We see this
frame. I hit it again. We see this frame, I hit it again. We
see this frame. I hit it one more time. And the rest of the
spreadsheet will run perfect. I played the game for a
while and you can see we get randomized four variations
of rhino morph enemy class. And they all get
damaged step by step. As we swap spread sheet
frames by hitting them, I might implement other
weapon types later. And this damage value might
not always be an integer, it might not always be
one damage per hit. This damage value
has decimal points. It would actually break this logic that
swaps sprite frames. To prevent that, I will
wrap this dot lives in math dot floor to round it down to the nearest
lower integer. And now we are safe even
if we have some kind of a laser weapon that
deals damage in smaller or larger
values per one hit. Even in that case, the frame swapping logic
here will still work. Now I had fun creating
the sprite sheets, and I was trying to come up with four different ways enemy shells can crack as we damage them. Step by step, we implemented
this new enemy type, and probably it was easier
than you thought it would be. I would like to hear
your design ideas now. Can you come up with
another enemy type with a unique
feature if you want? You can also give that
enemy type a name. If you come up with
something really good, I might actually design it, implement it, and I will release it as a bonus episode
in this class. Now I want the waves
of enemies to be made out of some beetle morphs
and some rhino morphs. And I want to be able to control what percentage of the wave is made out of which enemy type. To do that, I go here inside
our custom wave class. Here on line 193, we are pushing
only rhino morphs. Right now I will do this
method random trick again. We already used it to randomly
decide if the wave will first start moving to the left or to the right
when it's created. Again, method random called
on its own, like this, will return a random value 0-1 The full definition
is that method random returns a floating point
pseudorandom number that is greater or equal
to zero and less than one. If I create an expression that says method random is less than 0.5 it will be true in
roughly 50% of the cases. 50% of the wave will be
made out of rhino morphs, else meaning the other 50% it
will create beetle morphs. Now if I play the game, we are getting both enemy
types and they should be distributed somehow,
equally 50, 50. Of course it's random, we'll always get more of
one or the other, but overall it will
average out to give us almost
equal distribution. Let's say I want only a
20% of the enemies to be rhino morphs and the remaining
80% to be beetle morphs. I would use 0.2 here. This gives us a
very simple way of controlling enemy type
distribution in our game. Adding an enemy type that works slightly differently makes our game a little bit more interesting to explore
for our players. What else should we do to
improve this game even further? What other features
should we implement? Give me your creative ideas down below and
help me design it. See you in the next part.
17. Extra features: Boss battles: I have no data on this creature. Its shell is pure tellurium
polymer, impossible to scan. There are no records
on this species. No one who meets this creature
survives to tell the tale. I'm trying to get more data. In the meantime,
the only strategy we have is raw firepower. Hit it with everything we
have before it hits us. And let's try to avoid these
massive tellurium claws. Something tells me they would cut through our
shields like butter. Yes, any close contact will
result in immediate shields, failure, keeping our
distances imperative.
18. Boss class: When writing custom code
for a project like this, the most important
thing is to always be aware which code
runs just once. Which code block runs when
a specific event happens. And which code
runs over and over for every animation
loop in our game, the enemies come in
larger and larger waves. What if I want every third wave to be a randomized
boss encounter? And as the game goes on, I want the bosses to have
more and more health. Gradually increasing the
chal***ge for the player. You are watching the
full extended version. The full source code
from each stage is included in the download
section down below. If you are watching a
free Youtube version, don't forget to click
the like and let me know you want more
projects like this. As usual, I start by
importing the art assets. You can download boss PNG file in the resources
section below. Place that image inside
the project folder and link it in index
HTML file like this. I give it an idea of boss, we hide it with CSS. If you inspect the spread sheet, I created four different
boss creatures. The spread sheet is
organized like this. The first two frames will play
when we hit the boss with a projectile boss will move a little bit to
react to being hit. When we deplete all
its hit points, all its lives, the rest of
the spreadsheet will play. I hope you like the
pop animations I made. I was having a bit
of fun with this. Down here on 960, I create a custom boss class. Constructor will run only
once to create a boss object. Every time we call this
class using the new keyword, we will be pointing to the main game object
as we did before. To get access to all properties and methods that sit on there, I want the boss to be much
larger than regular enemies. Width and height will be
200 times 200 pixels. I want the boss to start exactly in the middle of
the screen horizontally. This game dot width times 0.5 minus half of the
width of the boss. At first, it will
be hidden behind the top edge of the
visible game area. The vertical y coordinate
will be minus dot height. Horizontal speed,
speed x will be randomly minus one or plus one. To do that, we use
ternary operator that evaluates mathrandom
expression like we did before. If mathrandom is
less than 0.5 in approximately 50% of the
cases speed x is minus one, the boss will start
moving to the left, else the boss will
move to the right. Speed Y will be zero. Initially, boss will always have let's say ten
lives at first. The next boss will
have more health. We will increase lives each time we create a new boss wave. I will also give it max lives. That property will remember the initial dis dot lives
value so that I can award the player the
appropriate amount of score points when
the boss is defeated. Marked for de lesion
will be false. At first, this dot
image will point to the sprite sheet I have that contains four different
boss creatures. Frame X will handle
horizontal sprite navigation. We will start at frame zero. So the left column
frame Y will determine which one of these
four bosses we will see it will handle vertical
sprite navigation. Each time we create a new boss, I want to randomly pick
one of these four. Frame Y will be a
random value 0-4 We use Matt floor here to round it down to the nearest
lower integer. We do that because there is no row 1.5 in the sprite sheet. For example, we
want only integers. Now this expression gives
us either zero or one, or two, or three. We need to know the maximum
horizontal frame to check if the entire explosion
animation finished playing, and we can remove the boss
object from the game. I will set max frame to 11. This reminds me that I forgot to do one thing in
the previous lesson. Down here in the new wave
method on the main game class, we push new wave object into this dot waves array whenever the previous wave of
enemies is defeated. If I consolo this
waves and I play, I defeat wave one, wave two, wave three. We can see in the
console that didot waves array is
endlessly growing and the old waves just
stay there even though they don't contain any
active enemies anymore. To fix this, I go up here
to our custom wave class. I give it a property called
marked for deletion. Initially it will be set to falls inside render
method on wave class. We know that this method runs
for every animation frame. Moving the wave around the
game area and filtering out destroyed enemies out of
its distort enemies array. When the player
destroyed all enemies and didot enemies,
***gth is zero. Are no enemies
left in this wave. We will set marked for deletion on this wave object to true. When this wave
object has enemies array that contains
zero enemies, it means we defeated all of them and the new
wave was created. We want to delete this old wave down here where we
create a new wave. We will filter out
old waves every time we add a new
wave to the game, check this dot waves
array and we filter it. Only those elements that have marked for
deletion properties set to face are allowed to
stay in this dot waves array. If they have marked for deletion set to true, they
will be removed. Now I play the game and I
only ever see two waves, the old one and the new
one that was just created. Actually, if I move the consolo
here and I play the game, we can see that this
dot waves array only holds one currently
active waves object. I hope this makes sense. Any questions about this,
you can leave a comment. Let's go back to the boss class. It will need a
custom draw method to draw the boss in game, and update method to move it around to define its behavior. We have a sprite sheet with multiple frames we
want to swap between. Which means we will need the longest version
of draw image method, a version that takes
nine arguments. The image we want
to draw source x, source y, source width, and source height of an area. We want to crop out from
the source image as usual, destination X, destination Y, destination width, and
destination height. To define where on
destination canvas we want to place that
crop out frame onto. I want to crop out
top left frame from coordinate 002 with height. I want to place it on
canvas at position x dot y dot dot height. Like this, the boss will start entirely hidden behind
the top edge of canvas. I want it to flow into view
until it's fully visible. If this y is less than zero, this dot Y plus equals ten
pixels per animation frame. This is a good basic
setup for the boss. Now let's actually draw it. So we know what we are working with down here on
the main game class. We know that all the code we put inside a constructor
is automatically executed at the
point when we create an instance of that class
using the new keyword, I am automatically
pushing wave number one, the first group of
enemies, into the game. As soon as we created the game, I delete this For now. I will create a boss array that will hold all currently
active bosses. We will probably only have
one active boss at the time, but we could easily have
multiple ones if you want to make the game even more
chal***ging for our players. Down here we have a
custom restart method that runs when we
get a game over. And when we press letter
R here you can see I'm just setting everything
back to the default values. The problem is that
if I want to make any changes to any of this
as I develop the game, I need to make the
same changes up inside the game class constructor
to those properties. And also down here in the restart method
to make sure it's consistent with the
time when the game first starts and
when it restarts. I could avoid having
to do that by actually calling restart method
from the constructor, meaning that it will reset all the values as soon
as the game starts. Maybe we could also call that method something
like initialize. Either way, now that
I've done this, these initial values of 11
here don't really matter. Because here, even
before the game starts, restart method will set
columns and rows two to two because those
are the values we use the down here when
we restart the game. We also need to set boss array to an empty array like
this to clean up. Now because we know that the restart method
runs immediately, we can put any code in
there that we want to run when the game starts
and when the game restarts. I'm already
automatically pushing the first enemy wave here. Let's also use our new
custom boss class to push one new boss object
into the boss array.
19. Boss movement: I need to go up here inside the render method and we need to call its draw
and update boss array. For each, for each boss object inside boss array
call its draw method. We know it expects context
and also call its update. Nice, we have both enemy
wave at the same time. Now we know that we can go down here inside the restart method. And maybe we want to start the game with a wave of enemies. Or maybe we want to
start with a boss wave. Which is what I will do now. Because we are building and
testing our boss logic, I can change this value to
make the boss float into view. Faster or slower. You can decide your
own speed value. Here I will go with four
pixels per animation frame. I want to give the
boss the same left and right movement pattern we
gave to the wave of enemies. If horizontal x coordinate of
the boss is less than zero, meaning if the boss is touching the left
edge of game area, or the horizontal position
of the boss is more than game width minus
the width of the boss. Meaning that the boss is touching the right
edge of the game area. In both of these cases, we swap speed x value from
line 167 to its opposite, which will make the boss
bounce left and right. Now I just need to increase this dot x by speed x for
every animation frame, I will also increase
dot Y by speed Y. Nice. We have a boss
that floats into the view and it bounces
left and right. I also want to
give it speed y of 200 when it touches either edge. But that means the boss will
just rush forward like this. I want that speed y property to increase only for one animation
frame for a quick jump. Then immediately we
set it back to zero. All of this movement stuff
is exactly the same logic we used for wave motion
before. Now it works. I will make the
vertical jump equal to half of the boss's
height. Like this. We can always see only
this frame because I'm dropping from 00 to
width and height. If I want randomized
row instead, I use source y
argument for that. Zero times to height is
this frame one times, height is this frame
two times height will give us this
version of mantis morph. Three times height will
give us this deep space, lower crafty and monster. We already prepared frame Y
property for this purpose, on line 174, let's use it here. Source will handle horizontal
sprite navigation. It will be frame x from
973 times width from 963. For example, if frame x is five, it will give us this frame. If you got this far
in this course, I think you understand
this part already. So let's just move on
whenever we reload the game or create a new boss using our custom boss class, we will randomly get one of these four boss
creatures perfect. The first boss will be easy, it will only have ten lives. Let's actually draw boss lives. We can choose to do it as a
health bar or as a number. I will wrap this
code between safe and restore to make
sure any changes to canvas state I make here
are limited only to this area will call built in fill text method which takes
at least three arguments. The text we want to draw
this dot lives from line 169 and x and y of an anchor point in relation to which the
text will be displayed. Let's start with x
and y of the boss, which as we know is the top
left corner of the boss. Hit box rectangle. I actually want to move
the text a bit down, maybe plus 50 pixels
vertically and horizontally. Let's try the width
of the both times 0.5 Doing this put the text anchor point
exactly in the middle, but by default the
text is left aligned. From there, I will use Text Align property
and I will set it to center because we wrapped this code block between
safe and restore. That means it will
only affect the boss lives and not any
other text we are drawing on canvas to make the white text more visible
against the background. I can also give it a shadow
of set X three pixels, shadow of set Y three
pixels as well. And shadow color can be black. Now that we see both lives, let's set up collision
detection between both and projectiles inside
update method. On both class, I will
take projectiles pull array that sits on
the main game class. I call for each method on it. For each projectile in
projectiles pull array, I will check the following. If check collision method
between this boss object and the projectile we are currently cycling over
with for each method, if the collision check on
the rectangular hit boxes returns true at the same time
double percent operator. I only want to check
if the projectile is active because
projectiles come from a re, usable object pool only if free property on
projectile is false, meaning the projectile
is being used. It's active in the game. One more check if lives of
the boss are more than zero. Because when we
depleted both lives and the explosion animation
frames are playing, I want the new
projectiles to ignore that and fly through it,
not collide with it. Only after all these
three checks are true, both and projectile
are colliding with an active projectile and Bos
has more than zero lives. We reduce lives of
the boss by one. We will also call reset
method on the projectile, which means the
projectile object will become inactive and it will be returned back to
the object pool. I test it. Yes, when I hit the boss, we deplete its lives. This
is going really well. Instead of reducing lives
directly like this, I will create a hit method that will take damage
value as an argument. And it will reduce
lives by that damage. I want the boss to
move and animate a bit and react to being hit. We will swap between frames 0.1 only if lives
are more than one. When hit, we will set frame X to one displaying this frame. Now I use it method here and
I pass it damage of one. Okay, that still works. We hit the boss and we
swapped frame to frame one. This one now I want it to return back to the
original frame zero, but we want to wait for sprite update flag to be true
to slow the animation down. Same as we did with enemies. Also, we only return to that frame if boss lives
are more than zero, because when they get to zero we want to play the
entire animation role. Nice. Now if we hit the boss, as long as boss lives
are more than one, we swap between frame 0.1 to make the boss wiggle
and react to be in hit. If I reload and I get a
different boss creature, we can see it moves
in a different way. I gave each boss a saddle way to react to being hit like this. It helps to give the player more visual feedback to make
the game feel more alive. I think we are using
basic static images, but they react to
inputs and actions. Sprite sheets have only a
small number of frames, but everything is reactive. This is one of performance friendly ways to
animate your games. I will say more than zero here. Actually, when both
lives reach zero, we want to animate the rest
of the sprite sheet where the boss is getting
inflated and destroyed. If the lives of the
boss are less than one. If sprite update flag is true, we will start slowly
increasing frame eggs by one traveling through
the sprite sheet horizontally. Let's test it. I hit the boss ten times. Nice. It plays the animation. You can see that the
boss is still there. It's just showing a
blank sprite frame. Actually, I don't need
to draw zero lives. We will only draw lives if
this lives is more than zero. Now we destroy boss. We are no longer
drawing its lives, and projectiles no
longer collide with it, but the invisible
boss is still there. Moving around the screen, we need to remove
it using Marked for deletion property we
defined here on line 171. As we update horizontal frames, frame X is more than max frame. We set max frame
to 11 here on 975. At this point, we know that
the boss was destroyed and that all animation frames of
explosion were displayed. We know at this point
it's safe to remove the boss object from
the game entirely. I will set it's marked for
deletion property to true. I will also increase
game score by this max lives which holds
the initial number of lives, the boss head in this case ten boss with ten lives will
award ten score points. When defeated, we set marked
for deletion to true here. And I go down to the
main render method and I will filter
out boss array. This line of code runs for
every animation frame, which is a bit of a waste. To be honest, we
could easily just put it somewhere
else, for example, into new wave method and only clean all the boss
objects periodically, we don't have to check it
for every animation frame. We already covered how filter method works
and how it filters out all the objects from
the array that have marked for deletion
property set to true. Let's see if it
works by consolo. In this boss array,
we have one boss. Inside the array, we
deplete all its lives. Explosion animation plays. Now I can see in console
that boss array is empty. Great, I delete the consolo.
20. Boss vs player collision: Now we need to decide
what happens if boss gets all the way down and
collides with the player. I check if custom
check collision method between this boss object and player object returns
true at the same time. Only run this code when boss
has more than zero lives. Because there could
be a scenario where player collides with
explosion animation frames. Which would be fine because the boss was destroyed already. We don't want to register
collision in that case. Inside I set game over to true. We need to destroy the boss
before it touches the player. Otherwise, its claws goes
straight through our shields. The boss is very dangerous. At the same time, I can
set lives of the boss to zero to trigger the explosion animation sequence if I want to. Let's say our shields will also destroy the boss. I don't know. Come up with your own
story. Let's test it. I will speed this up. When the boss
touches the player, we get game over and
the boss explodes. That worked perfect. Another loose condition would
be if boss gets all the way down here and touches
the bottom of the screen. If vertical position
of the boss plus its height is more than
the height of the game. Meaning that the bottom
edge of the boss touches the bottom of
the visible game area. We set game over to true. When I destroy the
boss like this, I want the game
to continue and I want regular waves of
enemies to keep coming. Boss was destroyed here. If game over is false, we call our custom new wave
method. Let's test it. I destroy the boss, we get a wave of enemies. I destroy all
enemies in the wave, I get another larger wave. This is a pretty good
game loop already. Isn't it done if you follow
it all the way here? Every time a new wave comes, we are increasing
wave count value and we are displaying it. Here we are calling
a new wave method we defined on line 393 here. Every time wave count is divisible by two
without any remainder, for example, four
waves, 02468, so on. We will push a new boss, else we will create a regular, increasingly large
wave of enemies. I have to be careful
about the brackets here. I say this boss
array, push new boss. I passed this to point to the main game object we
are inside of right now. I don't want to increase
wave count here, I deleted. Instead we increase wave count here inside new wave method. That way both boss and enemy waves will increase
wave count value. Now let's test it. We start the first wave with a boss as we defined
inside restart method. On the first load
we defeat the boss. Wave counter increases to two, which is divisible by two
with a remainder of zero. This block runs and
we get a boss again. We defeated wave is three. This is false else
statement on line 396 runs and we get
a wave of enemies. Four is divisible by two
with a remainder of zero. Now we get a boss. You can change how
often you want to get a boss by adjusting
this value here. If you want the first wave wave zero to be boss or enemies, we define that inside
restart method here. When the explosion
animation starts playing, I don't want it to bounce from the edges anymore because
that looks strange. If explosion starts playing, just let it flow off screen
if it's close to the edges. One more condition here that says if this dot lives
is more than zero, only do the regular left
and right bounce in motion if the boss is not
animating the final explosion. Now we are getting closer to the edge and when
I shoot the boss, the explosion doesn't
bounce anymore. It just flows and continues in the direction the boss
was already moving. I think this looks better. I notice that when we
defeat a boss wave, we don't get a bonus
player alive to fix it. I cut this line of code, I paste it down here. Inside a new wave
method, I defeat a boss. Yes, I get a bonus life Good. Every time we create a new boss, I want it to have, let's
say five more lives. To make the game gradually
more and more chal***ging. I will store boss lives value here on the main game object. We will start with ten lives. When we restart, we also
need to go back to ten. I will pass boss lives as the second argument to boss class constructor for the boss in the initial first wave and then for every other
consecutive boss we create, I will make sure that value
is expected here on line 161. And we use it as the value for this lives property when we defeat a boss and it was destroyed before we
call the next wave, we increase boss
lives property on the main game object
by let's say five, making the next boss a
little bit more chal***ging. This boss has ten
lives, I destroy it. The next boss has 15
lives that worked. We have waves of
enemies that get larger and larger as
the game goes on. And we have bosses with
more and more health. Thank you everyone
who commented on the videos and helped
me design this game. If you have any more ideas and you want me to
add more features, let me know in the comments, If you come up with
something really good, I will do it and I will
release it as a bonus lesson. More enemy types, super weapons, larger super bosses
that actually shoot projectiles back at us. What do you want me to
do next? Let me know. It's more fun for me if
we design it together. As I play, I notice that the boss already loses some
lives before it's even fully visible on the screen from the projectiles that are
flying close to the top edge. Let's say I only want collision between boss
and projectiles to happen after the boss floated all the way to its
starting position. And it's fully visible
on screen when the vertical position of the boss is more
or equal to zero. Okay, I'm happy with this. What do you think? See
you in the next part.
21. Extra features: Super weapons: As we filled our game with
swarms of dangerous enemies, we need a better way
to deal with them. Time for super weapons. Let me show you something. I collected enough plutonium
to upgrade our reactor. I can focus its energy into a powerful column of light
and use it as a weapon. Sounds like things can
get very hot. Very fast. Yes, our solar shields will give us a source of
renewable energy, but be careful not to
overheat the cannon. The security lock will
disable the valve. If we surpass the maximum
safe temperature noted, we will manage our energy and avoid overheating.
Sounds easy enough. The laser can burn through multiple enemies
at the same time. It can destroy the
entire enemy wave in seconds if timed
correctly. Happy hunting.
22. 2 laser classes: In this Java script
game development class, we add resource management with a rechargeable energy bar. And we will implement
two more weapons, a long range laser
and a super laser. These new weapons will be fundamentally different
from our basic attack, because they will
hit all enemies in a column in front of them, even the ones all
the way in the back. It can be very effective, especially later in the game, when we face larger and
larger enemy waves. We will have the parent
laser class that will define the main properties and
methods of both laser weapons. I will extend it
into two subclasses, small laser and big laser. I do it because each
laser will have different size and it will
deal different damage. Those specific things will be defined separate
on each subclass. Constructor will need access to the main game object
to give us access to all important methods and properties we previously
put on the game class. From inside laser
class render method will draw and updated
the laser weapon. It will need context
as an argument. We will extend the constructor
on both subclasses. Let's just first
do everything on a small laser subclass
to keep it simple. When we create an
instance of small laser, we want to run all the code inside its parent
class constructor. We know that the parent class can be also called a superclass. We call superclass
constructor like this. And we know it expects a
reference to the game object. I pass it here like this. It is expected here. And it gets converted into this
dot game property. Only after we run the code
inside Superclass constructor, we can use this dot
here and define properties specific only
to small laser subclass. We will do that in a minute. I also want to extend, I don't want to
override it this time. I want the code from inside render from line
five to actually run. This code block contains
logic shared for both Small Laser and
Big Laser subclasses. After that, I will add a
little bit of code that is specific only to
Small Laser subclass. Here. The syntax is
a bit different. It's like this. Here, I'm
calling superclass constructor. Here, I'm calling a method
that sits on the super class, on the parent class. I hope it makes sense. Constructor is a
special type of method. That's why the syntax
is different here. In these two cases, even
though both are just calling a method on
the parent class, there is a reason
why I'm putting render method on the subclass, but we will get to that later. Laser will need x and y
width and height properties so that Javascript knows
exactly where to draw it. Also, we will be checking
collision detection between the laser beam and the
enemies and bosses. We know that all
objects involved in collision detection need x y
with and height properties. X, y and height can stay
on the parent class, but each laser type will
have a different width. I have to put it on
the subclass here. We want the big super
laser to be much wider than the small
laser, the small one. Let's try five pixels. The players sprite
sheet will animate. With these weapons,
we will get width to match the spaceship
sprite in a minute. This, the horizontal coordinate of the laser will be
moving with the player. As the player moves
left and right. Initially I set it to zero. Vertical y coordinate on both lasers will
always be zero because I want the laser beam rectangle to start from the
top of the screen. The height of the laser will
always be the same as well. I want the laser
beam to come from here to somewhere around here. I guess from coordinate zero height of the
laser beam will be this dot dot height minus let's say 50 pixels to
cover this area. We know that all the code inside constructor is
executed only once. At the point when we create
an instance of the class, using the new keyword render method will
be very different. The code inside
render method will run over and over for
every animation frame. We have to keep
that in mind when we decide where
to put the logic. When the laser is active, I want its horizontal
coordinate to keep updating to match the
position of the player. Let's just start with
this and see what we get. Now I will draw the laser
beam as a simple rectangle. I will give it two colors. I will wrap this Cod
block in safe and restore built in canvas
methods to make sure that these fill
style colors affect only the laser beam and not the other shapes
drawn on canvas. I said it's fill style to gold built in fill
rectangle method. We'll draw the laser. I just pass it x, y, w from here and height. If any of this
doesn't make sense, leave a comment and
we can talk about it. I hope it's all clear. We have the basic version
of small laser. Let's draw it so
we can see it in game and we can tweak
it and adjust it. I will create an instance
of small laser class here. It will be created at
the point when we create an instance of the player
class using the new keyword. I could have also put small
laser on the main game class, but I think it makes more
sense here since it will be so closely tied with multiple properties
on the player. Here we are checking if key is pressed and we are
swapping player frames. I'm going to create an LF block. I have to be very careful about
brackets when doing this. Single wrong bracket can
break the entire game code. If one is pressed, we do the basic attack. When two is pressed
we will trigger small laser player spreadsheet is already prepared
for this from before. I just need to set frame
X to two like this. We are inside a
draw method which runs over and over for
each animation frame. As long as key two is pressed, we will be displaying
frame X two and we will be calling render on small laser
drawing it and updating it. As soon as we release
the key frame x will be set to zero and we stop
rendering small laser. If we press number one, we do the basic attack. When we press number two, we draw small laser. It moves around with the player. I want to move it to the
middle of the player. I adjust the code on line nine. Position of the
laser is position of the player plus half
of the player's width. Now it's almost centered. To center it completely, I have to offset it by a half
of the width of the laser. Now it's exactly in the middle. As you can see, when we
shoot the small laser, the arms of the space
ship move inwards to help focus the thin
laser beam in a small area. And the small muzzle
jumps a bit backwards. I will set laser
width to 50 pixels, just so we can better see it. And I will give it
additional color because the middle of
the laser is so hot, I want it to appear pure white. I copy this code block, I set it to white. It will be let's say 60% of
the width of the laser beam, which leaves us with 40% to center the white beam in the
middle of the gold beam. We need to push it
by 20% of the width, the left edge, pushing it
exactly in the middle. Now the middle of the
laser beam is white, hot. I think giving it two colors
makes it look much better. Since we used relative values, the white beam will be
centered over the gold beam. Regardless of laser width, ten pixels is still too wide. I want the width of the beam to match the muzzle
of our laser gun. Five pixels seems good.
23. Laser damage: Each laser subclass will
also have a damage value. This will define how much damage to the enemies it
will do per tick. Let's actually
implement that damage and hit enemies with laser. Right now, it still doesn't
interact with them at all. I can put the following logic in multiple different
places in my code base. I decided to put it inside
the render method on laser class because
I like to keep the code organized and
easy to navigate in. It makes sense because I
only want to check collision between laser and enemies when we are actually
drawing the laser. If you remember, we have
an array that holds all currently active
enemy waves in our game. There's always only one
active wave in that array. But you can make the game
more difficult and release the new wave before the
previous one is fully defeated. For example, each wave object inside waves array has
enemies array inside of it. As we defined here on line 280, that array holds all currently active
enemies in that wave. To cycle through
all active enemies, I will cycle through
waves array. For every object in waves
array, there will be only one. In our case, we access
its enemies array, also call for each on that, for each enemy object
in that array, we call our re, usable check collision
method we wrote before we pass it enemy
and this laser object. If enemy and laser collide, I'll call hit method on enemy And I pass it this
damage of the laser. We defined it here on line 32. Before I test it,
I will also cycle over all bosses
inside boss array. Again, there is usually
only one active boss, but if you want, you can push multiple bosses
at the same time. Or you can push
the next boss when the previous one has only
a few hit points left. Leave the game design
details on you. I'm giving you all the tools, Design your own game for
each boss in boss array. We also call check
collision method between that boss and
this laser object. If they collide, we
call hit method on the boss and we pass it damage of the laser
as an argument. We have our render method
which will run over and over as long as we are pressing number two
key on our keyboard. When I test it, you will notice the laser
is very powerful. It's fun to be able to
destroy all enemies easily, but it takes all the
chal***ge away from the game. We want to give the player
fun, powerful weapons. But we don't want to make
them overpowered like this. The reason it's so strong
is that it hits enemies for 0.3 lives per
animation frame. And we are running this at 60 frames per second
or even faster. I want the laser damage to tick slower and I want to
make sure the damage is same or similar for
people on old computers and people on new
gaming computers with high refresh rate screens. We already have a
variable called sprite update that we set to true only for one animation
frame every 150 milliseconds. We are using it to
slow down how fast we cycle through animation
frames in the sprite sheets. Here we can use the same
variable to slow down how fast the laser ticks when
it burns through enemies. Only when this dot game
sprite update is true, only once every 150
milliseconds or so. We want to check collision
and actually hit enemies and the boss with
our long range lasers. Now we can see that the
laser has a bit less power. I think this is so much better. We can always tweak
this by adjusting this dot damage value on
small laser subclass. Until now our basic attack was hitting for
one life per hit. But now we have lasers that can deal damage is not exactly one. We can end up in a
scenario like this, when the boss has
less than one life, it should already having
0.8 lives. Doesn't count. In this game, I want the boss to explode as soon as its
lives get below one. I go down to the boss class and I need to look
at this code block. It says if lives
are more than zero, which means that has 0.1 health is still
considered a live. I will change it
here, here, here, here, and here in
all these places. I replace that check
everywhere with this dot, lives are more or equal to one. Make sure you change it
in all these places. Here. Also here.
Sorry about this. Now let's try to use
small laser on the boss. I will slowly burn through
its tellurium shell. And notice that as soon as we get below one life,
the boss explodes. Perfect. We can adjust the
damage of the laser here. Good, It still works well. I put it back to 0.3 I think that's the right
value for the small laser. The big laser will be
much more powerful. Of course, we don't
want the boss lives to display so
many decimal points. I go down here to draw
method on both class. Here we draw both lives. I wrap it in Matt
floor to round it down and remove the small numbers that come after
the decimal point. Let's test it again. The boss should start exploding
immediately. As soon as it's
health drops below one and we should
never see zero lives. Yes, we are making
a lot of progress. Okay, super laser. I come here and I copy
all the code inside. I paste it inside. Big laser, subclass
width will be 15 pixels. Damage will be 0.7 per tick. More than twice the damage of the small laser on
the player class. We create an instance
of big laser like this. Then we can add one
more Lf block here. If key that was
pressed is three, player frame is three. Which will give us the
final frame where the arms open and make the room for
the massive laser beam. And also to make sure that the tips don't melt
from the heat. Big muzzle with laser
lines inside pops out to fire our ultimate
weapon, a super laser. We draw and update
the laser by calling its render method small
laser and big laser. You can see it deals
more than double damage. It's actually too small. I want the laser beam to fully
fill the cannon, maybe 25. Watch how the player animates
to match the weapons. Basic attack, small
laser and big laser. What do you think we can
destroy enemies easily? Now the new laser weapons are especially effective
against groups of enemies, since they damage
the entire column of enemies all at once. I want to put the player and the laser beam behind enemies. But in front of game text, I cut it and I paste it here
after we draw projectiles, but before we draw the boss, what gets drawn first is behind. In cases like this, when we draw everything on a single
canvas element, try to play the game for a bit, and let me know what you think. Do you have any
other weapon ideas, something completely
different than this? Like what if we send out
a swarm of small robots? Or how about electricity weapon?
24. Resource management: The game has become
trivial and easy. Now that we use lasers without limits and
actually there is no reason to use the small laser if we can use super
laser nonstop. It would be much better
if the player had a pool of energy
they need to manage. Before we do that,
I want to make sure the boss is only hit
when it's fully visible. I don't want it to have reduced lives already by the point
it floats on screen, we will check collision
between the boss and lasers only when the
vertical position of the boss is more
or equal to zero. Once the boss has fully
entered the game area, I test it. Yes, that works. Let's add resource
management mechanic. The player will
have an energy bar. Starting energy will
be, for example, 50 max energy will be 100. If we don't pay attention
to our energy levels, we can also overheat our laser which disables
it for a short while. I will call this property, for example, cool down. We will display the
energy bar down here inside Draw
Status Text method. Again, we will use
a four loop that will run once for
each point in energy. For each energy point, we will draw a rectangle. We will space them
out two pixels apart, vertically, I guess 50
pixels from the top. Each energy rectangle
will be only two pixels wide and
15 pixels tall. Since we are drawing them one next to another,
they create a bar. This value is the spacing
between the bars, and this is the
width of each bar. If they are the same value, we see one continuous shape. If the width is
less than spacing, we see individual rectangles. You can play with these values and it will make sense when you see how it reacts to
different values you give it. If I do 15 plus here, that will be margin from
the left edge vertically. I want it probably 130 pixels
from the top left margin. 20 spacing is two pixels, and the width of each
energy rectangle is also two pixels. We are getting one
continuous bar. Now when we shoot the lasers, it will be costing us energy. Up here I say this game player do energy minus
equals this damage. Which means that now small laser is cheaper than big laser, Sometimes small laser
is the better option. Now when I use lasers, you can see it actually
depletes energy. The rate at which it depletes the energy is equal
to laser damage. Small laser costs 0.3
energy per thick, Big laser costs 0.7 energy. I want the energy
to slowly recharge. If the current energy is
less than max energy, increase energy by 0.05
per animation frame, I test it, energy is recharging. If I deplete it, it will start
recharging automatically. Again, good, we need to prevent the energy to
go into minus values. Player has to manage energy
and watch the energy bar. If they leave it for so long that the energy gets below on E, it will trigger the cool down. We overheated the cannon
and now we have to wait. It's a small punishment
for not paying attention. If we want to play optimally, we never deplete the
energy below one. We never trigger the cool
down I can do else here, because energy can
never be below one and more than
max energy time 0.2 in the same animation frame. This will save us a little
bit of performance. If energy drops below one, cool down is triggered, the energy refills
back to 20% of maximum energy cooldown
is set to fall. Laser cannon had enough time to cool off and we can
use our lasers. Again, I want to make it visually clear to the player
when we entered a cool down, when we overheated our cannons down here where we
draw that energy bar, we will change its color. I will wrap this in safe and
restore to make sure these fill colors don't affect any other shapes
we draw on canvas. We are quite far
into this class. Instead of a standard
F L statement, I will use ternary
operator syntax here as a simple single line. If L, if player
cool down is true. Question mark, set
fill style to red L. Colon set fill
style to gold, easy. Let's test it.
Energy bar is gold. By default that means everything is all right
and we can use our lasers when I use my lasers
without paying attention to the energy bar and I
let it slip below one, we trigger the cool down
energy bar turns red, which means our lasers
are overheated and we can only use the basic
attack for a while. When we reach more than
20% of max energy. Now the bar changes back to gold color and we know we
can use the lasers again, I have a feeling we are still
dropping below zero energy. I can make this really
clean by doing this. Here is the parent laser
class and it's render method. We never call this
render method directly. We are always calling it from
one of the subclasses here. Let's make sure we only render the lasers when player
energy is more than one. At the same time,
if cool down is false only then we draw
and update small laser. I copy this and I do the same
thing for the big laser. Now for the reason why I'm
putting render on subclasses. So far these methods are identical and we know
when things are shared, they can sit on
the parent class. Subclasses contain
only properties and methods specific
to that subclass. If I deplete my energy and
I try to shoot the lasers, the player ship is still animating as if we are
shooting the laser. But since we are on low
energy and in cool down, the laser beam is
not coming out. It might be fine for your game. I'm not sure what you prefer, but in my game I only
want the players sprite sheet to animate
like this when we are actually shooting the lasers instead of swapping
player frame here. Here I delete these lines. We will actually swap players
sprite sheet from here. Which means that the
player will only animate these weapon attacks when we are actually rendering
those weapons. Those laser beams. Small laser is frame x two, big laser is frame x three. Same as it was before. Now when I'm in cool down, no laser comes out and players sprite sheet doesn't
animate that weapon attack.
25. Eaglemorph enemy class: We start by importing spread
sheets into the project. Place both images into
your project folder, and we reference them
here image with a source. Eaglemorphngid will
be Eaglemorph. We will also need
their spiky, slimy, mutated projectiles, which will come as a spreadsheet
of eight images. I call them Enemy Projectile. As usual, I will hide them with CSS on the first game load. Instead of pushing
the first boss. We will start with enemy wave. Because today we will be adding a new enemy type to that wave. I copy this code block we used to create Rhino
Morph enemy class. I rename it to Eagle
Morph and I point its image property towards that new spreadsheet we just
included in the project. I gave this enemy more
detailed explosion animation with some extra frames. Max Frame will be eight. We count from the frame
zero on the left on the wave class where we create each enemy wave by organizing
enemies in a grid. I add one more L if
statement block. I have to be very careful
about the brackets here. When I'm doing this, I want the most of
the wave to be made out of eagle morphs
because we will be implementing
and testing them. Let's say 80% will
be eagle morphs, 10% will be rhino morphs, the remaining 10% will
be beetle morphs. We are already
getting eagle morphs, and because we copied and re used the entire code block we used for rhino morphs before
already work quite well. You can see that
every time I hit it, it loses one body segment. When the last segment is gone,
explosion animation plays. So far it works the
same as rhino morph. It's an enemy with
multiple lives. And the spreadsheet reflects
its current damage state. We want to make it spit those missing mutated
body segments back at us as projectiles that the player needs to
destroy or avoid. I'll start by making the eagle
morph twitch a little bit. Every time it's hit, it will look good and it will
make sense in a minute. When we give it projectiles, it looks like it's
pushing forward and spitting the projectile
towards the player. It's just a little twitch.
I like it this way. All good. I will give
it a shot method. We can either run
that method randomly, making the enemies shoot, and spit the projectiles
at us at random intervals. But what I will do today, because I like the game to react to player actions
and be interactive. The shoot method will run when this enemy type gets
hit by a player weapon. When we hit it, it
tries to hit us back, it will react to being hit. Enemy projectiles will
be an object pool. Since we already
know how to manage that simple code structure
from earlier in this class, I will actually copy this
entire projectile class. We are using it to create a
pool of player projectiles, the basic attacks
of our spaceship. I copied the entire code block, I pasted it down here between my Eagle Morph and boss glasses. I will rename it to enemy Projectile with 50
pixels height 35 pixels. Enemy projectiles will travel down from enemies
towards the player. The positive direction on the vertical Y axis speed
will be a random value, 2-5 pixels per animation frame. This is an object pool member. We will create a
re, usable object pool of let's say 15
enemy projectiles. Because it's more efficient to reuse objects rather than to constantly create new ones
and discard the old ones. Each object pool
member needs to have a property that
indicates if it's free and available
to be pulled from the object pool or if the
object is currently busy. Active in the game.
We only draw and update enemy Projectile
if it's being used. If it's free property
is set to falls. Every object pool member also
needs a method to run when we take it from the pool and
make it active in the game. In this case we call that
method start and we pass it x and y coordinates of the enemy that will
shoot this projectile. And we set its free
property to falls. Because it becomes active, it's no longer free and
available to be used. It also needs a restart method
which will deactivate it. It will return it back to the object pool by setting
its free property to true, which means it will no
longer be drawn and updated. This is our enemy
projectile class, An object pool member. We will manage this object
pool from the main game class. It will work the same as player projectile pool we created in the beginning
of this project. I copied these three
lines of code. I have to be careful
about camel casing. Here we will have an array
that will hold all objects. I will call it enemy
projectiles pool. The number of enemy
projectiles will also be 15. We will create and re, use the same 15 objects
over and over to improve performance and improve
memory management of our Java Script project. As the game object is created, we will automatically trigger, create enemy projectiles method.
Let's write that method. Now here we have two methods that handle
player projectiles. Copy both of them and
I paste them here. As we said, we will
have a method that will create enemy projectiles
object pool, create enemy projectiles method. We set the number of
enemy projectiles, 215, so the loop
will run 15 times. Each time it runs, it takes enemy projectiles pool
array and it will push one new enemy projectile object into the array using the
class we just created. Also need a method that
whenever we call it, it will get one free
enemy projectile object from the pool so that
the enemy can shoot it. Get enemy projectile
method will cycle over enemy projectiles
pool array which we just filled with 15 objects. As soon as it finds the first one that has
three property set to true, as soon as it finds the first inactive available enemy
projectile object, it will stop searching and it will just return
that object to us. All right, we have all the
logic in place now we can use enemy projectiles object pool to shoot back at the
player. Let's try it.
26. Enemy projectiles: I go up to my Eagle
Morph enemy class. Here we created a
custom shoot method. If you remember inside, we will pull one new
enemy projectile object from enemy projectiles
pool array. Using our custom get
enemy projectile method, we check if we actually
managed to get a free projectile
because it might be that all 15 are used
and not available. But if we did manage to
get one from the pool, we will call its start method. We know that start method needs X and Y
coordinates to tell the projectile where on canvas it should
start animating from. We pass it X and
Y coordinates of the Eagle Morph enemy that
just shot this projectile. Now I have to go down here
to the main render method. Same as we draw and update
all player projectiles. I copy the score block and
I run for each method, for every object inside
enemy projectiles. Pull array, Careful
about capital P here. For every enemy
projectile in the array, I call its update and draw methods and we pass it context. Let's try. I shoot
and hit the enemy. It's not shooting
back at us yet. Okay, I go up here to
enemy projectile class. Here we want the projectiles to animate downward in the
positive direction. On the vertical y axis. This y plus equals this speed. Still they don't shoot. I change fill style to red. Projectiles travel down. This check needs
to be more than, let's say 200 pixels. Okay, actually let's comment
this line out for now. Consolo always helps us. Our consolo projectile, we pulled from enemy
projectiles pull here. When I shoot and hit the enemy, I can see that this code
block is not running at all. There's no consolo.
Okay, I see it now. When we hit the enemy
with player projectile, we want to call its
shoot method like this. Now it will react. That's it. When I hit the enemy, it will shoot back at us. Now it works perfect. We want the projectile to start from the
middle of the enemy, not from its top left corner. I pass it X of the enemy plus
half of enemy with enemy, plus a half of enemy height. Now they come from the middle. That's better. We don't want
to draw red rectangles. We want to draw mutated
enemy projectiles from our custom spread
sheet draw image. We pass it image we want to draw and x and y coordinates
where to draw it, which will draw the entire
image at its original size. We need to create this
dot image property and we pointed towards the image we
referenced in index HTML. Earlier, I gave it an
idea of enemy projectile. We are drawing the
entire sprite sheet. Now if I pass it width
and height like this, it will squeeze the
entire sprite sheet into an area of one frame. As always, we need to
also pass it source X, source Y, source width, and source height to
define a crop in area. Frame X will define
horizontal crop in position, It will be zero, or
one, or two, or three. Frame Y will handle
the vertical crop in area and it will be zero or one. Source X argument passed to
draw image method will be frame X multiply by frame
width of 50 pixels. Source Y will be frame Y times
frame height of 35 pixels. We pass it with height as
source width and source height. Now each projectile is one of the random mutated baby aliens. I guess each ego morph has
four body segments in total. Every time we hit it
with the basic attack, it loses one segment and it shoots one projectile
back at us. If we hit it with the laser, it releases more than
four because the laser ticks faster and triggers
this shoot method more often, we want to limit it to maximum of four
projectiles per enemy. I call it for
example, this shots. It will simply count how
many shots have been fired by this eagle morph
every time we shoot. We increase this do shots by one when the enemy takes damage
from player attacks. If this do shots is less
than four, it will shoot, it will shoot four times
when this dot shot is 012.3 Now lasers release all
four projectiles very fast, but the enemy will
not be able to shoot any more projectiles
back at us After that, with the basic attack,
it will be one hit, one enemy projectile.
Are there any backs? With this implementation, I will test it thoroughly
a bit later. Right now we have 15 enemy
projectiles in the pool. At some point, I will
run out of them. I need to make sure
they get reset and returned back to the pool
when they float off screen. If the vertical coordinate of the projectile is more than
the height of the game area, reset it and return it back to the pool of
available objects. To do this, we will need
access to this game. We pass it as an argument and we convert it to
a class property. This is just a reference
pointing to the place in memory where the
main game object sits. This is not making a
copy of the game object. When we create enemy projectile, we need to pass game
as an argument. We are inside the
main game object. Here I pass it this
keyword to test it. I will have only two enemy
projectiles in the pool. I want to check if they
are reset in I should I only get two released and enemy projectile pool is empty. As soon as they move off screen, they reset and they
can be used again. This works. I set it back to 15.
27. Projectile interactions: Right now, enemy projectiles don't
interact with the player. Let's check collision between enemy projectiles
and the player. If our custom check collision method between this
enemy projectile, the player is true. Keep in mind that this
code block runs only for active enemy
projectiles, which is ideal. If active enemy projectile
collides with the player, we reset the projectile
returning it back to the pool. We will also reduce
player lives by one. We check if player lives
are less than one. If they are, we set
game over to true. I shoot the enemy shoots back. We get hit 123. Game over. This works nice. I want the player to be able to shoot the enemy projectiles, but only with the basic attack. Mutated enemy projectiles are small and they have big eyes. They can easily avoid
massive laser beams. We can only hit them
with the basic attack. This is just my choice. I'm just trying to
give the game play some complexity and give the player more reasons
to use the basic attack. We are insight update method
on enemy projectile class. This code runs only when
enemy projectile is active. For every object in
player projectiles pull array check collision between this enemy projectile
and player projectile. Also, we only run this code if player projectile is active, if it's free property is false. If active enemy projectile and active player
projectile collide, we take player projectile and
we call its reset method. Returning it back to the pool. We will call custom hit
method on enemy projectile, passing it damage of one. If lives of enemy projectile
are less than one, we call its reset method, returning it back to
the pool as well. Each enemy projectile will actually have five
lives in my game, They're quite tough
when they get hit. We reduce the lives by damage. We will also reduce
their speed to, let's say 60% of
the current speed. We have an option to
destroy them completely, or just to slow them down
and avoid them in that way. Again, giving us more
strategic options, more choice and more control. Player can decide to destroy these tough projectiles or
just slow them down and let them flat by I'm playing
the game and I can see that new projectiles are becoming very slow and
they have only one life. I play a bit more trying to control the projectiles
with my basic attack. Yes, it becomes more
clear now since enemy projectiles
are an object pool, when we return them
back to the pool, we need to reset their
lives back to five. I can also randomize their
Frame X and Frame Y to give them a different frame from the spread sheet, a
different mutation. Since we are modifying their
speed by hitting them, we also need to reset that speed to the
original range of values. Down here, we handle
enemy type distribution. 30% of the enemies in my
game will be eagle morphs, 30% will be rhino morphs, and the last 40% will
be beetle morphs. I'm rolling to random twice
here. That will not work. I need to refactor the code. I hope that by now you are comfortable enough
with this code base. You can tweak it and
adjust the rules yourself. In my game, every
Eagle morph enemy type can shoot a maximum
of four projectiles. These projectiles are tough
and they have five lives, so we can avoid them
or slow them down. But only with the basic attack, They will ignore laser
beam advanced attacks. An eagle morph with
the laser will release all four enemy
projectiles quicker. Basic attack will release one
enemy projectile per hit. It's really fun for me to design a draw and code different
enemy types like this. I could make ten more episodes implementing different
alien species. You are the expert.
Now I will give you another spreadsheet
with squid morph. Now if you want, you can implement it as your
own unique enemy. How will your quid morph work? Let me know in the comments.
28. Squidmorph Enemy Class: Now that we have everything, let me show you how easy
and quick it is to add more enemy types into
our existing code base. The spread sheet I'm giving
you is quite flexible. You can give this enemy different properties
and behaviors. I will give you a regular
sized enemy version and also a both
sized spread sheet. Let's implement both
now to make our game even more diverse
and fun to explore. You can download all art assets in the resources section below. I put it inside my project
folder and I reference it here inside index HTML image
with a source squid morph. Ng and ID will be squid morph. We hide that image
with CSS because we only want to draw it with
Java script on canvas. Squid morph is a soft,
flexible space bug. It can absorb a lot
of our projectiles, but we noticed that each
time it absorbs one, it inflates a little bit. Let's just feed it
projectiles until it pops. I constructed this
spread sheet to create an inflatable enemy type. If we give it, let's
say ten lives, each time we hit it, it just
grows a little bit bigger. But this spread sheet
will work even if you want to design your
squid morph differently. If you give it only
two lives when you hit it with the
third projectile. The rest of the frames will just play in one quick sequence. This spreadsheet will
also work for that, I hope it makes
sense what I mean. But let me show
you code as well. Basically, we have an enemy that you can modify however you want. The spread sheet I'm
giving you will work if the enemy has
anything 1-10 lives. If you remember
here on line 161, we have our parent enemy
class contains the methods and properties shared among
all different enemy types. Then we are extending that
class to add properties and methods behaviors specific
to individual enemy types. Because we want each enemy type to have a different image, different number of
lives, and so on. Beetle Morph is our
basic simple enemy. Rhino morph is an armored back
with a thick exoskeleton. Eagle Morph will sacrifice its own body segments and it
will spit them back at us. Now we will add Squid morph, an inflatable enemy that eats our projectiles until it pops. At this point, it's very simple to add more enemy types because we already have so much of the logic in place from
the previous lessons. I can just copy the
rhinomorph code block. I rename it to Squid Morph. And we pointed to the Squid
morph sprite sheet here. And that's it, It will
work just like this. Down here on line 405, we have our wave class. Creates enemy waves and
organizes them in a grid like formation using this custom create method we wrote earlier. I'm calling thotrandom
here and here. Which is not the best way to do this because I want the
code to be easy to read. I want it to be
very easy to read. Which percentage of the wave is made out of which enemy type. I will roll maotrandom just
once per enemy in the grid. From that one random value, we will decide which
enemy type will be added. I will call that
variable, for example, random number and it will
be random value 0-1 Like this if random
number is less than 0.3 enemy will be Eagle Morph. Roughly in 30% of the cases, 30% of each wave
will be made out of Eagle morphs L. If the
same random number is less than 0.6 when that random
number is between 0.3 and 0.6 another 30% of the wave
will be rhino morphs L, meaning that when
the random number is between 0.6 and one, the last 40% of each wave will be made
out of beetle morphs. I hope it makes sense. We
actually have four enemy types. I will add one more else. If block here, I have to be
very careful about brackets. When I do this,
it's easy to break my code with a single
misplaced bracket. We are testing squid morphs now. I will add them here. I can put eagle morph
here, for example. It doesn't matter yet, because I want the
most of the wave, actually the entire wave to be made out of squid morphs now, because we need to test them. As you can see,
they already work. And all we did so far is we re used the code block
for rhino morphs. When I play the game, I can see that we need to
tweak a few things. The logic we have here is
that time we hit an enemy, we swap its spreadsheet by
one frame horizontally. When the lives of the
enemy are less than one. As defined here on line 186, we keep increasing frame X to play the explosion animation. Once frame X is more
than max frame, we know we can delete
the enemy because its entire explosion
animation finished playing. To actually see that
explosion animation, I check my squid
morph spreadsheet. And I see that I
need to increase max frame to, let's say 16. To make sure all these
frames get displayed before the enemy is deleted from
the game. Now I shoot it. I deplete all four lives. It plays the rest
of the spread sheet showing the enemy
inflate and pop, I designed the spread
sheet so that it will work when the enemy has
one life or nine lives. For example, we just jump
frame by frame as we hit it. Whenever we deplete the lives, it will just play
whatever frames are left. If I do this, I notice we have a bug. Something is pushing
frame Gs back when it was already showing
the explosion frame. It's because we are moving
frame eggs here and also here. Let's make sure these two lines of code are mutually exclusive. And they don't run at the same time in the
same animation loop. Code block only runs when the enemy has less
than one life. Here I make sure this
line only runs when this lives is more
or equal to one. This should fix the bug. If you go to line 243, you give your squid morph
two or five or nine lives. You can test it and see that the spreadsheet will
work in all these cases. I also prepared a Bo
spreadsheet with squid. Moorphi added them to
the existing Bo sprite. You can download
this new version in the resources section below, called it boss eight because there will be eight
different boss creatures. I bring them into the project. Here boss eight, dog, and ID is boss eight. We have eight different boss
visuals on this spreadsheet. Eight different rows. I hide it with CSS. I go to my boss class and I reference that image
here on line 347. Now I can set frame y
to be a random value, 0-7 Like this, I commented out just to
test the spreadsheet, I will set frame Y to zero. Down here, I want the waves to start with a boss to
make it easier to test. I Sat Frame X20, Frame Y zero is this version
of the mantis morph. Row one is this frame Y
two will give us this row. Frame Y three is this frame Y four will give
us the new squid morph. You can see it reacts
to being hit by a saddle animation of
its two front tentacles. Same as the other
bosses from before. When we deplete its lives, it just plays the rest
of the spread sheet in a nice low frame rate
explosion animation. Frame six will give us this
strange space creature. Notice that all
of them animate a little bit every time
we hit them with our basic attack frame y seven will give us
this space squid. We know that everything works. So I will set frame y to a
random value 0-7 giving us one of these eight bosses
randomly down here. I want my game to start
with an enemy wave. I want each wave to be made
out of 25% squid morphs, 25% rhino morphs, Ego morph is actually very
difficult to deal with. We make it only 20% of the wave, and the last 30% will be
made out of beatle morphs, the simple, basic enemy. If you use the same settings
I used for this game, try to play it as
long as possible and let me know your
score in the commands. It can become quite difficult and chal***ging as
the game goes on. And both health and the size
of the waves keeps growing. Well then if you managed
to get this far, this was a big project
and we learned so many different
tricks and techniques. If you had fun coding with me, you can check out my other
classes. I'll see you later.
29. Lobstermorph Enemy Class: Analysis, successful
enemy contact confirmed. It seems that our
lasers are triggering a cellular mitosis
in this species. Al peculiar, what
does that mean? If we shoot them, they divide into multiple smaller critters. Ah, so we first have to split them and then we
target the clones. Yes, that seems to be the
most potent strategy. Let's divide and conquer. I made other projects with art assets that are
compatible with this game. Let me show you how to
take a sprite sheet from that utter game and use it
here. It's very simple. You can download
the spread sheet in the resources section below. I put the image into my project folder and
I reference it here. The source is lobster. Moorphpng ID will
be lobster morph. We will hide the image with CSS here we already have
all the logic in place. I can just copy this code
block we used for Squidmorph. Max frame will be 14
lives will be eight, as long as lives
are more than one. Every time we hit the eneme, we will increase the frame
in the spreadsheet by one, slowly progressing
the animation. This eneme splits into multiple smaller
clones when we hit it. Now we just have to
decide which portion of total enemies will be made
out of this new enemy type. I create new L if block. Again, I'm very careful about the brackets because single wrong bracket
can break my code. You can choose your own values. Here we have five
different enemy types. I think it's appropriate
to make each enemy type roughly 20% of the wave we are dealing with
random numbers. Some waves will have more
lobster morphs than others. I test it, it mutates. Every time I hit it, it splits into clones that works well. I made another game that focuses more on this
particular space bug. If you are interested,
I will see you in the other class where we build
a planetary defense game. Having multiple enemy
types like this makes the game more
interesting to explore. And we add the
enemies here to make the player think and
strategize a little bit. As the game gets more difficult, we have to prioritize targets, use different weapon types, and position ourselves well. Here we are, achieving all of
that by creating a variety of enemy types with different
properties. This was fun.
30. Project 2: JavaScript Planet Defense: Every game is made out of a
few basic building blocks. Once you understand
these techniques, you can get creative, combine these elements
in different ways, and create many
unique games easily. In this class, we will
cover many of them. From keyboard and
mouse controls to performance friendly sprite
animation tips and tricks. And the main focus this time is object pool
design pattern. We will use it for enemies
and for player projectiles. Because re using our objects
rather than creating and discarding them over and over can be a massive
performance benefit. Today we are building
an animated two D game completely from scratch
with no frameworks and no libraries using just
plain van Java script and HTML five canvas analysis. Successful enemy
contact confirmed. It seems that our
lasers are triggering a cellular mitosis in
this species al peculiar. What does that mean?
If we shoot them, they divide into multiple,
smaller critters. Ah, so we first have to split them and then we
target the clones. Yes, that seems to be the
most potent strategy. Let's divide and conquer the interrogam revolves
around the planet. Literally, we
control a spaceship and we need to defend the planet from asteroids and
all different kinds of aliens and space bugs. This project is completely
standalone and independent, but I designed the
art assets to be compatible with Space Invaders
game we built before. And at the end, I will show you how to take
sprite sheets from that game and implement them in this project with just a
few lines of extra code. This class is for creative
coders who want to learn about the fundamentals of
two D game development, as well as for Javascript
beginners who want to practice their object oriented
programming skills on a fun visual and
interactive project. Let's go as usual, we start with a normal generic blank webpage where we link CSS style sheet, create a HML five canvas element with an ID of canvas one. And we link script JS
file in style CSS. I give canvas a border, I put background PNG image into my project folder and I use it as a background
for my canvas. You can download all
project art assets in the resources section below. I want the canvas
in the middle of the page both vertically
and horizontally. So I use the absolute
positioning technique. For example, everything else will be done with Javascript. In script JS file, we will use images and other
resources in this project. We need to wait for all of the assets to be
fully loaded and available before we run any Javascript code
that depends on them. For that reason, I will
put my code inside a callback function on
a load event listener. When load event fires and
all resources are ready, we will run a Javascript code. First, I point Javascript towards my canvas
element using its ID. I sat context to
two D. As usual, we always have to do this line. When we're setting canvas up, we are making a two D game. Today I set canvas to 800
times 800 pixels here, which will reveal the
full background artwork.
31. Project 2: Planet and Game Class: I can define my
planet class up here, outside the event listener. As long as I make sure
I only instantiate that class from inside
the load event listener. Later, let me show you all objects in this project
will need XY coordinates and radius because
everything will be a circle and we will be detecting collisions
between them. Draw method will take context as an argument to specify
which canvas we want to draw on and also to keep our classes as self
contained as possible. Inside we will draw a circle
representing the planet. We pass arc method
XY coordinates of the centerpoint
radius of the circle. Start angle of the
circular shape path and angle which will be method
pythons to a full circle. In radians, we call fill method to fill the path with the
default black color. Inside the load event listener. I instantiate the class
using the new keyword. Now I can use this
planet variable which holds an instance
of planet class. I can call draw method we
defined on line seven. It expects context. I pass it CTX from line 15. We have our planet here drawn
at coordinates 200, 200. I can change its radius, I can stroke it instead. Let's set stroke style to white and maybe line
width to two pixels. We can move it around and resize it by adjusting
these values. This is our planet class. I actually want to have
a main game class, the brain of the code base. We will draw, update, and control
everything from here. Constructor will expect
canvas as an argument. Inside we convert it
to a class property. From that property, we will
extract width and height. Because I want width and
height properties of our main game object
to be the same as the width and height of
the actual canvas element that we will be drawing
and animating the game on. Instead of creating
a variable called planet and instantiating
planet class, that way, I will create a
property here called this. Planet Game class will
also have a render method. This method will be called over and over for each
animation frame. Updating and redrawing
our shapes and objects, we will need context
as an argument inside. I will take this planet
property from line 19, which now holds an
instance of planet class. And I call its associated
draw method from line seven, passing the context
argument along like this. Now we are taking this
planet class and we are instantiating it inside the
game class constructor. I can delete these lines and instead I will create an
instance of game class. It expects canvas
as an argument. I pass it canvas from
line 27 through that. Game will now have
access to width and height properties
of canvas and width and height of game
is automatically set to 800 times 800 pixels. I can take that game
variable from line 34 and I can call
render method from it. We defined it on line 21 and we can see it expects
context as an argument. I pass it CTX from line 28. Important thing to
understand here is that the code inside a
class constructor is executed once at
the point when we create an instance of that
class using the new keyword. When I create an instance of
game class here on line 34, it also automatically runs all the code line by
line and an instance of planet class is
created and saved as this planet property
here on line 19. I like to do it this way. I think it's cleaner
and easier to navigate In structuring
our code like this in classes and objects is called object
oriented programming. This entire project will be a simple object
oriented code base. Now planet class is
connected to game class. And I can still
change properties of the planet by adjusting
any of these values. These same properties can
now also be accessed from inside game object through
this planet property, which will become
important later. What if I want this
connection to be two way? I also want the properties
that sit inside the game class to be accessible from inside
the planet class. Do it by passing a reference
that points towards the main game object to the planet class
constructor like this. Keep in mind that objects in Java script are so called
reference data types. By doing this I'm not creating a copy of
the game class here. I'm just creating a
reference that points to a space in memory where
the main game object sits. When any properties that sit on the main game object
update their values, those changes will be
immediately visible from inside the planet class let class constructor expects
a reference to game as an argument here where I
call the planet constructor, I need to pass it
that game reference. Since we are inside
that game class, I pass it this keyword
which when used here, represents the
entire game object. Now we have a nice and
simple two way connection between planet and game classes. We can test it by
accessing width property from line 18 from inside planet. Because I want the
planet to be exactly in the middle of the
game area game with Tams 0.5 If what we are covering here is too fast
or too complicated for you, check out the previous
class where I go much slower in a beginner
friendly pace. For the rest of this project, I assume you already
know the basics of Javascript and object
oriented programming. You can download
the planet image in the resources section below. I bring it into the
project as an IMG element. Here in index HTML, I give it an ID of planet. We can see the image
here. I want to hide it. We will draw it, but with Javascript on Canavas
not like this. There will be more
images in the project. I will put all of them
inside a diff with a class of hidden in CSS. I set this hidden
diff to display none. Great, we will bring
it into the project here as this image
property on planet class. Now I can call built
in draw image method. And I pass it the
image we want to draw and X and Y coordinates, where to draw it on
canvas x and y of a circle represents
the center point around which the
circle is drawn. But with rectangles
and images on canvas X and Y coordinates
define its top left corner. If I want to align the rectangular image
over the circular It box, normally I would
offset it by radius, which for all other
objects in this game, will be exactly
half of its width. But for the planet image, I made it a bit larger because we have these
clouds around it. And I don't really
want those clouds to be active in collisions. Enemies and asteroids will only collide with
the planet when they hit the actual body of
the planet, not the clouds. For that reason, I have to
offset it by a bit more. Let's say by 100 pixels. Now we aligned the
circular planet at box over the
rectangular image. We will keep the
white circle for now. It will represent the
collision area of the planet.
32. Project 2: Mouse Position: As we said before, any code inside a class constructor
is automatically executed at the
point when we create an instance of that class
using the new keyword. I'm already taking advantage of that and I'm
automatically creating an instance of planet class at the point when I create an
instance of game class. At that same point when we create game using
the new keyword, I also want some event
listeners to be applied. We will listen for
mousemove event normally with event
listeners we would create a standard callback function
and some code to be executed inside every time
a mousemove event fires. The challenge here
is that I'm putting the event listener inside a
constructor on an object. We are inside game class
and we are calling the event listener from window object with a
regular callback function. We would lose access to this dot properties that sit on the game class to
keep that access, to make sure that
this event listener remembers that it was
lexically defined inside the game class and to
allow us manipulate this properties on
game class from a callback function on this
mousemove event listener, we have to either bind
the function or much simpler we can use ES six
arrow functions in tax here. Because one of the
special features of arrow functions
in Java script is that they automatically inherit this cured
from the parent scope, it will have the autogenerated event object as an argument. I give it a variable name. For example, since this
function has only one argument, the brackets are not mandatory. I can delete them like this. Now, whenever
mousemove event fires, autogenerated event
object will be created. Let's consolog that object
so we can inspect it because we put the
event listener inside the game
class constructor. The event listener
will be automatically applied when this
line of code runs, when an instance
of game class is created using the new keyword. Now I move mouse
over canvas and I see those objects every time
a mouse move event takes. If I open the mouse
event object, I see it contains all kind of information about the mouse
event that just happened. What I need is the X and Y
coordinates of the mouse. We can see those
properties here. I want those mouse
coordinates to be available all over my code base, not only from inside the
mousemove event listener. I will create this
dot mouse object on the main game class, and I give it x
and y properties. We use ES six arrow
function here, so we can use this dot from
inside the callback function. I can actually take this dot, mouse dot x from line 25. And I can set it equal to x to the current horizontal
exposition of the mouse. I also do the same for the
vertical Y coordinate. Okay, as we move
mouse over canvas, we are taking x and Y coordinates from the
event and assigning them to our custom
dot mouse property to make them available
all over the project. We can test how accurate
these coordinates are by drawing a line from the middle of the planet to the mouse. We want to draw a simple line. So we start by begging path. Move two method will define the starting x and y
coordinates of the line. It will be the middle
of the planet. Line two method will define
the ending coordinates. It will be the current
mouse position. Now we stroke that line to
actually draw it on canvas. The line doesn't react
because we are calling render method only once
on the first page load. We want to call it
over and over for every animation frame
to see some movement. Let's create a simple animation
loop custom function. I call Animate. We put this render call inside. Then we call built in request animation frame method which sits on the window object. But that part is optional. I can delete it. Javascript
knows where to find that method without us having to tell it that it's on
the window object. I pass it animate
to create the loop. I call it once on
the first page load like this to actually
kick off the first loop. Now as I move mouse around, render method runs
and draws line from the planet to the
mouse as we defined on lines 38.39 It looks like this because
we see old paint. We see all the previous
animation frames. We actually only want to see
the current animation frame. I delete the entire
content of canvas between every frame by using the built
in clear rectangle method. We clear the entire
canvas from coordinates 00 to width and height
of the canvas area. Now we are animating the
line and we can check if the coordinates we are
capturing from mouse event line up with
our game objects. It's all good now, but look what happens when
I make the window larger because canvas
is not full screen. Those x and y coordinates
don't account for the white space between the
web page and canvas element. Suddenly the coordinates
are completely wrong. The line no longer goes
from planet to the mouse. We can offset those mouse
coordinates by accounting for the white space around canvas
manually or even easier. Mouse event object already does that for us automatically. It gives us those
adjusted coordinates. Instead of using these
X and Y properties, we will use offset X
and offset Y from here. Replace them here. Now the line will go exactly from the mouse to the
middle of the planet. We know we are capturing the
correct mouse coordinates. Perfect. Offset X and
offset Y properties are useful for projects where your canvas is not full Screen. I delete the consolog.
33. Project 2: Player Spaceship: Time to create the player class. Its job will be to
manage visuals, behavior, and controls
of the player. I'm not sure if I made it clear, but we already have a
fully animated code base. I'm not animating and
moving the planet around, but I can already do that. If I want to look at this, we know that render method runs over and over for
every animation frame. I can increase the horizontal
position of the planet by one pixel per frame
and we get movement. This code base is already fully ready for all sorts
of animations. I'm just not moving
things around yet. I can move the
planet horizontally. I can move it
vertically down and up. It just looks static now, because I'm not moving
things around yet. We will get there soon. Same as we did with the planet class. I need access to properties and methods that sit
on the game class. From inside the player class, I'll create a property, a reference pointing towards
the main game object. As we said before,
all active objects in our game need to have x and
y coordinates and radius. So that we can use
these values for collision detection
between circles later. Also, all objects
in this code base from now on will have
radius of 40 pixels. Radius is a half circle. The rectangular sprite frames
will be 80 times 80 pixels. Art assets from this project
are fully compatible with the main Space Invaders
game we built before. Feel free to take them and use them in that
project as well. If you want, I will
show you a fun way, how to do that in a bonus
lesson on that project. I'm sure some of you
can figure it out. Even without my help, it might be fun to add the
unique enemy type we will be implementing in this project to that previous
project as well. Just as an exercise, I made sure it's all compatible. As usual, player will
have a draw method and we will draw a circle
representing the player. Whenever we draw a circle, we have to start
with the begin path. We use method to map the path. We actually draw it on canvas using built in stroke
or film method. This is the player class. We will just repeat the pattern
we did with the planet. I instantiate it as
this player property. Here we will call
player draw method. We defined on line 24
inside render like this, player will move around. I will need an update method if I want the planet to
move around in some way, for example, rotate,
I would have to give it update method
like this as well. For now, let's just increase the horizontal exposition of the player by one pick
Siler animation frame. Just a test it, I call the
update method we just defined. From here player
is moving because render is called
over and over from inside animation
loop on line 75. We could also attach players horizontal position
to the mouse. Like this, I want the
player to rotate around the planet and always face the direction
towards the mouse. This part will be a bit more challenging to implement
for beginners, but once we get that to work, the rest of the
game will be easy. First, I will bring the player
image into the project. You can download it in
the resources section. Below, I give player class this image property
and I pointed towards that new image we just included I call built
in draw image method. And I passed the image
and X and Y coordinates. Now if we move the player both circle and image
move together like this. I want to offset the
rectangular image so that it sits directly on top
of the circular hit box. I offset X and Y by the half of the player width and height
by the radius value. I prepared all art
assets for this project at specific sizes that we
will need in the game. Doing this saves me a lot of
work now while I'm coding, because the images
are ready to go, and I don't need to scale or
resize anything with code, which would be wasteful anyway
in terms of performance.
34. Project 2: A Little Bit of Math: I want to move the player
around the planet depending on where the mouse currently is
in relation to the planet. We will write all that
logic in a reusable method. I call, for example, calculate Am doing it this way. We will be able to reuse
this method later to make enemies face towards the
planet as they attack us. Method will take object A
and object B as arguments and it will calculate a vector direction between
these two objects. Let's say we have two objects
that have x and y position. For example, the center point of the planet and the mouse cursor. These two objects sit
somewhere in the game area. We want to calculate
the distance between these two objects on
the horizontal x axis. First, it will be the
horizontal position of object A minus the horizontal
position of object B. We do the same for
the vertical axis. Now we have two sides of an
imaginary right triangle. And we want to calculate the distance between
these two points, in this case between planet
and the mouse, for example. We are calculating a distance between two points
in a two D space. We need to calculate a hypotenus of this imaginary
right triangle. Right angle would be here. This is the hypotenus
of the right triangle. The longest side opposite
to the right angle. This is a perfect use case for Pythagoras theorem formula which would look like this
in Javascript code. But since we are
using Javascript, it's even simpler
because we have a built in method hypotenous
method available. We just need to pass it
this side and this side, and it will give us the
length of hypotenus, which in this case is the actual distance between
object A and object B. We passed here as arguments. Now I need just two
very small numbers that point in a certain direction, vertically and horizontally. And combination of these
two values will give us a direction vector between
object A and object B. We will use that to make the
player spaceship point away from the center point of the planet towards
the mouse cursor, I will reduce my vectors
to a value range between minus one and
plus one by checking the ratio between
x and hypotenus. Distance, that will be
the horizontal direction because the distance the
hypotenus is always more than x. Because mouse can be to the left or to the
right from the planet. The resulting value here will be anything between minus
one and plus one, but it can never be more
than one because the Dx is always less than distance
less than hypotenus. Keep in mind that the mouse can be to the right
of the planet, but also to the left
like this for example. That's why the
resultant ratio value can be negative as well. Anyway, now we have
everything we need. We know in which direction we need to push an object if we want to push it towards another object or away
from another object, mx and my values are telling us that horizontal and
vertical push I will make this calc Am method
return an array of values because we will
need multiple values. From here to position
the player hit box and then to correctly
rotate the player image, we have to remember in which order we are
returning these values. We know that when
we call Calc Am, it will return an array where the element
with an index of zero is the horizontal direction between object A and object B. Value with an index of one
is the vertical direction. The vertical vector index two is the distance between
a and B horizontally. Index three is a
D Y the distance between object A and
object B vertically. I know this was a lot of math. Sorry, we are almost done. Now, if this is your first
time using this technique, don't worry about it too much. Just follow along
what I'm doing. You need to use it more
often For other projects, it will eventually make
more sense to you. Repetition really
helps with this. For now, just understand
that we wrote a method that gives us a distance between
two points in a two D space. And we will now use those
values to point and rotate objects towards
or away from each other. I want to move the player
around the planet, always facing the mouse. I can implement
this in many ways. For example, I can
give the player this dot am property for
every animation frame. I will set this
dot a property to that custom calc Am
method we just wrote. We will make it
calculate and produce those four values and
return them as an array. That method gives us the distance and direction
between two points. So I want to check those values between mouse and the planet. The relative position
between mouse and the planet will determine
where the player is facing. I will consulate this dot am and we will get that array with four values that Lkm method returns for performance reasons. It will actually make sense
to only update this dot. Aim only when mouse
move event fires. Because if the
mouse doesn't move, we are recalculating
the same value over and over for
each animation frame, which is not necessary. As we said before, calc method returns horizontal direction, vertical direction,
horizontal distance, and vertical distance, in this case between
mouse and the planet. Because those are the two
values we passed to it as object A and object
B. I'll get here, so I delete the consolo. Now I can use these values to rotate the player
towards the mouse. Horizontal position
of the player will be always starting from the
middle of the game area, which in this case is also the horizontal x
position of the planet, the center point of
the planet circle. We just want to add to it that small directional
vector value. But because that value is very small between minus
one and plus one, we would barely see the motion. I multiply it times,
let's say 50. Perfect. That already
works horizontally. I can do the same for
the vertical y position. We go from the center point of the planet towards the mouse. And we enhance that vector again by multiplying
it times 50. Yes, that works. The number we use here to multiply
the vector by, will now influence the distance between the planet and
the player spaceship. Let's say I want the distance to be equal to the
radius of the planet. We get some interesting motion. If the vertical and
horizontal values don't match like this, it would be useful if the planet was elliptical, for example. Now the center point
of the players circle moves along the radius
of the planet circle. If I want to move it out
of the planet completely, we have to push it by half of the player circle further,
plus player radius. Like this, we need to use brackets to make sure the
addition happens first, and then the resulting value
is multiplied by the vector. You can play with the values
if you are coding along, and it will make it
clear what's happening.
35. Project 2: Understand Canvas Rotation: We don't need this line anymore. We also want to rotate the player image to always
face towards the mouse, away from the middle
of the planet. I will give player
a property I call, for example this angle. When you call canvas rotate, it will transform canvas state. So everything drawn after
that rotate would be rotated. We only want the spaceship
image to rotate. I restrict any changes
to canvas state by wrapping it between built
in safe and restore methods. We save the canvas state, we do any changes here, then we call restore
and reset everything other shapes drawn on canvas
after remain unaffected. Canvas rotation is a bit unintuitive if you
never saw it before. Basically remember this. To rotate something on canvas, first you have to translate
rotation centerpoint, which is the same thing as
coordinates 00 on canvas. You have to translate
that over the coordinates of the middle of the
object you want to rotate. Then you call rotate, you pass it angle, you want
to rotate by in radians. Then you draw that object
you want to be rotated. But we draw that object not
at its x and y coordinates, but at coordinates 00. Because the actual position
of the object is now expressed in the values
we passed to translate. I think it's safe to say
that canvas rotation is much more complicated
than CSS rotation. But it's actually easy if
you do it a few times. Let's actually do it in code. I want to rotate the
player spaceship. I translate rotation
centerpoint over this dot x and z dot y
coordinates of the player. Rotation centerpoint is also
coordinates 00 on canvas. Now I'm pushing player too far because I'm expressing
its position here by translate and I'm pushing
it even further from there by dx dot Y coordinates. This might not be
the best example to teach someone
rotation because we have the player spaceship image here and also the
hit box circle. So there's a lot going on here. Exposition of the circle is zero and the exposition of the
image is minus radius. Because of the
difference between how rectangles and circles
are drawn on canvas, x and y is the
centerpoint of a circle, but it's a top left corner of a rectangle or image when we're
drawing shapes on canvas. Anyway, now we fixed it
and we are successfully translating rotation centerpoint over the player spaceship. Now when I rotate, it will rotate from the
middle of the player image, which is exactly what I
want built in Canvas. Rotate method takes the rotation
angle value in radians. I pass it this dot
angle from line 25. This is rotation by
0 Radians method pi will rotate the spaceship
like this by 3.14 radians, which is approximately
180 degrees. Half circle method
pi times two is 6.28 radians, 360 degrees. We rotated the spaceship
all the way around. Half pi is 1.57 radians, or approximately 90 degrees, we are facing upwards. Now I hope it makes sense. We need the angle to be value
0-6 0.28 radians, 0-2 pi. We need that angle value
to match the direction between mouse cursor and
the planet center point, so that the player is
facing towards the mouse. For that, we have
another useful built in method called method A 102, which gives us a
counterclockwise angle in radians between the positive x axis and a line projected from coordinate 00
towards a specific point, towards specific
x, y coordinates. The result in value is actually a value between minus
pi and plus pi. Important thing to
note here is that we pass it DY first and x second. We pass these arguments
the other way around. This is just how the
built in method works. We are already calculating
these values in calc method. They are being
returned as values with index 2.3 in this array. Because we count indexes
in array from 00123. I don't have to run
calm method again. We are already running
it on line 38. I will just take this aim that
holds that returned array. And I take element
with an index of two, element with an index of
three x and D Y values. Okay, so we can see
that it can work in, this is useful for
so many things in creative coding
and game development. By the way, I need to fix this just buried me a
second until I figure this out. How about if I swap
these two values? Trigonometry can
be tricky, okay? That if I multiply these
values by minus one, right? Give me one more second, okay? I made a mistake here. I said that math 8102 needs
DY first and dx second. But yeah, I'm
passing it x and DY, I need to swap them around. Yes, this is exactly
what we need. Perfect. This was a lot of math. If you made it this far, the rest of the project
will be easy and fun. We did the most
challenging part already. Now we have our
reusable calc M method. And we can use it again whenever we need to get a
distance between two objects and point
them towards each other or make them move
away from each other. I will show you how
to do that when we implement enemies
a bit later.
36. Project 2: Debug Mode: We define event listeners here. Let's implement another
event listener. This time for a key up event. Inside I consologe the auto
generated event object. Now when I click on Canvas and I start typing on the keyboard, we see the key that was pressed. I can say if the key that was
pressed is a lower case D, set this back property to true. We will create this,
do debug here. When we press letter D, we will actually
set this dot D bag to its opposite exclamation
mark, Like this. I consolok this dot D bag when
I press D on my keyboard. Now I toggle debug
mode on and off. If it's true, letter D
will set it to false. If it's false, it
will set it to true. This trick is useful for
many other things as well. It's good to remember
this technique inside the draw method
on player class, we are drawing the player image, the spaceship, and we are also drawing a circle around it. This circle will represent
the collision area. Players hit box. It's a useful thing
to see sometimes, but I want to have a way
to show and hide it. I will say only draw
the collision area circle around the player
if D bug is true. Now when I press letter
D over and over, I'm showing and
hiding the hit box. It's the same with the planet. We have the planet image and wide collision circle around it. Let's only draw
it when this game do debug is true, like this. Again, I can press
letter D on my keyboard repeatedly and it will show and hide these
collision elements. We can show and hide all kinds of helpful information
in our game. Like this, we are now able to enter the debug
mode in our project, which will help us
to develop the game. At the same time,
we can easily hide these elements to see
what the final result for the user will look
like when the hit boxes and maybe some other
support elements are hidden.
37. Project 2: Object Pool: This is starting to
look pretty good. We have a planet, a player
that rotates around it, and we implemented a debug mode. Now I want the player to be able to shoot laser projectiles. We need to make sure
those projectiles fly in the correct direction
from the tip of the player spaceship
towards the mouse cursor. On top of that, we will optimize performance
of this project by implementing projectiles
as a reusable object pool. It means that instead
of constantly creating and discarding these
projectile objects as the player shoots, we will create a
pool of objects. And we will pull
from the pool when we need them and we will return them back to the object pool instead of destroying
and deleting them. Object pool design pattern
helps us to better manage automatic memory allocation and garbage
collection processes. We allocate the memory just once when we create the
initial object pool, which is more
performance efficient. Garbage collection is
an automated process where Java Script reclaims memory by removing objects that are no longer referenced
anywhere in the code. Garbage collection
happens automatically, and it can sometimes happen
at inconvenient times. For example, at a time when we need to animate a
large enemy wave. And as a result, it can cause lower frame rate and
animation stutter. We want our game to
run smooth and fast and to always respond
quickly to player inputs. So we will completely eliminate the garbage collection
process by reusing the same, let's say 20 projectile
objects over and over, rather than discarding
the old ones and creating new ones. This simple performance
enhancement technique can do a lot for your projects. Let's learn how to implement
it on Projectiles, and when we understand it, we will use it for
enemies as well. Class Projectile will be used
as a blueprint to create that initial pool of let's say 20 reusable
projectile objects. As usual, we need a reference to the
main game class to get access to properties and methods that sit on
the main game object. Projectiles will be involved
in collision detection, so we need to make
sure they have x and y coordinates
and radius property. I will make them large at first so we can
clearly see them. The objects created by this projectile class will be so called object pool members. We need to give them a property
that indicates whether the object is available in the pool or if it's
currently being used. Initially, free
property will be set. The true projectile object is sitting in the pool inactive. It's waiting to be used. It's ready and
available, it's free. Every object pool member
needs two methods, start method that
will run when we pull the object from the object pool and make it active in the game. At that point, we set
free property to face. This object is no longer
free and available. It's being used, drawn
and animated in the game. We also need a reset method instead of deleting the object. When we are done with
it, we will call its reset method that will set its free property
back to true. It means the object
becomes inactive, ready to be used again
when we need it. When we shoot a projectile, we will call its start
method to activate it. When the projectile
collides with an enemy or when it
flies off screen, we will call its reset
method to deactivate it. We will give it a draw method. We will only draw the object when its free property is false, when it's active in the game. We don't want to draw
it when it's just sitting in the object
waiting to be used. Update method will move
the projectile around. Again, we only want to
run the update code for active projectiles if this free is false inside
the draw method, we draw a circle representing the projectile begin
path R method. Don't get confused here
by the line breaks, it's all on one line, it just breaks for
me to another line because my code editor
window is too narrow. Because I want to
show you the code and the project side by
side inside update. If the projectile is active, we will update its X and Y
position by speed X and speed Y values created the
horizontal speed property and I set it to one pixel. One pixel per frame means it will move from left to right. Speed Y will also be one pixel, which means movement
from top to bottom. These two speeds will combine to determine the
final direction. The combination of these two
particular speed values, 11, will result in a motion in the bottom
right direction. The Projectile class is ready. It is an object pool member. It has a free property, start and reset methods. And the code inside the draw and update only runs when
the object is active, when its free property
is set to fall. We will manage the
object pool from inside the game class from the main
brain of this code base. Projectile pool will be
an array that holds all active and also the inactive
projectile objects. Number of projectiles
will determine how many objects we want to create and have
available in the pool. If we have too few of them, we can run out and don't have any available
when we need them. If we create too many, we might be wasting
memory if we are never using a large portion
of the pool we created. We will get to the right number later when the game is playable and we can test how fast we
are using and re using them. As the game is created, we will automatically
run a custom method. I call create projectile pool, which will fill
projectile pool array with five inactive
projectile objects. I will write that
method down here. Inside I create a four loop
which will run five times. Each time it runs, we take projectile pool and we push
new projectile inside. I can see it needs
game as an argument. I pass it this keyword. The main game class will
manage our projectile pool. We store five object
pool members in projectile pool array and we create them on
the first page, load as soon as we
create the game object. Because as we said before, all the code inside the class constructor is
automatically executed. When we create an instance of a class using the new keyword, we need a method to create
that projectile pool. And we also need
one more method. I will call, for
example, get projectile. When we call this method, it will cycle over the projectile
pool array we created. As we cycle through the indexes in the array we created earlier. As soon as we find the first projectile object with free property set to true, we know this object is
currently inactive. It's free and available. We take it and we return it. Get projectile method, finds the first free object in the pool and it
returns it again. Please don't get confused
by the line breaking here, it's all on one line. It's just that my browser
window is too narrow. If free is true, get
projectile method returns the object and it stops
searching initially. All projectiles are free, they are inactive
and free to be used. If we run start method, we activate them and
they are no longer free. I add one more event listener, this time for a
mouse down event. When we click the
left mouse button on mouse down set mouse X and Y to X and Y of the event and call shoot method
on the player class. This player is accessed through this reference here
is that class. Let's give it a
custom shoot method. When we should we create a
temporary helper variable, we set it to that custom get projectile method
we just wrote. We access it through
this game property here and it sits on the
game object down here. This method cycles
through the pool and it returns the first
free projectile object. Let's say we are currently
drawing all projectiles. They are all currently
active and we run out of free available ones that
could break our code. To fix that I say if we
found a free projectile, if get projectile
method was actually able to find one
that is free and available only then take that free projectile and call its start method
to activate it. Here some properties of the projectile
object change values as we animate and update it. We need to reset them to
default at some point. We can choose to do it
when we activate it, but also when we
deactivate the object. I will do everything
inside the start method. When we activate the object, it will be simpler to
implement in combination with other choices we already made with this
particular code base. Start method will expect x and y coordinates
as an argument, and we set those
values as tx and ty. At first, let's start all
projectiles from coordinates. Let's say 100, 100. I should have also consologed the pool just to check if
after this method runs, it was actually filled with
five objects. All good. I inspect one to make sure we
don't have any errors here. If you see any non
value, not a number, it could mean we have a problem somewhere as we
calculate these values. All is good here. I delete the consolog inside
render method. I delete this begin path. I take the entire
projectile pool array, and for each projectile
object inside the array, I call its draw method, passing it context and
I also call update. If I click, nothing happens. I follow the code
to find my mistake. First I check if this mousedown event is
actually running. Quick. Consolog will help me here. Yes, I know the
problem is not here. The next step would be to see if the shoot method is actually running icsolog projectile
from inside here. When I click, I can see
that Get projectile method is correctly returning projectile objects
from the pool for me, great, that means
the problem must be somewhere on the actual
projectile class. I come down here and I
see I forgot one line. Arc method doesn't actually
render shapes on canvas. To actually draw it here, I need to call either
stroke or fail. Now when I click on canvas, we are drawing projectiles. I want to give them
a different color, but I want that color to
be restricted only to the projectile drawing
code and don't affect any other shapes
we draw on canvas. Later I wrap this code between safe and restore methods at fill style to gold projectiles
move towards bottom right. Because of the combination
of these two speeds, one pixel to the right
and one pixel down per animation frame will result in this direction of movement. Start method expects X and
Y coordinates as arguments. I actually want
the projectile to start from the player
who shoots them. I pass it to this doxy because here we are inside that
player class that worked.
38. Project 2: Player Projectiles: If I keep clicking the mouse, we shoot five projectiles. And after that, instead of
returning a projectile, get projectile method
starts returning undefined. It's because we run out
of objects in the pool and it can no longer find
any free available objects. There is no projectile
object with three properties set
to true anymore. Because we activated
all five of them, I will make sure
that projectiles automatically reset when
they move off screen. The horizontal exposition of the projectile is
less than zero, if it's more than game width, or if the vertical position
is less than zero, or if it's more
than game height. If any of these four
conditions is true, we know the projectile moved all the way to the
edge of the screen. So we can call its reset method, which will set its
free property to true. It will deactivated. It also means that
because of these checks, that projectile will no
longer be drawn and updated. Now I can shoot if all five
projectiles are active, we are still getting undefined. We click more, but as soon as one of them moves off screen, it gets reset and we can reuse it and we shoot it
again. Great job. We have a working
reusable object pool. Now I want the projectiles to fly in the correct direction. We already have a custom Calc Am method that takes
two circular objects. It gives us horizontal
and vertical direction. We can use it to make them move towards or away
from each other. We are already calculating
that value from before and we are setting it to this Dom
property on the player class. We can use the
same value because we still want the direction
of the movement of the projectile to
be determined by the relative position between
the planet and the mouse. We want the projectile
to fly from the middle of the planet
towards the mouse. First I pass start method speed x of minus one and
speed y of one. We make sure that start method
expects these values and it sets its speed x and speed y properties
to those values. They are being added to
its position coordinates here minus one pixel per frame horizontally and plus
one pixel per frame vertically will result in
this direction of motion. Minus ten and plus one
will give us this. Let's use the values we
get from calc method. We know that vertical vector
is index zero in the array, the horizontal
vector is index one. We are using these values, which are the ratio between horizontal distance and
hypotenous distance. Now we can fire projectiles
in any direction. They move quite slow. I can create a speed
modifier property. As long as I multiply both of them by the same modifier value, it will just scale the vector keeping that correct direction. We are running out
of projectiles. Let's create some buffer. Maybe I want to create 30 in the pool to make sure
we don't run out. It does depend on
how fast they move off screen and how
fast they reset. We can test it and
adjust the value later. The projectiles come from the middle of the
players circle, which is not ideal. I want them to come from
the tip of the spaceship. Here on the player class
inside shoot method, we extend the starting x coordinates of
each projectile by that horizontal vector
value scaled to the player's radius that
should sorted horizontally. I also use the
vertical vector here, it's all on one line. I hope you can read my code. We are passing a lot of
stuff to the start method. Now I'm using radius here, because I want the
projectiles to come directly from the tip of
the players spaceship. If you use different
values here like this, it should make more sense what
exactly is happening here. If you play with these
values for a while, if I use this dot
radius here here, the projectile will come out from exactly the
point that I want. We shoot when we
click alternatively. I can also say if the
key that was released is number one on the keyboard
or any key that you choose. Also call player shot. Now I can shoot by clicking the mouse or by pressing
a key on my keyboard. Every time we shoot,
get projectile method. We'll reach into
projectile array from line hundred seven. It will give us one
free projectile object. Start method will position it at the tip of the
player spaceship. And calm method will give it horizontal and vertical
speed that matches the relative distance at
the point in time between planet and the mouse to send the projectile out in
the correct direction. As soon as projectiles move
outside the canvas area, we reset them and they
are ready to be re, used whenever we need them. We implemented an object
pool in our code base. I make them move faster
and I make them smaller. Yes, this is closer to what I actually want them to look
like in the final game. I can delete this consolog now.
39. Project 2: Enemy Pool: Now that we saw how to implement an object
pool in our code base, let's solidify that
knowledge by repeating the same logic to create a reusable pool
of enemy objects. Remember that each
object pool member needs a property that indicates
if it's active or inactive. We only draw and
update if it's active. Start method will
activate the object. It will set properties
to some values that the object needs at the
start of its life cycle. In this case, we position the projectile depending
on the player position, and we give it direction of movement depending on
the mouse position. Reset method is here. To deactivate the object, we have an array that holds the objects. We set the number. And we need two helper methods, one to create the pool and another one to get a
free item from the pool. Let's implement something very similar for enemies to solidify this knowledge and
clarify anything in case you're still not entirely sure how some of this works. Repetition always helps
I create an enemy class. It needs a reference to
the main game object. It needs X and Y, and radius. For example, let's
set the starting X and Y coordinates to 100, 100. Just for now, we will be
cropping rectangular frames from the sprite sheet By
jumping by the width and height of the frame
around the sprite sheet. I created the art assets to match what we need
in game collision. Circles have 40 pixels radius. Individual sprite frames
will be 80 times 80 pixels. This frame size also matches the Space Invaders
game we did before. The enemy spride sheets are
fully compatible and can be easily used in that game as well if you
want to expand it. Speed x and speed Y
will determine in which direction the
enemy will be moving. Since this is a
object pool member, it needs free property. Initially we said it, the true start method will set free to
face to activate it. Reset method will set free
to true to deactivate it. Draw method will expect
context as usual. And the code inside will
only run for active enemies. Update method will also run its code only for
the active ones. At first, let's draw the
enemies as simple circles. The usual combo of begging path arc method, I stroke them. Update method will move
its x and y coordinates around depending on speed
x and speed y values. Same as we did for projectiles. We need an array that will hold all active and inactive
enemy objects. Enemy pool number of
enemies will be 20. And we will
automatically create and fill that pool with
20 enemy objects. At the point when we create
an instance of game class, same as we did for projectiles. Create enemy pool
will create and push 20 enemy objects
into enemy pool array. Enemy class constructor
expects game as an argument. I pass it this keyword, Get. Enemy will cycle over
the enemy pool array we just filled with
20 inactive objects. I will check free
property for each one. And as soon as it
finds the first enemy object with three
property set to true, it returns that
object and it stops searching I consulate
enemy pool. At this point, it
should be filled with 20 enemy objects here. After we call the create
method, yes I see them. I open one just to check. All seems good here.
I delete the consolo. Same as we draw and update projectiles for each
animation frame, we take enemy pool,
call for each on it. For each enemy we call
its draw and update. We remember that these methods will only run if the
object is active, if it's free property
is set to false. Initially, all enemy objects have a free property
set to true. Calling start will
activate the object. I want the first object in the enemy pool array to be
automatically activated. Immediately as the game starts, I take one object
with an index of zero inside enemy pool array
and I call it start method. I can see it here. Let's
activate the first five objects. To do this, we have
to be careful. If we don't have enough
objects in the pool, this would break
the code because I'm not doing an if statement. Check first, make sure the
number here is high enough. I'm drawing all five enemies
at the same coordinates. If I move them,
all of them move, I could set the starting x
and y coordinates to 00. When start method runs, let's set X to a random value between zero and game width, vertical y position to a random value between
zero and game height. Now when I refresh the page, the five enemies are scattered randomly around the
visible game area. As always, we are adding
speed x and speed y values to their x and Y coordinates for every animation frame to give them direction and
speed of movement. Combination of these values will determine their
movement vector. I set speeds 200. We will set their speed
when start method runs. I want the enemy to move directly towards the
middle of the planet. They are attacking us. We need a vector between that random enemy starting
position and the planet. We already did
something similar. I create a temporary
helper variable called am. It will run our utility method, but this time we want directional vectors
between the enemy. This keyword, we are inside
that enemy class as object B. We pass it the planet. If you remember, it returns
an array of four values. The first element,
the element with index of zero is the
horizontal direction. Element with an index of one
is the vertical direction. It doesn't work and it's right. It's because we
need to calculate the aim after we
position the enemies. Enemy Position is of course, very important in this
calculation to get the correct direction between
the enemy and the planet. First I set my position, then we call calm. Now the enemies are moving
towards the planet. Perfect.
40. Project 2: Collision Detection: I want the enemies to go
through the planet like this. I want them to collide with it. Let's create a reusable
check collision method. It will expect object a
and object BS arguments. In this project all
objects will have circular it boxes To check collision
between two circles, circle A and circle B, we have to check the distance
between the center point of circle A and the center point of circle B on the horizontal axis. That gives us this site. Then we do the same on the vertical y axis
to get this side. Now to get the actual distance between these two
points into the space, we need the length
of the hypotenus, which we can get by ptagoras theorem formula or built in mathod
hypotenus method. We have a distance
between two points, between the center point
of circular object A and circular object B. We calculate the sum
of their two radii. If the distance between the center points is
more than sum of radii, we know that the
circles don't collide. It's the same, the circle touch. If the distance is less
than the sum of radii, we know that the
circles collide. I will just directly return
the expression like this, making this method easy to use. We call it, we pass it
object A and object B. And this method will
directly return true or false depending on whether or not these
two objects collide. Let's see if it works by
going up to the enemy class. And inside the update method, we check collision
between enemy and planet. When collision
happens, we will reset the eneme which will
deactivate it and returns it back to the
pool of available objects. I copy this code block. We do the same thing
for the player. If enemy collides with
the player spaceship, we reset the eneme. Let's see. Yes, that works.
41. Project 2: Periodic Events: When we reset the
enemies, they are gone. But now I need more
enemies to keep coming. Right now we only activate the first five enemies
from the pool. Here I want to activate
new enemy periodically. Let's say every 1 second
request animation frame method automatically
generates a timestamp. And it passes that
timestamp value as an argument to
animate method. Imagine it's passing
it here like this. Even though it happens
invisibly behind the scenes, the timestamp value is simply the number of
milliseconds that passed since the first
request animation frame in this sequence was called. All we need to do
to get access to that timestamp value is to
give it a variable name. Here, the Java script will automatically know that
we wanted the timestamp. We also need to keep track of the timestamp from the
previous animation loop. I'll call it last time I put
it outside animate here. Initially I set it to zero. Then we use these values
to calculate delta time. Delta time is the number
of milliseconds it takes our computer to serve
one animation frame. It's the difference between
the time stamp from this animation loop and the time stamp from the
previous animation loop. If you have a fast computer, your computer can
serve frames faster. Your delta time will be lower because that
fast computer needs less milliseconds to generate each animation frame request. Animation frame method also automatically normalizes
the animation speed. If it can do it, it will adjust animation speed to the screen refresh rate because there is no point to calculate and draw animation frames that your
screen isn't able to display. Once we calculated delta time we set last time to the
current time stamp. So that it can be used as the old time stamp from the
previous animation loop. In the next animation loop, most of us have screens that
serve 60 frames per second. 1,000 milliseconds divided
by 60 is around 16.6 my delta time is 16.6 my animation is running
at 60 frames per second. If you have a high
refresh rate screen or if you have a very
old weak computer, your delta time might
be different than mine. Using delta time like this will make sure we
account for that, we count the actual time,
actual milliseconds. This allows us to make sure the timing in our
game is the same. On fast and slow machines, I delete the consolog. I pass delta time
to render method. I will make sure render method expects that value
as an argument, and I consolog delta
time from here. Yes, it still works. Always make sure you
delete your consologs. Leaving a consolo like this can slow your application down. These variables
relate to enemies. I will only add one enemy
at the start of the game. I will need two
helper variables, enemy timer and enemy interval. Enemy timer will count
delta time from zero until 1,000 Then we add new
enemy and we count again. We want to periodically
activate an enemy object from the object pool every 1 second,
every 1,000 milliseconds. If enemy timer is less
than enemy interval, we keep adding delta time value to the timer for every
animation frame, over and over until we get
more than 1,000 And then this L statement will run when timer accumulated
enough delta time. We reset timer back to zero so that it can
count again and we activate one
new enemy help er, temporary variable
called enemy will be equal to our custom
get enemy method. If get enemy was able to return a free and available enemy
object from the pool, we call its start
method to activate it. Nice, We are pulling a new enemy from the object pool
every 1 second. I don't really want the
enemies to appear somewhere in the middle of canvas randomly.
Let's try something. If I call method random
on its own like this, it will return a value 0-1 if
that value is less than 0.5 Roughly in 50% of
the cases we said horizontal position of the enemy between zero and game width, and vertical position
will be zero. Enemy will appear
somewhere in this area. In the other 50% of the
cases we set x to zero. Y will be a random value
between zero and game height. The resulting position
will be randomly, somewhere in this area. Nice, now we can do it for
the other two sides as well. How to do that? For example, we can use a so called
ternary operator. The only Javascript operator
with three operands. We use it as a simple one line FL statement. It goes like this. A condition to evaluate
if true question mark, do this ls colon, do that. If method random
is less than 0.5, vertical position of this
enemy will be zero L In other 50% of the cases
dot y will be dot height. If this code block runs, enemies will be coming
from here or from here. Yes, we do the same
in this code block. If method random is less
than 0.5 this dot x is zero, x is game width, while at the same time, vertical position is random
between zero and game height. If this code block runs, enemies will be coming
from here or from here. Now we have enemies randomly coming from all four directions. When they collide with the
player or with the planet, they get reset and
they disappear. I also want to be able
to shoot the enemies. I want to check collision
between enemy and projectiles. I take the entire
projectile pull array for each projectile object. I check first if the
projectile is active, if its free property
is set to false. Keep in mind that
this code block also only runs for
active enemies. We only want to check
collision between active enemy and
active projectile. If the projectile is active, we also check collision between this enemy and the
current projectile we are cycling over. If projectile and enemy collide, we reset the projectile to deactivated and return it
back to the object pool. When I test it, I can see that projectiles get destroyed
when they hit enemies. Great. I will also reset enemy when the
collision happens. Nice, now we can
shoot the enemies. We have a full game loop and we implemented projectiles and enemies as object pool which
involved some extra logic. But it's worth it for
performance reasons. The code is still clean
and easy to read. I think I don't like how
the enemies just pop in. I want that to
happen off screen, and the enemy just floats into view smoothly and seamlessly. I will push the enemy starting positions outside the screen. I account for the radius, here, here, here and here. Now they start off
screen and they float into view much better. I can make the enemies
comma more often. How about new enemy
every half second.
42. Project 2: Asteroid Enemy: The planet is really
under attack. Now white circles
are attacking us. Let's use some graphics. First, we do a simple
enemy type, an asteroid. We can download the enemy spread sheet in the resources
section below, Give it an idea of asteroid. This is the parent enemy class. It contains all methods and properties shared
among all enemy types. I will create a child class
called asteroid class. Asteroid extends eneme. Now enemy is a parent class, also called a superclass. Asteroid is a child class also called a subclass
asteroid class. Constructor will expect
game as an argument here. First, I want to run all the
code inside the constructor. Inside the parent enemy
class inside its superclass. I want to run all the code here. I pass the game reference along. We know it's expected
there on line in 98. Asteroid child class
will contain properties and methods that are specific
only to this enemy type. Asteroid will have this dot
image property which will point to that asteroid spread sheet we
imported a moment ago. Shared properties
are on parent class. Specific properties and methods
are on the child class. Because we are using the
extent keyword here. Javascript automatically sets up a prototype chain behind
the scenes when we call draw and update method on asteroid class and Javascript
can't find them here, it will automatically travel
to the parent enemy class. It will try to
find those methods there since all enemy
types will have images. I can go up here and I can call build in draw image method. I pass it the image I want to draw and x and y coordinates. Where to draw it, we get an error because we
are instantiating the parent enemy class and the Java script can't see
this image property on it. I have to create an instance of asteroid child class instead. Now we are drawing the
entire spreadsheet. I will reduce the maximum
number of enemies to two so that it's less busy and easier for us to
see what's happening. We can pass draw image method, optional width and
height like this. Now the entire image
will be stretched or squeezed to fill that
predefined area. In this case, I'm squeezing
the entire asteroid spread sheet into an
area of one frame. We can also pass, draw
image nine arguments to get the maximum control over
the image we want to draw. We pass it the image we want
to draw source x, source Y, source width, and
source height of the area we want to crop
out from the source image. And we pass it destination
X, destination Y, Destination with and
destination height to define where on destination canvas we want to place that cropped out
piece of image on two. First, I crop out this
frame from coordinate 002 with and height
of one frame. Nice again, as we know, circles and rectangles are
drawn differently on canvas. To align the rectangular
asteroid image over its circular head box, I need to offset the horizontal
position destination x argument by the radius. Like this, I also offset the vertical Y
coordinate by the radius. Like this planet is being
attacked by asteroids. Now I increase the
size of enemy object, pull back to 20. I want a new enemy
asteroid every, let's say 1,700 milliseconds. I only want to draw the
collision circles on enemies. If debug mode is active, I want to be able
to show and hide them by pressing D
on the keyboard, by totaling debug
mode on and off.
43. Project 2: Sprite Animation: We can navigate around the spreadsheet and
decide which frame we are currently cropping
out by adjusting the value we pass to
draw image method. Here source x argument
can be like this. Zero times width of
a single frame will give us this one times width, we get this frame. Five times width is this frame. The way I structure
this spry sheet. Horizontal navigation
around the spry sheet will play explosion
animation frames. Source y argument will go up and down around
the spread sheet. Zero times height is this frame, This is one times height, two times height,
three times height. Source y argument passed
to draw image method will determine which one of these four available
asteroids we are drawing. I will put this multiplier
into a variable. I call frame Y. I
can put it here, but maybe each enemy
type will have different spreadsheet with different number
of animation rows. I put frame Y here on
the asteroid subclass. I want to randomize it. Each asteroid object will
be randomly assigned one of the four variations of the
asteroid I created for you. I need to wrap it in method
floor to round it down to integers because there is
no row 1.5 For example, frame Y will be
randomly zero or one, or two, or three since
enemies are an object pool. If I play like this, I'm constantly just re using the first one or two
objects over and over, returning them to the pool, taking them again, that's
why the images don't change. I can, for example, randomize the row every time enemy object
is activated here, inside the start method. For now this is fine, although it can
give us issues if other enemy types don't
have force sprite roles. I can also make the enemies more challenging by giving
them multiple lives. I give enemy object
a custom at method. This method will take damage
value as an argument. When it method runs, we reduce lives of
enemy by damage. Here we check collision
between enemy and projectiles. Instead of resetting
the enemy immediately, I will call that hit method
and I pass it one as damage. Doing this means that when we hit an enemy with a projectile, we reduce its lives by one if lives of the
enemy are less than one. I want to keep
increasing frame X to play the explosion animation, we need to use that
frame X value here. As it increases from 01234, we are cropping out different
horizontal sprite frames and we are playing
the animation. I could put frame X
on the parent class, but let's keep it on here on the child class next to frame Y. Now when I hit asteroid five
times and deplete its lives, the rest of the spread sheet will play the explosion frames. Now we need to also
reset that asteroid when all its horizontal
animation frames are played. I will give it a
max frame property and I will set it to seven. We know that once all seven
frames were displayed, we are safe to reset it and
return it back to the pool. If frame X is more
than max frame, we call reset since enemies
are object pool members, we also need to
reset these values. As we reuse these objects, I will give it max lives property and it will
remember what was the original amount of lives of that enemy before we
started damaging it. Now when I shoot the asteroids, they don't come back
because their frame X is stuck at a high value
and they keep resetting. When we start an asteroid, we need to make sure it's
Frame X is set back to zero. Its lives need to be
set back to max lives. Nice, now it all works. When the debug mode is active, I want to display the
number of lives as a big number drawn
over each asteroid. We will do it here inside this conditional code block that runs only when debug is active. I call built in fill text method that takes at least
three arguments, the text to draw and
x and y coordinates of anchor point in relation to which the
text will be drawn. The text I want to
draw is lives property of the enemy and we
want that number to travel around as
the enemy moves, I give it xy coordinates
of the enemy. Down here where we
set our canvas up, I will define canvas
font property. Let's set it to 30 pixels. Helvetica fill style
will be white. I increase the font size. As you can see when we
position the text anchor point exactly in the middle
of our circular asteroid, the text is drawn
towards right top. From there, the anchor point sits on the left bottom edge. Text Align Property defines where the text we draw on canvas sits in relation to its anchor point on
the horizontal x axis. I set it to center text
Baseline property defines the relationship between text and its anchor point Vertically, I set it to middle. Now the text is centered
over the asteroid. Nice, when we destroy
the asteroid, the explosion, animation
frames play too fast. We are using sprite sheets
with limited number of frames to help us
with performance. Now we need a way to slow down the speed at which the
sprite sheets swap frames so that
users can actually appreciate the details
of the animations. We will use the same
technique we used here with timer and interval
helper variables. Sprite update will be false. It will switch to
true periodic ledges for that one animation frame to allow all the sprite sheets in our game to jump
to the next frame. Sprite timer will
count delta time from zero to sprite interval
value over and over. Every time it reaches
the interval it triggers sprite update and resets back to zero so that
it can count again. Let's say I want to trigger sprite update every
150 milliseconds, we will write the logic to
periodically update sprites. Here we are repeating the same logic we used
to activate enemies. If sprite timer is less
than sprite interval, keep increasing sprite
timer by delta time, which we are already
calculating from before. It's the time in milliseconds. It takes our computer to serve
one animation frame else, meaning that sprite timer
is higher than interval. It accumulated enough
milliseconds through delta time, we can reset sprite timer
back to zero so that it can count again and set
sprite update to true. I want sprite update to be true just for that one
animation frame. Here, I will set
it back to false. Now this code block means that sprite update is set
to true for one frame. Every time sprite interval
value that we set, 250 milliseconds is reached. When I consolo sprite update, we see it is false and it is
periodically set to true. Great, every time it is true, I want to animate sprite sheets, I want to jump to a new
frame in the sprite sheet. Let's not forget to
delete the consologe. We will handle sprite
animation logic here if the lives of the
asteroid are less than one. Only if sprite update
is true, only ten. We increase frame x by one. Now the explosion
animation play slower. We can set that
speed to anything we want using sprite
interval property. The problem is that now when
the explosion play slower, I notice that the
projectiles still collide with asteroid that
is already exploding. I want the projectiles
to ignore explosions. We only check collision
between enemy, asteroid and projectiles if the lives of the enemy
are more or equal to one. If the lives are less than one and explosion
animation is playing, projectiles will fly through it. Yes, this is much better. I can show and hide hit
boxes by pressing letter D, by enabling and disabling
the D bug mode.
44. Project 2: Lobstermorph Enemy: I want the asteroids
to have only one life. It will be a simple,
basic enemy type. Our lasers are calibrated for asteroid defense and we can easily destroy each
one by a single hit. Let's add a more
challenging enema type. You can download Lobster
Morph Spread Sheet in the resources section below. As usual, I put it into my project folder and
I included it here, giving it an idea
of lobster morph. At this point, it's
easy to implement more enemy types because we already have most of
the logic in place. I copy this entire code block for asteroid and I rename it. I point this dot image property to the lobster
morph spreadsheet. I can set max frame to 14. We know that when we
reach that point, it's safe to reset the enemy. I created the spread sheets
with a specific plan in mind. Asteroid will have one
life when we hit it. The rest of the spreadsheet will play the explosion animation. Lobster morph will
have eight lives. Each time we hit it, we jump by one frame until we
deplete all eight lives. Then the rest of the frames will play in one animation sequence. Down here I comment
out asteroid class, and instead I will push lobster morph enemy type into
enemy pool array. Okay, we have some
aliens incoming. I play to see what
we have so far. I can press D to
disable debug mode. I can see that when I hit them eight times and
deplete the lives, the entire spreadsheet plays. I can go here inside hit method. And when we hit the enemy and we decrease its lives
by the damage value, I can set frame X to be max lives minus
the current lives. This will work already. It goes to a new stage
every time it loses a life, and then the explosion plays. I can do it differently here. I can say if enemy lives are more or equal to one,
every time we hit it. Increase frame X by one
we get the same behavior. Enemies get instantly
reset when they collide with the player
or with the planet. When enemy collides
with the planet, I instantly set its lives 20 which will play
the spreadsheet. I also want the enemies to stop moving at the
edges of the planet. I set speed x20 and speed y20. Collision with the planet happens when enemy
hit the planet. Now they stop and they play
their animation sequence. I think this looks
more interesting. They play the sequence when the current frame is
more than max frame, they reset and return
back to the object pool. When enemy collides
with the player, I will just set lives to zero. To make it animate. I'm not
going to stop its movement. You can make your
own choices Here, design the game how you want it. I'm just showing you how
the code base works. We have a bug if you
consulate enemy. Each time we activate one, you will notice some of
them are stuck off screen, not coming from one side
of the screen at all. Because their horizontal
position is none, not a number. It's because I said this
game dot radius here, I need to use the radius
of the enemy instead. You probably noticed
that before. Anyway, now it's fixed. I want to rotate the
enemies to make sure they face towards the planet in the direction
of their movement. Every time we rotate canvas, it adds up and it affects
everything drawn on canvas. I want each enemy to be rotated by its
individual angle value. I will restrict any changes
I make to each enemy drawing code by wrapping this code block between safe and
restore methods. We save canvas state, we make any changes
here we want to be applied to these
drawing calls. Then we reset back to what it was before to make
sure the shapes drawn after this remain
unaffected on canvas. I can't just simply rotate
if I rotate by 0.1 radians. Here you will notice
that everything is a bit off to rotate
anything on navas. First we have to translate
rotation center point, which is the same
as coordinate 00 on navas over the center of the
object. We want to rotate. I translate to zx dot Y of the enemy we
are about to draw. Now that we translate it, we have to keep in mind that we are translating
rotation centerpoint as well as the point that is considered coordinate
00 on canavas. We are translating it
over the enemy position. Then we are going further from there to actually
draw the enemy. We effectively doubled
the enemy position, pushing it further
to the right and to the bottom of screen. Enemy position is now
defined by the translate. So we have to actually draw
everything at coordinate 00. If I draw enemy
collision circles at coordinate 00, we get this. I also have to do that for the text coordinates that
display enemy lives. For the actual image which
is centered over the circle. By offsetting it by radius, we do minus radius
here and here. To rotate something on canvas, we translate over the
object we want to rotate. Then we draw that object
at coordinate 00, because the object's position is now defined by translate. Now I'm free to rotate
it and it will work if I rotate by 0.1
radians, 0.5 radians. Instead, I will use a
distal angle property. I define it here, 1 radian means all enemies are rotated
like this, 2 radians. I want the rotation
angle to depend on the relative position between
the enemy and the planet. We already wrote
helper Lkm method and we are using it
to set enemy speed. Lkm method gives us directional
vectors as well as DX and DY vertical and horizontal
distance between the two objects we pass to
it as arguments I will need. I can see these are
elements with indexes of 2.3 returned in this array. Initially, I set angle 20. It doesn't really matter
because when start method runs, we set this angle based on the relative position between
this enemy and the planet. We already did
that when rotating player spaceship away from
the planet centerpoint. It's the same thing here. We use built in method 102 method that will
give us an angle between positive x axis and a line projected towards
a specific point. We know it expects dy first and dx as the
second argument. I have to pass these
arguments in this order. Enemies are not pointing
where I want them to. But it doesn't matter because
we know the angle we are getting is relative to the starting position of
the enemy and the planet. Start method runs just
once to set the enemy up and I can adjust that
resulting value in many ways. I could rotate the
entire lobster or spreadsheet in
my graphics editor, and then I could use that, but I could also easily
do that with code. I can see I simply need to rotate the enemy by 90 degrees, by a quarter of a circle, to make sure they
face the planet. I add method pi value here. If I add method pi, I'm rotating it by 3.14 radians, by 180 degrees, half circle. I only want to
rotate by a half pi, by 90 degrees, by a
quarter of a circle. Even if you don't fully
understand method 8102 yet, just remember how to use it. Pass it the points
that are relevant in the rotation you want and
see what result you get. Then you can easily adjust that angle further
like we just did. There might be even
a simpler way to do this that I'm not
thinking of right now. I know some of you will
give me your tricks and improvements in the comments,
and I appreciate that. Anyway, enemy aliens are now
facing towards the planet. Every time we hit them, we push the spreadsheet to the next frame when
lives are depleted. Animation, Play
animating spread sheets like this is very cheap. Performance wise, your game
will run smooth If you are happy with a low frame rate sprite animation like this, I think the game looks alive. Even with this performance friendly sprite
animation techniques, I like how it reacts
to player actions. It makes the game feel very
interactive and responsive.
45. Project 2: Game Text : Draw status text method
will display score, player lives, and
game over messages. It will expect context
as an argument. Font will be 30 pixels impact. For example, you can easily use any custom Google
fonts here if you want. Just import them in index HTML. Give canvas that font
family property in style CSS and use the name
of that font family here. Instead of impact it will work. I use custom fund
in other games many times before this time
let's just go with default impact fund and focus on other things I call built
in fill text method. Text will be score
coordinates will be 2030. I want the text to be drawn over the planet but
under the player and projectiles and
enemies will create a nice layer effect
when the text is in the middle
of game objects. When I call draw status
text and I pass it context, I can see that score
text is center aligned and enemy lives are now
drawn in different font. This declaration changed the
entire canvas state again. I wrap it in safe and restore so that I
can give status text specific settings
without affecting other texts and shapes
drawn on canvas. I said text align to left. I create this score property. I go to the main enemy
class when enemy finished playing
explosion animation and is reset back
to the object pool. I increase the score
by enemy max lives, which means easy asteroid, enemy will give one scorepoint. Lobster morph, enemy will
give eight scorepoints. It has eight lives here, I add score to actually
draw the value. Every time I destroy an enemy and the explosion animation
finishes playing, I get rewarded
eight scorepoints. You can see that every
time enemy explodes, we get eight score points. I only want to award the player scorepoints if we destroy the enemy
by shooting it, the enemy gets destroyed because it collided with the
player or the planet. We don't want to give
score points for that. I will give each enemy a
property called collided. Initially, I set it to false. When we start the enemy, we always have to reset
it back to false. If enemy collides
with the planet, we set collided to true. Also, if the enemy
collides with the player. If enemy collides
with a projectile, we do nothing, which means
collided will stay false. Here, we only add score
if collided is false. If I shoot the alien, I get eight score points. If the enemy collides with
the player or the planet, we get no score points
for that grade. I create a property
called winning score. Initially for the testing
purposes I set it to ten. We will handle win or lose condition here inside
the render method. If score is more or
equal to winning score, game over is true. Down here inside the
draw status text method, we will handle game over
messages Game over is true. I want two big messages in
the middle of the screen. We set text a line to center. I can, for example, create two Led variables called message one
and message two. They will be assigned
a different text when we win and when we lose. If we win, if score is
more than winning score, message one will say you win. Message two, your score is, We display the score value plus an exclamation
mark like this. Now we will draw them. Message one will be
large, 100 pixels. Impact, Again, you can use your own custom font
here instead of impact. Filltext method will draw
message one in the middle horizontally and 200 pixels
from the top vertically. Another fill text for
message two, again, in the middle horizontally, and let's say 550 pixels
from the top vertically. Let's try to win the game. I need ten or more scorepoints. Nice, I want the second
message to be smaller. How about 50 pixels
impact again? I shoot enemy one, enemy two. Here we go. Yes,
that looks better. I want the enemies
to stop coming when game over
happens on a pool, a new enemy object
from the pool, if gameover is false.
46. Project 2: Player lives: Now, how can we lose this game? We can, for example, give the player a
limited number of lives. I put those lives here on the main game object.
I go down here. Oh, this restore needs
to be down here. Otherwise the numbers we use to display enemy
lives will change. 250 pixels impact when game over message
appears, now it's fixed. I want to draw player
lives under the score. I create a four loop. It will run once for each life, and it will draw a
rectangle for each one. Left margin will be 20 pixels. Horizontal position
will determine the space in between rectangles, so it will be 15 pixels. We space them out by multiplying that space in Tams index in the four loop vertical position will be maybe 60
pixels from the top. The width of each rectangle
should be less than space. In, for example, ten
height will be 30 pixels. You can change these values
to see what they do. This is the left margin, this is horizontal spacing. If the width of each
individual rectangle is the same or more
than the spacing, they will turn
into a single bar. You can build loading bars
like this if you want. I want them to be separate here, so I go for ten, I guess. If enemy collides
with the planet, I take this game lives
and I reduce it by one. Also, when collision with the player spaceship
happens, I test it. When we collide, it immediately
takes all five lives. Because it's taking one
life per animation frame. We only want this
code block to run for enemies that have lives
more or equal to one. We enter this code block
and we set lives to zero, which will prevent the same
code block from running. Again, this should fix it. I do the same for
player collisions. Yes, now one collision takes
away only one life Perfect. Here inside win and lose condition code block I say
if score is more or equal to winning score or if the lives is less than
one in both cases, set game over to true, which will stop
enemies from coming. And it will display big
message one and message two. If we win, we set
messages to these else, meaning we lost all lives. Message one will say you lose. Message two will say try again. I test it. I shoot
enemies, I win. Enemies collide with the planet. We are losing lives, we lose all lives and losing
message appears great. We have a complete game loop. Initially, I will
set D bug to false. We can still toggle it on and off by pressing letter
D on the keyboard. I want to bring all enemy types into the game at the same time. Here where we create enemy pool, I create a random
number variable. I set it to random value 0-1 if random number
is more than 0.25, in 25% of cases we
create an asteroid L. The other 75% of cases we create lobster Moorph Asteroid is the easy enemy type
with one life. Lobster morphs are
tough enemies that morph and split so they
take longer to defeat. I want to add a new
enemy more often. Let's say every 1 second I want to be able to make
enemies move faster or slower. We did the same thing
with player projectiles. I create a speed
modifier property. I want to slow them down. In this case, speed modifier will be a random number between 0.1 and 0.6 Then I include
speed modifier here, where we calculate horizontal
and vertical speed. I'm scaling the vector here. I prefer the game like this. We have many more
enemies on the screen, but they all move slower, giving us more time
to strategize. It's up to you to choose
if you want the game to be more about fast reactions
or positioning. End strategy, you can
tweak enemy frequency and movement speed based on what
kind of gameplay you prefer. How about enemy interval, 800 milliseconds and 30
lives, 50 winning score? I will adjust these
values later.
47. Project 2: Beetlemorph Enemy: I built another
space game recently inspired by the classic
Space Invaders. I made sure that
the spread sheets from that game are compatible. Here let me show
you how we can take them and easily use
them in this project. If you follow that class, you already have
the sprid sheets. If not, you can download them in the resources
section below. I import beetle morph
spreadsheet into the project. To add more enemy types
into this game is extremely easy because we
already wrote all the logic. I can just copy this code block. I rename it to Beetlemorph. I adjust this dot
image property, I check the spread sheet, max frame will be three
and lives will be one. This enemy is easy to defeat. Down here where I
create enemy pool, I can say if random number is more than
0.5 Add Beetle Moorph. I have to be careful
about brackets here. I have to change
the condition here. If random number is less than
0.25 we create asteroid. If the number is less than
0.5 we create Beetle Moorph. In the other 50% of cases, we create a lobster morph. That was easy. Now the game
has even more variety.
48. Project 2: Rhinomorph Enemy: Now that we understand
the process, let's take one more
enemy type and use it. I copy this code
block and I create rhinomorph enemy type,
the armored enemy. Again, you can download the
spread sheet down below if you don't have it from
the Space Invaders project. Here I add one more L if random
number is less than 0.75, we push Rhinomorph into enemy pool array
Code editor colored this class name differently
to remind me that I still haven't written
that class up here. I copy this code block. I rename it Rhinomorph. I point its this dot image
to the new spread sheet. Max frame will be six, it will have four lives. Now our game has four
different enemy types. Simple enemies with one life will be asteroid
and Beetle morph, an armored eneme eneme that
splits into smaller clones. When we hit it said enemy interval to
1,200 milliseconds and play lives to ten if you watched the extended Space
Invaders class over there. We also have Squidmorphan
inflatable eneme that absorbs player
projectiles until it pops. We also have Eagle Moorph, an eneme that sacrifices its own body segments
and spits them back at us as
mutated projectiles made out of slime and tentacles. All of those art assets are fully compatible
with this code base. A challenge for you is
to take them if you follow the extended class and implement them in this game. If you want, you can also take the unique enemy
from this class, the Lobsta Morph, and implement it in the
Space Invaders game. These two games are
completely cross compatible. I only want to increase the
score if game over is false. Like this, it's easy to do
and if you need my guidance, I implement Lobster Morph into Space Invaders game in
the extended class. If you are watching this
on my Youtube channel, the most popular and recent
extended bonus classes are linked on the about page. Hope you had fun on
this space adventure. This was a big
project. Well done. If you go this far, let
me know if you found some value and learn something new by leaving a quick comment, I wonder which part of this project was the most
interesting for you. Can you just leave a quick
comment and tell me which is your favorite enemy
type Out of all of them, it can be either
what it looks like or its unique behavior
and mechanics. Looking forward to see
what you think and which one is your
favorite. See later.
49. Project 3: JavaScript Mobile Game: Headquarters. Let's
build a mobile game. Don't get any closer
to that gas giant, something is stirring in
the upper atmosphere. First we will write all the game logic and implement
all the main features. You can reuse that
boilerplate later for many different touch
based full screen games. And then we add
graphics, animations, and sounds, turning it into
a cartoon space horror game. I want you to build
projects you can be proud about and you can
show to your friends. This one will be a
full screen game. Touch events look layable on any device from a big
horizontal computer screen to a small vertical mobile phone so you can carry it
around with you if you want to show everyone what you learned and what you can
do with Java Script. Our presence triggered
a feeding frenzy. They seemed to be attracted to the life signs of our non
mechanical crew members. Immediate retreat
advised endemic to the supercritical fluid layers
of this distant gas giant. This creature lives in
an extreme pressure. They can face through
solid obstacles. We need to hit them
when they're in their solid state to time
your attacks carefully.
50. Project 3 Setup: In index HTML, I create
a simple blank web page. I give it a title, I link CS style sheet, I create HML five canvas element with an ID of canvas one. And I link my Java Script
file in style CSS. I do a global reset
to make sure we get a consistent behavior
across different browsers. By removing the default styles, I give canvas a border
position absolute. I center it in the middle of the page vertically
and horizontally. You can download two
background images in the resources section. Below. I take the
horizontal background and I set it as background URL. Here, background size is cover, which will scale the image to the smallest possible
size That still completely covers the
width and height, leaving no empty space. I create a diff with
a class of controls. We will have a full screen
button and a reset button. We will also control full
screen and reset with keyboard, but we will need these
patterns for mobile devices. I will give these
buttons with height, black background, and
white text if you want. You can also use a custom font. For example, I can go
here to Google Fonts. I search for a font
called bangers. I select the font
here and it gives me links that need to be
included in index HTML. Ideally, up here before I
include style CSS like this. Now I can use bangers
all over my code base. For example here for
font on the buttons, I also give them a white border.
51. Make Everything Responsive: We will write all
the logic here with Java script in main JS file. Maybe I will split this
into multiple files later. Here we can define our classes, but we make sure we only instantiate them after
load event has fired. After all the resources
are loaded and available. All the style sheets, scripts, HTML elements, images and so on. Once the page has loaded and we know HTML cannavas
element is available, we can set our project up. I point to Java script towards
my canvas element using Get element by ID, I define CTX. That will store an instance
of built in canvas to D API. I said canvas width
and height to cover the entire browser
window in sty Sss, I said background position to center to make sure the planet
is always in the middle. On any screen size, I position the controls
left bottom Like this. I can also organize the buttons vertically
using flex box, transparent background,
and no border on controls. Here, margin on
buttons five pixels, I can give it box
shadow and text shadow. For example, transition 0.3 seconds on hover,
active and focus. We animate box shadow
and text shadow, and also the button
background game class will be the main brain
of the code base. It will manage the
state of our project. It will expect cannavas
as an argument. Inside we convert it
to a class property, and from there we extract
width and height, because I want to make sure
that width and height of the game is always the same as the width and
height of cannavas. We give it a custom
resize method. It will expect width and
height as arguments. And we will resize canavas
element to these new values. It will also set the width
and height properties from lines 8.9 to
these new values. I create an instance of my
game class expects Canvas. So I pass it canvas
variable from line 20. When we create an
instance of a class, all the code inside a class constructor is automatically executed
line by line. I can take advantage
of that and I can put my code in here
that I want to run. At the point when an instance of this game class is created
using the new keyword, I can even put event
listeners here. We will listen for resize event. The only problem is that
the event listener is on the window object
and I'm placing it lexically inside
my game class. If I want to access this keyword from
inside this callback, it will refer to
the window object. For that reason, when I try to resize my browser
window and built in resize event triggers Using this dot resize here
will result in an error. Game object is not
visible from inside here. We need this keyword in here to point towards the game object to allow us access to properties and methods that sit
on the game class, including our custom
resize method. I can do that by using ES six arrow functions here instead of a regular
callback function. One special feature of
arrow functions is that they inherit this from
the parent scope. Now using this dot
here will point towards the game object which
is exactly what we need. Now when resize event triggers, we are setting
width and height of navas game to 150
times 150 pixels. If I consologe the
auto generated event object itself, I can explore it. This object gives
us all kinds of information about the
event that happened, in this case the resize event. For example, here inside target, we can find in width and inner height properties
which will give us the new dimensions
we resized to. I can take these
values and resize my canavas and game
to these new values. Now when we create an instance
of game class on line 29, all the code inside the
constructor is executed. Resize event listener is
automatically applied. Now I can resize my
browser and canavas will also cover the entire
available browser area. We can also click down here
to emulate mobile devices. It is not always 100% accurate compared to testing your code
on that device natively. But it is close enough
to get started. This is Google Chrome
browser, by the way. It might look
different for you if you are using a
different browser. I recommend using Google
Chrome for this course. I can now select different
devices from this drop down, and I can see what the
game will look like. Sometimes the preview
glitches out. When we swap between devices, I need to manually
refresh the page. This doesn't really matter
because in the real world, no one will be swapping
between iphone and Samsung phone without
reloading the page. Reloading the page always fixes
the positioning problems. If they come up,
you can see that our simple prototype works
relatively well already. Let's keep working on this. I can also create a media
query for small screen sizes. I will assume this
is a mobile phone. And in that case, I will use a different background image
that is more vertical, suitable for vertical
mobile screens. I'm giving you both of these
high resolution images in the resources section below. You can also crop this image and use it for other
projects if you want. Now we have a very simple responsive game
world that works on wide horizontal screens as well as on tall vertical
screens. Perfect.
52. Enemy Class: To keep this code base
easier to navigate in, I will create another Java
script file called enemy JS. Here we will have our
enemy class and we will extend it into
multiple enemy types. I could use Javascript
modules here, but then I would
have to show you how to run a local server. I will keep this
simple and I will just include MGS file here like this. It needs to come before main GS. When we do it this way, the order in which we include individual Javascript
files can be important. I can cut enemy class from maina JS and I paste
it here inside NMS, enemy will expect a
reference pointing to the main game
object as an argument. Inside we convert it
to a class property. Through this reference, we
will be able to see and access all the properties and methods on the main game class. Each enemy will need
X and Y coordinates, so that the Java script knows
where on canvas to draw it, it needs speed x and speed Y. And within height, I
will set it to 50 50. Initially, custom draw method will draw the enemy on screen. Game will have a render
method that will draw all enemies as well as
all other game objects. As we build our project up, it's good to test
it step by step so we catch any potential
errors at first, render will expect
context as an argument. From there we will call built in fill rectangle method to draw
a simple rectangular shape. I call this render method using the game
variable from line 28. It expects context. I pass it CTX from line 24. The default fill style, if we don't override
it, is black. We are drawing a black rectangle at these x and y coordinates. With this width and height, I can set fill style
to a different color. Here, for example, green. We are building a game and
things will be moving around. We achieve that by
drawing, updating, and redrawing the
objects over and over to create an
illusion of movement. Custom method I call,
for example, animate. I will call game dot
render from inside. And I call built in request
animation frame method sits on the window object. But we don't need
this part Javascript, we'll know where to find
that method automatically. I pass it animate, and I call
it here to start animating. We are already animating. I can prove it by creating
position x property, using it here as X
position on the rectangle. I increase position x by one for each animation frame.
We see old paint. I can also clear canvas
from the old paint between every animation frame using built in clear rectangle method. Now we have a moving green
rectangle we are animating. I can delete this when I resize browser window Resize event
listener will trigger our custom resize method which sets canvas to the
new within height. As a side effect, when
we resize canvas, it sets canvas state to default. All the properties
will be reset, including fill
style, which will be reset back to the
default block color. For that reason, when we resize, we need to redeclare
these styles. One way to solve this is to
make sure we have access to this CTX variable from inside our custom resize method to make sure the green
fill style applies. Even after resize,
I can pass CTX from line 24 game class constructor as the second argument
here up here, I make sure it is expected, I convert it to a
class property. Now I can call built in canvas drawing methods
and properties from this. While inside the game class, this line gets overwritten
when resize method fires. Instead, I will set fills
style to green here. After we resized, I will give our game a custom start
method that will run just once when the game
starts or when it restarts. And it will set
everything up first. I just call resize to make
sure the green fill style applies when the game loads it expects with and
height as arguments. Initially, I pass it window in width and window in height. Same as we are automatically apply in the event
listener here. We will also automatically
trigger this start when an instance of the
game object is created in the new keyword here. Now the color we give
to Phil style will stay even as we resize
the browser window. Perfect. Now I can take
this Dogamex from here. Access through this property I call Phil rectangle
representing the eneme. We pass it xy with in
height of the enemy object. I can also give the eneme a
different Phil style here. We need to give it some
X and Y coordinates. Here I'm including enemy
GS before main GS. Enemy class should be available from inside the game class. Here I create a property
called dot enemy one and I set it to an instance of our
custom enemy class. It expects game as an argument. Here, here we are. Inside that game object. I pass it this keyword
through which enemy object will have access
to all properties and methods on the game class. From render, I want
to call draw method which is defined on enemy class. I say this enemy one
from line seven, draw from line 11. Here, nice, we are
drawing an enemy. And this confirms we have
successfully established a two way connection between
game and enemy classes. Nice work. This render method is called over and over for
each animation frame. Here we are just redrawing the same red enemy at
the same position. But we can also move it around by calling
its update method. It doesn't exist yet.
I define it here. For each animation
frame I want to increase horizontal position
of the enemy by speed x and vertical position by speed y plus one pixel on
the vertical y axis will result in movement
from top to bottom. Can also say if enemy moved
all the way to the bottom, set its vertical position
back to the top, I make it move faster. I can also randomize the
initial horizontal exposition. I also randomize it
every time we reset. I can create multiple
enemies like this. You don't have to do this part. I'm just showing you a very inefficient
way how to do this. It could work if we have just
one or two objects maybe. But in this game I
want to have tens, or maybe even
hundreds of enemies. I can randomize speed Y to make them move at
different speeds. If I randomize this
on the first lo, start from a different
vertical position, I can make them reset all
the way behind the top edge. So they slide in from the
outside of canvas area. We know that everything works. And we have an enemy
class that can be instantiated multiple times
to create a swarm of enemies. I delete all of this
because there is a better way to
structure this code.
53. Object Pool Design Pattern: I'm going to turn
each enemy object into a reusable
object pool member. It means that we
will take it from the object pool and activate
it when we need it. And when we are done with it, we will just return it to the object pool rather
than deleting it, and we will reset
its properties. Reusing our objects. Using object pool design
pattern like this can massively improve
performance of our code bases. Because we avoid
issues related to automated memory allocation and garbage collection processes that run automatically. When we create and destroy
many objects in a code base, we will reuse our enemy objects over and
over saving memory. Every object pool member
needs to have a property that indicates if it's currently
active or inactive. I will call it free. If the object is free, it is inactive waiting in
the object pool to be used. If free is false, it means the object is
currently active in the game. It is being drawn and
animated and it is not free. It is not available to
be taken from the pool. Each object pool member
needs two helper methods. Start method that will run. When we activate it site, we set free to
false reset method that will run when we are
done with the object. For example, when
enemy gets destroyed, reset method will set free
property back to true, returning the enemy into a pool of free and
available objects. We will only update and
draw the active enemies. We will run update
and draw methods only if this dot free
property is false. Enemy is an object pool member. It has a property
that indicates if it's free or if it's
currently being used. It has a method to activate it and a method
to deactivate it, update and draw Methods Only run their code for active objects. When the enemy moves off screen, we call reset method to
deactivate the enemy and return it back to the object pool some properties on
the enemy object. For example, the x and y
coordinates need to be reset every time
the enemy object goes through its life cycle. We can reset them at the point where we take the
object from the pool. Or at the point when we
return it back to the pool. I will choose to
reset the properties. Inside the start method, we will randomize the
enemy horizontally, and we will set it just behind the top edge of
canvas vertically. This is how you turn
a regular object into an object pool member. The second part to this would be managing the object pool itself. We will do it from our
state management class. Enemy pool will be
an array that holds all active and inactive
enemy objects. We need to choose how
many enemies we create. This number will be static. It needs to be high enough
so we don't run out, but also not too high because
we would be wasting memory. We can adjust it later
When the game is playable, 50 seems like a reasonable
number game class will need two helper methods
to manage the object pool. A method to fill the pool
with 50 enemy objects. We will run a for loop. Each time it runs,
it takes enemy pull array and it
will push new enemy. In there, it expects
game as an argument. I pass it this keyword
here. We did that before.
54. Periodic Triggers: I want to call create
enemy pool automatically. When we create an instance of game class I will cons log that enemy pool
After we created it. I need to use this dot here in console I can see we have an
array of 50 enemy objects. I open one to check, we don't see any
undefined or null values which could indicate a problem. Now I want to periodically
activate a new enemy. Let's say every 1 second,
every 1,000 milliseconds. We are using request
animation frame, which is automatically
generating time stamps for us. Let's use them. I will create a helper variable
I call last time. Here we will hold
the time stamp from the previous animation
loop request. Animation frame method is
auto generating time stamps. All I have to do to access them is to assign them
a variable name. Here it will know that we
want that time stamp value. If we do this for
every animation frame, I want to calculate delta time, which is the difference in milliseconds between
the time stamp from this animation loop and the time stamp from the
previous animation loop. After we calculated delta time, we will set last time to the current time
stamp so that it can be used as the old time stamp. In the next animation loop, delta time tells us how
many milliseconds it took our computer to generate
one animation frame. Then we can pass that delta
time value to render, where we will write some simple logic that will accumulate the small millisecond values until a certain
breakpoint is reached. Then we can trigger
an event request. Animation frame automatically adjusts the speed at which it serves the next
animation frame to the screen refresh rate because it would be a waste to
calculate positions for frames that your monitor or your phone screen is
not able to display. In most cases that would
be 60 frames per second. In render, I make sure
delta time is expected. I consologatee
thousand milliseconds divided by 60 frames
per second is around 16.6 It takes my computer around 16.6 milliseconds to calculate and redraw each animation frame. Perfect. As the code runs, we are generating time stamps, calculating delta time and
passing that to render method. From there, we can make this
value available all over our code base to create any kind of periodically
repeating event. Let me show you how it's easy. We will need two
helper variables, anime timer and anime interval. Animate timer will accumulate
delta time 0-1 thousand. Then it will activate one enemy and it will
start counting again. We need one more helper
method that when called, will search enemy pool for a free and available
enemy object and it will give it to us. Inside we run a for
loop over enemy pool. As soon as we find the first
object in the pool with free property set to true
the first inactive object, we will return that object. I can put that logic
that activates new enemy every 1,000 milliseconds
directly into the render method. But to keep the
code a bit cleaner, I'll write it in a
separate method handle. Enemies will expect delta
time as an argument. If enemy timer is less
than enemy interval, enemy timer starts from
zero and enemy interval is 1,000 as long as the timer
is less than the interval, we keep increasing
timer by delta time, else meaning that the timer has accumulated 1,000 or
more milliseconds. We set it back to zero so
that it can count again. And we call get enemy to
activate one object member, set it to a variable, and I call start method on that inactive enemy
object to activate it. It will reset the
object properties to default and it will
mark it as active. There is a chance we don't
have any inactive enemies in the pool when this code runs
which would break our code. I only want to trigger
start method if get enemy method was
actually able to find an available
inactive enemy object. I delete this consolo, I will call handle enemies
from inside render method. Which means it will be
called over and over for every animation frame
I will consolo enemy. Here I see that it is correctly
activating one enemy. Every 1 second I
delete the consolo. Each enemy object has
update and draw methods. These methods only run when
that object is active, when its free property
is set to false. I can now run for each method on the enemy pool and for
each enemy object, both active and inactive ones. We call their update
and draw methods. I put with and height up here. I want the initial
vertical position to be minus height like this. This is how you implement a simple object pool design
pattern with Java script. We are activating new enemy object every 1,000 milliseconds. We are returning them back to the object pool when
they move off screen. Reusing our objects like this, rather than constantly creating new ones and deleting old ones, will improve performance
of our code base. Especially in projects where we have large quantities
of objects, I can make them float
into view very fast and then start moving at their
regular speed like this. As soon as the entire object is fully visible, it
will slow down. This small detail will
look even better when we add graphics and
animated spreadsheets. We want this project to
be fully responsive. When users play on
mobile devices, they will not be able to
change the screen size, but we could have some situation
on computer screens or devices where we are swapping between landscape
and portrait mode. Or when we are switching
to full screen, mid game. Where the game area
changes in size. To account for these unlikely
but possible scenarios, I will make sure that
all the active enemies are always inside the
visible game area. If the right edge of
the enemy rectangle is past the right edge of
game area, move it. Now if the game area
changes size mid game, it will just push the enemies
into the visible game area, making sure we don't have any active enemies floating off screen where
we can't hit them.
55. Mouse Controls: We will hit enemies by
mouse and by touch events. We will create a
customer mouse object and we will give it X, Y and pressed properties. This will help us to make the mouse position available
all over the code base, not just inside the
event listeners. Same as we did with Re size. I will automatically
apply mouse down event listener here inside
the game class constructor. I need to use ES
six arrow function here to make sure this keyword points to the game object from inside its call back inside. I could call prevent default in case the event is doing
something we don't like. It might be more relevant Later
with touch events we will test it and see for now I
don't think I need it here. From here I set X and Y
position of the mouse down event to X and Y properties
of our custom mouse object, making them available
in an easier way. All over the code base I will consulg the autogenerated
event object. And we are accessing these
X and Y properties here. If your canvas is not covering
the full browser window, we would have to use offset
x and offset y from here. Instead, I will consulg these values just to check
if everything is working, I click around and I'm getting x and y of the click. Nice.
56. Collision Detection: I will create a reusable
check collision method. We will use it to check collisions between
enemy and mouse, but it can be used for other
features later as well. Custom check collision
method will take rectangle one and rectangle
two as arguments. It will use the usual
four lines of code to check if two rectangular
objects collide. Both objects passed
to this method. Both rectangles need to
have x and y position, and width, and
height properties. For this to work, I will
directly evaluate the expression and I will just
simply return true or false whenever this
method is called. The usual common formula for collision detection
between two axis aligned, meaning non rotated rectangles. Check if the left
side of rectangle one is to the left of the right
side of rectangle two. At the same time, we check if
the right side of rectangle one is to the right of the
left side of rectangle two. If both of these
checks are true, we already know that the
rectangles collide horizontally, but they can still be
far apart vertically. We need two more checks
at the same time. We need to check if the
top edge of rectangle one is above the bottom
edge of rectangle two. At the same time, we also need to check if
the bottom edge of rectangle one is below the
top edge of rectangle two. Only if all four of these checks are true,
we have a collision. Even if one of these
four checks is false, we know the rectangles are far apart and they cannot collide. This entire expression
will return false. Now we need to make sure that whatever object we pass to check collision as rectangle one or rectangle two needs to have x, y, width, and height properties. Otherwise the collision
check wouldn't work. We will check four collisions. Here we are inside update
method on enemy class. If check collision method
which is defined between this enemy object and
mouse object returns true, we call reset on the enemy returning it
back to the object pool. I get an error. I need to make sure mouse object has
with and height properties. It will be just a
very small rectangle, one times one pixel. The problem is that when I click somewhere and move
the mouse away, that mousedown event placed my custom mouse object there and it is still
colliding with enemies. When mousedown event fires, we set mouse pressed the true, I create another event listener
for mouse up event there. I will also update X and y coordinates to that
last known location. And I will set mouse
pressed to false. Now I can add one
more condition. If this enemy and
mouse objects collide, and only if mouse is
pressed at the same time, only then reset the enemy. Now I can click the
enemies and they will correctly reset and return
to the object pool. I could do other things here. For example, when I click them, they slip down like this. We will use that for
something later with our phasing dimension
shifting alien. I go back to reset here. I can clean this code up. Since we are defining values of x and y inside the start method, we can just declare
the properties here without any values. Because we know that the
start method needs to run anyway before the object
is drawn in the game. It's cleaner because now if I want to make any changes
to these values, I just do it inside
the start method. I don't have to check these values in two
separate places. Enemies can also
have multiple lives. I can just define it here, but I will only assign it
value in side start method. That way when we reuse
an old enemy object, its lives will be reset
back to the original value. Each Neme will have two lives. We need to hit it two times. I will draw the lives here using the built in
fill text method. We pass it three arguments, the text we want to draw, and x and y coordinates of the anchor point in relation to which we want
to draw that text. I will put that
anchor point exactly in the middle of the
red enema rectangle. We need to set fill style of the text to a different color. For example, blue in Maine GS. Inside the resize method, which also runs on
the first page load, we will set cannavas font
property to 50 pixels bangers. We put the text anchor point exactly in the
middle of enemies. There are two Navas
properties that handle the relationship between the
text and its anchor point. We can set text align to center
to align it horizontally. We can set text
base line to middle to align the text
vertically exactly over the anchor point
coordinates as we define them on line 49
in enemy GS file. Now instead of immediately
resetting the enemy, when we click it, we will
reduce its lives by one. The collision registers
once per animation frame, so the lives decrease very fast. Before we fix that, we also say if the lives of the
enemy are less than one, we reset that enemy returning
it to the object pool. We will need this check later in multiple places to make
the code easier to read. I will put it inside
a separate helper. Method is alive, method will directly evaluate
this expression and it will return true if
the enemy is alive and falls if its lives
are less than one. If enemy is not alive,
reset the enemy. Now we need a flag
that will indicate if the mouse click was already
used to fire our weapon. Because we only want to
fire once per click, I give mouse a new
property called fired. Initially, it will be false. The shot hasn't been fired yet. We only decrease enemy lives
by one if fired is false. And then we immediately
set fire to true. To make sure this code
block runs only once, then we need to reload
our weapon and set fired back to false so
that we can shoot again. I think we can set fired back to false on either mouse
down or mouse up. Let's try. Yes, this works. Now we remove only
one life per click. We need to click
each enemy twice to deplete both its
lives and reset it. I remove the fill style here. Instead, I set it to white here. I will also set stroke
style to white as well. It's better for performance to put these declarations here inside the resize method that runs only once on
the first load. Or when we resize, the screen draw method
runs 60 times per second. Declaring fill
style here is less efficient when it
comes to performance. I delete this fill
style, smaller font, we have a responsive
world that resizes to accommodate for any screen
size, vertical or horizontal. It will work on a massive, super wide computer screen. It will work on tablets. And it will work on mobile in landscape well as
in portrait mode. Let's make it even more mobile device friendly by
including touch events.
57. Touch Events: I could create a separate
custom touch object, but I will actually use mouse object which will
work perfectly here. It will automatically work for both mouse clicks
and touch events. Touch start will basically do the same thing as mouse down. Touch end event will do the
same thing as mouse up event. This will not work yet
because for touch events, this autogenerated event object structures the information about the event in a different way. I consologate here in
Google Chrome browser, I click Device toolbar to
trigger responsive screen view, which will allow us to emulate touch events in the
responsive view. I click on screen, and I know it worked because we are
consologing touch event object. I'm looking for X
and Y coordinates of the touchstart event. And I can see that
the browser organizes the contact points with a touch surface inside
this touch list property. I think we can treat
it as an array. We will test that later. I can see that to access x and y coordinates
of the touch event, I need to access
the event changed touches element with an index of zero dot pagex and pagey. I also do the same for
the touch end event.
58. Game Text: Now we have a
responsive game world. And we can use touch
events to shoot enemies. I will disable device
toolbar in Google Chrome. Now we are viewing our game in a regular computer browser view. I can put the code
to display score and other game text
directly inside render, but I want to keep this clean. Again, I will put it
into a separate method, make our code base
easier to navigate in. Inside the draw
status text method, I will call built in fill text method and I pass
it the text I want to draw and X and Y coordinates of the anchor point in relation to which I want to draw that text. I call it from inside render, I can see that the score
is not fully visible. I want the text that displays enemy lives
to stay centered, but I want the score
text to be left aligned. I wrap it between built in
safe and restore methods that will restrict any changes I make to canvas
state to this area. All the drawing code outside this block will
remain unaffected. That way I can have text
line left for score and text line set the center
for enemy lives score. Text will now flow to the right
from this x and y anchor. Let's actually count the score. I define it here,
start method inside, we set score to zero because this will also be used
as a restart method. Now I add this variable
here, smaller font. When we deplete enemy
lives by shooting it, we reset the enemy. Returning it back
to the object pool. We increase game score by one. I need to spell score correctly if we have
multiple enemy types. We could also here at score that is relative to enemy
lives for example. Or each enemy type can have its own score property that will define how much score it awards. Now when we click enemies,
we get scorepoints. I also want to give
the game lives. These are the player lives. If we lose them, we
get a game over. Don't confuse it with
lives on each enemy. These indicate how many times
we have to hit each enemy. These are player lives. When we start or
restart the game, we set player lives
to, for example, ten. Inside the draw status text, I will create a four loop that will run once per each life. I will draw a simple rectangle
representing each life. At first it needs x, y, width, and height. Now I'm drawing
all of them on top of each other, okay? This is x, y, width, and height. This value will be the
margin from the left. I multiply horizontal x position by the index from the four loop, which will align them in
a row next to each other. If the width of each
life rectangle is more or equal the
space in value, it will look like a
continuous health bar, which might be
something you want. If the space in is more than the width we get to
see individual lives. I will go for this view
because later I want to change each life into a different
humanoid alien species. It will add more stakes to our space horror game because we don't want the enemies to
eat our unique crew members. If this is not clear,
just play with the values. Watch what happens. It's pretty straightforward if enemy manages to travel all
the way across the screen, all the way down and we don't interrupt it,
we don't shoot it. It managed to catch up with our escaping fleet and it will eat one of
our crew members. Reset the enemy and we
reduce game lives by one. If we change the
number of lives, the rectangles representing
each life will appear. This works well. Let's
close the game loop. Winning score will be three. We will display three
messages on screen. When the game is over, we will have three sets
of these messages. One message will get displayed
only on the first load. Before we press the
button to start the game, I remove this value
from Game Over. When we start the game, we set game over to false. I will create a separate
trigger game over method to make sure this
code block runs only once. At this point, I could put
this logic inside the render, but if I put it into
a separate method, it will make it easier to play a special horror sound later when we win and
when we lose the game. It will run only
once when called. And we make sure of
that by only running its code when game
over is false inside, we immediately set
game over to true. We triggered game over, which could mean
we lost the game. Player lives are less
than one or we won the game when the score is more or equal to winning score. When we lose, we set some unique text to message
one and message two. If we win we give
those same variables different texts then we
need to call that method. I can do it from render or
from draw status text here, if this lives is less than one or if this score is more or
equal to winning score, we call trigger game
over which will set game over to true and it will
set the messages up. Then if game over is true, we want to draw message 12.3
in the middle of the screen. Text align center, large font
may be 80 pixels bangers, we call fill text message one, x and y coordinates will be
the middle of the game area. Message two and message three. We do the same. I adjust their vertical coordinates
to try and align them. -25 message one, plus
25 for message two, and plus 50 for message three. I play. I wait until we lose. Okay, that worked. I want messages 2.3 to be drawn
in a smaller font. I try 20 pixels, that's the losing message. I refresh the page,
I play again, and I win win message. We have one more message, we will make a
display in a minute. I said game over to true.
59. Start Restart: I created this
reset button here, I want it to trigger start
method when clicked. First I create a property
on the main game object and I pointed towards that
button element using its ID. Then I add event
listener on that button. We listen for a click event. Again, I have to use
an arrow function here to make sure we
have access to this dot from inside the event
listener to make sure that this keyword points
towards the game object. From there I call this Do Start. Now I can see the
start messages, but they are not formatted. Well, I fix that
by calling this, resize automatically from inside the game class constructor. Doing this fixes fill style
and text baseline settings, but now score is undefined. I will initially set it to zero here inside handle
enemies method, we periodically activate
new enemy object and add them to the game. I only want new enemies to keep coming if game over is false. Now when I refresh the page, we just see the starting set
of messages and nothing is happening until we click on button R here to
start of the game. Having a button that
starts the game by user interaction will also
solve issues with game sounds. We will talk about that later. But basically in
modern browsers, sounds will play only after the user interacted
with the document. User click in this
button will count as an interaction which will allow the sounds in our
game to be played. I create another event listener. This time for a key up event, I caso log the event object. We are looking for
this key property. I say if key, the key that was pressed
on the keyboard is Enter, or if the key is R, I use to lower case
method here to account for both lower and
upper case being pressed. We run a start method
to start the game. R needs to be in quotes here. Right now, start method
will basically just reset the score back to zero and
it will replenish our lives. I also want to reset
all active enemies. I run over all objects inside enemy pool array and
I call reset on them. Maybe I want to activate
two enemies quickly. Immediately as the game starts. I could do a four
loop like this. I tried to get enemy if there was an available
free enemy object, we found we activated by
calling enemy start method. Don't get confused here. Start method that sits on the game object
starts and restarts. The game start method we
defined inside enemy GS on the enemy object activates enemy object pool member
two different methods. Now we can start or restart the game at any
time by clicking or tapping on this R button or by pressing Enter or letter
R on the keyboard. This is just to show
you how you can give your players
different options. We can also create a
faster initial wave by immediately activating a few enemies in
the beginning here. Then the rest will be coming
in 1 second intervals.
60. Fullscreen Games: Modern web development has
a lot of cool features. Have you tried to use built in full screen
functionality before? Basically, it just removes the browser buttons
and navigation bar, and it will expand
your application to take up the full screen. This will work on
computer monitors, on tablets, as well
as on mobile screens. We will use it to make a
proper full screen game. Now, it's very simple
to implement it. Let me show you, are you
interested in full screen games? Let me know by
leaving a comment. And if people like it, I can use this feature more often
for other projects. You can go to this
web address to read the documentation if
you want more details. All we need to make this
work is this simple if L statement that
basically says, if full screen is currently
not active activated. If it is currently
active, deactivated. All these properties are built in into every modern browser. So you can simply just copy
this small code block I pasted here on the
main game class togal full screen method. Whenever this method is called from anywhere in our code base, it will check is full screen
active, If not activated. If it is active, deactivated, we test if it works here, else if the key
that was pressed is space bar or if the key
that was pressed is letter. Feel free to use
different control keys here if you know
what you're doing. Now I press letter
or Spacebar and my game toggles between full screen and
regular browser view. I'm not really showing
it off well here because I'm not recording
the edges of my screen. But if you are coding alone, you can do it yourself
with your code base and see that this
is a very nice, proper full screen
functionality. We also want to be able
to toggle full screen on mobile devices where there
is no keyboard that we have, this full screen button
I created earlier. You can also enable and disable
full screen by gestures, for example, by
swiping up and down. If the swipe is longer than
a set number of pixels, when it is in a
specific direction, we can trigger our custom code, which could be, for example, enabling and disabling
full screen. I did that in a
different tutorial for this simple mobile friendly endless runner and jumper game. It was some time
ago on my Youtube. Same as we did with
the reset button. I reference full screen
button in my code. I attach event listeners to it. In the call back, I call togal full screen method game is fully playable in
the full screen mode. What you see now is
not completely right because I'm not recording
my entire screen. The planet and the text is a bit of center and the
buttons are not here. But if you are coding along, you can see in your project that everything is working well. We have a blank
slate of a game with a full set of features,
complete game, loop, win and lose state, mouse keyboard and
touch controls, reset, and full
screen functionality. This game will work on large horizontal
computer monitors, as well as on small
vertical mobile screens. You can adjust winning score
on line 16 or lives on line 77 to tweak the win and lose conditions in the
video description. I'm giving you a full source
code from the project. At this stage, you
can compare it with your own code if you
encounter some problems. You can take this code and use it to create many
different games, get creative with the
themes and graphics, and make it your own. In the next part, I
will turn this into cartoon space horror
game with sounds and randomized crew
that we have to protect from phasing
flesh eating aliens. This concludes the beginner
friendly part of the course. In the next part, I will show you an improved implementation of state design pattern and
I will go a bit faster. Well done. If you go this far, let me know by leaving
a comment saying, I finished the beginner project.
61. Simple Crew Members: We have a game that works
on small and large screens, portrait and landscape,
vertical and horizontal. Now it's time for fun. We add some graphics and animations. I set lives to 15. I create a diff with an
idea of assets inside. I will put our project
images and sounds. You can download
everything I'm using here. In the resources section
below crew PNG image and the ID will be Crew I
give assets, display none. We want to hide
everything in there. We just want to load
those images in memory so that we can draw them
with Javascript on Canvas. I want to draw lives as little crew members.
Let's start simple. I bring crew image into my project using get
element by ID here. Inside the draw
statistics method, we draw rectangles to
represent player lives. Instead, I will use built
in draw image method. We pass it the image we want to draw and X and Y coordinates. Where to draw it, I adjust with and hide here to match
the image we are using. This is the left margin, this is the space in between
the little crew members. I can make them
have space around or I make them stuck up
closer on top of each other. When we lose all crew
members when they were eaten by the aliens,
we lost the game.
62. Simple Enemy Type: I want to create
different enemy types. This is the main enemy class. It contains all
methods and properties that will be shared
between all enemy types. I will first do a simple enemy with the most basic
implementation, but my ultimate goal
is to show you how to use a complex enemy
with multiple states. And how to structure your
code where each state is in its own dedicated code block using so called state
design pattern. Basic simple enemy
with one life, no special features, no special abilities
and interaction. I call it Beetle Moorph. I will go back to my
Space Invaders game from a different tutorial
and I will get the spreadsheet we used
there in this project. The enemies are a bit bigger. If you are watching
the extended version, I'm giving you all
larger exports of all enemy types we used in
this entire space game series. If you want, you can
play with this later and implement whichever
one you want. For now, I will just grab the Beetle Moorph spreadsheet in the resources section and I'll put it into my project folder. Beetle Moorph subclass extends the enemy superclass constructor will expect the game
as an argument. And I will pass it along to superclass constructor
when we call it here I'm calling class constructor on the parent enemy class
we are extending. That class is defined here that will make all this
code here run first. These are all the
shared properties. The properties that
are the same for all enemy types on the
beetle morph subclass. I will only define properties specific to this
particular enemy type. It will have a unique
image I imported here. Download Link in the
resources section below, I point Javascript towards the image using
get element by ID. It will also need its
own start method. First, we will call
the start method on the parent enemy class. On the superclass like this, when we call start method
on Beetlemorph enemy, it will first run all the code inside this parent start method. Here from line 13, I copy speed x and speed Y. When we call start
on betlemorph, I will give them values. I want this enemy
type to move directly down speed x zero and some positive speed Y. I remove
these values on lines 8.9 They will be set to
something different for each enemy type inside their
associated start methods. I also remove lives from
the shared start method, because each enemy type will have a different
number of lives. In the extended version,
I'm also going back to Space Invaders and
Planet Defense projects, which are all part of
the same big course, including this game and others. I'm adding bonus lessons
implementing the new phase in enemy type that can only be hit when it is in the same
dimension as the player. We are extending a
class shared properties sit on the enemy superclass. Properties unique
to each enemy type will sit on the subclass. First we trigger
constructor and start method on the superclass
on the parent class. Then we add some properties
and values specific to this particular enemy
type inside update method. Here we check for collision
between enemy and mouse. I will cut this and I will put
it into a separate method. It's mainly because we will have a phase in enemy type that
cannot always be hit. I need to be able to better control when we
check for collision. Now it's in a
separate hit method. All this code inside
update will be shared and executed for all different
enemy types in our game. Beetle Moorph will have its own update method
that will override it. If this enemy object
is currently active, we call all the code inside the update method from line 31. Then if this enemy is alive, we call hit method checking collision between
this enemy and mouse. Now I need to call
that subclass. Here we push New Beetle
morph into the enemy pool. Right, I forgot here I have
to call update method on the parent enemy
class. That works.
63. Sprite Animation: Now we want to draw
the spread sheet. I can put this code inside the draw method on the
shared parent enemy class. Because all the enemy
types will have a spreadsheet I call built
in draw image method. And I pass it the
image I want to draw and x and y coordinates,
Where to draw it. This will just draw the
entire spread sheet with all its frames
at its original size. We can give it optional
width and height here, which will squeeze or stretch the spread sheet into
the area we defined. We need the longest version of draw image method
with nine arguments. It will give us complete
control over this spreadsheet. We pass it the image, we want to draw source x, source y, source width, and source height of the area. We want to crop out from the source image
and destination X, destination Y,
destination width, and destination height
to define where on destination canvas we
want to place that cropped out piece of image onto the sprite sheets
we are using have individual frames size
on 100 times 100 pixels. I define those properties here I set within height
relative to that. These two values
are for crop in. These two values determine
the size at which we will draw the image
on Destination Canvas. These two sets of values
don't need to be the same. We can scale this and
we will do it later. What if I want to crop
out the top left frame from 00 to sprite
with sprite height. Nice, We are using
draw image method to crop out this frame from the sprite sheet
Frame Y property will help us to travel around the spread
sheet vertically. The way I organized
this spreadsheet, you can see it will give us different variations of
the same enemy type. All enemies will have four
sprite rows, four variations. So I can randomize frame Y here inside the
shared start method. If each enemy had a
different spread sheet, we would have to put this on each separate enemy
subclass method random times four wrapped in method floor means
that every time start method runs and we
pull an enemy from the pool, it will randomly
be assigned frame y zero or one or two, or three. Source Y argument passed to draw image method will determine the vertical crop in coordinate. So we pass it frame y
times sprite height. Frame y can be 01230
times sprite height, is this one times sprite height is this
two times sprite height, three times sprite height. This is one simple way how to randomize your sprite sheets a little bit and give
us some variations of the same enemy type. They all have the same
features and color pallet, but they have different details makes it more interesting. I think we will
also need frame X to navigate around the
sprite sheet horizontally. I will also have
last frame property to define how many
horizontal frames we have per row when the start method on enemy runs To activate it, we set frame X to zero. Each enemy might have a different Last
frame Beetle morph has a very simple sprite sheet, only three frames per row. Inside the shared update method, we will handle horizontal
sprite navigation when the enemy loses all lives. When this dot is alive is false, we keep increasing
frame X by one. When frame X is more
than last frame, we know that all the explosion animation frames
have been displayed. And we know it's safe to reset the enemy and return it
back to the object pool. If game over is false, we increase score by one, and I can delete this. Finally, as the source
x argument passed the draw image method
which determines the horizontal coordinate
of the cropping area, we pass it frame x
times sprite width. When frame x is 00
times bright width, we draw this frame one
times sprite width, two times bright width. I hope it makes sense. I play, I click each animate twice, deplete its lives, and the
animation plays that worked.
64. Animation Timing: It is playing very fast. I want to be able to control how fast we jump from
frame to frame. When animating our
sprite sheets, we will do another
periodic event, same as we did before
when we activate new enemy every
1,000 milliseconds. We will update all sprite
sheets in our game. Every, let's say
200 milliseconds. Sprite update will be false. Every time sprite counts to 200, it will set sprite
update to true. I can put that logic directly
into my render method, but to keep our code cleaner, I will separate it out
to its own code block. I will call it, for example,
handle sprite timer. It will expect delta
time as an argument. If sprite timer is less
than sprite interval, we keep accumulating delta time. Else once we accumulated enough, we reset timer back to zero
so that it can count again. And we will set sprite
update to true, allowing our sprite sheets
to jump from frame to frame. It will be true only for
that single animation frame. All other times it will
be set back to face. Now we call handle
sprite timer here, this delta time value
will get passed along. That should be all good here. Inside the update
method on enemy class only if the enemy
is not alive and if sprite update is true only then jump from frame to
frame in the sprite sheet. I will adjust this code block. We have to be very careful
about the brackets here. It's easy to break our code with a single
missing bracket. It doesn't work because I have
to use sprite update here. Actually, one more
mistake I made somewhere. I see I misspelled sprite
interval here on line 24. I can slow it down or
I can speed it up by giving different values
to sprite interval here, 1,000 milliseconds will make
them animate Very slow. 50 milliseconds is very fast. 150 milliseconds seems
like a good value here. For now. Now it all works well. We get one behavior
I don't like here. If two enemies overlap
and we click them, it destroys the
enemy in the back. Why is this and how do we make it destroy the
enemy at the front? We are cycling over the enemy pool and
calling update and draw. We are drawing all enemies on the same canvas element in
a single animation frame. As we run this for
each method we draw enemies one by one enemy. We draw first will appear behind the enemy that
was drawn after. At the same time,
the collision checks inside update method
will run first for the enemy that was drawn first and that enemy
gets destroyed. One way to fix this would be
to run over enemy pool from one direction to call update and then from the opposite
direction to call draw. That way the enemies
that are drawn last, meaning on top, will
get checked for collision first With
this code structure. When two enemies overlap
and I click them, the one on top, the
front gets destroyed. That's better since we separated the crop in size and
the drawing size. We can actually do
some scaling here. I can multiply
width and height by the same size modifier value. I can make the sprites
larger or smaller. How about a random size
modifier between 0.4 and 1.2 Makes the game a little
bit more interesting, maybe. How about random value
between 0.6 and 1.3
65. Debug Mode: I want to be able to show and hide some helper
elements in the game such as these white
collision boxes and numbers displaying
enemy lives. Let's create a debug mode
on the main game class. I created this bug
property and initially I set it to true inside key. If the key that was pressed
to lower case is letter D, we will set back to
its opposite value. If it's true, we
set it to false. If it's false, we
set it to true. Inside draw method
on enemy class, we only draw the white rectangle around enemies and enemy lives. If the bag is true, I make sure I save changes
to all my files and now I can press D on my
keyboard to toggle debug mode. On and off I give Beetle
Morph only one life. Because when we don't
display enemy lives, that first click doesn't have any visual feedback and we don't know if
we hit the enemy. I adjust size modifier
range here on line six, I said winning score 220. Initially, back will
be set to false. If you are happy with having just simple enemies like this, you can implement them
the way we just did. I created a series of games and projects
with this art style, and all the art assets are compatible and can be used
between the projects. If you want, I will now grab one of the more
complex enemies we implemented before
and I will try to implement it in the
same way we just did. Then we will add a
third enemy type using state design pattern. Which has a little bit more
complexity but gives us much more freedom
while keeping our code clean, modular, and organized. In the other projects,
we have for example, an armored enemy,
inflatable enemy, vengeful enemy that
shoots back at us. We have an oversized
bost enemy type with a massive health pool. We have a charging enemy that
will speed up and hit us. I will grab, for
example, this one, an enemy that splits into
clones when we hit it. Because I have an interesting
idea on how to use it in this particular project
with this type of gameplay.
66. Enemy Variety: In this class, we are using slightly larger
sprite sheets with frames 100 times 100 pixels. You can use the
sprites you already have if you followed
the other classes, they have frames 80
times 80 pixels. In the extended version
of this project, I will also include exports
of all enemy types in 100 times 100 frame size to
better match this project. If you want to play
with this code base later and include the
other enemy types as well, you can use those
bigger sprite sheets. I included my image in index HTML and I
referenced it here. Last frame will be 14. Again, start method will first trigger start on the
parent enemy class. Horizontal speed x zero. Speed y will be a random
value between 0.2 and 0.7 This enemy
will have three lives, so I need to make
sure it's a bit slower to give the player
enough time to deal with it. My plan is this enemy
has three lives. We hit it once. The frames will play until here,
we hit it again. The frames will play
until this point we hit the third time and the rest
of the spreadsheet will play. This would be best implemented using state design pattern, when each of these states
has its own code block. But I will try to
implement it somehow in a simple way like we did
with the beetle morph. Then we can compare it
with the last enemy, where I actually show you how to use state design pattern. You can compare the
relative complexity between these two approaches
and you will see the advantages of
the final approach. Update method will
first run all the code inside the update method
on the parent superclass. Then, if this enemy object
is currently active, if the enemy is alive, we will make it react to
being hit by the mouse. We will have a horizontal
frame range defined by min frame and max
frame helper variables. If frame X is less
than max frame, if sprite update is true, we increase frame X by one. Okay, what I'm doing here, frame Y handles vertical
sprite navigation. Frame X handles the
horizontal sprite navigation. Last frame is the last
horizontal frame. Once it was displayed, we know the enemy
can be deactivated. Mint frame and Max frame will define some animation range we are currently working with. Depending on which state
the enemy is currently in. There are many ways I
can implement this. I want to do it in the
simplest way possible. We are going to do if else if enemy lives are more
or equal to three. Max frame is zero, if enemy lives are two. Max frame is three When
we hit the enemy once, because of this line of code, it will animate from here
and it will stop here. Else if enemy lives are one, we will let animation go all
the way to max frame seven. I think that might
be enough because we have some other
animation code inside the shared update method
that when enemy is not alive and has
less than one lives, it will animate the rest
of the spreadsheet, the explosion frames
automatically. Let's see here where I
create the enemy pool. I want to randomly create beetle morph or lobster
morph enemy types. I can, for example, roll a random number 0-1 The
random number is less than 0.8 In roughly 80% of the cases we create
a lobster morph. In the other 20% of cases
we create a beetle morph. Okay, both enemy
types are coming. I click the enemy once,
twice, three times. That all worked perfect. I adjust sprite interval to make the animation a
little bit faster. This code block is working well, but when you write your code, always think about
which code is running once and which code
runs over and over. This code is running all
the time and I'm setting max frame to the same value 60 times per second,
over and over. So what if I want to
play a particular sound when the enemy morphs
into the next stage. I don't really have a good
place to put that code here, since this code block is
running over and over. So that sound would be played over and over if I call it here. Ideally, I need some
code block that runs only once to play
that sound for me. We will solve all these
issues in a minute. First, let's use spreadsheets
for crew members.
67. Randomized Space Crew: Right now, all our crew
members look the same. Let's give them a little
bit more personality. An intergalactic crew of different kinds of humanoid
and robotic crew members. All the art assets are available to download in the
resources section. Down below, I bring crew
sprite into the project, give it some ID, and I point crew image to this new
spreadsheet. How do we do this? I will actually have to hold the randomized coordinates to
these frames out somewhere. Maybe here inside crew
members property, I set it equal to
an empty array. I create a helper method. I call generate crew inside. I set crew members
to an empty array, deleting everything in there
in case it's not empty. When we call this method, then I run for loop. Each player life will have a random crew member
image assigned. Each crew member will be a simple object with frame
X and frame Y property. Frame X will be a
random number 0-5 and we use method floor to round it down to the nearest
lower integer. It will be zero or one or two, or three, or 44 frame Y. Each time we push
new crew member, it will get some
random coordinates somewhere on the spreadsheet. We will call this
method every time we start or restart the game. After we set value
of dist lives. That's important here
in row status text, I define with and height of a single frame in crew
members spreadsheet. I need to add four
more arguments to define the cropping area. For example, I can crop
out the top left frame from the Corint 00
to width and height. I will also use with and hide here in destination width and destination de
arguments to define the size at which they
will be drawn on canvas. We always have to start our game to see the
lives by pressing Enter letter R on
the keyboard or by clicking or tapping
on this R button. Nice, Now let's use frame coordinate from
crew members array. We created that
property here on line 100 As we cycle in this
four loop 15 times, because we have a
total of 15 lives, we use the index
from that four loop, access the corresponding element inside crew members array. And we access those frame
x and frame y coordinates. We multiply them by width
and by the height of a single frame to correctly travel around
the spreadsheet. Now every time I start
or restart a game, we generate a set of humanoid
and robotic alien species. I hope it puts a little
bit more pressure on the player to try and make sure none of
our unique crew members get eaten by the aliens. This value here defines
the left margin. This value here is the
spacing between the images. We have a simple enemy type and more complex enemy type implemented in the simplest
way I could think of. We have a randomized set
of crew members and we need to protect them
from eating aliens. If you are watching
the extended version, there is a source
code available to do from this stage
of the project. If you struggle with something, feel free to use it and
compare your code with mine. If the spacing here is a little bit less than the width
of a single frame, we will get them stuck
up behind each other. You can play with these values and use whatever
looks good for you.
68. State Management in Games: Now it's time to learn how to use state design
pattern to implement a phasing enemy type
that can only be hit when it's in the same
dimension as the player, when it's in a solid state. If you hit the enemy when it's phasing when it's in
the invisible state, it will boost it
and speed it up, making it even more dangerous. We will also attach sounds to different phases and actions. Because it's important
that our games give player feedback when you click or interact
with something. We will give visual feedback
by animating the enemies. We will also get audio feedback by playing a specific sound. Small buck to fix. I only want to reduce player lives when
game over is false. Like this, I bring a phantom morph spreadsheet into the project and
I give it an ID. Here we have the
shared enema class with all shared methods
and properties. We are extending it into a simple beetle morph eneme and a more complex lobster
morph enema type. Now we will extend it into the most complex phantom
morph enema type. I was having a lot
of fun with this and if you are watching
the extended class, I went back to Space Invaders
and Planet Defense projects and I added a bonus
lesson to each one. As well as the full source
code where I implement this Phantom Morph phasing enemy type into both
of these projects. You can also just take the spreadsheet I'm giving you here and try to implement it yourself if you
follow those classes. But if you need my help, those lessons are there for you. Phantom Morph will
have last frame 14, some speed x and speed Y. We will change
these in a minute. It will only have one life, but it will not be that easy. Because you can only hit it when it's not phasing
when it's solid. If you hit it at the wrong time, you can accidentally
give it a boost and cause one of our crew
members to be eaten. And it's probably not
going to be pretty because if you listen to
the sounds that will play. When that happens, I go to the main GS file
here inside create enemy pool and I will only
create phantom morphs. I have to be careful
about the brackets. I save changes to all my
files and here we go. I'll copy update method from beetle morph code block
and I use it here. Now when I hit it once, it will just play the
rest of the spread sheet. As expected, vertical speed will be a random value between 0.2 and 0.7 I also want to
make them move left and right. Horizontal speed will be a random value between
minus one and plus one. We want to make sure they
can't move off screen, so I will make them
bounce left and right if the horizontal x coordinate of the enemy is less
or equal to zero. Or if the right edge of
the enemy rectangle is touching the right edge
of the visible game area. In both of these cases, just flip horizontal speed
to its opposite value. Making the enemy bounce,
move in the other, in the opposite direction. Perfect that worked. The other enemy
types were static, always showing a single
animation frame. Unless they were animating
into another state. This enemy type
will be different. I want it to always animate
in a certain frame range. Initially in frame minimum frame will be zero and maximum
frame will be two. I give this enemy type its own special method that
will handle animation frames. For me, if spray update is true, we move horizontal frame. If frame X is less
than max frame, we keep increasing frame X, we reach max frame, we set it back to min frame
so the cycle can continue. I will call handle frames
from inside update here. Nice. This will be the frame range for the
basic flying state. I try three to five probably. That will be the phasing
state animation range. Okay, if you are a beginner, now comes the complicated part. We are going to implement state design pattern that
will allow us to define properties and behaviors
of this enemy for each state separately
in its own code block.
69. State Design Pattern: I create a completely
separate class called enemy State constructor will expect game and
enemy as arguments. We will need these to be able
to point towards and access our main game object
and also the particular enemy object that will be
in these different states. Then we will define each
state in its own subclass. All these three subclasses will extend the main
enemy state class. Here we will store
the shared properties inside each of
these code blocks. We will define
properties, values, and behaviors we want
the enemy to have. Only while it is in
these particular states. It gives us a lot of
freedom and flexibility. And it makes our code
easier to debug. Because if we get an error and we know which state
the object was in, we know where in the
code to look to fix it. The only challenge for
beginners will be to keep track of how all these objects connect and communicate
with each other. If you are a beginner, the
next part will be challenging, so don't worry
about it too much. This is one of those
techniques you have to use a few times to
create a mental map. It helps if you use this for
a few different projects. It becomes easier to navigate eventually as you
get used to it. All you need to
understand now is that we have a separate
enemy state class. We are extending it into
three state subclasses, flying, phasing, and imploding. Each of these classes will have its own start method,
where we define, for example, frame range for the animation while the
enemy is in that state. We can also play a
specific sound from here. When the enemy
enters that state, each state will also have its
own update method that will first call the
shared update method on the main enemy class. Then it will add a little
bit of unique logic that will only apply when enemy
is in that particular state. For example, maybe we
only want to check collision detection
when enemy is flying. When the enemy is phasing,
it cannot be hit, so we will not run
check collision method from there. Things like that. Each enemy object
will have an array of all possible states
it can be in inside. We instantiate all three
state subclasses like this. Here on line 174, I can see enemy
state constructor expects game and
enemy S arguments. I pass it this game reference. Along through this reference, we will be able to see player lives score or game over state, all the things we might need that sit on the
main game object. Then I will pass it this keyword representing this
particular enemy object. This keyword used here in this court block
means the enemy. Through this second reference, we will be able to access
all the methods and properties that sit on the enemy object,
on the enemy class. This is probably where
it's getting a bit more complicated for the
beginners, just keep going. I'll try to guide you now. Phantom Morph will have a current state
property that will be pointing towards
different states we have here inside this states array. Through this connection,
we will be able to set enemy to a
different state. Custom set state method will
expect state as an argument. This state will be a
number representing index. In this states array from 93090 will be flying
state because this is an array and arrays
start from the index of 01 will be the phasing state and two will be imploding state. When we call set state, we take this dot
current state property from 940 and we pointed towards one of the
objects inside this dot states array
zero, or one or two. At that point, this dot
current state holds an entire instance of
let's say flying class. We gave that class
its own start method. I said and state to flying class and I call
its associated start. We define that method
here on line 186. And the job of this method
is to set properties on the Phantom morph eneme to whatever values
they need to be. When Phantommorph enemy
type enters this state, we need to make sure
each state subclass has its own start method. We have multiple layers here. Now when we call start on the Phantom morph enemy type to activate it from
the object pool, we first run the code that is
shared for all enemy types. Then we run the code that is specific only for
phantommorph enemy type. Then we call set state method. We set the current state to an instance of flying
state subclass. From there we call start
method on the flying state which will set properties to that particular
phantom morph state. It will be the same
inside the update method when we call update on
Phantom Moorph enemy class. First we call all
the code inside the update method on the
shared parent enemy class. Then we add some code specific only to phantom
morph enemy type. On top of that we trigger
that last bit of code specific only to the current state phantomorph
enemy is currently in. Depending on which code block current state is pointing
towards at the moment. It could be this code block from 191 or this code
block from line 200, or this one from line 209. Now we have all
the layers and you can place your logic
wherever you need it. Do you have an idea for a behavior that is specific
only to one of these states? We created a space for
you to put that code in. I will show you some examples
of how this is done. This is the entire state
design logic loop. I'm not sure if I
explained it well enough. If you still don't
fully understand it, let me just expand each code
block and see what happens. Hopefully, while
we work with this, it will become more clear. This code structure, we have a complete and detailed
control over our objects. We can do so many
different things. Basically, we can do anything we want with our
space creatures. Now we can access properties on the enemy through
this enemy reference. When we enter flying state, we set min frame to
zero and max frame. This means I can remove
those values from here because they will be triggered when this line runs anyway. When we enter facing state, we will set min frame to three and max frame
to five when we enter imploding state in frame will be six and max frame will be
equal to last frame plus one. Maybe when we activate the enemy it is set to state
zero flying state. This start method sets the frame range as
we just defined. We can also make them start
from the phasing state. I can see they start
from frame X zero. I actually want
them to start from whatever the min frame
is in that range. I can say sdtenemetframex
is equal to didot enemy in frame I do the
same thing here and here. Now they are
immediately invisible. They start from the
correct frame range. When they enter a
particular state, I want them to be randomly, starting from either flying
or from phasing state. I want zero or one here. Math at random times two wrapped in Mathot floor
will do that for us. Now we get some enemies start invisible, some start invisible. I want the enemies to
automatically switch between flying state
and phasing state. We already used periodic events to activate enemy
every 1 second, and also to allow
sprite sheets to update every 120 milliseconds. We can use the same
technique for this. Each eneme will have
its own switch, timer, and switch interval. I put them on each eneme object. Each eneme having their own
timers will allow us to, for example, give each
enemy different interval. If I wanted all
enemies to switch states at the same time
for performance reasons, it would make sense to put this logic on the
main game class. For now, I want each
eneme to switch state every 2 seconds, every
2000 milliseconds. We will randomize this
value in a minute. When we get this work in, I can put that logic
here. The enemy is alive. We will make it
periodically switch between flying and
phasing state. If switch timer is less
than switch interval, keep increasing switch
timer by delta time, else, meaning if switch timer
accumulated enough milliseconds, set switch timer
back to zero so that it can count again towards
the next periodic event. And call custom switch
method, which we will define. Now switch method will simply check if this
dot current state from 940 is equal to this
dot states index zero which is the flying state as we defined it
here on line 139, we call set state
and we pass it index one phasing which will reassign the current
state to phasing. It will call start to adjust the frame range and so
on. Index one is here. I could have created an
Enum object translating 01.2 indexes into
something more readable, but I think since we
only have three states, we can work with numbers. We just have to remember, index zero is flying, index one is phasing, index two is imploding else, meaning the current
state is phasing. We set state two to activate
the imploding state. Just to see what happens, we are using delta time. Here we are calculating it
on line 215 inside main GS. We are passing it to
the render method, then we are passing it
along to update method on enemy objects that goes
here inside enemy GS. It is available to the main update method on
the enemy class, but we don't really
need it here. We can just go directly to the Phantom Morph class and
just access delta time here. Then it will be available to
our state switching logic. We are switching state
between flying and imploding, which results in this. We actually want to switch
between flying and phasing. That's better. I can randomize this interval to make
it more unpredictable. I want the enemy to be
destroyed when we click it. Only in a flying state. If we hit it when it's phasing, we will give it a speed boost. This will encourage the player to time the clicks properly. Here on the
Phantommorph subclass, if the enemy is alive, we call this method that sits
on the main enemy class. I will actually intercept this. I will give it its own hit
method that will first run all the code inside the hit method on the
parent enemy class, here on line 34, to check collision between
mouse and the enemy object. When we hit the enemy
by clicking it with the mouse or tapping on
it with our touch events, we will check if the
enemy is not alive. We will switch it to the
imploding state set state two. I'll cut this code block. We want to call hit
method only from while we are inside the phasing state or inside the flying state. I will remove hit method
from phasing state. Maybe the enemy cannot be
hit when it's phasing, When it's invisible, it can
only be hit when it's flying. I can see that we are displaying explosion animation
frames too fast. I will remove this
handle frames method that cycles between min
frame and max frame. I will only call it
while we are in flying state or in phasing state
when the enemy is imploding. We don't need to handle
frames anymore because this code block on the shared parent enemy class
will take over. And when the enemy is not alive, lives are less than zero. We keep increasing
frame X until we reach last frame and
then we reset the enemy. Okay, get we clean that up. I can do all kinds
of things here. Maybe when we click the enemy, I copy this code block, I put it here inside
the phasing state. If we click the enemy
while in phasing state, give it a speed boost. We push it vertically
by 25 pixels. Maybe I can remove this
mouse fired check for this. We are inside state subclass I have to say this anime here. If we click an enemy
while it's phasing, we make it jump forward
instead of destroying it. If we click when it's
in a flying state, we switch it into
imploding state. And when explosion animation
frames finish playing, we get a score point. I could, for example,
copy these two lines. Every time we enter
flying state, I add enemy here and here. Every time we enter phasing
state as well we randomize speed x and speed Y again to make sure the enemy
changes direction. If we click the enemy
while in phasing state, we make it move directly
down speed X zero. And speed Y will be two
pixels per animation frame. When we click the enemy,
when we are not supposed to, we are giving it a
speed boost making it more likely to eat one
of our crew members. Okay, we have a
shared enemy class. We are extending it into
a Phantom morph subclass. This enemy type
can be in flying, phasing or imploding state. We are switching between
these states by pointing current state property to
different state subclasses. Using this set state method, we are calling update on the current active state
subclass from here. With this code structure, we have a separate code
block for each state. And we can add any behaviors and properties to
the enemy here. This allows us to control every detail and keeps our
code clean and organized. We gave our enemy a flying
state where we animate this frame range and enemy
can be hit and destroyed. Phasing state where we
animate this frame range. Enemy will get a
boost if we hit it in this state and imploding state. Which will just animate the
destruction of the enemy. The way I structured my code, we need to keep this
update method here or our code would break if
you don't want it here, because we are not using
it for anything right now. You can also put it on the
parent enemy state class, and Javascript inheritance will make sure the code
will still work. Structuring our code in this
way has so many advantages. I prepared a set of sound
effects for this game. Let me show you how easy
it is to implement it now.
70. Sounds: You can download my horror sound set in the resources
section below. We will bring them
into the project. Here inside the assets stiff, I create a HTML
audio element and I pointed towards my new
game P three file, and I give it an ID of new game. One more. Let's take boom one P three ID will be boom one. Up here in main GS, I create a class called Audio Control Constructor Will just have a property for each sound pointing to the
audio element using its ID. I create an instance
of this class, so the sounds will
be available from disdotoundpperty sitting
on the main game class. When we start the game, I can simply just take didotundegame and I call
built in play method. Most modern browsers will not
play audio automatically. When a web page loads
in our project, user has to press Enter R or click this R button
to start the game. That is considered
a user action, and it will allow
game audio to play. We have another sound
called Boom One. Here I go to NeMGS down here
inside the imploding state. When the enemy enters this
state and start method runs, we can play it now, You'll notice a problem
when I quickly destroy multiple enemies because we are accessing the same
audio element. The sound will not start playing until the
previous loop finished. I want the sound to play each
time we destroy an enemy. We could create multiple
instances of that sound, which would not be
performance friendly. I think I can use
Web audio API here, which gives us much better
control over sound. Or the simplest solution
here is to simply rewind the sound to the beginning every time
it's supposed to play. It might sound strange, but it will actually
create the effect we want. Let me show you. I create
a custom play method here. This method will take audio as an argument, which
will be one of these. It will rewind it
to the beginning by setting its current
time property to zero, and only after that it will automatically call the
built in play method. I need to call the custom
play method like this, and I need to pass it the audio. I want to rewind
and play like this. Now, even when I quickly destroy enemies, the audio reacts. We will improve this
sound even more. But before we do that,
I import slide P three. I give it an idea of slide. I want to have visual
and audio feedback to every important
action the enemy takes. I bring it into the project here inside the phasing state. If we click the enemy
while it's invisible, while it's phasing, we
will play the slide. Sound Now if the players click the enemy when
they shouldn't, while the enemy is phasing, it will give the
enemy a speed boost. And we will also get an audio confirmation that it happened by hearing the slide. Sound I bring more boom
effects, boom, 234. And I give it ideas
of boom, 234. I bring them into
the project here inside the audio control class. When we destroy an enemy, I want to play Boom two, a slightly different
version of the sound. I save changes to all my files, I press Enter or are
to start the game. Nice now, boom to play. Let's test, Boom three. I added some squishy
slime undertones when I mixed this one to
make it extra juicy. Boom four has some cracking
and flapping noises in there. Let me describe an enemy. I want one of these four
sounds to be played randomly. I create an array
called Boom Sounds, and I put all four of them
in there as elements. When we destroy an enemy. The sound I want
to play is one of the indexes in
boom Sounds array. Instead of hard
coding index one, I want a random number
0-3 When I do this, it will give me an integer, either zero, or one,
or two, or three. I destroy enemies and random sounds are
playing perfect. We also have a victory
sound when we win a game, scary horror sound when we lose. And we have a horror scream when one of our crew
members gets eaten. To figure out where to place the code that plays
these sounds, we need to understand
which code blocks are running over and over for every animation frame and which code blocks run only once. I want the losing sound to be played only once when
game over happens. I can do it here inside our custom trigger
game over method. If the player lost all lives, if all the crew members were
eaten, we play the lose. Sound If the score is more than winning
score, we play the win. Sound Go to enemy
GS here on line 57, inside the update
method on enemy class. If the enemy moved
all the way across the screen from top to bottom
and we didn't destroy it, player will lose one life. One crew member will be eaten, and we will play the scream. Sound Okay, I set winning score to three
so we can test this easily. We have 15 lives
here on line 108. 15 crew members. When the enemies
get to the bottom, they will start getting eaten. It's a bit strange when multiple enemies
reach the bottom, The stream Sound gets reset from the beginning
as we designed it, but I don't think it works well in this
particular scenario. When we lose our
lives, we get lost. Sound That worked. I restart the game and I killed three enemies. I win the game. And the winning sound plays. We have a nice set of sounds and they don't have only
an aesthetic effect, they also have a
gameplay effect, giving the player audio
confirmation that the interaction was
registered and actioned. I don't want to
rewind the scream, so I only want it
to play again when the previous scream fully
finished playing, I will play. Let's lose some crew members and see now when multiple
enemies get to the bottom, we don't get a scream sound for each lost crew member if
they get lost too fast. But I think it's
clean when we are not resetting this particular
sound until it's finished. Now it might be a good idea to adjust your winning
score and play your lives to make the game as difficult or as easy
as you want it to be. If you are testing it
on a mobile device, you can leave a quick comment and let me know if
it worked for you, or if you had any issues on some specific mobile phones
and which browser was used. I recommend using
Google Chrome for this. If possible, you can just
leave a quick comment. For example, Galaxy S
22 Firefox browser, it works, or
something like that. If you do that and test
this game on your phone, it would help me to make the next mobile game
tutorials better. We can test our code and emulate other devices
in Google Chrome, but the emulation might
not be 100% accurate. The only way to be
absolutely sure is to run the code
on an actual device. This was a big
project, well done, if you managed to get
all the way here. A.