Transcripts
1. Introduction: What makes a perfect game? Detailed, handcrafted visuals,
fantasy environments, and a wide variety of
animated characters? Or is it more about
the game mechanics, physics for interactions, and AI to make the
creatures feel alive. What is the special ingredient in game development recipe? In this class, we will dive deep into the secrets
of JavaScript, web animation and front
end web development. Let's try to discover what makes a great game and how
we can build it from start to finish using just our own
JavaScript code with no frameworks and libraries. This class is for beginners, but some basic knowledge of front end web development is required to get
the maximum value. Let's go.
2. Basic setup: I'm giving away a ton of free game art when this
class, I'll be like it. We will control the blue ball. Its job is to protect hatching eggs from waves of
hungry enemies. Player can position
all the game objects by pushing them around. We can push the X and
hatchlings to safety, or we can push the
enemies out of the way while building
this project, I will show you how
to use HTML, CSS, and plain vanilla
JavaScript to implement many important web animation and game development techniques, we will apply physics to make a game objects interact
with each other. We will learn how to restart our game by pressing a button, how to control FBS
of the whole game, how to trigger periodic events. We will apply most controls. Learn how to manage and animate a directional
spreadsheets. We will trigger and
animate particles when a certain event
happens, and much more. Let's take it
step-by-step to make sure we really
understand the code. And by the end of this class, you will have all the
skills you need to build your own games and
animation projects. I create an IMG element
with an ID of overlay. The source will be
overlay dot PNG. You can download all project are the assets in the
resources section below, there are individual images and sprites as I'm using
them in this class, as well as a bonus folder with source files where
each game object Gmb, split into high
resolution pieces. You can edit and animate. So if you want, you
can mix and match, combine them with
art I gave you for other classes where we are using this mushroom
forest theme. And you can create your own
unique game environments. All the art in this
class is copyright-free. Feel free to download
them, modify them, and reuse them in your own
projects in any way you want. I'm already giving
you all art you will need to follow this course. But for those of you, I want to take it further. If you have a graphics editor like Photoshop, you can e.g. color shift the images to create even more visual variety. Character source files can
also be raped and animate it in 2D sprite tools like
dragon bones or spine, check out the source files and
use them however you want. It's my gift to you as a thank you for spending
your time with me. So we have a basic setup in
index.HTML and style CSS. Gammas defaults to this small
size of 300, 150 pixels. If I set its size with CSS, I would be setting
only its element size. And that would
stretch my drawings. Html canvas has
actually two sizes. Element size, the
drawing surface size that can be
set independently. I want both sizes to be the same to prevent any distortion. So I will size my canvas
with the JavaScript here. I wrap everything inside load event listener because we will use a lot of art assets. And I wanted to make sure
all my images are fully loaded and available before
any JavaScript code runs. First, we need to point
JavaScript towards our canvas element using
get element by ID. We save that reference in this custom variable
I call e.g. canvas. Then I take that
variable and from it I call built-in getContext method, passing it to D as
context type argument. This will initialize a
built-in object that holds all Canvas properties
and drawing methods. We can now call them
from this CD x variable. So as I said before, I want to set the canvas size, both elements size and drawing surface size
to the same value. We can do it like this. Canvas dot width is 1,280 pixels and canvas
height is 720 pixels. Now, the full
background artwork I prepared for you is
revealed perfect.
3. Object oriented programming in JavaScript: I wanted to write this game as object oriented code base
to make it more modular, we will have a class
for player and another class for game to
manage all the game logic. The main brain of
this code base. We will also need animation
loop to draw and update our game over and over to
create an illusion of movement. Game class constructor
will expect a reference to canvas element as
an argument like this. Inside, we convert it into a class property and we will
need to width property. And the value will be this
dot Canvas from line 15, dot width. Like this. This will give us 1,280 pixels as we set
it up online for, we do the same thing
for this dot height. We are taking a reference to canvas element and
we're setting the width and height of our game to be the same as the width and
height of Canvas. We will finish this
setup by connecting this canvas argument to this
canvas variable a little bit later when we
create an instance of our game class using the new keyword, we
will get there sooner. I'll show you. Before we do that, I also need the player
to have access to width and height properties of our game because the
player needs to know e.g. when it moves outside
the game area and so on. I will give it access to the entire game class and all its properties
and methods by passing it a reference
to this game class as an argument like this inside, we convert it to
a class property. Keep in mind that I'm not creating a copy of
Game Object when I create player objects in JavaScript are so-called
reference data types. So this dot game here on line
nine doesn't create a copy. It just points to a
space in the memory where our main game
object is stored. The code inside a
class constructor. It gets triggered when we create an instance of a class
using the new keyword, we will do that in a minute. I want our codebase to
automatically create player when we create an instance
of our main game object. So I can do this inside
the class constructor. I create a property
called this dot Player and I set it to new
player like this. I can see that player
class constructor, online aid expects
game as an argument. So I pass it this keyword, since here we are
inside that game class. This keyword here refers
to the entire game object. Here on line 18, we are
creating an instance of player class and
we're saving it as this dot Player property on the game class structure in our code like this will
automatically create player. When we create a game, we create an instance
of game object like this custom
variable I call e.g. game, and I set it
equal to new game. On line 14, I can see that the game class constructor
expects Canvas as an argument. So I pass it canvas
variable from line to this variable
will get converted to a class property and
the width and height of the game area will be extracted
from it as we planned. Let's check if
everything worked by consolidating this
game variable. Nice, I can see the correct
width and height properties and we have an instance of
player class in there as well. This is one of the ways how
you can organize and connect your objects in an object
oriented JavaScript code base. Keep in mind that the order in which we define the
classes matters. Javascript file is read line
by line from top to bottom. Javascript classes are hoisted, but they are not initialized until that particular
line is right. So player class needs to be
defined before it's used. A very good idea would be to
split our JavaScript into individual modules and import
and export our classes between files as needed. For this project, I will
write all the code in a single JavaScript file to keep it as beginner
friendly as possible, using JavaScript
modules would require us to run this code
through a local server. It wouldn't run the
code by simply open an index HTML file in
a web browser anymore, if you are more experienced, it will be very easy for you to finish the project with me. And then if you want, you can split individual classes into separate modules yourself. This class is for beginners, so let's focus on object oriented principles and
animation techniques.
4. Drawing the player: The function that sits on an
object is called a method. Player will need to draw
method to draw and animated. It will expect context
as an argument to specify which kind of
us we want to draw on. We will connect this
context argument to our CTX variable
from line three, when we call this draw method a little bit later,
I will show you. I wanted to draw a
simple circle at first, representing our player. To draw a circle on Canvas, we take contexts and we call begin path to
tell JavaScript, we want to start
drawing a new shape. We want to close previous shape. If there are any, then we
call built-in arc method, which expects at
least five arguments. It expects x and y coordinates of the center
point of the circle. Its radius start angle
in radians measured from the positive x axis and an
angle where the arc ends, again in radians measured
from the positive x-axis. There is an optional sixth
argument for counterclockwise. If we don't define it, it will default to false, which means that the arc
will be drawn clockwise. So start angle is 0 rad, and end angle is math.pi times
two, it's a full circle. Now we can choose to call fill, to fill the shape with color, stroke just to
outline the shape. Or we could use both. We will do that soon. How do we actually draw
the player on Canvas? Now, on the game class, I create a method I call e.g. a. Render. This method will draw and
update all objects in our game. It expects contexts
as an argument. Inside, I take this dot Player
from line 23 and through this reference we
access the draw method on player class we just
defined from line 11. This method contains
the code to draw a circle representing
the player. I can see it expects
contexts as an argument, so I pass it along this context, we passed to the render method. Now I can take this
game variable that holds an instance of
the entire game class. And from there I call render, and I pass it to CTX. From line three, that CTX will be assigned a variable
name context here, and it will be passed along to draw method on player class. Nice, we are drawing a black circle representing
the player is here. Maybe you can't see it. So let's give it a different x and y coordinates to move it. Instead of hard-coding
all these values, I want you to create properties
on the Player class. Then use those here. We will need x and y coordinates
for player position. But because in this class we are learning about
position and hit boxes and character images that can have very different
shapes and sizes. I will have to
create property for X and Y position
of player ID box. And I will need to have
a different x and y property for player
sprite sheet image. It will make more
sense as we build it. I want to be very explicit with my variable names to
make it absolutely clear which value is the position of the collision box and which value is the
position of a spreadsheet. So instead of naming these
properties just x and y, I will name them collision
x and college and Y, X and Y position of
the collision hit box of the center point
of the collision circle. All objects in our
game today will have circular hit
boxes because I wanted to show you how to
make them push and slide along each other on the starting position
of the player to be exactly in the middle
of the game area. So collision x will be this
dot game from lines nine. And from that property, I will extract
width from line 23. And in the middle, so times 0.5 collision y
will be the same. This dot games with
height times 0.5. Now I can use these
values as x and y arguments to Canvas
arc method like this. Now we can move the
player around by changing values of collision
and collision. Why properties? We will also need a property
called collision radius, which will define the size of this circular
player. He'd books. I use it here inside
the arc method as well. Default fill color
is always black. I can override, adhere
by certain kinds of us fillStyle property
to white like this. Instead of filling
the shape with color, we could also just stroke it. Again. By default, linewidth of stroke is one pixel and
the color is black. I set the line width to three pixels and I said
stroke style to white. Notice I'm defining
these Canvas properties outside of any class or method. I do that on purpose
because this code will only run once on the
initial page load. You can't always do
that if you have multiple objects with
different fields, styles, and stroke colors. In that case, you will have
to define these properties inside the draw method and switch between them
over and over. The problem with that
is that the draw method will be called 60
times per second. And change in a state like this could get
performance expensive. It's a good idea to
structure code in a way you change a state as
little as possible. And when I say canvas state, I mean anything from transforms to changing colors of
fillStyle and stroke style. That's why I put this code here instead of placing
it directly inside the draw method to make it
run as little as possible while still applying the colors and settings as I need them. I can also call fill here. So now we are
filling and stroking the same path defined
by arc method. I want the fill to be white, but I wanted to be
slightly transparent. Kind of us has a global
alpha property to set opacity of the shapes
we are drawing. The problem is that when I said global alpha to a
different value, everything drawn after that
will be semi-transparent. I want the transparency to only apply to the fill color of player collision circle to limit certain kinds of settings
only to specific draw calls. We can wrap that
drawing code between safe and restore
built-in Canvas methods. Than if I said
global alpha to 0.5, it will affect only that
specific drawing action. In our case, it will only
affect the fill of this circle. So save method creates a
snapshot of the current state, including its fillStyle,
linewidth opacity, as well as the
transformations and scaling. If we are doing that, then I can do any changes to
that kind of a state I want. In this case, I just
set opacity to 0.5. This fill color will be affected by that
change the opacity. And then we call restore, restoring all kinds of
settings to what they were when we first called its
associated save method. For that reason, the stroke will not be affected by
a reduced opacity. Save and restore
methods allow us to apply specific
drawing settings only to select the
shapes without affecting the rest of
our Canvas drawings.
5. Mouse controls: I want to move the player
around using mouse. We already know that the code inside the game class
constructor will be executed at the point
where we create an instance of this class
using the new keyword. We are taking
advantage of data by automatically creating an
instance of player class here, we can actually run any
JavaScript code in here. I can even put event
listeners here to make sure they are
automatically applied. When I create an
instance of game class, I create an event listener
for mouse down event. When mouse button is clicked, the code inside this
callback function will run. I tested by just console
logging the word mouse down. If I save changes
because I'm already instantiating this
class online for D6. D7 listener is
automatically applied. Now when I click on Canvas, console log is triggering nice callback function
on EventListener auto-generate an event
object that contains all kinds of information about the event that just happened. To get access to that object, we just need to give
it a variable name. You can name it
whatever you want. But the convention
is usually event or E. Let's console log this event. I click on Canvas
and I see it here. I inspected. You can
see it contains a lot of information about
that mouse-click, e.g. we see x and y coordinates
of that click here. There are many other
properties that tell us which mouse button was pressed
and many other things. I want you to take the
coordinate of the click and save them as properties
on the main game object. And from there, we
will be able to access them from our
player object as well. I create a new property on game class called
this dot mouse. It will be an object
with x property with default value of this
dot width times 0.5. And y will be this
dot height times 0.5. So the middle of canvas,
horizontally and vertically. We will also want
to monitor when the mouse button
is pressed down. Initially, it will
be set to false. If I console log e dot x and y, you can see we are
getting x and y coordinates as we
click around us. The problem is that the
coordinates are from the top left edges of
the browser window. I would like to measure
the clip coordinates from the top-left corner
of Canvas instead. So when we click here in the top-left
corner of the canvas, we get x and y zero-zero. For that, we can use a
different property on this auto-generated event
object called offset x, which will give us
horizontal coordinate of the click on the target node. In our case, the target node, the target of the click
is kind of as element. Now you can see the
values get very close to zero as I click close
to the edge of Canvas. I could also add the
event listener to the canvas element itself rather than the entire
browser window object. If I click closer to
the top-left corner, we are getting
values close to 00. Perfect. Probably it
would make sense if I use this dot Canvas property
here from line 31 instead, since we are inside
a class and we have the reference to
Canvas available here. So now we are getting coordinate
of the click measured in pixel distance from the
top-left corner of Canvas. Even when we resize the browser window,
this is working well. I want you to save the
cyclic coordinates inside our customer mouse
property so that they are available to other
objects in our code base, such as the player inside the
mouse down event listener. I take that this dog mouse
dot x property from line 36, and I set it equal
to E dot offset x. This dot mouse dot y
will be e dot offset. Why? I create
another console log and I will look at these newly
updated mouse properties. When I click on Canvas, we get an error that says
cannot set properties on undefined certain
x online for D3, it's telling me that I can't set x property on something
that is undefined. For some reason,
this dot mouse is undefined when accessed
from inside Event Listener. It is because when
this callback function on event listener runs, it forgot it was
originally defined inside this game
class constructor. It forgets what this
keyword stands for. This is expected to make the event listener remember
where it was first defined, where it sits in the lexical
scope of our code base. We can simply use ES6 arrow
function here instead. One of the special features of ES6 arrow functions is
that they automatically inherited the reference to this keyword from the parent
scope are all functions. Remember where in the code base they were originally
declared lexically. And they adjust
their desk keyword to point to the correct object, to the parent object. Now, this mouse dot x and this look mouse dot y are correctly updated
to the new values, making the current mouse
coordinates available all over our code-base
whenever they might be needed. Later, I delete
the console logs. When mouse down event happens. I said priced from
line 38 to true. I copied this event listener. This one will be
for mouse up event. When the mouse
button is released, everything here
will stay the same and reset breast to false. I also create an event listener
for mouse move events. Let's consulted to check. Yeah, that's working. Let's make the player move.
6. Making the player move: Create a custom
method I call update. Inside I said
collision x from line 14 to the current
mouse X position. And collision y will be the current mouse Y
position, like this. To run this code, we actually need to call the update method. I will call it from
inside render down here. I delete this console log. If we wanted to
see any movement, we need to be calling
render over and over. So let's put it inside
the animation loop here. I call built-in Request
Animation Frame a method which sits on the
browser window object, but we can also call it
directly like this. If we want. I pass it, animate the name of its parent function to create
an endless animation loop. Now I need to call animate to actually start the animation. When I move mouse over
us, we get trails. I only wanted to see the
current animation frames. So between every loop, I use built-in clear
rectangular method to clear the old paint. I wanted to clear the
entire canvas area from coordinate zero-zero to
canvas width, canvas height. Now, the player
sticks the mouse as we move it around
cannabis, perfect. I wanted to create a line
between mouse and the player to clearly show the direction in which the player will move. Inside the draw method
on player class, we start a new shape
by Colin begin bath. Move to method will define the starting x and y
coordinates of the line. In this case, I
wanted the line to start from the coordinates
of the player object. Line two methods, we'll set the end in x and y
coordinates of the line. In this case, it will be x and y coordinates of the mouse. Then we call stroke to
actually draw the line. This works, but
since the player is always able to catch up
with mouse cursor so fast, we can barely see the line. Let's give player speed, speed X horizontal speed. Initially, I set
it to zero speed. Why vertical speed also
initially set it to zero. Inside update method, we
will calculate speed x. First, I set it to hard-coded one pixel
per animation frame. And I increase player
exposition by horizontal speed. That worked. I also do it
for vertical position. There are two ways we can
make player follow the mouse. One way would be to simply
take the difference between the current mouse position and the player position on
the horizontal x-axis. And set that difference
as horizontal speed. And we also do that
for vertical movement. Now, the player position
is correcting for the difference by the entire
amount of the distance. So it makes the
movement instant. What if I make it move only by the one-twenty-fifth of
the difference between player and mouse position for animation frame
horizontally. And also vertically. I create a class
properties for dx, distance between
mouse and player horizontally and
vertical distance. I replace those values here. It's easier to read it this way. I don't want the player
to follow all the time as we move
mouse over Canvas. I want only when we
click somewhere or when we hold the mouse
button down and move around. Inside mouse move
event listener I say only update x and
y mouse position. If mouse is pressed. Now I can click around to make the player move
to that location, or I can drag that point around while holding the
mouse down. Perfect. The problem with this
technique is that the speed is not constant. Moves very fast at first, because one 20th
of the distance is at first a big chunk
when they are far apart. But as they get closer, one 20th of that
distance becomes smaller and smaller amount of pixels to be travelled
per animation frame. You might want this particular
emotion for your project, but for the game
we're building today, I wanted the player to
move at a constant speed. We will have to use the
second technique for that insight update method. I calculated the distance. We already have dx, the distance between mouse
and the player horizontally. We also have d, the distance between mouse and the
player vertically. We want to calculate the distance between
these two points. We can do that by
calculating hypotenuse, the longest side of this
imaginary right triangle. We can use Pythagoras
theorem formula, or in JavaScript, we have this built-in math dot
hypotenuse method. This method will calculate the length of the
longest side for us if we pass it to other sides of the
triangle as arguments, keep in mind, it expects d, dy and dx second, which might be a bit unexpected if you never saw this before. Horizontal speed is
the ratio between dx, horizontal distance between
mouse and the player and the actual distance.
Sandwich speed. Why? It will be the ratio
between the distance on vertical y-axis and
the actual distance between the two points. As a backup, when some of
these values are undefined at first we say or
zero, like this, we are divided in horizontal
and vertical distance, these sides by the
actual distance represented by the longest
side of a right triangle. Dx and DY is always a
smaller number than the distance because
the distance is hypotenuse, the longest side. For that reason,
the values we get a speed x and speed y
will be somewhere 0-1. That will give us the
correct direction of movement at a constant speed. There is much more to be
said about this technique. But for now, this is
all we need to know. I'll get back to this. Now. The player is moving at a constant speed
towards the mouse. I can have a speed modifier. I set it to five, e.g. I. Use it down here and I
multiply speed x and speed. Why by that modifier, after we add the speed modifier, the player circle will actually
never stay still anymore. It will be swinging
back and forth, in this case by
50 pixels because the speed modifier pushes it
too far in both directions. I can fix it by saying, only move the player when
the distance between mouse and the player is
more than speed modifier. Else said speed x to zero
and speed y to zero as well. This works perfect. So we covered one simple and one more advanced technique to make the player move
towards the mouse. Now it's time to add solid, randomized,
non-overlapping obstacles.
7. Creating obstacles: I create a class
I call obstacle. Constructor will expect
the game as an argument. And inside I converted
that reference to a class property,
same as before. It will be pointing towards
the main game object. And we needed here
because through this reference we have access
to game width and height, mouse positions, and
some other properties. We will be added later. We will have access to
all these values from inside obstacle class through this.name reference
from line 54. As I explained before, the objects in our game, we will have a circular
collision heated box, and a separate
rectangular spreadsheet. For that reason,
I will be calling these properties with a very
descriptive names to make sure it's very clear
what's happening when we are moving and animating
everything later. Collision x, the central
point of collision circle of each obstacle will
be a random value between zero and the
width of the game. That width is coming
from line 62 here. And we're accessing it through this dot game reference
we created on line 54. We will also need collision. Why vertical central point
of collision area circle. It will be a random value
between zero and game height. This value collision
radius will be 60. We will also need to
draw a method that expects contexts as an argument. I want that circle that
represents a hit box area of each obstacle to look the same as the circle
representing the player. So I take the drawing
code from up here, just for the circle. So this code block, I copy it and I
paste it down here. This code will work here
because the same as with the player we gave our obstacles property
is called collision x, collision and collision radius. We will also need
the same name in on all these properties between
different object types. In case we want to have a reusable collision
detection function. I'll show you how to
use that one later. It's simple. Anyway, here we have a
code to draw a circle with a radius of 60 pixels
with 50% opacity, white fill and white, fully visible, full
opacity stroke. This obstacle class
here is a blueprint. We will use it to create
individual obstacle objects. The actual logic to
create and manage these objects will be down here inside the main game class, which is the main brain
of our code base. I create a property called
this dot obstacles. It will be an array that holds all currently active
obstacle objects. It will start as an
empty array at first, the number of obstacles
will be e.g. five. I create a custom method on
our game class, I call e.g. in IT, initialize its job
for now will be to create five randomized obstacle
objects and put them inside obstacles
array we just defined. Inside, I create a for loop. It will run five
times because we said number of obstacles to
five up on line 76. Each time it runs, it will take this dot obstacles array
from line 77 and on it, it will call built-in
array push method to push method as one
or more elements to the end of an array, and it returns the new
length of the array. I will pass it a new
obstacle like this. The new keyword will look for a class with the name obstacle, and it will trigger its class
constructor up online 53, I can see that obstacle
class constructor expects game as an argument. Down here, init method sits
inside that game class, so I pass it the, this keyword, which here represents
the entire game object with all its properties
and associated methods, making all of these available
from inside obstacle class. Now, I console log
game and I can see obstacles array
is completely empty. To fill it, all I have to
do is called init method. We just wrote like this. Now I can see the array
contains five obstacle objects. I double-check to make sure
all properties have values. If you see undefined
in any of these, it means there is a
problem in your code base. All is good here. Same as I'm drawing
an update and the player from inside the
game render method here, I would like to draw all five
obstacle objects on Canvas. I take obstacles array from -77. We already know that it contains five objects and that each of these objects was created using our custom obstacle
class from I'm 52. They all have access to this draw method we
defined on line 59. So here inside render, I take that obstacles array, I call built-in array
for each method, the forEach method executes a provided function once
for each array element. First, we need to define a variable name which
will be used within this forEach method to refer to individual objects
in that array. I will call each
object obstacle. So for each obstacle
object in obstacles array, I call their associated draw
method from 1959 online 59, I can see that it expects
a reference to context as an argument to specify which kind of Us element
we want to draw on. I simply pass along
this context that was passed to the
parent render method. Nice. We are drawing
one player and 12345 randomly
positioned obstacles. I go up here and I make the
obstacles a bit larger. Every time I refresh
browser window, they get positioned randomly somewhere within the canvas area because that's how we define their position on lines 55.56. What if I wanted to make sure
that the obstacles never overlap like this and maybe
take it even further, since this will be solid
obstacles that player can't move through and
has to walk around them. I would also like there
to be a minimum spacing between them and also between
the edges of the game area. Just to make sure all
the creatures that will soon be crawling
here don't get stuck and can eventually find their way automatically
around each obstacle. It's actually easier to implement all of that,
then you might think, but we have to take
it step-by-step and explain a couple of tricks we can use here to achieve that.
8. Non-overlapping obstacles: Inside init method,
we are simply adding five randomly
positioned obstacles. Right? Now. I have to delete this. We will need this structure is called a bit differently here. So first, I wanted to make sure the obstacles don't touch, that they don't
overlap like this. We could also adjust
the number of obstacles to be the
maximum number of circles possible can fit into a certain area without any
two of them overlapping. Sometimes we call
this circle packing. So let's write a
very simple circle back in algorithm here. I will use the basic
technique where you just try to play circles at random
positions many times. And only those that
don't collide with already existing circles
will actually be turned into obstacle
objects and drawn. This is also called a
brute force algorithm. It's not very smart, it just tries over and
over many, many times. I will create a lead
variable called attempts. It will be my safety measure. We will count how many
times we tried to draw a circle and we will give up after a certain
number of attempts. The assumption being that
there must have already been enough opportunities
to place the obstacles. I will use a while loop. You have to be careful
with this one. If you create an
infinite while loop, you will slow down your browser and you will need to restart it. Very old computers might even freeze if you use
while loop Rome, new browsers can
usually deal with it. My goal here is to randomly
place circles over and over. And before we actually turn that circle into
obstacle object, we check if it overlaps
with existence circles. On if it doesn't overlap, we added into the
obstacles array. I want this while loop to run as long as obstacles array
length is less than, number of obstacles,
less than five. We defined that array here, and number of obstacles
was defined here. As a backup, I also set
a secondary condition, only continue running
this while loop as long as attempts
is less than 500. This is important
because if I said radius of an obstacle to
be a very large number, or I set number of obstacles
to be so large that they can't physically fit
into the available area, we would get an
endless while loop. But when the secondary
condition JavaScript, we'll just try 500 times. And if by that time
they couldn't find placement for all the
obstacles, it will give up. I think 500 attempts
is more than enough. Every time the loop runs, we have to increase
attempts by one for our safety
back-up plan to work. Every time this while loop runs, we create a temporary
object I call e.g. test obstacle. It will be equal to
the new obstacle and I pass it a game, this keyword as an
argument as we did before. Let's console log
this test obstacle. Nice. We have 500s test
obstacles in console. Now, you can see
they have collision, collision y and collision radius properties
as they should. My goal now is to take this temporary test
obstacle object and compare it against every other obstacle
in obstacles array. Of course at first, this array is empty, so the first obstacle should always be placed without issues. The second test obstacle
will compare itself with the first one that's already
in the array, and so on. So for each obstacle, obstacles array, I will run a circle collision
detection formula. Circle collision detection in
JavaScript is quite simple. We basically need to
calculate the distance between the two center
points of those two circles. Then we compare the
distance between two center points with
the sum of the radii. If the distance is less
than radius of circle one plus radius of circle
too, they overlap. If it's exactly the same, the circles are touching. The distance is more
than the sum of radii. There is no collision. We already did this when measuring the distance
between player and mouse. This time, the two points we want to measure
the distance in between is the central point
of obstacles circle one. And central point of
obstacles circle too. So again, we are creating
this imaginary right triangle where dx is the difference between two points horizontally. D is the difference between
the two points vertically. And the actual distance is the hypotenuse of that triangle. So here we use Pythagoras
theorem formula or a built-in math dot
hypotenuse method, passing it a DY first
and the second. Now we know what is the distance between
the two central points. Sum over ADI is the
radius of circle one, in this case, radius
of test obstacle. And the second one
is the radius of whatever obstacle object
inside obstacles array, we are currently cycle and over. As we said, if the
distance is less than sum of radii, how will I do this? Outside the for each method, I create a flag length
variable I call overlap, and initially I set it to false. The distance is less
than sum of radii. We set overlap the true
because collision was detected outside the
for each method. If overlap is still
false after we created desk obstacle
and after we compare it using Collision
Detection formula with every other existing
obstacle in the array. If it doesn't collide
with any of them. And overlap variable
is still false. After all these checks on, then we take obstacles
array and we will push this test obstacle that passed
our checks into the array. Now, when I refresh the game, five obstacles will be randomly positioned and they will not be overlapping because those that
do overlap are discarded. And only non-overlapping
circles are used. Because I have my safety
measure here on line 10008, and we always stop this while loop when
we reach 500 attempts, I can actually go up here
and I can set the number of obstacles to a large number that I know will never fit our code. We'll just place as
many obstacles as possible and then it
will stop trying. We know this is
working because if I keep refreshing my
project over and over, we never see
overlapping circles. I mean, no obstacles
overlap with each other. Player can overlap at this
point With Obstacles. We don't care about
that right now. I said a number of
obstacles to ten.
9. Randomized images from a sprite sheet: Now let me show you how
we will be attaching images to the circular
collision hit boxes and how to position the image in relation to the
head box so that it makes visual sense and creates an illusion that this
is not flat canvas, but a three-dimensional
environment where the player can actually walk
around these obstacles. You can download
all projects are the assets in the
resources section below. In index.HTML, I create another
image element with an ID of obstacles and source
will be obstacles dot PNG. That image is a sprite sheet. We will randomly out one of these frames for each
obstacle object. I don't really want to draw
the actual image element, so I hide it with CSS inside
obstacle class constructor, I create a new property, I call this dot image. I pointed towards that
obstacle spreadsheet using get element
by ID like this. Let's set the number of
obstacles to one for now. Inside the draw method
on obstacle class, I call built-in canvas
draw image method. This method needs at
least three arguments, the image we want to draw. So this dot image from line 58 and x and y coordinates
where to draw it? I will draw it at this dot collision x
and this dot collision. Why? At first doing this, we'll simply draw the
entire image sprite sheet. And the top-left corner of
this spreadsheet will be starting from the center
point of obstacle circle, because this is how by default, images and circles are
drawn on HTML canvas. If I refresh the project or new obstacle is positioned
randomly somewhere on Canvas, I created this
spreadsheet for you. So I know that individual
frames are 250 pixels wide. I save that value as sprite with variable sprite height
will also be 250 pixels. If you are using a
different spreadsheet, you can get the width by
dividing the width of the entire spreadsheet by
the number of columns. And the height is height of the sprite sheet
divided by the number of rows in case we
want to add scale. And later, I will also create independent width and
height properties. For now, there will be equal to Sprite width and Sprite
height because I sized the sprite frames to the exactly same size as I want them to be
drawn in the game. Draw image can also accept optional fourth and
fifth arguments defined in the width and height. The entire image
will be squeezed or stretched to the area we
defined by these values. It will look like this. What I actually want
to do is to crop up one of these 12
obstacles and draw only that one at the size
of 250 times 250 pixels. For that, I need to use
the longest version of draw image method that
expects nine arguments. Those nine arguments
are the image we wanted to draw, source x, y, source with source height of the area we want to crop
out from the source image. And destination x, destination. Why destination with and destination height
to define where on destination us I want to place that cropped out
piece of image onto. So if I pass it a zero as a source x and zero
as source height. Sprite with sprite height like this as source width and height, I need to spell correctly. So now we are drawing the top left frame
in our spreadsheet. As I said before, I will set separate x and y
position for the spreadsheet. There are multiple
different ways to do this. I can just simply position the image directly on
top of collision x, which is the central point
of collision circle, minus the width of
the image times 0.5. This will center the image horizontally exactly over
the collision circle. To actually apply this, I need to use sprite X as destination x property passed
to draw image method here, be careful when passing
arguments to draw image method. The order in which you pass these arguments is
very important. Okay, if I refresh the page, I can see it's been correctly
centered horizontally. I do the same thing
for Sprite y, and I use it as destination. Why property passed
to draw image method. Now the spreadsheet is directly on top of
the collision circle. I said collision radius
to a smaller value. I want this small collision
area to be positioned at the base of the plant
where the stone is. Because that will be the
solid area that's touching the ground that are game characters will
have to walk around. Since our sprites set size
of 250 times 250 pixels, I can actually use a
hard-coded value here. Plus 40 will move it up. -40, -50 -60 -70. Yes, this seems alright. I said that the number
of obstacles to ten.
10. Positioning rules: What if I wanted
to make sure that not only the obstacles
that don't overlap, but also that there is a additional minimum 100
pixels space in-between. So that's they are
more evenly spaced out around the
available game area, as well as allowing enough space in-between
the obstacles. The game characters can
easily walk around them. I create a helper
variable I call e.g. distance buffer, and I set
it to 100 pixels like this. Then I included distance
buffer here in some of radii to apply this buffer in-between obstacles when
we are placing them. Nice. To make sure this is working, I increase the distance
buffer 250 pixels. That should make it
even more apparent. Yeah, so this is how we can easily control obstacle spacing. I also want to make
sure that obstacle sprite images are entirely drawn within the game area and not partially hidden
behind the edges. I could have done this insight
obstacle class constructor when defining these
values initially. Or I can also just do it here, since we are not drawing
that many obstacles and their positions are
calculated only once on the first
page load anyway, I make sure the left edge of obstacle spreadsheet
is more than zero, so it's not hidden behind
the left edge of Canvas. At the same time, I make sure that the right
edge is not hidden. So sprite X must be
less than the width of the game area minus the
width of the obstacle. Nice. When I refresh the page, I can see horizontally, obstacles are always
fully visible. For vertical position, I
want to check if the image, the central point of collision circle more than zero vertically
will not be enough. I want to define an area that is reserved for this
background artwork. I don't want to ground obstacles to appear over this area. I create a property
called top margin. I guess at this top area is
around 260 pixels of height. Let's check here. Yes, 2.6D looks alright, because I wanted to
make sure the base of the obstacles doesn't
overlap with this top area, but I don't mind if the top of the obstacle spreadsheets
overlaps like this because this looks like the obstacle plant is standing in front of the
background for us to view. So this is fine. I will also check if
the central point of obstacle collusion
area circle is less than the height
of the game area. I want some margins. I can e.g. create a helper
variable that's equal to collision radius of the
test obstacle times two. I replace this hard
coded value with this dotted top margin
property we defined. Plus I want to give it some additional top margin
so that characters and especially enemies can
squeeze in between obstacles and gain boundaries. Walking across the game field horizontally from right to left. I will also account
for the margin from the bottom of the game area
to create some space there. We wrote code that automatically places obstacles
in our game world. This obstacles never overlap and their correlation
areas are placed to allow enough space
in-between them. This will make the next
step is much easier because we need enemies
and friendly NPCs to be able to automatically
walk around them using very simple
artificial intelligence. We have different
images for obstacles, but right now we are drawing only the first top-left frame
at coordinate zero-zero. We can crop out to
different areas from the obstacle spreadsheet,
horizontal crop area. We'll start from the
position we pass as source x argument to
draw image method here, zero times sprite with
is this frame. One. Sprite width will be this frame. Two is this 13, is this one. Back to zero. To select from
which row we are cropping, we use source Why argument here? Again, we multiply
rule number by the actual height of
individuals bright frames, zero times sprite height is this one term sprite
height is this. Now we are unrolled
two and there is no row three because we
start from row zero. Images are drawn and
cropped from the top. So instead of hard-coding these values that are
currently set to 00, Let's turn them into class
properties for clarity. And is a control. This look frame x will determine which column we are on in
our obstacle spreadsheet. If I do a random number 0-4, this will not work. There is no column 1.74, e.g. we need integers, numbers
without the decimal points. So I wrap it in
Math.floor to round random value generated by Math.random to the
closest lower integer. This code will give me
either zero or one, or two or three. So one of our sprite columns, when we multiply
these integers by the width of a
single sprite frame and we pass that value as a source argument to
draw image method, we are defining horizontal
cropping coordinate. That worked perfect. I will do the same for frame y, which will determine sprite row. We have only three roles. This line of code will
give me integers, either zero or one or
two corresponding to the number of rows we have available in our
obstacle spreadsheet. Now we can replace this hard coded zero with
this dot frame y. So source Why argument but asked to draw
image method will be this dot frame y times
this dot sprite height. I'll do that in a second. Random values in frame x
and frame why combined will give us a random image out of these 12
available obstacles, each obstacle object will have random frame from this
spreadsheet assigned to it. I will finish this a bit later.
11. Reusable collision detection method: On the main beam object, I create a method I
call check collision. I want this to be a
reusable utility method that takes object a and object B and it will compare them and check if they
are colliding or not. We will be able to use this
all over the code base wherever collision
detection between two circles is needed. The way I'm building my game, all characters and objects will have a circular collision area, which will be a solid base
that nothing can walk through and everything will react and walk
around everything. Using this, we can also
push things around. I will show you check
collision between two circles. We have circle a
and circle B. Here. We need to check dx first, the distance between the
center point of circle a and the central point of circle B on the
horizontal x-axis. This reusable methods will
work only if all objects involved have properties with
the same naming convention. So we will make sure we
named x and y positions on each object as collision
x and collision why? I'm using this overly
descriptive property names so that it's very
clear when x and y coordinates relates to
collision area circle and when they relate to image
sprite sheet positions. This is a tutorial, so I want things to be very
clear and easy to understand. We will also need a D, Why the difference between the
central point of circle a and the center point of circle B on the vertical y-axis. Then we want to know the distance between
these two center points. So hypotenuse, the longest side of this imaginary
right triangle, right angle 90 degrees is here. And this is the
distance b dagger has theorem formula or alternatively built-in math
would hypotenuse method. And we pass it the Dui first and the x as the
second argument. To determine whether or
not there is a collision, we compare distance between
these two center points with radius of circle a
plus radius of circle B, I will save this value
as a custom variable, I call e.g. some of radii. So if distance is less
than sum of radii, we know the circus collide. If the distance is
the same as sum over ADI, circles are touching. If the distance is more
than the sum of radii, we know there is no collision. This function will simply
return true if there is collision and false if
there is no collision. Let's use our custom
check collision function. Up here inside update
method on player class, we check for collision
between player and obstacles. We have a one player object and multiple obstacle objects. So to compare all, we will call for each
on obstacles array, which holds all currently
active obstacle objects. I will call each object in the array with a
helper variable name, obstacle, and our console log check collision
method we just defined. We know it expects circular
object a and object B as arguments to compare
the distance of their center points to
the sum of the radii. So I pass it this, which means this player object, circle one and obstacle
we are currently cycling over with this
forEach method as circle B. Keep in mind that this reusable check
collision method can only compare objects
that have collision x, collision and collision
radius property is defined in their
class constructor. So I will make sure I keep the same naming conventions for all objects in the game
we are building today. As the player moves,
we are getting false and true in the console. Seems like this is working. Let's actually only console
log the word collision when collision between player
and obstacle is happening. Now, it's even easier to see
that our code is working. Perfect.
12. Physics: Inside render method, I
will draw obstacles first, so behind and player after it, so it will be drawn on top. What if I want to also
resolve our collisions? What I mean is if they are
collides with an obstacle, I don't want it to be able to
walk through it like this. I want the player
circle to be pushed one pixel back away from the obstacle circle in
the direction that points directly away from the center
point of the obstacle. This simple thing, we will
make the obstacles solid. The player will
actually slide around the obstacles and it will
create a nice physics. Let me show you. I'm already calculated everything I
need for that insight, our custom check
collision method, but this function currently
returns only true or false. I need this reusable method
to return more values so that we can use them
inside player class to calculate collision resolution
of vector functions and methods in JavaScript can
return one value like this, but they can also return an array that contains
multiple values. I want to return true or
false collision status as the first element in the array element with
an index of zero. We also want to
return the distance we are calculating on line 127. We will also need some
of radii from line 128. And we will need DX and
DY from lines 100.2526. So now our custom
check collision method not only checks if collision
is happening or not, it also gives us
other values from calculations that
happened along the way. It's important that we remember the order in which we are
returned in these values. Element with an index of zero is collision status true or false? Element with an index
of one is the distance. Some of radii is indexed to. Dx is index three, and d y is indexed for. I will just copy that array
that gets returned here. And I commented out just so I can see it as a
helper reference. Now I want to take each of these values and save them as
separate variables so that we can use them to calculate collision resolution
here and push two player in the
correct direction away from the obstacle it's
currently collide and width, I will use something called
restructuring assignment. Let's just write it and I will explain it when we
see the whole thing. I say let variable
is this array. And it is equal to
check collision between this player object and the obstacle that for each method is
currently cycling over. I have to replace
this first expression with a variable name. I wanted to call it, I want to call it a collision. It will be that
true or false value depending on the distance
between circle's center point. So if this is true,
there is collision. If this is false,
there is no collision. This structure and
assignment syntax is a JavaScript expression
that makes it possible to unpack values from arrays or properties from objects
into distinct variables. Basically saying here, create
five variables for me. The first variable
called collision is the array returned when we call check collision
method between this player and
obstacle index zero. Distance variable is that
array index one, and so on. This structure and
assignment does this automatically
behind the scenes. It creates these five
variables and pairs them. The values that sit
at these indexes in the array returned by
check collision method. This might be a
bit strange if you never saw it before JavaScript, this structuring is a good
thing to get familiar with. Modern frameworks, use
it a lot and basically just taking the array returned
by check collision method. And I'm assigning each value to its separate variable names
so that I can use them here. So here we are inside update
method on player class. We are cycling through
obstacles array comparing player with
each obstacle object. If there is collision
between player and obstacle, if collision variable is true, we console log AAA. This works. I wanted
to create a vector, kind of a small line, 0-1 pixels in length. That line will point in the
direction in which we want the player to be pushed to
resolve the circle collision, to make sure that
collide and player and obstacle repel each other, causing the player to
slide along the radius of the obstacle rather than
going directly through it. Horizontal vector will
be the ratio between Dx, distance between player and
obstacles central point on the horizontal x-axis, and the actual distance
between these two points we calculated before using
check collision method. Because dx will always
be less than distance, because distance is
hypotenuse is always the longest side of the
imaginary right triangle. Unit x will always
be a value 0-1, because we are dividing smaller
value by a larger value. Unit y will be the
ratio between Dui, the distance between
central points on the vertical y-axis, and the actual distance
between the two center points. Again, it will be a
value somewhere 0-1. This could also be
negative values depending on how our objects sit in relation to each other on horizontal and vertical axis. So actually unit x
and unit y will be a value between minus
one and plus one. If I console log
unit x and unit, why we can see these values, the combination of
these two values being added to players horizontal and vertical position for each animation frame will make it move in a
certain direction and certain speed away from the
center point of the obstacle. I do that by taking player
collision exposition, the central point of
player's collision circle, to push it outside the radius of the obstacle it
is colliding with. I move it horizontally
to the position of central point of obstacles
circle plus the sum of radii of player circle and the obstacle circle plus one additional pixel
outside terms, that unit x ratio to give it the right direction away from the obstacles
central point. We do the same thing vertically. Center point of player collision
circle will be moved to the position of collision
center of obstacle circle plus sum of radii of obstacle
and player circle plus one pixel towns unit Y to give it the right
direction of the push. I am trying to explain this in a very beginner friendly way, but don't worry if it's
still a bit unclear. This is an important technique
and every time you use it, you will feel more and more
familiar with this coat. Eventually it will click
for you how it works. All you have to
understand here is that this code is pushing
the player one pixel outside the collision radius of the obstacle in the direction away from the central point. And this is how you create very simple but very effective physics simulation in your game. Try to move the player around. This feels very
good, doesn't it? Suddenly our obstacle circles turned into solid,
impossible objects. Well done if you've
followed all the way here, this is the main trick we are using today for
our physics game. I adjust the speed modifier
to a smaller value. We learned how to make
the player move towards a mouse or towards a specified
point in a 2D space, and how to make it
navigate its way automatically around
solid obstacles. This is a powerful
technique and you can do with it more
than you can imagine. We will explore
some of that today. Hope you're having fun.
13. 8 directional sprite animation: I prepared a special aid directional Player's sprite
sheet for this class. You can download it in the
resource section below. I will include some
alternative colors. Probably mine is blue too much to mushrooms
in my game art. I will hide it with CSS here. Inside player class constructor. I create a reference to that image using
get element by ID, and I save it as this
dot image property. Inside our custom draw method, I take contexts and
I call built-in canvases draw image method
that we already used before. We already said that the draw image method needs
at least three arguments. The image we want to draw
and x and y were to draw it. This will just draw the
entire spreadsheet. We can pass it width
and height to squeeze the entire spreadsheet
into that specified area. We actually don't have
these properties defined. Sprite with the width of a single frame will
be 255 pixels. Sprite height is 255 as well. Then we create separate
width and height properties to allow the potential if we wanted to
introduce it later. Now, we're squeezing
the entire spreadsheet into the area of
one sprite frame. You probably already
know that we will need the longest version of draw image method
where we add source x, y, source width, and height. These values will first crop
out a portion of the image, in our case, a
single sprite frame. After that, we draw that frame, the position defined by
the last four arguments. To draw the top-left frame at coordinate
zero-zero is simple. We just did it with
obstacles spreadsheet, source x, y, z, zero to define the beginning
of cropping rectangle. And Sprite with and
Sprite height as source width and source
height arguments to define its size. Now we see only one frame. I will calculate position of spreadsheet image in relation to collision and collision
y-coordinates of player. He'd box in these two
separate properties just for clarity. So keep in mind,
these properties define the center point of
player collisions circle. These two properties, we'll
define the top-left corner of spreadsheet frame image we are currently drawing to
represent the player. On us. Spherical coordinates
r from the center point, rectangle and image coordinates are from its top-left corner. And image and rectangle
goes towards right bottom, depending on its width
and height from there. We have to consider this when
writing the following code. Sprite X will be positioned in relation to collision area. It will be collision x, the central point of
collision circle minus half of the width of
player frame. Like this. I need to use sprite X inside the draw as destination
x argument here. For this to work, we
need to recalculate this value every time
a collision x updates. So I need to put this inside
the update method here. And I also do it for Sprite y, which will be collision y minus the half of player height. I can delete it here. And I use sprite.
Why property here as a destination Y argument
passed to draw image method. Now it's positioned on top. I actually want this
collision circle to match the little shadow
on the ground below the player as
closely as possible, because that's the
contact point we will use when interacting with
other objects in our game. Since the player is
fixed pixel size, I can offset it by
a hard-coded value. If we were scaling our
characters in this game, I would use a
relative value here. -100 moves the player image up. This is alright for now.
14. Animation angles: Same as we did with
obstacles spreadsheet. I wanted to navigate within our spreadsheet by swapping
from frame to frame. Horizontal navigation
is handled by multiplying sprite with by an integer representing
the column in the spreadsheet past as
a source argument here. When we cycled through this, we will animate
individual directions, individual animation loops to swap between directions
in the spreadsheet, the way our specific
spreadsheet is organized today, we have to multiply
sprite height by an integer representing
sprite role, you can see a row, zero is player facing upwards
away from us. Rho one is top-right to is
the player facing right? Three is bottom-right for is facing down
towards the camera. Row five in our spreadsheet is the player face in bottom-left, six is facing left. I think you get the idea. I put these integers
into class properties. Frame x for horizontal
spread navigation. Frame. Why for vertical, I replace these hard-coded
values with my new variables, and now I can change what frame we are currently
cropping out from the player sprite sheet by given different values to
frame x and frame why? I wanted to change frame, why the row we are currently animating
from the sprite sheet, which will determine where
the player is facing. I want that to depend on the current angle
between mouse and the player on the
position where they are currently in
relation to each other. For this, we have a built-in
method called Math dot eta. And to Martha's, atan2 returns an angle
in radians between the positive x-axis and line projected from zero-zero
towards a specific point. We will use it to calculate the angle between the player
and the mouse cursor. And based on that angle, we will select which row in the spreadsheet we want
to animate so that the player is always facing in the direction
it is moving. Towards the mouse cursor. We will need DX and DY. So I moved them up here. So these values calculated
the distance between the player and mouse cursor
horizontally and vertically. Keep in mind that it
makes a difference if you use mouse first
or the player first. In this calculation, I already wrote this
code here before. Without thinking, we
would use it for this, so we might have to
adjust to it a bit. I will show you
exactly what I mean. Martha data to expect dy, dy and dx as the
second argument. I will consult angle we are calculating and I can
see it is changing. And the values are from a -3.14 minus pi to
plus 3.14 plus Pi. This checks out because we know that full circle is two pi, approximately 6.28 rad, which
converts to 360 degrees. I will repeat that method. Atan2 returns an angle
in radians between the positive x-axis and align projected from zero-zero
towards a specific point. Because I'm using mouse first and the player position second, when calculating DX and DY, I'm getting values from a
master data and to learn, the current mouse position
represents 0.00 and player position is the point we are projecting a line towards. For the purposes
of a nice visual, it would work much better if
Player was the static zeros, zeros, one important, but
I will leave it as is. And from the values I'm
getting in the console, I will create this graph
with breakpoints in radians. It was actually easy
to make because I just needed one
value as an anchor. And I knew the whole area is
from -3.14, two plus 3.14. And we have eight
player directions. So each slice was 6.20 8/8. Anyway, you don't necessarily have to understand
all of this right? Now, when we have the full code, you can play with the
values which will hopefully bring more clarity. It took me a while using math but atan2 before I
fully understood. So if this is your
first time seeing it, don't put too much
pressure on yourself. If I pause the screen, this is the 0.00, this is the line projected
towards another point. And Martha data and two
gives us an angle in radians between this positive
x-axis and this line. So by using this
console log to get an anchor point so that I can see which angle values
we are getting. I constructed this
helper visual, which I will now use
to correctly swap rows in our spreadsheet to make the player always
face the mouse. If angle is less than -1.17, set frame y to zero. I will copy this a few times. -0.2, 39 is a frame y one
plus 0.39 is a frame. Y to 1.17 radiance is framed. Y3, 1.96 is frame Y4. Let's see. So far this
is working great. I think we got it. I
can delete the console. Log 2.74 is frame five. This area is a bit weird as
the circle ends and starts. I have to say if
angle is less than -2.74 or if angle is
more than plus 274. I want to use frameworks six. If angle is less than -1.96, frame y is seven. Pay attention to brackets here, minus n plus values and
less than operators. If you are getting any
unexpected behavior, make sure all of this
code is the same as mine. It's easy to make
a small mistake here and break your
code accidentally. For this to work
for all directions, I have to adjust it
a bit. I can e.g. take these slides and put them here because with
else-if statement, it matters which one
is checked first. Sometimes, I reduced the
player speed modifier to three so we can
clearly see how it turns. While Destin. Now we can turn the player in
all eight directions. Perfect. We will learn more about sprite animation
later in the class. For now, I'm happy with this.
15. Debug mode: Since this is a physics
game where we are placing collision areas at
the base of each object. We want to be able to quickly
swap between the view where this collision areas
are visible and invisible. It will help us to tweak the physics and gameplay
elements as we are developing them while giving us an easy way to check how the changes we just made will be visible for the player who will
not see these boxes. I want to create a debug mode by pressing letter
D on the keyboard, we will toggle all helper
collision elements on and off on the
main game class, I create a property
called this to debug. And initially I set it to true down here where we
placed our event listeners, I create another one. We will listen for
key down event. Let's just console
log event object. When I select Canvas
by clicking on it, and then I press
any keyboard key, we get this auto-generated
keyboard event object. Inside we have a
property called key. You can see I press
the letter R, So the key that was priced sits inside E dot key property. I say if E dot key is D, set debug property from line
123 to its opposite value. So if it's currently true, set it to false. If it's false, set it to true. This way, present
the same key letter D will toggle debug
mode on and off. I tested by Console
login test or debug. I press the d over and over. And in console I see it switches
between true and false. Perfect. I remove
the console log, appear inside the draw
method on obstacle class. I say if this dog came to
debug is true on a diner, draw the collision area circle. Now I can press letter D, the show and hide them. That works well. I want to do the
same for the player. If this game to debug
is true on a diner, draw the collision circle and also the line between the
player and the mouse. In this case, position and size of the head boxes
is not perfect yet, but we do have all the logic
in place now, great job. I can make collision
radius of each obstacle smaller to better match the part where it
touches the ground. We have different
obstacle types here. The mushroom and this big
carnivorous plant should probably have different
collision circles which can easily be done. But for now, I'm
happy with this. You can see that the
collision formula we wrote before is all we need to give the player very
simple path finding ability. It will just walk around
obstacles automatically. And since we placed obstacles in a way that they are
always spaces in-between, it's unlikely that the
player will get stuck. I can also see I'm only getting the first four obstacle images, which reminds me
to go here to line 10008 and include randomized
frame y-value as source. Why argument in obstacle
draw image method. Now I'm randomly getting one
of all 12th obstacle images. You can play with this
and position and size. Your handbook says
differently if you want. I'm happy with what
we did so far.
16. Player movement boundaries: I wanted to make sure
the player can't walk so high that it's above this
background artwork area. Let's create some
horizontal boundaries. First. If the center point of player collision
circle is less than x coordinate zero plus
collision radius, set it to zero plus
collision radius. So when the left
edge of the circle is touching the left
edge of canvas area, don't allow it to go
any further left. I want to do the same
thing for the right edge. Of course, we can delete this
zero plus here and here. If the center point of player collision
circle is more than the width of the game minus the radius of player
collision circle. Make sure it can't go
any further, right? Nice. Vertical boundaries. If collision y is less than vertical coordinate
zero plus top margin, we defined to be 260 pixels from the top plus
collision radius. Make sure that the player
can't go anymore up. That works nice. Yes, this is what I wanted. Again, we can delete
zero plus here and here. Bottom boundary. That's simple. If collision y is
more than the height of the game minus
collision radius, make sure it stops
there like this. That works if I make player
collision radius smaller than obstacle radius because of the margin we defined while
positioning obstacles, there will always be a place for a player to squeeze
between obstacles. The edges of game area. There is not. So down here, I increase that
margin. They should do it. You can compare your code with in-progress source code I
will include to download in the project section below in multiple points
during this class as we progress with our project.
17. FPS: I can see the bottom
of the spreadsheet is being cut off on the lower rows. It's because the height
of a single frame is actually 256 pixels. Now it's fixed. In Visual Studio Code editor, I select the View word
wrap to make it a code break to another
line if it cannot fit. If it's too long. I
wanted to set FBS for the entire game because
with my previous projects, a lot of you mentioned
that games run too fast on your game in high
refresh rate screens request animation frame or
method we are using here will automatically adjust
itself to screen refresh rate. So for normal screens, that will be around
60 frames per second. But on some of the new gaming
screens that people use, that speed would be double, or maybe not exactly
double, but much faster. We will calculate
the delta time, the amount of
milliseconds that passed between each goal of
request animation frame. And we will only allow
the game to serve the next animation frame when a specific number of
milliseconds has passed. We can calculate
the delta time down here inside our custom
animate function. First, I defined last time outside the
function like this. Initially, I set it to zero. This variable will always hold a reference of the
timestamp from the previous animation
loop so that we can compare it with the
current timestamp. And the difference between
them will be delta time. Request animation frame
has two special features. As we said, it will
automatically try to adjust itself to the
screen refresh rate, in most cases, 60
frames per second. It will also
automatically generate a timestamp for us
that we can use. And it will pass
that timestamp as an argument to the
function it calls. In our case, animate. Imagine it's passing that
timestamp here, like this. Automatically, it's
auto-generated. All we have to do
to use it is to assign it a variable name here. I will call it a timestamp,
spelled like this, be mindful of lower and
uppercase letters when defining your variable names
in JavaScript, it matters. Let's do a console log this auto-generated
stamps temp variable, that request animation
frame has given us just to see what format
it is n. You can see it gives us milliseconds
since the first animate was called 1 s is
1,000 milliseconds. So here I can literally
see that the game started 9101112, 13 s ago. I delete this console log. We know we have access to
the current timestamp, so let's use it to
calculate delta time. It will be the difference
between the timestamp from this animation loop and the timestamp from the
previous animation loop. Delta time is the number
of milliseconds it took our computer to serve the
next animation frame. Once we used last time to
calculate the delta time, we assign it to the
current timestamp. This way, the current
timestamp can be used in the next animation loop
as the old timestamp. For the very first
animation loop, last time will be
zero, but after that, it will always hold the value of the timestamp from the
previous animation frame so that we can compare
it with the value of timestamp from this currently
running animation frame. And the difference between
them is delta time. So let's console log delta
time to see if it worked. My delta time is around
16.6 milliseconds. Thousand milliseconds
divided by 60 is 16.6. So this checks out. I wonder how many
of you got the same Delta time and how many of
you got a different number. If you have a high
refresh screen, your delta time will be
a much smaller number. If you have an old computer that is struggling to
animate our game, your delta time might be much higher if you have a
second right here, delta time in the comments. So we know if most people get the same or very
different values than me, it will help me to better
optimize my future courses. If I scroll up console to
the very first timestamp, you can see that the
first two values of delta time are
non, not a number. It is because the very
first timestamp is undefined because only
on the second loop, this timestamp value gets auto-generated by
request animation frame. The first loop is not triggered by request
animation frame, it's triggered by this line. So in the beginning when
we say that delta time is undefined minus zero here
we get none, not a number. It automatically fixes
itself as the loop runs. But these two initial
not a number values could break your code. That depends on delta
time unless you account for it with some kind
of oral statement, e.g. the easiest way to fix
this is to pass zero here as the timestamp for the
first animation loop. From the second loop, the value will become the
auto-generated tau stamp. Because after that,
animate will be called by request
animation frame. As you can see, we
get numbers here and there are no
non values anymore. Perfect. I delete
this console log. Let's use delta time to set
the frame rate of our game. We will need some
helper variables. Fps, frames per
second will be e.g. 20. Timer will count
over and over from zero. Towards a specific value. When it reaches that value, it will trigger the
next animation frame and it will reset back to zero. Interval will be that
breakpoint value that when reached,
will reset timer. It will be thousand
milliseconds, 1 s divided by FBS. This will give us the
amount of milliseconds needed to achieve
this specific FBS. We will manage this
frame handling logic down here
inside render method. If timer is more than
interval, do something. At the same time,
keep increasing timer by the value of delta
time over and over. When timer accumulated
enough delta time, enough milliseconds that its
value is more than interval. We will animate the next frame. We will also reset timer
back to zeros so that it can count again for
the future frame update. I will take all this
code and I put it inside the if statement. Like this. We are using delta time
value on line 173. Dot value will be passed as an argument to render
method up here. And inside animation loop, we are calculating delta time here and we will pass
it to render like this. Okay, so something is happening. The reason everything
is blinking is that we are deleting
old paint all the time, but only redrawing our game when the timer reaches interval. I got this clear
rectangle from here. So now we are not clear
in old paint at all. And everything is animating
at 20 frames per second. And everything is
leaving trails. And I will clear
old paint only when we are ready to redraw the next updated
game A-frame here. So context, clear rectangle from coordinates zero-zero
to this dot width, this dot height to clear
the entire game area, one optimization would
be to draw our game on multiple cannabis
elements and only clear the portions of cannabis
that actually update it. That way we wouldn't have to redraw everything all the time. For now, this will work fine. You should be able to
see a slow down in game animation speed
because we are setting FPS here to 20. To make it even more obvious, maybe I only want to animate
five frames per second. 30405060. I am certain FBS to
60 frames per second, but we are not actually
animating our game at 60 FPS because every time I
reset timer back to zero, there is some
leftover delta time I am not accounting for. So even though I say 60, the actual FPS is a bit lower. I can go down and I can account for that
leftover delta time. But maybe I want to keep
this code based lightweight. Maybe I don't want to
make JavaScript to do even more calculations
over and over. So while keeping this in mind, I will know that I
have to set FBS to a slightly higher value here to actually get something
around 60 FPS. If I set this to 70, I think we get smooth
enough movement and we're not making JavaScript
calculate leftover delta tan, which would slightly
increase how performance demand
in our game is. I just thought of
this now I'm not sure which is a better solution. I will leave my code
like this for now, but I guess the right solution here will be up to everyone's
personal preference. So three to discuss
this in the comments. I will consider your feedback in my future projects
now that we know how to control animation speed of
our game using delta time, the game will run at a similar
speed on every machine, even for those of us who are using high refresh rate screens.
18. Egg class: I wanted to add eggs
that can be pushed around to include even
more physics in our game, those x will be hatching into creatures after a specific
time or has passed. And players job will be to protect the creatures
that Hodge. The x can be pushed
around by enemies, but they won't be destroyed. The challenge for the
player is when they hatch, enemies will eat the hatchlings. So players job in our game
will be to position the x, protect them, or to
push enemies away from the path of the
newly hatched creatures. The larva that
comes from each egg will always try to
crawl into safety and hide in the bushes inside the mushroom
forest at the top. This will introduce a
lot of player choice and their options into our game while using physics
we implemented, I got an idea for this
game mechanic while watching a nature
documentary where little baby turtles are
hatching on the beach and trying to get to
the sea for safety. In our game, we
control the blue ball. Its job is to protect the
hatchlings by pushing x, lavas and enemies around. This game is all about
physics and positioning. I will have a custom
class I call e.g. eg. Constructor will
expect a reference to the main game
object as usual, to make sure Ec
class has access to many important properties
held on the game object. X will be part of
the game physics. So I need to make sure I define separate x and y coordinates for collision
circle central point. And for the sprite sheet. Let's start with
collision x property. It will be a random
value between zero and the width
of the game area. Collision y will be between
zero and game height, like this, collision
radius, e.g. 40 pixels. This load image will be
document dot get element by ID, and the ID is in index.HTML. I actually have to create
that image element. As always, images can be downloaded in the
resources section below. I hide it with CSS here. I'm keeping the same
naming conventions across my objects. It's a good practice. So sprite width will be 110 pixels and Sprite
height is 135. Width and height will be
set to the same values. In our game, every object has
collision x and collision. Why properties that represent the central point of
collision area circle, even player and obstacles have the properties named
like this so that we can use reusable collision
detection method to implement our
physics to everything. Same goes for Sprite
x and Sprite. Why properties? Those represent the
top-left corner position from which the object's
image will be drawn. Sprite X of image
will be collision x plus half of the
width of the image. To center the image over
collision circle horizontally. Sprite y will be collision y
plus half of image height. We might have to adjust this a bit later because vertically, we want the collision
area to be at the base of the neck,
not in the middle. We'll get to that soon. Draw method will expect
context as an argument. As usual, we call draw image, and this time we just
need three arguments. The image we want to draw and x and y coordinates
where to draw it. If we were scaling, we would also include optional width and height
arguments like this. But I'm giving you all the
images in the same size. We are drawing them in game. So it's actually not necessary. As with all game objects, we are not only drawing the image representing
the object, we are also drawing collision circle if
debug mode is active, since we are keeping the
same naming conventions for properties all
over our code base, we are making our life easier. I can just copy this entire
code block and use it here. So we are drawing x MHz
and if debug mode is on, we are drawing collision area. Yes, probably it's
a good idea to put this code into
reusable methods, since we are using
the same code to draw collision circles
for everything. I might do that later. For now, I wanted to
create a method that will periodically add a new
egg into our game. On the game class, we will
have an array that will hold all currently
active egg objects. I will also have another
property called number of eggs. Or a max x describes
it even better. We will only be adding
new to the game until the total is less or
equal to max x value.
19. Periodically adding new eggs: Inside render method
here we will handle the logic to add x periodically. We already did it. We did periodic event in this codebase where
we use the delta time and triggered new game frame only when a certain
interval value was reached. We will actually do
the same thing here. We will need some helper
variables for that. Egg timer will go from
zero to x interval value. Then it will add a new egg and it will reset so
that it can count. Again. We are operating with
delta time, so milliseconds, I want to add a new eggs, Let's say every
500 milliseconds. Down here, I will check if egg timer is more
than x interval. We add a new egg by calling
this method from line 220. Inside there, I just
take the eggs array, I call built-in
array push method. I will push one new instance of our customers
eg class in there. As usual, we know that the egg glass expect the
game as an argument. So I pass it this keyword because we are
inside that game object here. So if egg timer is
more than x interval, we call add egg. We will also reset
timer back to zeros so that it can count again
towards the next egg. Else, we keep increasing
egg timer by delta time, which we are already passing here to render
method from before. I console log this dot x to see if the objects
are being added. Sorry, this will just create
an endlessly grow in array. I need to create
additional condition here, only add new x as
long as the length of x array is less than
max x, That's better. I inspect one of the objects. I need to make sure
all the properties have values. If e.g. I. Have undefined as
collision x-coordinate, my x wouldn't be drawn
on Canvas because JavaScript wouldn't know
where to draw them. I see values on everything.
This looks great. Inside render method, I would
also like to draw my x. I just copy this line and I adjusted for each
element in x ray, we will call it eg. We will call the
draw method on it. Perfect. Now we are getting some visuals
which will make it even easier for us to tweak the
details and polish it. The first thing I notice is that all the eggs are added
almost instantly, even though I said, I want one egg every 500 milliseconds. If I increase x
interval to 1 s here, I can see that something
is wrong with my code. I know the problem must be
inside this code block. And it's because egg timer is more than an interval and
only then add a new egg, we need to use this
comparison operator here. Sorry about that typo. You probably noticed it
already before. Now it works. We get one new egg
added every 1 s, as long as we have
less than ten x. I can also see that
the images are positioned outside the
collision circles. I change this plus to minus here and also here.
Now that's better. I want to adjust the spreadsheet in relation
to its collision circle. Plus 35 will move it up -35, we'll move it down -30. I wanted to match the bottom of the image as
closely as possible, but also I don't want the collision circle to
be too tall because I want the player to be able to walk behind the egg
without pushing it, to have this illusion
that our game area is in 3D and that
it has some depth, even though in reality
it's just a flat surface. We will do more to
enforce this in a moment. Now I want to create
a margin to make sure the x appears certain minimum
distance from the edges of the game area so that
we can get the player between the egg and the edge
to push it anywhere we want. I want the margin to be, let's say collision radius terms to the initial x position of collision circle will be a random value
starting from left. From that margin
value we just defined and game with minus that margin. Down here I set interval
200 milliseconds. I want 50 x. Just to get a better idea where they can possibly
spawn in our game. We have that left
margin I wanted, but I can see that the x or
too far to the right edge. So here I increase that
right margin like this. Yeah, I'm happy with this. Dx are now always fully
visible horizontally with some extra space left and right between the ages
of the game area. Vertically, I will do
something similar. The random position
needs to start below the top margin area
we defined earlier. I don't want any x in front
of this background artwork. The way Math.random works, I just pushed the range
from here to here. I also need to narrow
the range down by reducing the span of
random values we get. So game height minus top margin. Now x can appear
from here to here. I will reduce the randomized
value by margin to give it some space at
the bottom, like this. Perfect. If you are a beginner, Math.random can be unintuitive. It's actually very simple. It can just take some practice. So here I'm saying
Set collision. Why property to a random value starting from top margin here? Because we're going from
the top and the range of random values is game
height minus that margin, minus this other margin. So x can appear anywhere between here and here. Vertically. I set interval to 500
milliseconds and max x to be e.g. ten. For now, this means
we will get an egg appear every half second
until the total count is ten. This is a physics game, so we wanted the player to
be able to push the eggs around to move them out of
the way of incoming enemies, or to position them strategically to give
the larva that will hatch from it the best chance
of survival in this game, I choose that x will
be indestructible. Enemies will just push the x out of the way
if they collide, but the larva that hatches will be eaten if enemy catches it. So positioning and pushing the x around is very important. Let's give the x some physics.
20. Egg physics: I give each egg and
update method inside, we will create a
temporary helper array called collision object. This will contain all objects in our game that x
will interact with. We will check for
collision between each egg and these objects. So we will need player here, this dot game, dot
player like this. And of course we need the
solid obstacles in here. I want all these elements
to be on the same level. In collision object array. We have player object here. And we will spread
obstacles array into collision object array using
spread operator. Like this. Spread operator allows
us to quickly expand elements in an array
into another array. I will actually call this
collision object with an ice. I will call for each on it, for each of these objects. So in this case, for player and all individual
obstacles we have here, I want to use this
check collision method we defined on line 225. If you remember, it takes object a and object B as arguments. And it returns an array
that gives us true or false for collision distance between two collisions circle
center points, some of their radii
and horizontal and vertical distance between
the two center points. I copy this array. I paste it up here. And again, we will use this
structure in to quickly define these five variables
using a single line of code, I need a variable name here, so I will call the first one
collision true or false, whether the collides with
player or any obstacle. We did this before. I want this to be lead
variables and they will be equal to this dot game. Don't check collision between
this egg because we are inside update method on Ec class and one of the objects in
collisions objects array. So as the forEach method runs, we are running check
collision method between this egg and each
element in this array. And we are getting
five variables that tell us more details about position and collision of these two objects
we are comparing. If collision is detected, we will actually use all
these values to calculate how far and in which direction
we want to push the egg. Horizontal direction
of the push will be the ratio between the x, the horizontal distance
between the two center points, and the distance hypotenuse of that imaginary
right triangle. Because dx is a site of that triangle and the
distance is hypotenuse, the longest side,
we are divided into smaller value by a larger value. Because of that unit, x will be something between
minus one and plus one. D x can be positive
or negative. Unit y. Vertical push direction will be the ratio between
D and distance. Now, we will use this
to move the egg. We are inside a code block
that will only run if x is colliding with player
or any of the obstacles. So when collision happens, we take collision x of
the egg and we push it one pixel outside of
the radius of the object. It is collide and width. So collision x of the
object, the center point, plus one extra pixel to move the egg outside of
collision area. So this is how far and the
horizontal direction of that move will be defined by
multiplying it by unit x, which, as we said, can be positive or negative. We also need to move the
collide and egg vertically. Collision center
point y of the egg will be collision
y of the object. It's colliding with
plus radius of egg plus radius of the object
plus one extra pixels. So that in the next loop
condition is false. Terms vertical direction
defined by unit y. Notice that here
we are detecting collision and I am moving collision and collision why of the egg based on the
position of the obstacle? This is just my choice
and doing this will make the egg move and all these elements will be
solid in that interaction. When interaction happens, We'll move player and obstacles
will remain solid. They will not be
pushed by the egg. Down here we call draw
method on each egg. We also want to call this new update
method we just wrote. Let's test that. We are pushing collision
circle of this egg. I need to make sure
that collision area and Sprite stick together. The simplest way
would be to take this code that position
sprite X and Sprite. Why in relation to
collision and collision y and I will call it
insight update method. I can remove the values here and now we can push dx using player, and they will also
collide and slide around our mushrooms and plants, around the solid obstacles. Dx don't interact
with each other. This is just my choice. I want the x to overlap so that a skilled player
can push multiple eggs, kind of hurt them together, and push all of them
at the same time. This choice will also keep our code simpler because
we are learning.
21. Draw order: We are drawing everything
on a single canvas element. So the draw order of
our game objects, what is the drone
behind and what is upfront will depend on the order in which we call draw methods on each object from inside
the render method here. Right now, I drew obstacles, so mushrooms and plants. Then I draw x. So x are
drawn on top of obstacles, as you can see here. And then we draw the player. Player is drawn on top
of everything else. If I take the x and
draw them first, there will always
be drawn behind the obstacles behind the player. And here we can see that it doesn't really
make visual sense. I wanted to create an
illusion of depth, fake 3D or maybe
two-and-a-half d. Objects that are low
should be upfront. As we move up, objects drawn on that vertical baseline
should be drawn behind, e.g. this egg should
be drawn in front of this plant, not behind it. We can't really achieve that. If we draw all x,
then all obstacles, and then the player, we need to adjust our
code a little bit. I will have to put all these
objects into a single array, and I will sort that array
based on vertical coordinates. Let me show you what I mean. I create a new property
on the main game class. I will call it game objects. At first, this will
be an empty array. Inside random
method, I will take a game objects and I will use spread operator to expand
the entire array inside. I will also expand the entire
obstacles array in here. And I will add the player. Now, we are holding all the game objects
in a single array. This array will be
recreated every time render method takes
good optimization tip here would be to only
do this operation one vertical coordinate
of any element changes. Or when we add or remove an egg, I will repurpose
this code block. Instead of calling for
each method on array, I will call it on this
new game object array. For each element in the array, I will assign it a temporary
variable name e.g. object for each object
in game objects array call their associated
draw and update methods. For this to work, it's important that
all objects we add into game objects
array on line 217 have draw and update methods defined
on their classes. Otherwise the code
would break because JavaScript wouldn't be
able to find that method. So we are calling draw and
update on all game objects, on all x, on all obstacles
and on the player. That means I can now delete
these lines of code. Typo here, I spelled
game objects with an S. Now we draw the first obstacle
and then we get an error. We call the draw on
the first obstacle. And when we tried to
call update on it, we get a type error that says object dot update
is not a function. I go to my obstacle class
and you can see, as I said, we have draw method here, but there is no update method. We are asking JavaScript to call a method that doesn't
exist on this object, I will create an
update method here. Inside that method we could e.g. animate spreadsheets
of these plants and mushrooms every time the player collides with them or something, maybe I'll add some
interactive obstacles later. For now, we will leave
this method empty. Now it's working and we are calling draw and
update on all x, all obstacles and on the player. Alternatively, I could have
also solved the lack of update method on obstacles by some kind of if else
statements down here, there are always multiple
ways to do something. Feel free to discuss how you would approach this differently. Maybe we will get some
better solutions in the comments that I might
use in future classes. I appreciate when you
give me feedback on my code and when you
suggest improvements. Okay, so now we are
calling draw and update as we cycle
through game objects, which means x are drawn first, obstacles are on top of x and player is drawn on
top of everything. If I change the order
of these elements here, the player is drawn
behind now than x and obstacles are
on top of everything. So now we can understand
how the layering works. It depends on the order in
which we call draw method on each object to draw our game objects in the order
that makes visual sense. I can now sort
game objects array based on each objects
vertical position. I take game objects array and I call built-in
array sort method. This method sorts the
elements in an array and it returns the
same array now sorted. By default, if we call it
without passing any arguments, it will just convert array
elements into strings. And it will sort them based
on their Unicode values. We don't really want that, so we will pass it an argument. This optional argument
we can pass to array sort method is
a compare function, a custom function that
defines some logic. It defines some specific
sort order we want. This special function
will take two arguments, element a and element be
all elements in the array. In our case, in
game objects, array will be represented by this a and B used in this
logic we define here, I can do return like this, and I want to sort the
elements in ascending order based on the value
of each object collision. Why property? So we
are sorting based on the vertical center point
of collision circle area. Alternatively, we could also sort by the bottom
of sprite image, which would be sorted
by the value of Sprite y plus sprite height. Sort method can be complicated to understand if
you are a beginner, all you have to understand
here is that I'm putting all my elements into
game objects array. Then I'm Colin built-in
sort method on that array. And I'm organizing
these elements based on their
vertical position. It doesn't work now
because I'm creating an array I'm drawing
and then I'm sorting. I need to sort the elements
after I created the array, but before I draw them. So like this. Perfect. Now this is drawn
in front of the plant, but if I use the
player to push it up, the eggs vertical
position becomes less than the vertical
position of the plant. Now the egg is drawn
behind the plant. This is one simple way how
you can draw elements in a 2D game in a way that
makes more visual sense. It is a very powerful technique to add to your coding toolkit. Do you have any questions?
Leave a comment. You don't have to do the
following thing I will do now. I set interval to 20 milliseconds
and max x will be 150. Just testing if there are any
problems we need to solve. As I move through them, the physics seems very good. It's all working well. Having so many eggs for
testing also gives me a pretty good idea where they
might potentially spawn. They appear in the
area we specified with large top margin and smaller margin from
left, bottom and right. We got x, Nice job coders. I said interval
2000 milliseconds, one egg every second, and max x will be 20. Since we have this
collision formula in place, if it appears on top of
obstacle or the player, it gets automatically pushed outside its collision radius. I remove this console
log on line 237. We have our game world. We have randomized
solid obstacles. We have mouse controlled
player character that can move in a direction. And we have x that spawn
in specific interval. On top of that, everything collides and reacts
to everything. We can make so many
different games with this, Let's add enemies.
22. Enemy class: I create a custom
class I call e.g. enemy. As usual, constructor
will take a game as an argument and we convert that reference
to a class property. We do that to get an easy access to all properties sitting on the main game class from inside enemy class through
this dog game reference. We need to keep the same
naming conventions here. So collision radius 30 pixels, collision x, center point of collision circle will be
the right edge of Canvas. This dot game dot width. Collision y will be a
random value between zero and game height value
coming from here. Speed X horizontal speed will be a random value
between 0.5 and 3.5. You can download enemy image
in the video description. At first, we will start with
the static single frame. Id will be towed, source will be told the dot PNG. I hide it with CSS
because we want to draw it where the
JavaScript on Canvas, this dot image property
will be get element by ID. And the ID we gave it was towed. Sprite with, in this case 140 pixels and Sprite
height is 260. I will also create width
and height properties. And we will define sprite X
and Sprite y positions of the spreadsheet image which
will be positioned in relation to collision
circle coordinate. Draw method will take context as an argument built-in
draw image method. And because the image
file I'm using is already the same
resolution as the size. I want it to display
it in the game. I only need to pass
it three arguments. The image I wanted to draw
and x and y were to draw it. I will also copy collision
circle visual that will appear only when debug mode
property is set to true. Only when debug mode is active. I just copy and paste it here. Since that code is the same for all our objects. Update method. In there, I want enemies
to move to the left in the minus direction on
horizontal x-axis by speed x, a value we defined on line 179. So we are moving
enemies to the left if the right edge of
the spreadsheet is hidden behind the left
edge of cannabis. We can set its x
position back to game width so that it can walk left across
the screen again. We will also randomize its vertical y position to get the enemies walking
in different lanes. So I will have enemies
that walk from right to left and then they
reset and walk again. Alternatively, I could have
also created new animate objects over and over
and destroy them, discard them when
they move off screen. The code would be
simple but reusing objects by resetting
their position rather than creating new ones
and discarding them later is a good
optimization technique. Let's reuse your objects and reset their
positions if you can, rather than creating new ones, using the new keyword to
create a new object is more expensive than
resetting existing object. On the main game class, I create a custom method I call e.g. at, NME. Up here, we will have an
array that will hold all currently active
animate objects. Whenever at enemy runs, it will push new animal
object into enemies array. Up online 174, I can see that enemy class constructor
expects game as an argument. So I pass it this keyword, because here we are
inside this game class. Doing this, I'm just passing
a reference that points to a space in memory where
a game object is stored. I'm not creating a
copy of game object. Each time I create a new enemy. Init method will run. Just want to initialize our game and set
everything up inside. We are already
creating obstacles. We will also create enemies in here FOR loop that will run, let's say three times. And each time it runs,
it will call out. And then the method
we just defined, I can select this dot enemies. And as expected, it contains
three animate objects. I inspect one of them just to make sure nothing is undefined, which could be a problem. All is good here. I can
delete the console log. It's a good idea to always check your objects and arrays
with console logs as you build your
projects step-by-step to catch potential bugs
and typos in time. To draw and update enemies, I just have to expand
enemies array into game object using
the spread operator. We did that before. I see only collision hit boxes, I need to give some
values to sprite X and Sprite y so
that the JavaScript knows where to draw the images position of the
sprite. We'll be moving. As collision circles move. So I put that code
inside update method. Let's first centroid,
then we offset it. Sprite X is collision
x minus half of Sprite with like this sprite. Why is collision y
minus half height? I want the collision circles
to match the shadow on the ground below are floating enemies as
closely as possible. We need to adjust the image
positions vertically. -40 -60. What about minus
height? Plus 40? I can enable and disable debug mode to show
and hide hit boxes by pressing letter d.
We have enemies coming from right to
left and then resetting. I want them to reset
all the way behind the right edge so we don't see them just pop into existence. So we reset them to game dot
width plus width of enemy. And on top of that,
another game with term 0.5 to give each one a
different random delay. Why is this here? I delete that. Just for testing. Let's increase the speed. Resetting them at random offset behind the right
edge works well, maybe that's a bit too fast. I need to restrict their
vertical positions. Let's say start from
the top margin. And from there a
random range between zero and game height
minus the top margin. I copy this value to be used as the initial collision x position
inside the constructor. And I take this new collision, why I use it inside the receptor
check in update method. This will not work because
up here on line 176, I'm using this dot width, but it's not defined until
later here on line 183. The order here matters. I just take these two
lines and I put them here. Now we have enemies walking
from right to left in a correct corridor and resetting
behind the right edge. This way we can reuse
the same enemies over and over unless
I decide we want to add some other features
like allowing player to destroy enemies or something
like that. We will see. Again, you don't have
to do this part. I'm just testing our code. So I create 30 enemies. Having so many enemies gives
me a better idea how they reset and I can spot any
potential issues faster. Everything seems to
be working well. This would be a very
dangerous forest. Luckily for our
hatchling creatures, the final game will not have swarms of enemies
like this unless you want to create a periodic
wave of enemies like this. As one of the game and
mechanics the player has to prepare for and avoid. We can do so many things in
terms of game design here, I'm just giving
you the tools and techniques and showing
you some of my ideas. Feel free to expand this game
with your creative ideas. When the course is finished, Let's go back to three enemies. Right now. The enemies
are just flying through the forest without
interacting with anything. First, I want them to react to solid obstacles and the player, we already have code that does that insight update
method on EKG class. I can literally use the
same code block and put it inside update
method on enemy class. This will make enemies,
treat obstacles, and player a solid, impossible objects and they
will slide around them. We are inside update
method on enemy class, and we are passing
enemy as object a, player and obstacles
as object B. And here we are saying adjust position of a based
on position of B. Player and obstacles
will be solid and dominant in
this interaction, and enemies will be
pushed around them. This simple collision
check-in will also create very basic
artificial intelligence. As you can see,
enemies are walking from right to left
across the game area. And they are
automatically slide in and avoiding obstacles
and the player, because they avoided
the player in this way, we can also use the player
to push enemies around, same as we can
push the x around. I adjust enemy speed. If I add x into collision
objects array on enemy class, x will also become solid, impossible obstacles
for the enemies. And enemies will have
to walk around them. It might be a good
thing depending on how you're
designing your game. By the way, I want
my game to be, I actually wanted to
remove the x from here. Instead, I go up to class
and inside update method, I will add enemies to
collision objects. Here. This will make the eggs object a path to
check collisions method. And enemies will be solid, impossible object be enemies
will push the x around. If I do it this way, we implemented a
lot of features. So I will leave the
source code from this stage in the
resources section below, you can download it and
compare it with your files. If you are coding alone.
Hope you're having fun.
23. Larva class: Down here between egg
and enemy classes. And I create a new
class I call e.g. larva. I want x to have a hidden timer and after
a certain time interval, each eggs will hatch
into smaller larva. That larva will try to
hide in the forest. And our job as a player is
to protect it by pushing it closer to the safety zone or by pushing enemies
away from it. The constructor on
the larva class will be a little bit
different because I want each larva to appear at the same position as
the egg hatched from. So we will pass it a reference to the main game
object as usual, but also we pass it
x and y coordinates. Now, I convert all these
arguments into class properties. I need to name these
x and y coordinates, collision and collision, why these variables will be involved
in collision detection. So we need to name them
the same as we did other objects in our
physics game so that we can use our reusable check collision method on this as well and involve these
larva hatchlings in the game of physics loop. We will also need
collision radius here, let's say 30 pixels for now. You can download larva image
in the video description. I added here with an ID of larva and source larva dot PNG. It's a spreadsheet
with two frames. On this role, we
have a bold larva and this one has some
fluff behind the neck, just to give it some
visual variety. The fluff makes more
sense if you know what these hatchlings turn into
when they are adults. I bring the larva
image into the project using get element
by ID, like this. Width and height
of a single frame. Now, sprite with is 150 pixels, sprite height is
150 pixels as well. If you are a beginner,
I strongly suggest you follow my code exactly
and use the same images. Amusing. When the
project is complete, it will be easier to make
your own custom changes. Changing the code as
you go while following this tutorial is not a
good idea unless you are an experienced the
JavaScript developer making your own changes before
the project is complete, we'll make it harder to debug. We will also need
sprite X and Sprite y coordinates of the
spreadsheet image. Each larva will need to draw and update methods. As usual. Draw method will take
contexts as an argument. In the site we call built in a drawer image and we pass it image we want to draw and x and y coordinates
where to draw it. I want each larva
to be moving up towards the safety of
the mushroom forest. This open area is full of enemies and it's very dangerous. Speed, why vertical
speed will be a random value, 1-2, e.g. we defined sprite X and Sprite. Why properties that determine where the sprite
sheet will be drawn. But because they will have to move every time larva moves, I need to be updating
them over and over from inside update method. Sprite X will be collision
x minus half the width. Sprite y will be collision
y minus half the height. I hide this image with CSS. We gave it an ID of larva.
24. Egg hatching: So we have a class we can
use as a blueprint to create a larva every
time an egg hatches, Let's write egg hatch in logic, we will need to
help our variables. Hatch timer will go from
zero to hatch interval. It will count milliseconds. So let's say I want
the egg to hatch after five milliseconds after 5 s. Inside update method, we handle collisions
in this area. And down here we will
handle hatching. If hatch timer we just defined is more than
hatch interval, we do something else. We keep increasing hatch
timer by delta time. Counting and accumulate
in milliseconds. Delta term will be passed to update method up
here on line 159. This update method
will receive it here. Inside render. We calculated Delta time value before inside animation loop, delta time contains the number of milliseconds that happened between this animation frame and the previous
animation frame. So hatch timer is accumulating this delta time milliseconds
between frames. If hatch timer is more
than hatch interval, we will delete the egg and we will replace
it with a larva. Up here I create a property
called marked for deletion. Down here we set marked
for deletion to true. I can be check-in and
removing these marked for deletion objects for every
animation frame about it might be a little bit
more efficient to only restructure our erase when something gets actually marked. So here we will call
a custom method. I will call e.g.
remove a game objects. Down here on the
main game class, I will define that method. So far, we are only
marking X for deletion. So whenever this method runs, I will take the entire array and I will call built-in array
filter method on it. Or a filter method will just
create a copy of this array. But that new filtered array
will only contain elements that pass the check provided
in the callback function. The check in this case will be, I only want X array to
contain elements that have marked for deletion
property set to false. If anything has marked
for deletion set to true, it will be filtered
out of the array. This is so-called ES6
arrow function syntax. I'm saying take elements
in array one-by-one, assign each one a temporary
variable name object. And on each of these objects, check if they're marked for
deletion property is false. Exclamation mark means false. If marked for deletion
on this egg is true, that egg will be filtered
out of this new array. And this new filtered
array is assigned to the original x ray and it overrides it. Am I over-explain? And again, let's console
log this dot game dot X to see if x are being
removed and added. Yes, this is working perfect. When we are in debug mode, I want the Hajj timer to be visible as a number
floating above each egg inside the draw method here I call built
in filtText method. This method needs at
least three arguments, text we want to draw and x and y coordinates
where to draw it. The change font size, rather than changing
font size every time draw method
runs on each egg, I will set it up here on line
ten on the first page load. This is a powerful
optimization technique. Font is a part of a state and frequent changes to a state
can affect performance. Defined Canvas properties such
as fillStyle, line width, stroke style, and phoned in a code block that runs
as little as possible. Ideally, don't do this
in a method that runs 60 times per second on multiple
objects at the same time. Although in some cases
it might be unavoidable. All I'm saying here, if you can set canvas properties
on the first page, load like this, all this
code will run only once. I said kind of as
fond to 40 pixels. Helvetica. I could have also defined Canvas font here
in this draw method. But as I said, that
line of code would run 60 times per second
on each active egg. That's a lot of
operations that can be easily avoided by doing
what I just said. Ij dimer is this
ridiculous number with so many digits after
the decimal point. Let's clean this up. I create a helper variable
called display timer. In here, we will
format that number a bit before we draw
it. I can e.g. take hatch timer and call
built-in to fixed method. Javascript to fixed method
converts a number to a string and it rounds the string to a specified
number of decimals. I actually want
the zero decimals. I just want to see
the milliseconds. I use that display
timer variable here to actually draw it. I can adjust the vertical
position of the text, maybe by collision
radius like this. Up here, I will set the
text line to center. This will align the timers
and x horizontally. Nice. I wanted to push the timers even further up above the eggs. Maybe even more.
Let's go with 2.5. Instead of milliseconds, I
just want to see the seconds. So I close hatch timer in brackets and multiply
it times 0.001. Now this is more readable. We can see number of
seconds above each egg. Let's make each egg
hatch after 3 s. Nice. When the egg hatches, it gets removed by
the filter method. On the main game class, I create a property
called hatchlings, and at first I set it
to an empty array. Whenever an egg hatches, we will push new larva
into this array. Up here on line 190, I can see that the larva expects game and x and y coordinates. So when an egg hatches, I take this to the game dot hatchlings array and
I push new larva inside. I pass it to distort game. And I pass it x and y coordinates of these
eggs that hatched from I remove this console log. Inside render method,
I use spread operator or to expand hatchlings
into game objects, arrays so that they can be
sorted, drawn, and updated. Perfect, we're making progress. Now that we can see what
our hatchlings look like, we can go up to larva
class and clean this up. First, we will check if
the larva moves to safety. They are safe as
soon as they reach this mushroom and bushes
area where they can hide. So if collision y is less than this dot game
dot top margin, if larva moved past this point, we can set marked for
deletion on it to true. We will delete that larva because we don't want
to draw it anymore. And we will call remove game objects online 350 insight, remove game object's method. I do the same thing
we did for x, filter the hatchlings array. And if any larva object
in this array has marked for deletion
property is set to true, filter it out. Just to check our console
log game objects. Every time we remove an egg
or a larva, I get an error. And it's because this needs to be this dot game dot remove game objects because that method sits on our main game class. I want you to draw collision
circles on hatchlings. I copy this code block
from enemy class. And I just pasted here because our classes have the same naming conventions
on their properties. It's easy to reuse
code like this. Right now, we are drawing the entire spreadsheet
for each larva. I wanted to crop out
just a single frame from coordinate zero-zero two. This looks bright width and
this dot sprite height. And we want to draw
that cropped out image at position
sprite, sprite y. And we need to give it a
width and height like this. Nine arguments. I wanted to position the larva
spreadsheet image in relation to its
collision circle. I try -50. Yes, that will work. I want the collision circle to align with the
bottom of its body. I think that will make the
most visual sense when interacting with game
environments and characters. I remove this console
log on line 362.
25. Larva sprites and collisions: Larva spreadsheet
has two frames, but we are drawing
just the top one. Let's randomize that frame. Y will be a random value 0-2. And since there is
no row 1.5, e.g. we need integers, whole numbers
without decimal points. So I wrap this in Math.floor. This line of code will give
us either zero or one, because Math.floor
will around the value down to the nearest
lower integer. Frame X will be always zero until later
in the class where I give you advanced
spreadsheet. For animation. We will use frame x
times sprite with here as source x argument passed
to draw image method. And we also need to
define a source. Why Source x and y coordinates combined will
determine which area of the spreadsheet we
are crop in from source Y will be framed
y times sprite height. Nice. Since frame why is randomized
to be either zero or one. Some hatchlings use sprite
image from row zero, and some use the other
one from row one. Collision between hatchlings and game objects will
be handled here. I go up here inside
update method on a class and I copy
this code block, we will make some changes to it, but most of it will be the same. So we might as well copy it so we don't have to
write all of that. Again. I paste it down here inside update
method on larva class. I will remove enemies
from collision objects because enemies will have a different type of interaction. So larva will automatically
avoid player and obstacles. And we can also use
the player to push larva around like this, which will be an
important game mechanic. Collision with enemies
will be handled down here. I take enemies array and
I call for each on it. For each enemy object, I will call them enemy, e.g. for each one of them, call this callback function. Basically I want to say if this larva and this enemy we are currently cycling over with
this forEach method collide. One way to easily check
if to game objects collide is to use our custom
check collision method. Again, I want to check
collision between this larva. We are inside larva class
and each individual enemy. As this for each method runs, we know that our custom
check collision method returns an array of values. It gives us collision distance
some of radii, DX and DY. Here I only want the
first argument, collagen, which will be true or
false depending on whether or not this larva
and this enemy collide. Check collision
returns an array and I want collision status
true or false. I want the first element in the array index zero, like this. So here we are using
the structuring to extract all five
individual variables from check collision method. Here I am directly accessing just the first value with index zero because I don't
need the other ones. The reason I don t need these other values
when larva and enemy collide is because
there will be no pushing around and no physics. The larva will just get eaten. So I said marked for
deletion on this larva. Object to true. I will also call
remove game objects to filter that larva out of
the hatchlings array. I will also keep track of how many hatchlings we lost
in disgust and variable. This dot game that lost hatchlings will be
increased by one. And here where we
check if Florida move the safety of
the mushroom forest, we increase the score by one. So if we protect larva
by position and x, Pushing enemies
away or by pushing larva towards the forest. If larva successfully
heights in the bushes, we get one score point. If larva gets eaten, lost hatchlings variable
will increase by one. Down here on our main game class we create these properties. Score will be
initially set to zero and lost hatchlings also
initially set to zero.
26. Gaining score points: I can use console logs
or something like that to check if the code
we just wrote works, but we needed that text
displayed to the player anyway. So down here, we will
draw game status text. Again. We will use built-in
cannabis fill text method, which expects the
text we want to draw and x and y coordinates
where to draw it? I want to draw the word score at coordinates 25 horizontally
and vertically. Problem we have
right now is that I want this text to
be left aligned. And if you remember
the text above x, that timer is center aligned, I will have to isolate this
filtText called by wrapping it between safe and restore
built-in Canvas methods. Then I can set the
text-align to left. And I call a restore. Save and restore methods work together and we use
them when we want to apply specific settings only to a certain draw call on canvas without affecting
the rest of our project. I save canvas state. I set the text-align to left, which will affect only
this filtText call where we draw score and then restore method will reset all kinds of settings
to what they were. Safe was called. In this case, it will
only revert to x line back from left to center
out. That makes sense. This is a very useful technique, especially if you want to scale and rotate your canvas drawings. I do that in some
other classes, e.g. in my creative coding
fractal class, if you want to know
more about it. Now, score is left aligned and timers above eggs
are center aligned. Perfect. I adjusted the text to draw, I want to say score
colon space in quotes plus this dot score
variable to make it dynamic. Now, as hatchlings move to
safety and hide in the forest, we can see our score
increases in debug mode. I also want to be able
to see lost hatchlings. So if this game
to debug is true, I copy this line of code. Text will say lost plus this dot lost
hatchlings variable. Actually we are inside
game class here, so I need to say
just this dot debug. Yes, I change vertical
coordinate here. And now, while in debug mode, we are also keeping track of how many hatchlings were eaten, how many hatchlings
collided with enemies.
27. Particle effects: Sometimes the larva hatches quickly and it's
eating too fast, or sometimes larva
disappears very close to the top edge while
enemy is nearby. So it might not be
100% clear if it managed to get to the
safety or if it was eaten. I would like to add an
additional visual effect that will help us to make our
game clear and easy to read. I wanted to add two types
of particle effects. When the larva heights
in the bushes, it will interrupt a swarm
of fireflies that were sitting on the branches and
they will fly up in the air. One player sees those fireflies. They know that larva is safe. If Florida gets eaten, I will try to come
up with a different, very distinct particle motion. So if there are many game
objects in the same area, we can still tell what's
going on by seeing what type of particles
are flying from there. I will also use this
opportunity to talk about subclassing in JavaScript, we will have a parent particle
class that will contain all properties and methods shared between all
particle types. This is a so-called
parent class, also called a superclass class. Firefly extends this
particle class. This is a child class, also called a subclass. We will also have a
class I call spark that extends particle
class like this. We have one parent class
and to child classes. Child classes will
automatically inherit properties and methods
from the parent class, which will save us
some cold repetition. Let's write the code and talk about it a bit more
as we go along. The parent particle
class constructor will expect game X and
Y position because fireflies and sparks
will always fly out from the position where
the larva disappeared. And let's add one
more e.g. color here. Maybe I want one
particle type to be gold and another one blue, depending on where in our code base was that
particle created. I'll show you what I mean. I convert all these arguments into class properties as usual. I'm saying take color that was
passed as an argument here and save it as scalar property on this instance
of particle class. You know how this works by now, radius will be a
random value 5-15. But I wrap it in Math.floor
to only allow integers, so no decimal points. If we are creating
many particles, you will notice a big
difference in performance when we are using randomized
values like this, compared to setting
the initial radius to a fixed value of, let's say ten pixels
for all particles. Randomize an object
values when creating many object is very
performance expensive. You can improve your game's
performance by trying to avoid Math.random as
much as possible. If you know, you
will be creating many copies of that
particular object. I know we will be
creating many particles. We only have one player object, so there, it doesn't
really matter. It gets created once. But over the course of our game, we will probably create
thousands of particles. One way to avoid this would be a technique called
object pooling. You create a pool of
particle objects and only draw them and
use them when needed. When it comes to performance, that will be much
better than constantly creating new ones
and deleting them. Speed x will be a random value between minus three
and plus three. This means some particles
will move to the right in the positive direction on
the horizontal x-axis, some particles will
move to the left if their speed x is
a negative value. For vertical speed, it will
be a random value between 0.5 and 2.5 pixels
per animation frame. I want to use a little bit of trigonometry to make
the particles rotate, float, and swirl around. So we will need an
angle value initially, I set it to zero. V. Velocity of angle
will determine how fast is that angle
value increase in. I will show you exactly how to use this in a
minute. Don't worry. The a will be a random value between these two
very small numbers. We will delete particles
that move off screen. Initially, I said they're
marked for deletion to false dramas that will be on the parent
particle class as well. So it will be shared for all
Fireflies and all Sparks. I want to make sure that
changes to a state we make here remain isolated to
this particular particle. So I wrapped the
drawing code between safe and restore
built-in Canvas methods. We said fillStyle to this
dot color from line of 3/3. I call begin path
to start new shape. Built-in arc method
will take horizontal, center point, vertical,
center point, radius, start angle, end angle. I want you to draw
a full circle. So from zero to math.pi
times to math.pi times two is a value in radians and
it converts to 360 degrees. It's a full circle. We have to use values in radians here when passing
them to arc method. Then I call fill to fill
the path width color. And I will also stroke
it because in our game, everything has this vector art
style with black outlines. I want the particles
to match it. Plus Firefly will contain
only update method. Same for Spark later when we call draw method
on Firefly class. Since this class
extends particle, JavaScript will
automatically look for draw method and for constructor on the
parent particle class. Doing this will save
us code repetition. I don't have to define
constructor and draw method twice if it's shared for
fireflies and for sparks, we can only define it once on the parent class and the
JavaScript will find it, it will be inherited. Fireflies will have
unique motion, I'm thinking floating up and
sway and left and right, which is very easy to implement. First, we will be
increasing angle by VA, angular velocity for
each animation frame. Then we will increase
collision x by speed x. Since speed x can be
positive or negative, they can start
going left or right randomly collision y will
be minus equals speed. Why? Because I want them
to float up in the negative direction
on the vertical y-axis. If a firefly moves
all the way up and it's hidden above the
top edge of game area. So if it's collision
y center point is less than zero minus its radius. We said it's marked
for deletion to true, and we will call
remove game objects. The main game class I create
a property called particles. It will be an array and it will hold all currently
active particle objects. I expand particles into
game objects so that they can be sorted,
drawn, and updated. Inside remove game objects, I add one more line of code to filter out particle objects with marked for
deletion property is set to true like this. Let's try to create
some particles. I will create them up
here on larva class. In this code block that runs when larva heights
in the forest, we get a score point. I want a swarm of fireflies
to fly out of the bushes. I take this top game
look particles array, and I push one new
Firefly inside. I remember that particle
class constructor expect for arguments, a reference to the main
game object as usual. So I just pass it
to this dot game along from larva constructor. Initial starting x and y of this particle will
be the last X and Y position of the larva we just deleted and color
will be red, e.g. just to test it. Nice, we have red particles
with white outlines. If I want more than one, I just created a for-loop. Let's say I want three
fireflies each time. I will change color to yellow. Let's see what that looks like. Nice particles
need black stroke. I can set it inside the draw
method on each particle. But in this project,
I'm not using a stroke on many other things. I can just set the
stroke to black globally here on line nine, this will be more
performance efficient. It will also give
black strokes for collision circles while
we are in debug mode. But I don't really
think it's a problem. It might look even
better like this.
28. Particle motion: Fireflies have a very basic left and right upwards motion. We are not using that angle. We are increasing
endlessly on line 327. Easy way to make
something cycle back and forth is to pass every increase in angle value to Masdar
sine or cosine method. When your code is like this, va, by how much angle increases for each animation frame will
determine the speed of swaying. And this dot speed x will
determine the curve, the radius, how far, left and right, the motion goes. Perfect. Whenever
larva successfully heights in the forest, we get a score point and we get confirmation by seeing
this Firefly effect. I want sparks to look
different so that in case that larva and enemy are obscured behind
obstacles or something, you can still see that
the larva was eaten based on the particle
effect that happens there. In here, I will define a different update method
with different code inside. So firefly and Spark are
both child classes of the parent particle
plus they both inherit code in particle
class constructor, and they both share
its draw method, but each one will have a unique update method
where we handle motion. I will increase angle by
angular velocity VA again, but I will slow it down. So times 0.5, horizontal position will be
minus equals mastered cosine. We pass it this
ever-increasing angle value to map it along
a cosine curve. And we define the
radius of that curve using speed x.
Collision y will be minus equals method
sign and I pass it the same angle value and radius of the curve
is speed y value. We go back up to larva class. And here, if Florida collides with an enemy, we delete it. And I wanted to create
a swirl of particles. I will just copy this code
block we used to create a three fireflies
and I copy it here. In this case, we want to create three sparks and
color will be blue. How do I do this? Now I need to push the
larva in front of an MA, see what happens
when it gets eaten. Nice, we get blue sparks and some kind of
circular motion. Just for testing purposes, I will create Sparks even when larva heights in safety so that the animation happens
automatically more often without me having
to chase them around. I need to see it so that I
can adjust the movement. What can we do with
this olive particles? You can do so many different
movement patterns so easily. E.g. let's make them shrink. If you try to draw
a circle using arc method that has
radius less than zero, you will get an error. So we have to be
very careful here. I say, if radius
is more than 0.1, keep reducing radius
by a small value. That value needs to be less than 0.1 to make sure we
can't get below zero. Also, if you remember, we are only removing old
particles if they move past the top edge of Canvas and the swirling
particles never do. So, I need another
condition here. If radius is less than
0.2, just to be safe, said, it's marked
for deletion to true and call remove
game objects method. Nice, this looks interesting. I want them to kinda
explored to both sides. I haven't mentioned it before
because it's not necessary to fully understand
trigonometry for this course. Sine and cosine and work
together to map a circular path. If we give them the same values, look what happens if I
swap sine and cosine. All you have to understand
about sine and cosine for animation purposes is
that if you pass them, ever increase in angle value. And when you attach
these values to vertical and horizontal
positions of your object, you will get a
circular movement. The best way to get
some clarity is to play with the values
tried to break it, adjusts the code and
see what happens. Don't worry about fully
understanding trigonometry today, it can be complicated for beginners and it takes
a while to master. I'm drawing a 30
particles here to give me a better idea about the
motion that happens. And if we get any edge cases appear that needs to
be accounted for. You don t have to do this. I recommend using
only three particles each term for
performance reasons, you can see the sparks are kind of swirly
explosions. I like it. The motion is very
different from fireflies. I give them yellow color here, and I will go back
to using fireflies. Let's see what that looks like. So we know how to extend JavaScript class to
avoid code repetition, we created a parent particle
class and to child classes, Firefly, that indicates to the player that larva
managed to hide in the forest by
disturbing swarm of growing bugs that
float away upwards. And when larva is
eaten by an enemy, it will turn into a swirl of magical sparks that slowly
shrink and disappear. I will go back to a three
fireflies here. And down here. Let's create three
sparks, or maybe five, because they shrink so
fast and often they are obscured by obstacles
and other game objects. We want to make sure they get
noticed when they appear. I would also like to allow
the player to move the xs to the forest to get
score point immediately, I want X to instantly
hatch when we push them high enough
into the safe area, that egg will
automatically turn into larva and into
fireflies instantly. And we will get a score point. I can do that up here on
line 179 where we check if Hodge timer is more than hutch interval to turn
back into a larva, I want this code
block to also run if the egg was pushed
to safety zone. If a vertical
position collision y is less than this dot
game dotted top margin. To test it, maybe I should
increase hatch interval. Otherwise these eggs
hatch too fast. I see an egg here, no here, and I push
it the medulla. Okay, Now here we go. It hatched instantly while
hatch timer was only 7 s. I try again with this egg, and yes, this works and we're also getting score points
for that. Perfect.
29. Randomised enemy skins: I peptide many different
enemies for you. You know how we have
a single image file with multiple obstacle types. I also prepared many
different toads gains for you that will play the role
of enemies in our game. I had a lot of fun making different variations and imagine and what types of
special abilities and special moves
they could have. Let's say for
performance reasons, we want to use just a set of
static images for enemies. We will also animate
these later if that's what you want in the final
version of the game, everything will be animated. All the project images can be downloaded from the
resources section below. As usual, I'm giving you all this premium
game art for free. This course is designed to give you the maximum value possible. Feel free to use
these art assets for your own projects
if you want. So I bring this spreadsheet with four frames for enemy
types into the project. Id is toads and source
is towards the dot PNG. I hide it with CSS as usual. In script.js inside
enemy class constructor, we create two helper variables that will help us
navigate around the spreadsheet and crop out one random frame for each
enemy object, frame x. We'll navigate around the
spreadsheet horizontally. And it will be initially
set to zero for MY will navigate vertically and
it will also start at zero. I will need the
longest version of draw image method to define what image I wanted
to draw, source x, y, source width and
height to specify which rectangular area
I wanted to crop out from the source image
and Destination. Destination. Why
destination with and destination height to define why we put
that cropped out piece of image on
destination Canvas. Nine arguments in total. Image to draw cropping area. And where to put
that cropped image. Let's start by cropping
just the top-left frame. So we crop from
coordinate zero-zero, two coordinates, sprite
with sprite hide. This spreadsheet has
only one column, so frame x will stay at zero. I want to crop out one
vertical frame at random. So source why of
the cropping area will be sprite height pushing the start of the
cropping area here, given us this frame,
to actually see it, I need to use the new
image with an ID of toads. So to be clear, zero times sprite height
will give us this frame. Two times sprite height
will crop out this frame. The value we pass a source. Why to draw image
method will determine where do we start cropping
from a vertically. Instead of this
hard-coded value, we can use frame. Why? Now, when I give frame, why a different value? It gives us different
enemy types. I want each enemy object
to be randomly assigned to one of these images
when it's created. So we have four rows,
Math.random times four. And if I wrap it in Math.floor frame y will be
either zero or one, or two or three. I will also replace source X with a disk dot frame x times sprite with this
will become relevant a bit later when I give
you the next spreadsheet. For now, let's leave
frame x at zero. It would also be nice
to randomly reassign the image every time and then it moves off screen and reset. So we make use of all
available images. I just randomize frame y here, like this to achieve that. To quickly test this or increased enemy speed
to a random value, 5-8 pixels per animation frame. Enemies are resetting
and images are randomly changing
every time they reset. Perfect. Let's go back
to a lower speed. I set x to five, since we are using static images for enemies at this stage, drawing them is very cheap
in terms of performance, maybe I could increase
the number of enemies to five as well. This is starting to
look really good.
30. Win and lose condition: I wanted to display
a winning and losing message on
the main game class, I created a property I
call e.g. winning score. For testing purposes,
I set it to five. We will draw winning and
losing message down here. If score is more or
equal to winning score, I will wrap it in a safe
and restore to restrict some drawing
settings we will use now only two that
win or lose message. I want to draw a
semi-transparent layer to indicate the game is over, and also to make the message more contrasting
against the background. So I said fillStyle
to block zeros, zeros, 0.0, 0.5 opacity. Then we will draw that black semi-transparent
rectangle over the entire canvas
from coordinate 00 to the width and
height of the game area. Let's run this code to test it. When we reach score three, we should get the Dutch
semi-transparent layer. Yes. After that rectangle is drawn, I will change fillStyle
to white to make sure the text is contrasting
against the dark background. I set the text-align to center. We will have a main
message in large letters and a secondary message
in smaller font. I will just define them like
this as lead variables. And the values of
these messages get will depend on how
well we played. In any case, this message
will only display after score is more or equal
to winning score. But let's say if
we played well and we lost only five
or less hatchlings, we will get a winning message. If we lost more than five, we will get a losing message. The goal of the game is
pushing eggs, hatch links, and enemies around
to make sure we protect as many as possible. So if we play well, message one will say, let's play with words a bit. You can write anything you want as a winning and
losing message here. So if we win the main message in large letters
across the screen, we'll say bulls-eye and
some exclamation marks. The secondary smaller
message will say, you bullet the bullies. If we lose, the main
message will say, Borlaug's secondary smaller
message will say you lost. Plus, we will display dynamic variable value to show how many hatchlings got eaten. Plus hatchlings coma. Don't be a pushover. This is a physics game about
pushing things around. We need to learn to push better. I'm using a good old
string concatenation to construct this
dynamic message, even better would be to use
a template literals here, if you are familiar with
modern JavaScript syntax. Now we actually want to
draw these messages. The large message
one will be e.g. 130 pixels. Helvetica. Filttext method
will take the text we want to draw message one and x
and y coordinates will be the middle of
canvas horizontally and vertically, like this. The second message will be
smaller for the pixels. Helvetica. Filttext will draw message two in the middle
of cannabis as well. One more filtText
goal that we'll say final scores space plus we will display the final
score value there, plus full stop to
end the sentence. And another sentence will say, press R to butt heads again. I want to show you
how to implement a very simple restart
functionality. Whenever we press the
letter R on the keyboard, I will also draw it
in the middle of kind of as vertically
and horizontally. Now I have to space
those messages out so they are not drawn
on top of each other. I adjust the
vertical position of message one by -20 pixels, moving it up in the negative direction
on the vertical y-axis. Message two will be plus
30, pushing it down. Message three will be plus 80. Let's see what that looks like. Yes, this space and
is much better. We have our winning message. When score is more
than winning score, we said game over to true. On the main game object, I define the game over property and initially I set it to false. The simplest thing we
could do here is to freeze animation when Game Over
happens, I can do it e.g. down here, I say only serve
the next animation frame. If game over is false, when game over becomes true, the game will freeze. I said winning score to
one just for testing. And it will display
winning or losing message. And it will freeze like this. I go down to line 519
and I delete this bit. I would like the game
to keep running, but we will make new X stop appearing and I will
prevent enemies from reset. And so they just float off
screen and stay there. So whenever game over happens, the objects that are
already in the game area, we'll just finish the
action they are doing, but new objects will no
longer appear the game we'll just finish playing out behind the winning
or losing message, but new x will no longer appear and enemies
will stop, come in. Player will still be
interactive and we can still move it around in
the empty game world. How do we implement
this? It's simple. First of all, let's say winning or losing
message appears and we still have some eggs
hatching larva class, we will make sure that we get score points only if
game over is false. When Game Over happens, hatchlings will still appear
from the remaining x, but whatever happens
will no longer affect our score points.
On the enemy class. I only want the enemies to reset if game over is false like this, when GameOver happens,
and then this will finish their movement
across the game area, but they will no longer
reset to the right. So new enemies will stop coming. Down here, we only
want to add new x into the game if
game over is false, winning or losing message
appears, the existing x, we'll still finish hatching, but new x will stop appearing. Let's test it. I have a winning message. I have some leftover eggs, but these hatchlings no
longer increase our score. We have some remaining enemies, but they just slowly move off screen and don't
come back again. Player remains interactive and we can still move it around. Perfect. I wanted to test
the loosen message, rather than
intentionally pushing hatchlings in front of
enemies to get eaten, I will just manually
set lost hatchlings, the ten here on line 380. Yes, we get are losing message. This is great. I said plus touch
links back to zero. Adding a custom font is easy. You can choose any
font you want. I will go to Google fonts. They have thousands of
fonts available there. I want to use this comic book inspired font called bankers. I click here to select the font, and I click up here, which will generate
some code I can use to include this
font in my project. I copy these three link tags
and I paste them up here in the headstock in
my index HTML file, delay before my
custom style sheets, so the font is
available from there. Then I copy the CSS rule. I use it here. In the case of this project, I can just apply that
font-family to all elements. You might want to be
more specific and only applied to the
canvas element e.g. now, the font is
available in our project. So up here on line ten, I said canvas fond to
40 pixels bankers. This will affect the
text that displays score and timers
above hatching eggs. We also want to set
bankers here and here, if we want to use it
for game over messages, Let's quickly when the game yes. This looks better, doesn't it? Because we use safe and restore. Hear everything I do in
this area will remain restricted to game over
message is Canvas. Shadows are
performance expensive and some browsers don't
deal with it well, but if I limit them to
this piece of text, we will be okay. Let's try it. We need to define
horizontal distance between shape and the shadow. It can be a positive or a
negative value depending on if you want the shadow to be
to the left or to the right. Shadow offset y can also
be positive or negative. It will determine
vertical position of the shadow in relation
to its parent shape. Shadow color will be black. We can also define
shadow blur if we want. That one is optional,
so I will leave it on default zero, which
means nobler. Keep in mind that kinda
shadows will only be drawn if shadow color is set to
a nontransparent value. And if at least one of the
properties shadow blur, shadow offset X or shadow offset y is set to a non-zero value. Shadows give the
text nice highlight and make it stand out from
the background a bit more. I said lost hatchlings
to ten again, just to see the loosen message. What it looks like with
new font and shadows. Nice, this looks great. I said flossed
hatchlings back to zero.
31. Restart game: We have this message
here that says press R to butt heads again, which means press R
to restart the game. What I'm about to show you
is the simplest way you can implement restart game
functionality on the player class. I create a custom
method I call e.g. restart. Inside, I want to set player properties to what they are when a new game starts. Let's have a quick look
what we have here. I want the player to move
to the initial position. We will reset collision
x and collision why? We will also make sure that sprite image moves along
with the collision circle. So I will need these
two lines of code. So play restart method will move player collision area and player sprite sheet back
to its starting position. We go down here to
the main game class and here inside key
down event listener, I say if key that was pressed is our call restart method
on game class like this. I define that method down here. And in here, we will set all properties to
their initial state. This will be different
from project to project. We just wrote this code base so we are familiar
with it and we understand what needs
to be changed to restart the game back
to its initial state. First, we will call
restart method we just defined on
the player class. Let's test it. I move the player and when I press letter
R on the keyboard, player will move back to its starting position.
So far so good. We can use a similar
technique to give the player and ability
to rewind time. E.g. we just set state and position on x and n is n hatchlings
to what it was, let's say 5 s ago. That might give us some
interesting gameplay. Anyway, we wanted to
restart the game. So up here in the constructor, I check what needs to change. I know I will have to reset
the contents of my arrays. I take obstacles, x, enemies, hatchlings,
and particles. And I said all these back
to empty arrays like this. I test it. Okay, so when I press R, Now this happens and
needs to run init method again to recreate enemies and obstacles since we deleted them. Yes, now, when I restart, obstacles get regenerated at random positions and we
also generate new enemies. I can see I also need to reset the current
mouse position. I take this dot
mouse and I set it to these starting
values actually, so I can just copy this. I also reset the score back to zero and also lost hatchlings. I also need to set game
over to false to make sure x keep appearing and
enemies keep resetting. Let's test it. I play. I win the game. I press artery side. I play. I when I resize it, I play, I win, seems
to be working. Alright. I said
winning score to 30. Okay, I spend some time testing the gameplay and there are a couple of more
things we can adjust. As you know, we can enable and disable debug mode by pressing letter D. I'm here on
our custom larva class. I would like our
larva hatch links to interact with x inside
update method on larva class I used
spread operator to expand x into collisions
objects array. Doing this will
make the X solid, impossible obstacles
and hatchlings will have to crawl around them. This will make the
game a bit more difficult and player
will have to get more involved to get the x
out of the way to make sure the hatchlings reach safe
area as fast as possible. Sometimes hatchlings
can even get stuck between multiple
eggs and obstacles. And if we don't freedom in time, they will get eaten by enemies. Alternatively, I can
remove X from here. I go up to the egg class
and insight update method. On a class, I will expand hatchlings into
collision objects array. This, on the other hand, will make the game easier
because not only will the hatchlings be able to
push the x out of the way. They will also
sometimes put the x in front of them closer
to the safety zone. You can choose what you
want to do here yourself, depending on if you
want your game to be easy or more challenging
at this point, you know this
code-based well enough. So if you want, you can take some freedom and adjust the
gameplay however you want. I also want the collision
circles on hatchlings to better match the sprite sheet
-40 here will work well, I think to better
align the bottom of collision circles
where the bottom of bodies of the hatchlings. I want this collision
areas to match the artwork as
closely as possible. We are in debug mode now, but keep in mind that
the game is meant to be played with
collision circles hidden. When debug mode is switched off. You can use the
same technique to better align
collision circles on enemies with the little shadow on the ground below each enemy. I will do that in
a minute because the next lesson we will focus
on adding new enemy types. I will also give you all the
characters spreadsheets and we will add a lot of
animations to this project. I had so much fun designing these creatures and making
them move. I hope you like it. I noticed one small
edge case buck, when we get game
over message and leftover enemies eat some
leftover hatchlings. Lost hatchlings variables
still increases. We need to make sure that
lost hatchlings value stays the same after game
over message is displayed. So I will only run this code
block if game over is false. This will also make
the hatchlings in vulnerable under the
game over screen. If you don't want that, you can apply this
if statement only to one line of code that
increases lost hatchlings. If game over is false, lost hatchlings plus, plus.
32. Extending enemy class: Okay, multiple enemy types. I want to create two child
classes that extend. Parent enemy class. Told skin will be
this slimy green one, bark scan will be the
hollow wooden want with antlers,
spikes, and leaves. We are doing magical
forest theme. So I was having fun with
creature design on this one. So again, we are extending
JavaScript class here. We do it to avoid
cold repetition. Any properties and
methods shared between all enemy types will be on
the parent endemic class. Only the properties
that are specific for individual enemy types
will be on child classes. You can download all
these premium or the assets in the
project section below. We will start by adding
eight different bark scans. What do you think about
my designs too much? I wanted this one to be made of wood and to be hollow inside, but light coming
from eyes and mouth, kinda like a Halloween pumpkin. I hide the image with CSS. I have also replaced
toads image. We had four characters
on it at first. Now it has eight
different versions. Lots of words, slime,
eyes, and tentacles. Up here on the main enemy class, I can see that the constructor expects
game as an argument. I want to run the constructor
on the parent enemy class and add a little bit to it
from inside TO skin class. Mainly the properties
that will be specific only to toad's skin enemy type. So in the child
class constructor, we will expect the
game as an argument. And then first we have to run constructor on the
parent enemy class, also called a superclass. We call superclass constructor. And we pass the reference, the main game object
along because we know the expected up
here on line 264. All these properties
will be specific only to tote skin NMAI because
image will be different. Sprites size as well, and width and height will
depend on that sprite size. And collision x uses
width and its formula. So I cut out all these, base them on the
child class here. So these properties will have a different value for
each child class. They won't be shared. I will copy this code block
and I put it on bark skin. Here we will have to
adjust the values. Sprite width here is 183 pixels. Sprite height will be 280, width and height
of a single frame. This enemy will be larger. This dot image will
be Id of barack. Did we gave it an index HTML? So these are the properties
shared for all enemy classes. They will also share this
drawer and this update method. Properties on the child classes will be unique for
each enemy type, we have two enemy types. I would like to randomly
add one or the other. I handle adding
enemies down here inside add enema method
on domain game class. In here I can say if
Math.random is less than 0.5, Math.random called on its own like this will return
a random number that is greater or equal
to zero and less than one. So we can say that
it will be less than 0.5 and around 50 per
cent of the cases. If it is less than 0.5, we will push new toad's skin
into our enemies array. Else, in the other 50
per cent of cases, we will push new buckskin. Nice. Now we are randomly add
in both enemy types. Frame x is set to zero, so we are only getting the
frames from the first column. I want it to be zero or one. So Math.random times two wrapped
in Math.floor like this. Now we are getting all frames. I will also show you how to
animate these spreadsheets, but that will make the
game more performance expensive as the character art I chose to use for
this project is very detailed and images
are quite large. There are optimization
techniques that can be used for
sprite animation, but that's beyond the scope of this beginner friendly class. We will talk about
this a bit more soon. When enemies reset, we are randomized and frame
y, but not frame x. Let's do it by copying line 270 and we put it here inside
this if statement block. Now we are fully randomized in the image when enemies
reset position, I can put these lines
that randomized frame x and frame y into
shared update method. Because both of my enemy
types have the same amount of frames for rows and two columns. If each enemy sprite sheet had a different
number of frames, I would have to extend
this update method. On each NMS subclass, we are adding and
deleting objects like particles,
hatchlings, and x. It's always good to check our objects to make sure
the old objects are being correctly removed and we don't have an endlessly
grow in array. I could console log it in
my update method, e.g. but running a console
log like that, 60 times per second is very
performance expensive. We could just console log r
objects on demand whenever we press a button inside our
key down event listener, I say if we press
C on the keyboard, only then console log
this dot game objects. Now I can play the game and occasionally I press
C to check if game objects array only contains the correct objects that are currently
active in the game. If you e.g. notice
that it's endlessly increasing and it contains
more and more particles. We know that we are not removing the old particle
objects correctly. All seems good in this case.
33. Simple fullscreen mode: So in our game, we can press letter
D to enable and disable debug mode letter
R to restart the game, C to run a custom console log. And I'll also show
you how to toggle a very simple full-screen
mode on and off by pressing letter F. We will
call discuss the method e.g. toggle full-screen. We will define it down here
on the main game class. Javascript has a native
full-screen functionality. This built-in API contains a set of methods
that allow us to present a specific element and all its descendants in
a full-screen mode, removing all browser
user interface elements and other applications
from the screen. When I call document dot
full-screen element like this, it returns the element that is currently being presented
in full-screen mode. If it returns null, it means that full-screen
mode is currently not in use. So here I'm saying, if we are currently not used in full-screen mode on this page, I want to enter
full screen mode. Request full-screen
method will display the element it is called
on in a full-screen mode. In this case, I wanted to
display the entire webpage. So document, which represents the webpage DOD
document element, which represents the root
element of the document, in this case the HTML tag. I want it to full screen
the entire web page from its root element code
to exit full screen. This simple, so we
just say else if document dot exit full screen, Run, document that exit
full screen method. It's a bit weird,
but it will work. During this will turn
the entire web page. Fullscreen Canvas will not take up the entire screen width. We are moving the
player using mouse, so we have to be careful
how we re-size Canvas in this project as the
mouse coordinates are calculated in a certain way. If I give Canvas
dynamic width with CSS, it will start giving
us wrong coordinates. In Google Chrome browser. If I go full screen like this and I zoom in to make the
game cover the full screen, mouse coordinates will
still be calculated correctly and the gain
will be playable. You can test it for
yourself by pressing F to enter full screen
and then hold down Control key and tap plus or minus keys to zoom
in and zoom out. On Mac, we use
Command key instead. We are about to animate
the spreadsheets. These images are
large and it will put some strain on computer
performance when we start swapping through
these images very fast on each active
animated object, you might want to save
the code base as it is right now and
continue in a copy of this folder so that you
can keep static and fully animated codebases
and compare them side-by-side in terms of
performance on your machine. It's up to you. I will also leave the full
source code from this stage of the project available for download in the
resources section below, the folder with full
source code from this stage is called
source code version five.
34. Player sprite sheet full animation: Time to fully animate
the player sprite sheet. We are already using
the sprite sheet, but we're cropping only
the first frame on each row to turn player
in different directions. We have helper variables here, where a frame x will cycle
on the same row from left to right over and over loop in
that particular animation. And frame y will determine
which row we are animating. In the case of
player sprite sheet, different rows have the
same player character facing in different directions. We are using draw method to draw this spreadsheet on Canvas. We are using the
longest version with a nine arguments image
we want to draw. Then we define the cropping area by the following four arguments. Source x, y, source
width, and source height. And the last four arguments and define where on
destination cannabis, we place that cropped out image onto destination x, destination. Why destination width
and destination height? I explained this a few times in this course because
it's really important to understand if you want to use the draw image for
a sprite animation. We already have a functionality
that swaps frame y to a different row based on where we want the player to be facing. All I have to do now to
fully animated is to make frame X cycle between frame zero and max frame
over and over. We will handle sprite animation down here inside update method. And we will reuse this code
for other objects as well. We will need one more helper
property called max frame. Sprite sheet has
58 frames per row. It's a massive
spreadsheet image. We will be flying through it, cropping out different
frames to create an illusion of movement
and animation. Let me show you. It's easy. If frame x
is less than max frame, I want to frame x to increase by one as the update method
runs over and over. This will travel over the animation row
from left to right. When it reaches the end frame, x is no longer less
than max frame. So else statement will run. It will reset frame x back to zeros so that it
can cycle again. That's it. We are
animating the player. I try to walk around, turn the player in all
possible directions to see if everything is okay. Now, our player is
properly alive. When you prepare
your spreadsheet and you'll game art correctly, like I prepared it for you. Animating it with
code is very easy. All you need is this
simple code block.
35. Larva sprite sheet full animation: I copy this code block. I will reuse it on larva class. I go down to larva class. Here. We will also need
max frame helper property. In this case, max frame is 38. Larva spreadsheet has
38 frames per row. As usual, you can download all spreadsheets in
the resources section. I put it into my Project folder and I bring it into
the project here. Id will be larva
underscores sprite and source will be the same
larva sprite dot PNG. In script.js, I point this dot image property
towards this new ID. It has 38 frames per row. And again, I want to cycle between frame zero
and max frame. I copied that sprite
animation called blog from update method
on player class, and I paste it here. It's very simple, so there's
no need to make any changes. If frame x is less
than max frame, keep increasing frame x by one. Else, reset frame x back to zero so that it can cycle again. All the spreadsheets are set
up so that the last frame and the first frame connect in a continuous animation loop. So looping over them
over and over like this will create a seamless
animation sequence. Now you can see our hatchlings are crawling with their Antonio, wobbling up and down and
they're squishy bodies moving as they crawl to safety
of a mushroom forest. Perfect.
36. Enemies sprite sheet full animation: I also prepared animation
spreadsheets for the enemies. I give it an ID of bark skin and ID will be bark skin dot PNG. The green slime.
One will be Id of adult skin and source
told skin PNG. I Hyde Park skin with CSS. Also told skin. And the larva sprite. We will again copy
this code block. It's important to notice how are these enemies
spreadsheets organized. We need to consider that
when writing our code, I can see we have four types of toads and four types
for dark skin, each type on a separate row. In that case, we know
that frame y will control which enemy
type is displayed. Frame x will again cycle
through that animation row, bring in that creature to life. It's also important
to notice that I have the same structure for both tote and
dark-skinned sprites. Both have four rows, 38 frames per row. So I can handle the animation on the parent enemy class
as the logic will be shared for both
animus subclasses. I said frame x to zero here. Frame why we'll
randomly choose one of the four available
enemies to animate. Max frame will be 38. In update method, I handled
sprite animation up here, I paste the code block I copied. First of all, I wanted to
use toad's skin spreadsheet here and bark skin
here like this. When you see the animation
glitch like this, it usually means that the frame
size we defined is wrong. So the frames are
kinda slide in. Dark skins are
animating correctly. I check frame size untold skins, and I can see that the single
frame in the spreadsheet is 154 pixels wide and
238 pixels tall. You get the width of a single
frame by dividing the width of the entire spreadsheet
by the number of columns, by the number of frames per row. And you'll get the height of
a single frame by dividing the height of the
entire spreadsheet by the number of rows. Perfect, we are animating the player hatchlings and
eight different enemy types. We are seeing collagen
circle areas, but the game is
meant to be played with the debug mode disabled. We need to make sure
that the collision areas match the little shadows
under our float and enemies, since I want them to
be pixel perfect here. And toads, canes, and buck skins have
different sprites sizes. I can't offset
sprite X and Sprite. Why here in the shared
update method by a certain value and align both of them from
the same place. In a situation like this, I want to comment this line out and I want to extend
this update method on each subclass to give it slightly different values
for each element type. To do it as simple. On torts can subclass
that extends NME superclass parent
class, we call update. If I just define
update like this, this method would take priority
and it would completely override the method with the same name on
the parent class. I don't really want that. I want the code inside update method on
enemy class to run. And I wanted to add some custom code that
is specific only for toad's skin class down here inside this update
method on a child class. I do it by using the super keyword
representing the parent. And then my class. Here I'm
saying run all the code inside update method on the parent and then the
class on the superclass. Then I want to take
this line of code, and this line will only
run four toed scans. I try plus 50. My goal is to align collision circle with
the little shadow on the ground below
the floated enemies as closely as possible, we broke bark skins
because they're sprite y-value is
not updated now, We will fix that in a minute. First, I want to align towards, I will do height -100. Okay, how about height of the
enemy divided by two -100? This is getting closer to
what I want. Maybe -90. Yes. When I press the d to enable
and disable debug mode, I can see that little ground
shadow undertone skins aligns very closely with
collision circle area. Perfect. I copy this code block and I put it here on our bark skins. Here I will do -100. So this is how you extend
a method that exists on a parent class like this
update method we have here. And on each subclass, we first call the code inside update method on the parent
class from line 302. And then we can add some
additional code to it that will be unique only to
this particular sub-class. Using this technique can give each enemy type of
different movement pattern, different behavior,
different attacks, different animations and so on. You can download the
final source code folder I called source
code version six in the resources section
below and compare it with your own code if you are
experiencing any issues. Now it's time for you to add
some additional features. Maybe try to check my other game development
classes where we apply sounds or state management and use it for this game. Checkout bonus art
asset collection available in the
resources section below, I will leave a lot of art
in multiple formats with separate pieces so
you can mix and match and have fun
with it if you want. Thank you for spending
your time with me. I hope you've got a
ton of value today. Well done for completing the
project. I'll see you later.
37. Bonus project (optional): Create an animated
game environments for directional keyboard
controlled characters, randomly positioned obstacles, and environmental art assets. Let's make games
using HTML CSS and plain vanilla Javascript with no frameworks and no libraries. As usual, if I draw all my interactive elements
on a single canvas element layering can be a challenge. We can use built in array
sort method to reorganize game objects based on their vertical y coordinate
and draw them in that order. With that simple trick in place, we can have a player
character that walks around obstacles in a way
that makes visual sense. Let's build a game from
scratch and learn about Front and Y development and object
oriented programming.
38. Bonus project setup: We will work with three
files in index HTML. I create a generic
simple web page and 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 GS file. I create a dip with an ID of a wrapper and I
put canvas inside. I create an image tag
with Source overlay, Alt overlay, and ID overlay. You can download
all art assets in the video description in
the project files section. I'm also giving you
all separate pieces in high resolution so
you can compose your own unique
background images. If you want optimization tip
number one, use plain CSS. For large background images, I can draw and animate
everything on cannavas. I usually do that today. I want to show you how
you can combine CSS and canvas draw image method
to maximize performance. This will negate the need to
draw these large images on canvas over and over for
every tick of animation. And it will improve
performance of our games. Css transforms use GPU. They are faster and they
have simpler markup because CSS has a lot of
built in keywords which makes animated
transitions much easier. Maybe for your game, you need everything to be
drawn on canvas. But today I will combine
it with some CSS to take full advantage of the
tools and techniques front end web development
environment offers.
39. Enhancing game worlds: Intalsiass. I give Canvas a
background image, you can download it in
the video description or you can put together
your own background from the individual pieces
I'm providing as well As you can see we
created two layers. Background art, our character
will be drawn on top of, and some overlay images, leaves and grass that will be in front of our forest scene. Player character will be
able to hide behind them. Unless we set canvas
width and height, it will always default
to 300 times 150 pixels. We will make it the right
size with the Java script in a minute to reveal the
full background artwork. I could have also put this background image
on the wrapper element. I give the main wrapper
element some border. My browser is a zoomed out so I can fit everything on screen. The images I'm giving you
today are optimized for 1,280 times 720
pixel resolution. The game we are building
is pretty large. You can adjust the
sizes if you want. If you are a
beginner, it might be a good idea to follow
my code exactly, use the same sizing
and the same images, and only make changes once we got the entire
project working. I want to center the container, so I give it position absolute. You can see that the wrapper doesn't start from
the top left corner. There are some default
margins and paddings to make sure our page looks the same across all
different browsers. I will do a global reset target all elements on the page
and I set margin to zero, padding to zero, and box
size into border box. To make sure that
elements padding and border are included in its
total width and height, I want to center the
rapidive in the middle of the page vertically
and horizontally. I already gave it
position absolute. Now I can give it top 50% left 50% And transform translate -50% -50% I also need to
align the overlay this image. I give it position
absolute top 50% left 50% transform
translate -50% -50% This will align it exactly
on top of the wrapper. There are many more
things we can do here. For example, let's bring another two images
into the project. I create an image called
leaves underscore left. I give it the same
old attribute and ID. You can download
all these images in the video description, I create another image called leaves underscore,
right? Like this. These could have
been just a part of the main overlay image, but I want them to be
animated and interactive. As we said, CSS transforms
are processed by GPU. Let's take advantage
of that to make our game world feel a bit
more alive and interactive. Css do some work for us. Not everything needs
to be drawn on canvas. It will all blend
together really well and our game
will look great. I target leaves left
at top position to 50 pixels left position
to -100 leaves right will be positioned
-150 pixels behind the right edge up here
on the main wrapper. Set overflow to hidden, to clip and hide
everything that's outside the main game container. We can do so many
things with it now. For example, we can have the
leaves move out of the way. When we hover over canvas, we are just a silent observer
hiding in the bushes. Let's push the leaves out of the way a little bit
so that we can see. When I hover over canvas, I want to apply some
CSS to leaves left. I will change their
left position, and I will rotate them
by plus 15 degrees. This CSS declaration
will not work. It would work if leaves left was a child of canvas
element, but it's not. If we look here, we can see
that they are siblings. They are on the same
level in element three and they both share
wrapper as a parent. To apply something
to leaves left, when canvas is hovered over, we need to use something called the general sibling selector. General sibling selector
selects all elements that are next siblings
of a specified element. If I look at our
element structure, we have canvas
element overlay image is directly on top of it. It's actually blocking
the hover event on canvas from triggering. I want overlay
image to be on top, but I want it to be invisible
for all mouse interactions. So that I can interact with
canvas that is underneath it. I can do it by setting
pointer events property on overlay to non like this. Right now canvas is only
in this small area, but soon we will
resize it to fill the entire container to
test the hover event. For now, I have to move my mouse over the
small canvas area. Here it works on hover, I set left position
to -150 pixels. I will give leaves
some transition time so that it actually animates. Let's try 0.3 seconds. Nice. I do the same
thing for leaves, right? I also give it some transition, maybe just a 0.2 seconds. So they are not
completely synchronized. We can do so many things. For example, apply key
frames animation to make it look like the leaves
are moving from the wind. I'll call it wobble At 0% it
will start at top 50 pixels. We will have 50%
breakpoint at 55 pixels. And at 100% it will
go back to 50. We can apply it by giving
leaves left animation property. We call it wobble. Timing will be easing out. Time of animation loop
will be 1.5 seconds. Like this, like this, it will run just once, but I want an infinite loop,
I don't see any movement. Let's set this to
65 pixels, right? I need to make sure I have the right syntax for key frames. This semicolon has to be here we have leaves
moving in the wind. Let's try 56 pixels. I copy this line and apply
it also to leaves, right? I want to desynchronize them. Loop on this one
will be 1.6 seconds, same as we did with overlay. I need to set pointer events to none on both leaves to make sure they don't
block mouse events on canvas that's underneath it. This is how we can
draw and enhance part of your game world
scene with CSS. It's a good practice when
it comes to performance. If your project allows it, feel free to use it more often.
40. JavaScript setup: We will also draw some
images with Javascript, mainly our animated keyboard
controlled Alber character. When we work with
images in Javascript, we always need to make sure that the image is fully loaded and available before the code that depends on that
image is executed. Otherwise, we will get an error. There are many ways to do this. We can use promises or
a very simple way is to wrap all Javascript code
inside load event listener. Load event is fired when
the whole page has loaded, including all
dependent resources such as style sheets and images. This way I can just
put my spreadsheet as a regular IMG element
in index HTML, and Javascript will
wait for that image to load inside the anonymous
callback function. Here I set up my canvas. We need a reference to
the canvas element, a constant variable I call
canvas and I want to point Javascript towards
this canvas element we created on line
12 in index HGML, we gave it an ID of canvas one. We don't really need to use
get element by ID here, because all browsers
automatically create Javascript variables for
all elements with an ID. This automatic variable name
is always the same as ID. It's good for the bug in, but because browsers put all these variables into
the global namespace, these variables are easy
to accidentally override. It's better if we use get
element by ID, like this. Ctx context is canvas dot get context two D to generate
an instance of built in canvas two D API that holds canvas state and all
two drawing methods. The parent wrapper
element was set to 1,280 times 720 pixels. Let's make sure that canvas
element is the same size. This will reveal the
full background artwork. What do you think about it? As I said, I will also share individual pieces with
you in high resolution. You can mix and match
them and create your own unique game
environments if you want. All art assets I'm sharing in this video are copyright free. They were designed and drawn
by me or by artists I hired. You can modify them and
use them however you want. I will comment out CSS
animations on lines 39.31 I think it will
be less distracting. We have some Java
script coding to do now to bring our animated
character to life. Let's follow the principles of object oriented
programming today and split our code into
individual classes. Each class will have different responsibilities
and we will make them communicate and work together to create
the final product. Custom class I call for example, input handler will
handle keyboard inputs. When a key is pressed
on a keyboard, this class will process
that and it will transform and store that value so that it can be used by other classes. Class Owl Bear will be our
main animated character. Player will be able to
control it using keyboard. Class object will be used
as a blueprint to create randomly generated
trees, bushes and grass. Once we place them
into the game world, we will learn how to
organize them in a way that they are drawn in front
and behind the player, depending on where the player
is currently standing. This is very important because
without it in game like this drawing player on top of everything wouldn't
make visual sense. We need some logic so that
the player can walk around these two D obstacles and I will show you an
easy way to do it. Finally, we will need
a class I call game. This is where all the logic from all the classes
comes together. This will be the main
brain of our code base. Class constructor will
create an instance of a class for us based on a
blueprint we define inside. Let's define a blueprint
for our main game class. I want these classes to be
self contained and modular. We will pass it
with and height of the game area as
arguments like this. We convert them into
class properties. We take width and height past as argument to the class
constructor and we convert them into width and height properties on this instance of game class. To create an instance
of game class, I create a constant variable. I call for example game, and I use the new keyword. The new keyword in Java
script is used to create an instance of an object that
has a constructor function. New keyword will look
for a class with that name and it will
trigger its constructor. Constructor will create one
new blank Java script object and it will automatically set its prototype behind the scenes. It will bind this keyword to point to this newly
created object and it will assign the object
all the properties and values as we define them
in the blueprint here. So far, our game object will only have width
and height properties. In this case, I can see
that these properties are expected as values that are being passed
from the outside. We do this to keep our classes self contained and modular. I pass it canvas
width from line four. As with canvas height
from line five as height. Because I want the game to
be the same size as canvas, I want the game area to use
up the entire canvas element. This is how you
create a class in Java script and how you create
an instance of that class. How you use that class to create one object based on
the class blueprint. Let's consolok game
variable from line 26 to check if everything
went well in console. I can see that game
variable stores a reference pointing towards a game object with
height and width properties set to the
right values perfect. If you get any errors when writing your object
oriented code base, you can always consolo
your objects like this and check if all these
properties have values. If you see undefined here, you know there is a problem in your code and you can track that value back through your
code base to find the issue.
41. Keyboard controls: Input handler class is here
to keep track of keyboard keys as they are being pressed
and released by the user. And it will convert them to
values that can be used by other objects in our code
base and it will store them. I give it a constructor as well. This is how we will make our objects communicate
with each other. Input handler will expect a reference pointing to the main game object
as an argument. This entire object inside we convert that reference
into a class property like this game that was passed as an argument is converted
to game property. On this instance
of input handler, class objects in Java script
are reference data types. I'm not creating a copy
of game object here, I'm just creating a
variable that points to a space in memory where the
main game object is stored. This is great because when
values of game object update, those changes are immediately visible from this
dot game reference. Here on the main game class, I create a property
called this dot last key input handle class will be updating this value on game class as keys are
being pressed and released. Initially I said
it to undefined. Today we will learn about keyboard controls
and we will only hold the last key that was
pressed or released in memory. It's all we need to control, four direction onto
the character. If I wanted to keep track
of multiple key presses, I would create an
array here instead. And I would add and
remove keys from there. I've done it before and I might do it later
in this series. Depending on how complex and precise keyboard
controls we will need. I also want to automatically
create an instance of input handler when I
create the game object. Let's put a consolo here
that says input handler created inside game
class constructor. I create a property
called this input. I set it equal to new
input handler like this. On line eight, I can see that input handler class constructor expects a reference to
the main game object. Here I'm creating
input handler instance inside that game class. I pass it this keyword. This keyword used
inside game class points to the entire
game class itself. It represents the object
inside which it was defined. Nice because I put this code inside game class
constructor when I create an instance of
game class on line 31, it will also
automatically create an instance of input
handler class for me. It will save it as a
dot input property on the main game class itself. I can confirm that input
handler was created because I see this
consologe here. Perfect. This is a simple way
how you can connect your classes and make them work together to
create a full game. It's also very modular, so you can later
add more features and more classes to
your game if you want. As we see with this consolo, all the code inside
class constructor is automatically executed
when an instance of that class is created. That code doesn't always have to be just defined in
class properties. We can put any code in here. In this case I want to apply event listeners
automatically. When an instance of input
handle class is created, I will just put them in here. We will listen for
the key down event in the callback function. I will run a consolo
callback function on event listener in
Javascript has access to autogenerated event object that contains all details about
the event that just fired. I can assign it a
variable name here, convention is usually
a letter like this. I've done this before, so I know that this autogenerated
event object has a property called key that gives us the name of the
key that was pressed. I consolo key, I need to click on Canvas
to make sure it's selected. And when I press
anything on keyboard, the names of the keys are
being printed in the console. Today we will focus on
directional arrow keys. So arrow left, arrow right, arrow down, and arrow up. I want to save these
key values inside last key property from line
28 on the main game object. We have a reference to
the main game object here on line nine. But when I consolog this game from inside
the event listener, we notice that the reference
returns undefined. It's because this
callback function forgets that it was initially defined here inside
constructor method on input handler class. I want the function to remember, I want to bind its scope so that a reference to this keyword
in this case representing this input handler
object as well as access to this game
property will be available from inside this
callback function on key down event listener whenever that event
listener runs Later on. I want to make sure this
function never forgets that it was initially defined
sitting inside an object. I can use Javascript
bind method to do that, or I can simply use ES
six arrow function. Es six arrow functions
don't bind their own this, but they inherit the one
from the parent scope. We call this lexical scoping. The scope of arrow function will always depend on where in our code base it was originally defined on its original
lexical scope. In our case, it was defined here inside this
constructor method. So it will always have access
to this dot game property and any other properties we might define inside
this constructor. Later on when I refactor this callback function
to a narrow function, we are able to consolo did game. And we see that it points
towards the main game object. And all its properties are visible there,
Including last key, if I consolo last key
value from line 28, initially it is
set to undefined. We take the key property from event object and I assign
it to it like this. Now, last key property
on the main game class always keeps track of the
last key that was pressed. I also need to know when
the last key was released. I create another event listener, this time for key up event. Now we are storing the name of the last key that was
pressed or released. The problem is that it just
registers the key's name. I need to know if that key
was pressed or released. I will concatenate
capital letter P inside key down event to specify that the key of that
name was pressed. I will then add capital R
in front of the name of the key inside keyup event so that we know that
key was released. Now we are getting these
values in console, you can see the value
of this dot gamet. Last key property is changing. We press arrow right here and we release
arrow right here. I can remove the consol. Ok. Now that we know it's working on the main game object, we defined last key
property and we created an instance of
input handler class. As input handler class
gets initialized, we apply a key down and
key up event listeners. This will dynamically update that last key property so
that our game object and all objects connected
to it are always aware which key was
pressed or released. We will use those values to
control the player character, the Owl bear. Let's
define it here.
42. Player character: First, we need to connect
it to the main game object. We make sure it
expects a reference to the game object as
an argument insight, we convert it into a
class property as usual. Let's start by giving it width
and height of 100 pixels. Its initial position
will be 200 pixels from the left edge and
200 pixels from the top. I give it a draw method
to draw it on canvas. It will expect context
as an argument to specify which canvas
element we want to draw on. Take that context variable and I call built in Phil
rectangle method on it. Phil rectangle expects x, y width and height. So I pass it properties
from owl bear constructor. We want to draw a
black rectangle representing the
owl bear at first, same as we did with
input handler class. I want to create an instance of Owl bear object automatically. When I create an instance
of the main game class, I instantiated here inside class constructor and I assign
it to a class property. I call this owl
bear up on line 20, I can see that it expects
game as an argument. I pass it this keyword because we are inside this game class. Game class will have a special
method I call render to update and draw all
game objects on canvas. I take oil bear from line 42 and I call its associated
draw method from line 27. That method expects
context as an argument, which is this CTX
from line three. I need to pass it along. I will still call it context. It will be expected as an argument by the
main render method, we pass it along to draw
method on Owl Bear. I take a game, I call its
render method from line 44, and I pass it CTX from line
three as an argument that will get assigned a
variable name context and passed along where needed. Perfect. We are
drawing our Owl bear. It looks like a
simple black square because that's what
we defined here inside draw method on owl bear
class. Let's make it move. I give it a custom update method for every animation frame. I want X to increase by one, making it walk to the right. We have to actually
call that method from inside render
on game class. Here I delete this consologe to actually see the movement. We need to be calling this
update method over and over. Calling it from
render just once on the first page load like we are doing here, won't
animate anything. We will need animation loop. I create a custom function. I call for example
animate inside. I put this render call. To make it loop, I call built in request animation frame method. This method will update
our web page before the next repaint by running
a code in a function, we pass it as an argument. If I pass it animate the
name of its parent function, it will create an
endless animation loop. Animate gets called. It calls render to draw and
update our owl bear request. Animation frame will
trigger animate again, so the cycle will repeat itself. Request animation frame sits on the window object representing
the browser window. But it can also be called
directly like this. Without putting
window.in front of it. It's better to use request
animation frame for animation than using
set interval request. Animation frame adjusts
frames per second to your screen refresh rate and it also auto generates
a timestamp for us. We might need it later to
trigger the animation. I just call animate Like this nice player is moving to the right,
it's leaving trails. We need to make sure we delete all canvas drawings between
each animation loop. I do that by calling clear
rectangle built in method. I want to clear the entire
canvas from coordinate 002, canvas width and canvas height. Now we have a
moving black square representing the
player character. Perfect. It's moving
to the right because here inside update
method on Alber class, which is our player,
we are increasing horizontal x position by one
for each animation frame. I give it a property
I call speed X. Initially, I set it to zero. We will also need speed Y, vertical speed for up
and down movement. Remember that we are building a keyboard controlled character that can walk in
four directions. I will increase this dot x by the current value of speed x. Like this vertical
y coordinate of the owl bear will be increasing
by the value of speed Y. This is great because
I can give it a positive value to
move to the right, a negative value to
move to the left. I can make it move
faster by giving it a larger value
per animation frame. Here this number means
pixels per animation frame. Positive value in speed Y
will make player move down. Negative value will
make it move up. Positive value in both X and Y will make it move down, right? We could easily use this for
eight directional movement. As you can see up
here on line seven, we have Input handle class
that captures keyboard input. It transforms the
default key names to indicate whether the key
was pressed or released. By pre pending or R in front of the
actual name of the key. It saves the value
of the last key that was pressed or released
here on line 46. Inside this dot last key
property on the main game class. Because I'm passing
a reference to this entire game class when
I create Alber on line 48. Alber object has access to last key value through this dot game reference
here on line 21. I can say if this game do, last key is arrow left. If arrow left was pressed, set speed x to minus one to
make the character move to the left in the negative
direction on horizontal x axis. If I click canvas and I
press left arrow key, we are moving perfect. When I release the key, the
player doesn't stop up here. I can see we are capturing the value for press and release. I can say L, set
speed x to zero, Now we can move left
and we can stop. I could also create
a set speed method that whenever it's called, it will set speed x and speed Y. These will be passed
to it as arguments. We take those arguments and we update class
properties from line 26.27 Now I can replace this
line with this set speed. And I pass it minus one as
speed x and zero as speed y. In the L statement, I set both horizontal and
vertical speed to zero. It's better to have a
global max speed variable rather than hard coding
one and minus one here. In case that speed
needs to be dynamic, player can have some
temporary speed boost. Or maybe player is moving slower while crawling
and prowling. To do that, we define
max speed property. We set it to three pixels
per animation frame, and we replace it here. Let's expand this statement. I will also check
L if this dot dot last key is pressed
R right, like this. In that case we set speed x to plus max speed speed y to zero. Now we can move
left and right and we stop moving when the
RO key is released. Perfect another L if block. Be careful about the
syntax and the brackets. It's very easy to
miss one bracket and break the entire code
base while doing this. When we press R key, we call set speed. We set speed x to zero
and speed Y to minus max speed because we
want to be moving up in the negative direction
on vertical Y axis. We do one more L if block if last key is press
arrow down like this. We set speed x to zero and
speed y to plus max speed. Also we can move left, right, up and down. I want to make sure that the
player can't leave canvas. I need to set some
horizontal boundaries. If players horizontal x
position is less than zero, set it back to zero, that's the left boundary. Else if players horizontal
position is more than this game with
width of the game area, this game is coming from here. From there we are navigating
to this property. On line 64, the player's
current position is more than the width
of the game minus the width of the player
which we defined on line 22. Meaning that the right edge of player rectangle is touching
the right edge of canvas. Make sure that player can't move further to
the right anymore. Like this, I will increase player speed to ten
pixels per animation frame. You can now move left and right to test the horizontal
boundaries. Vertical boundaries
will be slightly different because
in our game world, the ground ends here. I don't want player to walk over this area unless it's
a flying creature. I guess this owl bear is
definitely too heavy to fly. I wonder if there are any
rare flying owl bear species, We might look for them
later in the series. We have this area on top where I don't want the player
to be able to walk. I create a class property
called top margin. Initially, I set
it to 200 pixels. Let's put it here. Up here, we will define
vertical boundaries. Say if this dot y
is less than zero, y is equal to zero. This will create
a boundary here. The player cannot go up past
the top edge of canvas. I actually want
the boundary to be lower because all of
this environment art, I say zero plus this game
doop margin like this. I can delete zero plus here. Of course now the top
boundary is here, 200 pixels from the top edge. We can walk around, but we
can't walk up here anymore. Perfect, it will make
more visual sense when the player is an
actual animated character, not just a black square, we are getting there fast. Bottom boundary is simple
if players vertical Y position is more than height of the game minus
height of the player. Meaning that the bottom of player rectangle is touching
the bottom edge of canavas. Don't allow it to go any
further down like this. Let's test it. Yes, we
have all four boundaries. Perfect. Let's turn this
simple black rectangle into animated four directional
character spreadsheet.
43. 4 directional sprite sheets: I start by adding spreadsheet image into
index HTML like this. We do it this way because all our Javascript is
inside load event listener. If I place my spreadsheet here, Javascript will wait for
these images to be fully loaded and available before
it starts animating them. You can download all
project art assets in the project resource
section below. I gave it an idea of, let's bring it outside
the wrapper like this. You can see we have eight animation rows in
the spreadsheet, Four directions,
and each direction has idle and walking loop. We will look into that
further in a minute, will hide the image
itself with CSS. We want to draw it with
the Java script on canvas. You can see that all the code is inside load event listener. All this code inside
will run only when Owl bear and all
other images have been fully loaded down here
inside constructor on Alber class which is our
player control character. We will add a
reference pointing to that spreadsheet image
as a class property. I call it this image, and I set it equal to a
variable called al bear. That variable was
not declared by us, but all browsers automatically
create references, autogenerated
global variables to all elements with an ID. This element has an ID. So we know that our browser automatically generated a
global variable for it. Normally, we would
use get element by ID here on line 29,
but we don't have to. The only problem is that
this is a global variable. So that we are pulling
global variables into our classes right now, that variable is in constant danger of being
accidentally overwritten. Let's just test if it works. Anyway, I take context referring to CTX variable
from line three. I call built in Canvas
draw image method. Draw image expects at
least three arguments. I pass it this auto generated
global variable that was created from elements ID as
the image we want to draw. And I pass it this dot x from
line 24 and this dot y from line 25 to tell Java script where on canvas
we want to draw the image. Now we are drawing the
entire spread sheet at these x and y coordinates. We can move around
using arrow keys. Because of the logic
we wrote earlier, it's safer to use get
element by ID here. So let's do it. I use
this dot image here. Okay, that still works. Nice to animate
any spread sheet, we need to know dimensions of a single animation frame
within that spread sheet. I will put these into
separate variables. I call the first
one sprite width, and I set it to 200 pixels. Sprite height will
also be 200 pixels. If you are using a
different sprite sheet, you can calculate sprite
width by taking the width of the entire sprite sheet and divided by the
number of columns. Sprite height is the height of the entire sprite sheet image divided by the number of rows. In this case, width and
height properties will be the same as sprite
width and sprite height. But keeping them
separate might be a good practice because
we can then use scaling or randomize
the sizes here without affecting these values that
always need to stay the same. To correctly crop out
individual frames from the sprite sheet draw
image has three versions. The first version
expects three arguments, we just did that
here on line 35. The second version
takes five arguments, optional fourth and
fifth arguments defined width and height of the image and the image will be stretched or squeezed to
fit these dimensions. Now I'm squeezing the
entire massive spreadsheet into an area of 200
times 200 pixels. The third version of draw
image method expects nine arguments to give us the most control over the
image we want to draw. That's the version we need to use to animate our spreadsheet. These nine arguments are
the image we want to draw. Source x, source
Y, source width, and source height, the area we want to crop out
from the source image. The last four arguments
we call destination X, destination Y, destination
width, and destination height. This will define where
on canvas we want to draw that cropped out
piece of image onto. Let's say I just want to crop out the top
left corner frame. I'll be cropping from
coordinate 00 to sprite width. And sprite height
values we set to be exactly the same size as a single frame in
our spreadsheet, in this case 200
times 200 pixels. Nice, we are using
draw image method to crop out a single frame. I can multiply
sprite width here by a number and sprite
height here as well. Now I have a way to jump around the sprite sheet from
frame to frame 00. Is this 1100? Is this
1200? Is this frame? This number navigates
horizontally. This other number will determine which row in the sprite
sheet we are on. You can see we can jump to a different animation row every time I change this number. I will put these values into Class Properties Frame X will handle horizontal
sprite navigation. Frame Y will be for
vertical sprite navigation. Again, when Frame X
and Frame Y are 00, we are showing the
top left frame. I can change these values to
jump around the spreadsheet. Now that we understand how
these arguments passed to draw image method crop
out individual frames, we can actually animate
our spread sheet and really bring this mighty
owl bear to life. Let's start by just animating a single row for each
animation frame. If frame X is less
than max frame, in this case, max frame will be the number of
frames per row, 30. If frame X is less than 30, keep increasing
frame X by one L, meaning it is equal
or more than 30, Reset frame X back to zero
so that it can cycle again. Good, we are animating this row, because here we set
frame Y to three. If I set it to zero, we are animating this row. Is I change this number, we are animating different rows. We want to swap these animation rows depending on two things. Which direction is
the player walk in and is the player walking
or just standing? If we had a complex character with multiple attack
moves and so on, I would suggest to do
a state design pattern here like I did in the
endless runner class. In this case, the movement
and animation logic is simple enough to be handled here inside the update method. We can always expand it to a state design pattern later if we decide to add
more player moves. Let me show you, here we are setting player
speed based on which of four arrow keys were pressed left, right, up or down. Let's copy this code block. Be very careful
about brackets here, it's easy to miss one
and break your code. If you remember up
here we are keeping track of keys that were
pressed and released. Pressed keys start
with capital P, Released keys start
with capital R. How do we do this? I
think this should work. When we press left arrow key, we want to animate this row. This dot frame y is equal to three walking left
animation row. When we release arrow
left like this, we set frame y22. We want just standing idle
to the left. Let's test it. I need to set speed to
00 when we release here. That's better when we
press arrow, right? I want to animate
walking, right, This row, this dot
frame is five. I copy this LF, careful
about brackets. We change to R here when we
release arrow, right key. We set speed to 00. And we set frame
Y24, Idle, right. Animation. Perfect. We have a character that can
walk left and right, and the logic we just wrote correctly navigates
around the spreadsheet. We are walking and
we are standing. Player is animated
and it is correctly reacting to keyboard
inputs. Nice work. Let's also do it for
vertical, up and down motion. When we press up arrow key, we want the player
to walk away from the camera frame y seven. I copy this code block. When we release up arrow, we animate frame y six, and we set speed to 00. We can walk left, right up here. When we press arrow down, we want to animate frame Y one. I copy this code block. When we release arrow down, we animate frame Y zero
and we set speed to zero. We are walking and animating
in four directions. We are correctly swapping
between standing and walking animation for
each of these directions. Amazing work to create
an idea of depth to make it feel
like the player is walking away and
towards the camera. When walking up and down, I will make it move a
bit slower on that axis. Look how much
difference it makes. It feels like there
is more depth, like the background is further from the camera than
the foreground. We walk at a normal
speed, left, right? But up and down we
walk a bit slower. Let's change max speed to five. This is a big, heavy creature. It should move slower. I comment
out the black rectangle, we don't need it right now. I really like this. We have a base for so many
different games. All you need now is
your creative ideas. We can do so many different
things with this. As you learn more about Java Script Canvas
and game development, it will become easier and
easier to actually turn those ideas into a
working code base. Let me show you more. I hope you are getting
some value today. Let me know in the comments
if you learn something new because we are using only the last key that was pressed or released to
control the player character. There is a common problem that when we walk
in one direction, let's say I'm walking right, I press left arrow key. Player turns left and only then I release right arrow key. The last key is release right, Which will make the player
stop briefly before continuous holding
of the left key makes it walk left again. You can try and you
find that we will have this problem in any
direction, walk somewhere. And when you press a
different directional key before releasing
the previous one, it will make the player
stop for a bit before it continues moving in
that new direction. To fix this is easy. When we release arrow left. We want this code only to run if players speed is less than zero. Only stop the player and
make it animate idle to the left if the current players
speed is less than zero, if the player is currently
moving to the left. That way we can enter idle left only from
walking left state. When we release right arrow key, we only want to stop movement speed and
animate idle, right? If player is currently
moving right, if its speed x is
more than zero. I also have to remove
this L statement here. And now we fix the issue
for left and right motion. Let's do the same thing
for up and down motion. Only run this code when
we release up arrow and at the same time
players movement speed y is less than zero when we
release arrow down. Only run this code if at the same time speed
Y is more than zero. Only when the player is walking
down towards the camera. Now we are moving around and the glitch is
gone. Well done.
44. How to control FPS: If I set max speed to
two player moves slowly, but the sprite
animation serves frames so fast that the player
is moon walking. The feet animate in
a way that doesn't match movement speed
and it looks very bad. One way to solve
this is to manually control how fast the
sprite sheet animates. Right now, we are just serving one new animation
frame per loop. Every time request
animation frame is called. Let's create a functionality
where we can manually set FPS frames per second for the animation speed
of our sprite sheet. It can be done
globally and we can set FPS for the entire game. Or as I will show you now, you can set FPS individually
for the player object. Maybe we will have some enemies later that will have
different sprite sheets. And we might want
those sprite sheets to be animated at
different speed. By default request
animation frame will adjust itself
to screen refresh. Rate. Our game will
run faster for someone with high
refresh rate screens, like those new gaming screens, we can control
that if we want to request animation frame has
a special feature where it generates a time stamp every time it calls animate function and it passes that time stamp as an argument to that
animate function it calls. Let's use it to count
milliseconds between frames so that we can set our own
FBS frames per second. I create a variable
I call last time, it will sit outside
animation loop like this. Initially I set it to zero. It will hold the value of timestamp from the
previous animation loop. So that we can compare
current and old time stamps and determine how many
milliseconds passed in between. The value is called delta time. Delta time is the number of milliseconds that passed between the time stamp from
this animation loop and the time stamp from the
previous animation loop. To calculate delta time, we know we need to
compare those timestamps. Timestamp from the
previous loop will be held in last time variable here. Initially we set it to zero before we start
generating timestamps. As I said, request
animation frame passes autogenerated
timestamp argument to the function it calls. If you are a beginner,
imagine that it's being generated and
passed here like this. At the point when
request animation frame triggers that function, we assign that autogenerated
timestamp a variable name. Here, I will just call it timestamp spelled with a capital S. Let's comment this out
and consoloc timestamp. Just so that I can see what
format we are getting, You can see that
it's milliseconds. The first value is literally
the number of seconds since the loop started, 45678. This is the time stamp I delete. The consolo delta time
is the time stamp from this animation loop minus
last time from line 113, which will always
hold a value for time stamp from the
previous animation loop. Once we calculated delta time, we set last time to time stamp. So that this new time stamp
from this loop can be used as old timestamp value in the
next following animation loop. Now we have delta time. It tells us how many
milliseconds it takes for our computer to serve
one animation frame. My screen refreshes at
around 60 frames per second. My delta time is around
16.6 milliseconds. This is perfect. Are you getting the same delta time or are you getting a completely
different value? Let me know in a comment. It will depend on
the refresh rate of your screen and partially also on the power
of your machine. Weaker machines will
have higher delta time. You can see that here the first delta time values
are none, not a number. Because we first run animate
here in the first loop, this time stamp is undefined. There was no previous request animation frame call
to auto generated. For us to fix this, I need to make sure
I pass it that first time stamp here
just to avoid non, not a number value
in the initial loop. Otherwise it might
give us problems later depending on how you want to
use your delta time values. Initial time stamp will be zero. You can see the values are
all over the place at first, but then they quickly
settle at around 16.6 as our animation
loop runs over and over. This is ideal. I
delete the consol, I pass this delta time value to render method on game class. I make sure that value
is expected here and I can pass it along
anywhere around our game. Now wherever we need it. I pass it to update method on Alber class because
we want to use delta time to control FPS frames per second
of sprite animation. There I'm passing
delta time to update method and I need to
make sure it's expected. Here on line 44 we are
inside Alber class now and we can use
delta time value here for so many
different things. To control FPS of
sprite animation, I will create three
helper variables, class properties,
FPS will be 30. Let's try to animate 30 frames per second and see
what that looks like. Frame interval will be 1,000
milliseconds divided by 30. This will determine how many
milliseconds should pass before we swap to the next
frame in the sprite sheet. Frame timer will be the counter. It will be accumulating
delta time over and over as our
animation loop runs. When it reaches the value
and frame interval, when enough milliseconds
have accumulated, we trigger certain
periodic action. In this case, we will serve
the next animation frame. Same technique can also be used to add a new
enemy to the game or some other event
that you want to be happening in a
specific interval. This is an important
technique to understand. Let's write the logic and explain it when we see the code. It's just a few short lines. I only want to run
this code that swaps between sprite frames when
enough milliseconds passed. If frame timer is more
than frame interval, only then animate
the sprite sheet. Else keep increasing frame
timer by delta time. This code will animate
spreadsheet from here. When this triggers,
we also reset frame timer back to zero
so that it can count again towards the
next periodic event inside animation loop, we are calculating delta time, the difference between
time stamp from the current and the previous
animation loop. Delta time is the amount
of milliseconds it takes our computer to serve
one animation frame. Frame timer starts
at zero and it adds more and more delta
time as animation loop runs until it is more
than frame interval. Frame interval is the
breakpoint that when reached, it triggers our periodic event. When we reach it, we handle sprite animation and we
reset frame timer back to zero so that it can count again towards the next periodically
served animation frame. I know this was a lot, but this technique
is very important. It will become
easy to understand when you use it more often. Trust me now, the character
is animating slower, just the sprite swapping and not the actual movement
speed over canvas. You can really see
that when I set FPS to two frames per second, it still slides over
canvas smoothly at 60 FPS. But we are throttling how fast the next frames in sprite
sheets are served. Hope this makes the
difference clear. I can try other values as FPS. I have to experiment
a bit here and find the right value
that will make the lex move at the speed that feels natural as the player moves
over the grass terrain. 50 is still some sliding. 60, we are actually
not accounting for leftover values of delta time every time we trigger
our periodic event. Even though I said FPS 60, the real FPS would
be less than that. For the purposes of a project like this, it's perfectly fine. We have a value we can increase and decrease to control
sprite animation. It serves its purpose well. I can simplify this
line of code and use ternary operator to write all
of this on a single line. Let me show you it's easy. Ternary operator is the only Java Script operator
with three operands. We will use it as a
simple one line if L statement we have
condition to evaluate. We check if frame X is
less than max frame. If it is question mark, increase frame X by one, L reset frame X back to zero. Now this single
line of code does the same thing as this
multi line L if statement. You can use either syntax.
I just wanted to show you.
45. Randomly positioned game objects: Let's create some
objects and spread them randomly around
the game world. I create a class, I call object. As usual, it will expect a reference to the main
game object from line 111 as an argument that we have access to
all important properties. From there we convert that
reference to a class property. Same as we did before. Draw method, we'll expect
context as an argument. Inside we call built
in draw image method. I want to show you how to extend classes and take
advantage of inheritance, which is one of the
main principles of object oriented programming. How we can use it to
reduce code repetition. I create a class
called Busch that extends object class
from line 102. It will have a
constructor as well. I create another class. I call plant one
more, I call grass. We have the main object class. We have three classes, Bush, plant and Grass that
extend that object class. Object is so called
parent class, also called a superclass. Bush, Plant and Grass
are child classes, also called sub classes. If I use a property or a method inside one of these
child classes, that property is not
defined on that subclass. Java script will go look
for it automatically on its parent class, on object. That way, I can only define
this draw method at once. And all child
classes that extend this parent class will
automatically have access to it, they inherit that method. We can do something similar
with properties inside constructor using super keyword I will show you in a minute. Object class will hold
all properties and methods shared between
all child classes. Busch, Plant, and Grass
will contain properties that are specific only for
that particular subclass. For example, each subclass
will have a different image, but all will pass that
different image to the same draw method
from 907. Let's do it. Before I can use
this keyword inside child class constructor need to trigger constructor
on its parent class. When I say super like this here, it means I want to run a constructor of
this class's parent. Parent class is also
called a superclass. On line hundred three, I can see that object
class constructor expects game as an argument. I pass this reference
along at the same time I convert it to a class
property on Bush class as well. Like this inside the shared draw image
method I pass it did image didot x and dis.yi can also give it dist
width and distot height. If I want to scale the
images as you can see, none of these are
present on this class. I want each subclass to
have a different image, and we need to know size of that image to calculate
its randomized position. I will have to put
all these properties on each subclass separately. Let's start with
Bush. This dot image will be document dot
get element by ID. Here in index HTML, I need to create that
image element as usual, you can download the images in the project resources
section below. I put it here, ID will be Bush, another one with an ID
of plant like this. The last one will be
grass in style CSS. I use their IDs to
hide these images. We want to draw them on canvas, not as actual image
elements on the web page. The ID is bush. I duplicate this code ID. Here will be planned
and here grass. I'm trying to keep the
naming simple and clean. I don't really want
to scale the images, but if we want to leave the
option for scaling open, maybe it's better if I define image width and image hide
properties separately. Even though at this point they
will have the same value. I already size the images to be the same size as we will
draw them on Canvas, which is a good practice. Bush image is 216 100 pixels. I could also define
these properties on one line like this.
It's up to you. Horizontal position will be a random value between zero and the width of the game area minus the width of
the image itself. This will probably
need some adjustments. Let's see, Vertical y position
will not start from zero, it will start from top margin. And from there a random value between zero and
game area height minus the height of the
image minus top margin. I think when we
start drawing them, we will see if I need to
make any adjustments here. Let's test it. On
the main game class, I create a property
called Number of Plants. Let's create ten randomly
positioned plant objects. I create initialized method
that will run only once on the first page load and it will randomly position
the objects inside. I create a four loop. It will run ten times. Every time it runs,
we create one plant. I create an empty
array here that will hold all the plant objects. Every time the four loop runs, I take this plants array, I, new Bush object
inside on line 111, I can see that Bush
class constructor expects game as an argument. I pass it this keyword because we are inside that
game class here. After I create an instance
of game class 160, I can call this init method. It will run and it
will fill plants array with ten randomized
instances of Bush class. If I want to display
them on canvas, I need to call
their draw method. I will do it from inside render. I take plants and for each
plant object inside the array, I call their associated
draw method from line 106. That method expects context. I pass it along this
context reference. Nice, I'm drawing ten
randomly positioned bushes. I go up here, I will copy all these properties that
are specific for Bush class, and I copy them here
inside plant class. I check the image width is
212 pixels, height is 118. I copy it again, and
grass width will be 103 and height will
be 183 pixels. Nice, We defined a
parent object class where we can place all shared
properties and methods, and three subclasses called Bush Plant and Grass that
extend the parent class. To quickly check if
these classes work, I can just swap the class
name here on line 168. Grass is working,
Plant is working. How do we randomize
them and control which portion of these ten randomly generated
plants will be bush, grass or plant objects. One simple way to do it
is to create a variable. Here I call for
example, randomize. It will be equal
to mathodtrandom. We know that methot
random code like this on its own
return a random value 0-1 Let's say I want roughly 30% of these objects to be instances of plant class. I say if random Ize
is less than 0.3 L, if randomize is less than 0.6 when math at
random rolls between 0.3 and 0.6 we create an
instance of Bush class L, meaning when Maat
random rolls between 0.6 and one, we create grass. It doesn't work
because this has to be inside the four
loop because we want to roll each time we
create a new object, not just roll once
for all ten objects.
46. Layering and draw order in 2D games: Perfect. We are extending
a class to create three different randomly
positioned game objects. Nice work. As you can see, the way these objects are
being positioned and the way the game is layering doesn't really
make much visual sense. Owl bear is drawn first
on line 162 and then all plants are drawn after plants will always
be on top of a bear. If I swap this around, then plants are always
behind the bear. Since we are drawing everything on the same canvas element, whatever gets drawn
first is behind, and whatever was drawn after
is on top in front of it. I want to draw objects in a specific order based on
their vertical positions. More specifically, based on the vertical position
of their bottom edge. Because each object has a different height
to make sure they are aligned in a way that makes
logical and visual sense. And so that our character
can walk around them and be in front
and behind the objects. When appropriate,
we are trying to simulate a virtual world where plants grow
from the ground. And where this owl
bear is walking on solid ground at all times, even when it walks up and down. The layering has to
match that visually. It's actually very
easy to achieve this. I just need to make sure
that all these objects, so al bear and all
plant objects, are in the same array. So that I can sort them based
on their vertical position. I create another array
called this game objects. Every time render method runs, I put Alber object inside. I spread all objects from the tot plants into this
array spread operator. Used like this allows us to expand one array
into another one. In this case, it doesn't make
much sense to do this every time render method ticks
for every animation frame. Since we have just Alber
and ten static plants, we could have done this
inside init method. It would just make
sense to do this periodically to construct
this array over and over. If we have more objects that are being added and
removed over and over, for example enemies or
power ups that come and go. Now we have alber, all ten plants on the same
level inside the same array. I can take this array for each object in
game object array. I call their draw method. When we do this and we combine different object types
into a single array, we need to make sure
all these objects actually have draw methods
defined in their classes. Be careful about brackets
Here I can delete this code. I will have to call update
method and pass it delta time. Otherwise, I will bear,
will not move or animate. When I call update like this, we draw and update all bear. When I try to draw and update the first
out of ten plants, it gets drawn, but there's no update method to run,
so we get an error. I need to add update method on object class for
this code to run. We are not really
updating the objects, but here we can have
some animations. For example, bushes and grass can move when the
player touches them, that code would be handled
inside this update method. I will leave it blank for now. Now we have the same
thing we had before, but all elements that need to
be layered dynamically are being drawn and updated from
inside game object array. All bear is first in the array. That's why it gets
drawn before plants. Plants are drawn on top of it. If I swap them around, plants are behind and aulber
is drawn last on top. Because that's the
order in which for each method cycles through
game object array. To call their draw methods, we want to sort Alber and all ten plants in this array based on their
vertical y coordinate, but not based on their top
but on their bottom boundary. Because we have objects
with different heights, we can do this using built
in array sort method. The sort method sorts the
elements of an array in place and returns the
reference to the same array. Now sorted, it takes
an optional argument. We call a compare function, where we can define logic that
describes the sort order. If we don't define
sort condition, by default all array elements
are just converted to strings and sorted based on
characters, Unicode values. A is the first element
for comparison, B represents the second
element For comparison, sort method will
automatically compare every element in the array
against every other element. To sort them based
on our requirements. We want the sort
condition to compare the bottom boundary of each
element drawn on canvas. So it's vertical y position
plus its height. Like this. I'm saying sort all elements in ascending order based on their vertical position
plus their height. Once the array gets sorted, we draw plants and owl bear
in that order on canvas. And now everything
makes visual sense. We can walk around obstacles and we can see that the
player character and plants are now always correctly
drawn in front or behind. You can expand this game
in many different ways, create mushrooms that grow in playable area to make the owl
bear grow as it eats them. Or you can make it avoid dangerous enemies or walk
around spiky obstacles. I suggest you create a copy
of this code base and you can use it as a base for
different gameplay experiments.