Frank Dvorak teaches this JavaScript game development crash course. You will learn how to create a 2D game using JavaScript, HTML, CSS, and object-oriented programming. And Frank provides a lot of free sprites and game art assets so you can follow along. Frank is a popular teacher who creates super creative courses. What a strange planet. It’s completely covered in a thick layer of smoke. Initial scans indicate there are no organic life forms present, but I register a lot of movement. Coders, hope you like making games. Let’s make a 2D steampunk game completely from scratch with HTML, CSS, and JavaScript. No frameworks, and no libraries. I will give you a lot of premium art assets for characters, environments, and props, and you can do whatever you want with them. You can use them in your projects, and you can modify them. I will show you how to create gameplay variety by giving different enemy types different mechanics that the player can interact with or has to deal with. For example, if you destroy a hive well, a bunch of small drones will come after you. Let me show you how to give your games beautiful visuals and how to apply many special features I came up with specifically for this class. Let’s go. These creatures have very similar physiology to the Earth’s seahorses. Body shape like this can cut easily through the thick atmosphere of this planet. They can move very fast. Seahorse hive is being attacked by something. This one has been damaged. I wonder if we can hack this creature’s central computer to control it for a while. It is surprisingly easy to override its circuits. Seems like these machines are not used to our technology. I’m getting a lot of new data. Okay, explorers. We are getting a stream of data about multiple different species. We will convert their source code into HTML, CSS, and JavaScript. Let’s analyze it line by line and see how it works. We have a lot to learn. I will answer all your questions as we discover more about their movement, special abilities, and how they interact with their planet’s environment. Let’s look under the hood of this alien ecosystem. The atmosphere is thick enough to allow heavy siliconbased life forms to float, but smoke is blocking most of the sunlight. Many creatures developed artificial lights and glowing appendages to see through the heavy clouds. Seahorse Sentinel has a basic attack that’s powerful against weak enemies. But if it absorbs energy from one of the overcharged creatures, it gets additional firepower for a short period of time, and it instantly replenishes its ammo. Ammo also automatically recharges over time. It seems we just need to help it to get through these aggressive swarms in time so it can join its hive. If this is your first time discovering JavaScript, you should try some beginner classes first. Today’s coding will be beginner friendly, but I expect some basic knowledge of JavaScript. when you get more comfortable with objects, arrays and for loops, come join me in this class.
So after a simple basic setup inside index html and style CSS, we move into script.js. All JavaScript logic for this game will be written here inside this file. I create an event listener for load event. Notice that we call it on built-in JavaScript window object which represents the browser’s window. load event fires when the whole page has been loaded, including all dependent resources such as stylesheets and images. We need to do this because we will use a lot of graphics in this project and they can take some time to load. If you’re trying to draw an image with JavaScript, remember that you always have to wait for that image to be fully loaded before you run the JavaScript code that depends on that image. I usually like to do HTML canvas setup like this. Custom variable I call, for example, canvas will store the reference to canvas element. I point it towards that element using get element by ID. I give it an ID of canvas one. Now I need so-called drawing context. It is a built-in object that contains all methods and properties that allow us to draw and animate colors, shapes, and other graphics on HTML canvas. To create it, we need to call a special built-in get context method on a variable that holds a reference to canvas element. So I say canvas dot get context. And then I need to pass it identifier. Sometimes we call this argument a context type. We can pass it 2D or WebGL here. WebGL represents three-dimensional rendering context. Today we will work with 2D. Now I access width property on canvas from line 3 and I set it to 500 pixels. Height will be 500 as well. As you can see 500 pixels is wider than my current browser size. For that reason, I also like to declare max width property and I set it to 100%. That way, the element will scale down until it’s fully visible in the available browser area. I also declare max height in case the restricted factor is the height of browser window. Doing this will make sure we can always see the entire canvas element even when the browser window is too small. I will zoom out a bit to adjust my workplace. For this tutorial, I set canvas width to a larger value and keep my browser window small. Canvas element will scale down to make sure it’s fully visible.
To build our game today, we will use object-oriented programming, which means we will wrap variables and functions in objects. JavaScript is a prototype-based object-oriented language, which means it doesn’t have classes. It has prototypes. But we can use modern JavaScript syntax that introduced classes as so-called syntactical sugar simplified clean syntax that mimics classes from other programming languages. Still under the hood, it’s just working with prototype based inheritance. One of the four main principles of object-oriented programming is encapsulation. Encapsulation is the bundling of data and the methods that act on that data in objects. Access to that data can be restricted from outside the bundle. In this project, I will encapsulate our data into multiple classes. Each class will have its special purpose. The challenge for beginners will be to keep track of how these classes communicate with each other. I will explain everything step by step as we write the code to help you understand. We need to declare our classes in a specific order. Class declarations are hoisted in JavaScript, but they stay uninitialized when hoisted. That means while JavaScript will be able to find the reference for a class name we create, it cannot use the class before it is defined in the code. This means that I need to declare input handler class first because I will need to use it inside player class and so on. Input handle class will keep track of specified user inputs. For example, arrow keys. Projectile class will handle player lasers. Particle class will deal with falling screws, [ __ ] and bolts that come from damaged enemies. Player class will control the main character. It will animate player sprite sheet and so on. Enemy class will be the main blueprint handling many different enemy types. Layer class will handle individual background layers in our parallax seamlessly scrolling multi-layered background. And background class will pull all layer objects together to animate the entire game world. UI class will draw score, timer, and other information that needs to be displayed for the user. and the main game class will come last. Inside this game class, all logic will come together. This will be the brain of our project. I hope this gives you a map of what we need to do to complete our game. Let’s write JavaScript logic inside individual classes and connect them together. I will explain everything step by step as we go along.
Constructor is a special method on JavaScript class. When I call this JavaScript class later using the new keyword constructor will create one new blank JavaScript object and it will assign it properties and values based on the blueprint inside. Player will need access to game width and height and other properties that will be stored on the main game object. So I will pass it the main game object as an argument like this. I convert that game object into class property on player class like this. I’m saying take this game object that was passed as an argument to the class constructor and turn it into class property called this.game. Keep in mind that by doing this I’m not creating a copy of the main game object and placing it on player. Objects in JavaScript are so-called reference data types which means that unlike primitive data types, objects are dynamic in nature. I am just creating a reference that is pointing to the place in memory that stores the main game object. So when the values and properties on the main game object get updated, those changes will be immediately visible from this.ame reference inside this player class. I made the size of player spreadsheet the same size as we will draw them in game. It’s a good practice. I know that the width of a single frame in my spreadsheet is 120 pixels and height is 190. If you are a beginner, it’s best if you use the same sprites and images I’m using. It will make the debugging easier in case you run into problems. You can always check my code to find the problem. All art assets I’m using are linked below. You can download them from the project section. Start in horizontal X coordinate on the player will be 20 pixels and vertical Y position is 100. Player will also need an update method to move it around and draw method that will draw graphics representing the player. For now, let’s start by increasing vertical Y position on the player by speed Y. I need to declare that property here. It will be zero at first, so no vertical movement. Draw method will take context as an argument. This will specify which canvas element we want to draw on. In case our game has multiple layers with multiple canvas elements, it’s also a good practice to use context argument like this rather than pulling ctx variable from the outside directly into our objects. At first, I will just draw a simple black rectangle representing the player. So, built-in canvas fill rectangle method and I want to draw the rectangle at player’s current X and Y position and it will have players width and height. So, we have our player object. How do we include it inside game logic and draw it on canvas? As I said, the main game class from line 45 will be the brain of our entire project. All the logic will somehow go through it. Again, I will give it a constructor. Constructor will need width and height of canvas as arguments and inside we convert them into class properties. This will make sure that the width and height of the game matches the size of canvas element. Constructor on JavaScript class is a special type of method that will run once when we instantiate the class using the new keyword. It will create one new blank object and it will give it values and properties as defined inside the blueprint. Here I can take an advantage of the fact that all the code inside the constructor gets automatically executed like this and any code I put inside the constructor on game class will automatically run when I instantiate my game in a minute. So when I instantiate game class I want it to automatically create an instance of player class and I want that instance to become class property on game class called this dot player. So this player is equal to new player like this. The new keyword is a special command in JavaScript. It will look for class with this name in this case player and it will run its constructor method to create one instance of it based on the blueprint inside. So by calling new player on line 49, JavaScript will find player class up here on line 17 and it will run all the code inside its constructor. I can see that constructor method on player class needs game as an argument. Down here we are inside that game class. So I pass it this keyword. This keyword used inside this class refers to this entire game object. Our game will also need update and draw methods. Update method will take this player property from line 49 which holds an instance of player object and it will call its update method we defined on line 26. Inside draw method we will render the player on canvas by calling draw method from line 29. I can see that it expects context as an argument. So I pass it context like this and that value will be passed along here. Now I create an instance of this new game class we just wrote and I save it in a variable I call for example game. It’s equal to new game like this. Again new keyword will look for class with that name. It will find it on line 45 and it will run its constructor method to create one new blank JavaScript object and assign it values and properties based on the blueprint. Here on line 46, I can see that game class constructor expects width and height as arguments. So I pass it canvas width from line five and canvas height from line six.
So the new keyword triggers a class constructor. As the constructor gets executed, we hit this line which will automatically create an instance of player class from line 17. So this is how by creating the main game object, we also automatically create player object and now it sits as a property on the game class on line 49. Perfect.
We will also need animation loop that will run update and draw methods over and over 60 times per second. updating positions and redrawing our game. I create a custom function I call for example animate. Inside I take the instance of game class from line 59 and I call its associated update method we declared on line 51. I will also call draw method from line 54 like this. I can see it expects context as an argument to specify which canvas element we are drawing on. So I pass it ctx from line 4. This means that this ctx variable will be passed here and it will get passed along to draw method on player object here. Now the player knows where we want to draw it. After we called update and draw, we want to trigger the next animation frame. So I call built-in request animation frame. Request animation frame method sits on the window object. But we can also call it just like this. It tells the browser that we wish to perform an animation and it requests that the browser calls a specified function to update an animation before the next repaint. So we need to pass it one argument and it will be the method we want to call before the repaint before browser window gets updated and redrawn on screen. I pass it animate the name of its parent function to create an endless animation loop. Request animation frame has two special features. It adjusts the user’s screen refresh rate. For most of us, it will be 60 frames per second. It also autogenerates a timestamp argument and passes that as an argument to its callback function. A little bit later, I will show you how to use this feature to create periodic events in our project. Events that repeat in a certain interval. I get an error because I misspelled the word constructor. Here, I’m using VS Code editor. It highlights syntax in different colors which makes it easier to notice typos like this. We have our main game logic and we are drawing this black rectangle representing our player. It looks static but it’s actually animating. We can test that by giving player some value here in speedy property. The reason the rectangle gets longer now is because we can see old paint. We can see old black rectangles that were drawn in the previous animation loops. I can fix that by deleting all canvas drawings between each animation frame. Between every animation loop, I call built-in clear rectangle method. And I want to clear canvas from coordinates 000 to canvas with canvas height like this. Now you can see that because player speed Y property is set to + one, the player moves by one pixel per frame in positive direction on vertical Y-axis. If speed Y is minus one, player will move upwards. If speed Y is zero, the player will not move at all. It’s all because for every animation frame, we are adding speed Y property to player’s vertical Y-coordinate here on line 27. We know that everything is working well. It’s good to test your code as you go step by step to discover potential problems. I want the player to be controlled by keyboard. When I press up arrow, we set speed Y to minus one and player will move up. When I press down arrow, we set speed Y to plus one and player will move down.
We will handle all player input up here inside input handler class. Constructor will take a game as an argument. Same as we did with the player. I convert it into this.ame class property. As we said before, when we create an instance of a class, all the code inside class constructor gets executed. We can take advantage of that. And I can even apply event listeners from here. I simply just apply event listener like this. We will listen for key down event. Callback function on event listener has a special autogenerated argument that contains all kinds of additional details about the event that just happened. If I want more details about the key down event, I just need to pick a variable name and I pass it as an argument to the call back. Usually we use letter E or the word event, but you can also type ABC. Whatever you put here will become a custom variable name containing a special object with additional information about the key down event that just happened. I will save it in a variable I call E and I will console log it. If you just console lock E, you will get the full event object with many properties. I’ve done this before. So I know I want a specific property called key. So I console lock E.Key. This will contain a string with the name of the key that was pressed. Let me show you. I need to create an instance of input handle class. So as we did with the player, I want my input class to be instantiated automatically as I create my main game class. So here inside the game class constructor, I create a property. I call for example this.input. I set it to new input handler and I know its constructor expects game as an argument. So same as we did with the player, I pass it this referring to this entire game class. So now as I create game object down here, it automatically creates player object on line 54 and input object on line 55. As we call the new keyword on line 55, JavaScript will jump to line 9 and it will run input handler class constructor which will among other things apply this key down event listener that’s console login the current key that is pressed. I open browser console. I click on canvas element so it starts registering events. And now when I press keys on my keyboard, you can see we are console login the key property
inside key down event listener I say if e.key is arrow up take keys array on the game object.
We will create that array in a second and push that key into the array.
I create that array here on line 59 inside game class constructor. Its job will be to keep track of all keys that are currently active that are currently being pressed down. I will console log this.game.keys from here to check if arrow up key is being correctly added. I get a console error that is saying that the variable name I’m calling keys from is undefined. By the time this callback function is executed, JavaScript forgot what this.game game stands for which object it points towards. We want JavaScript to remember that this code initially sits inside this object so that it can see and remember this.game property even when we call it later as we play our game. If you are a beginner, don’t worry about fully understanding this yet. All you need to know here is that if JavaScript forgets what this keyword stands for, we need to bind that code to the surrounding codes context. We can use built-in JavaScript bind method or even simpler, we can use ES6 arrow function syntax here. A special feature of arrow function is that this keyword inside arrow function always represents the object in which the arrow function is defined. Arrow function will never forget that it was originally defined inside constructor on input handle class and it will always see this reference for this.game from line 10. Therefore, it will be able to access this.game.keys keys and push keys inside. Now everything is working. When I press up arrow key, it gets added into this.game.keys. And if I press it multiple times, it keeps adding more and more. So I create another event listener.
This time for key up event. When we release the key, I want to remove it from the array. I do that by checking if the array contains that key. So built-in index of array method. The index of method returns the first index at which a given element can be found in the array or it returns minus one if the element is not present. So if this.game keys index of is more than minus one meaning if keys array contains the key that is currently being released in this key up event take this.game.keys array and remove that element from the array using splice method. The splice method changes the contents of an array by removing or replacing existing elements. Splice method needs at least two arguments. The first argument is the index at which we want to start changing the array. So it will be the index of that key we want to remove like this. And the second argument is delete count an integer indicating the number of elements in the array we want to remove from that starting index. So I call splice. I want to remove elements starting from this index and I want to remove only one element at that index. I put the same console lock inside key up event. You can see that it works well. When I press up arrow, it gets added. When I release it, it gets removed. Added removed. Nice. The problem is when I hold up arrow key down for too long. Key down event will start firing over and over but key up event doesn’t fire. So we are not removing those keys. We are adding multiple keys. we are adding the same key over and over increasing the size of the array. This can also be fixed easily. I only want to add that key into this keys array if it’s not already present in there. So additional condition here if this keys index of the key that was just pressed is minus one. Now when I press and hold the key gets added only once. When I release the button the key gets removed. Perfect. On key down event, I check if key that was pressed is arrow up. And at the same time, I check if arrow up is not yet in the array. Only then I push arrow up into this. Keys array. On key up event, I check if the key that we are releasing is present in the array. If it is, I remove it using splice method. We have the main logic now. And adding more keys is easy. Just be careful about brackets here. It’s easy to miss something and get an error. I wrap this part in another set of brackets and I check if key that is pressed is arrow up or arrow down like this.
Now keys array on the main object can contain nothing and can be empty or it can contain up arrow key or arrow down key and it can even contain multiple keys at the same time. In our case both arrow up and arrow down. So now keys property on the main game object is an array that always keeps track of presses and releases of up and down arrow keys. I store this array on the main game object on line 67. So the information of what keys are currently pressed is available all over our code base. It’s also available inside our player class because player has this.game property here on line 35 pointing towards the main game object. I can simply check if this.game.keys keys includes a specific key and I can change player behavior from here. I check if this.game.keys includes arrow up. Notice that before I use index of method to check if element can be found in the array. I can also alternatively use built-in JavaScript includes method to do the same thing. The includes method determines whether an array includes a certain value among its entries returning true or false as appropriate. If this line returns true, we want player to move up. So we set speed Y from line 40 to minus one. Else if keys array includes arrow down, we set speed Y to + one. I save changes and click canvas. And now I can move player up and down by pressing up and down arrow keys. The only problem is that player will never stop moving. We need to add one more small condition. So else meaning this.game.keys keys array doesn’t include arrow up or arrow down. We set speed Y to zero. Instead of hard coding minus one and plus one here, maybe player speed is dynamic and player can speed up during a power up. For that purpose, it’s better to save max speed in a variable like this. So, we have current player speed on line 40 and maximum player speed on line 41. Let’s try two pixels per frame. And I replace hard-coded values with variables on lines 44 and 45. If we press arrow up, speed Y is minus max speed. If we press arrow down, speed Y is plus max speed like this. Now we can change player movement speed if we need to by changing max speed value on line 41. And this is one of the techniques I like to use to handle keyboard inputs in my games. If you have any questions or any suggestions how to improve this code, let me know by leaving a comment.
player will be shooting lasers at enemies. We will create them by using a class I call projectile. Constructor will need three arguments. The main game object so that this class has access to game properties when it needs them and start in X and Y coordinates. Those need to be dynamic passed as arguments from here because starting coordinates of each projectile will depend on player’s current position. As usual, I convert the arguments passed to class constructor into class properties. Width will be 10 pixels.
Height will be 3 pixels.
Speed will be 3 pixels per frame. Update method will also increase horizontal xcoordinate from line 30 by speed from line 34. I will also need marked for deletion property which will be initially set to false. And if horizontal xcoordinate of this projectile object is more than width of the game meaning it has moved across the game area this object can be deleted. I will actually set it to 80% of the game area. uccessful projectiles will be coming from player here to somewhere around here because I don’t want enemies to be hit and destroyed off screen. Let’s say that the lasers have limited range. So if horizontal coordinate of the projectile is more than 80% of width of the main game area, set their marked for deletion property from line 35 to true. This will flag that projectile as ready to be deleted and removed from the game. We will remove them in a moment. Before we do that, we will also have a simple draw method that will take context as an argument and I call built-in fill rectangle method that will represent the projectile. I want to draw that rectangle at coordinates this do.x this doy and I pass it this dowidth this dot height from lines 32 and 33. We will use images and animations for everything soon. But first, let’s set fill style to yellow and draw yellow rectangles for our lasers.
I will also have to define fill style here on the player otherwise that yellow color will apply on it as well. I can set it to green or black like this. So we have our projectile class ready. How do we add them into our game? One way to do it is by creating a special custom method on player class. Player will have two different attack modes. So I will call the basic one shoot top. In this mode, the lasers will be coming out from the mouth of our mechanical seahorse. When shoot top method is triggered, I take this projectiles array I will create in a minute and I will push new projectile inside using the class we just defined on line 27. I create this projectiles array on player class. This will hold all currently active projectile objects. Up here I can see that class constructor expects game X and Y as arguments. So I pass it this.game game from line 51 and the current player coordinates. So this dox from line 54 and this doy from line 55. I want player to shoot when we press a key. So I go up to input handler class. And here I will make it very simple. I go inside key down event and I say else if key is spacebar. So just space like this. You can also use enter or any other key if you want. When we press spacebar, use this.ame reference from line 10 and navigate to its player property and from there call shoot top method we just defined. Since I’m holding all projectiles here on line 60 on player class, I will handle projectiles here inside update method. I take this projectiles array and for each element in that array, I call update method we defined on line 39.
Inside that method, I have this check. If the projectile moves across the screen, we set its marked for deletion property to true. I want to remove those elements from projectiles array. I will do it using JavaScript filter method like this. The filter method creates a new array with all elements that pass the test implemented by the provided function. So here I’m taking projectiles array. I call filter on it and the test is that I want all elements to have marked for deletion properties set to false. So exclamation mark here. This will filter out and remove all elements with marked for deletion properties set to true. And since we just said that filter creates a new array, we want to override the original array with the new one that was filtered here. With this line of code in place, every time we set marked for deletion property to true on any projectile object, it will get removed from projectiles array. I will check if it works by console login this. projectiles from inside shoot top method. I will also call for each on all projectiles from inside draw method and I trigger draw method from line 43 on each one. I remember that I need to pass it context. So I pass it along the value from here like this. I save changes and run my code. And I can see we have an error on line 45. I go up to line 45 and I forgot to call fill rectangle from context. So JavaScript doesn’t know what to do with it. I fix it like this. Now I click on canvas and I can move up and down using keyboard arrow keys and I can shoot using spacebar. In browser console I can see that projectile objects are being correctly added and removed from projectiles array. Perfect. Let’s remove these two console locks to clean up our code. Now the console is cleaner and it’s easier to see how projectile array adds and removes objects. Nice. Right now we can shoot without limits. I want the player to have limited ammo that slowly recharges over time. It will also get completely refilled when we collect a special power up fish. Down here I say if this.gamemo, game.mmo which we will create in a minute is more than zero only. Then create new projectile object.
I go down here to line 104 and I create this new ammo property. Initially we will start with 20 ammo for example.
Every time we create a new projectile we decrease that value by one. Now I can do it 20 times and then it stops. Even if I keep pressing spacebar, we run out of ammo. Here on line 80, I can change which coordinates the projectiles start from in relation to the current player position. We will match it even better when we apply player sprite sheet a bit later to make sure it comes out from the mouth of our steampunk seahorse. We will get back to this. I can offset it here or I can also do it inside projectile class itself.
When we use all 20 projectiles, we completely run out of ammo. I want it to slowly recharge over time. To do that, I want to run a periodic event in our codebase. And I want to be able to measure time in milliseconds and say, for example, every 500 milliseconds, every half second, automatically recharge one ammo. To do that is easy. If you know this technique with timestamps and delta time, let me quickly explain how it works. Here on line 116, I create a variable. I call for example last time. Its job will be to store a value of time stamp from the previous animation loop so that we can compare it against the value of time stamp from this animation loop. This difference will give us delta time, the difference in milliseconds between the time stamp from this loop and the time stamp from the previous loop. Like this. Where does this time stamp value come from? Request animation frame method has a special feature. It automatically passes a timestamp as an argument to the function it calls. In our case, animate. I can use it simply by giving it a variable name here. You can call it whatever you want as long as you keep referring to that value using the same variable name. I will call it timestamp spelled like this. And I will use it to calculate delta time here on line 119. After we used last time to calculate delta time for this animation loop, we reassign last time to the time stamp from the current loop so that it can be used to calculate delta time in the next loop. So request animation frame autogenerates a timestamp value which is a number in milliseconds since this loop started. We access it here in animate. Assign it a variable name for example time stamp like this. We calculate the delta time which is the difference in milliseconds between the time stamp from this animation loop and the time stamp from the previous animation loop. And then we set last time to time stamp from this loop so that it can be used to calculate delta time in the next loop. Don’t worry if this is a bit complicated to understand at first. It’s a very common technique and it will become more intuitive as you use it in multiple projects.
I can console log delta time to check if it’s working. My delta time is around 16.6 milliseconds. 1,000 milliseconds 1 second divided by 16.6 is around 60. So I can see that my game is animating at 60 frames per second. Let me know if you get the same value or something else. If you had an old computer, delta time value might be higher because it takes your computer more milliseconds to render animation frame. If you have high refresh gaming screen, delta time value could be lower for you. I wonder if we have any coders here who use that type of screen. Remember to remove your console locks when you don’t need them, especially this one that runs 60 times per second. It could affect the performance of our game if I just leave it here. So, we calculated delta time. We know how many milliseconds it takes for our computer to render one animation frame to run one animation loop. I will pass it to update method here. And we can use that value to run periodic events in our game or to measure game time. The advantage of this technique is that it doesn’t matter if you have strong supercomput or a very old slow one. Delta time measures actual milliseconds in real time. So periodic events will happen roughly at the same time on slow and fast machines regardless of their ability to animate our game slow or fast. To trigger periodic events in our game, I need two helper variables. one will be timer that will go between zero and some kind of predefined limit. Each time it reaches that limit, it will trigger some kind of event and it will reset back to zero to count again for the next loop. The second helper variable will be that limit that interval value that timer needs to reach. So I want to replenish some ammo every half second 500 milliseconds. I will also introduce some hard limit. I want the ammo to automatically replenish only up to this value not endlessly. So starting ammo will be 20. Maximum ammo will be 50. For example, inside update method on game class, I will use ammo timer and ammo interval helper variables and also delta time to trigger this periodic event that replenishes ammo every 500 milliseconds. I say if ammo timer from line 107 is more than ammo interval, so 500 milliseconds. Inside I will also check if ammo is less than max ammo like this
only. Then I will increase ammo by one. Then I reset ammo timer back to zero so that it can count again. Else keep increasing ammo timer by delta time.
I am calculating delta time on line 128 and I’m passing it to update method on game class on line 131. I make sure update method expects that value here on line 110 and that delta time value will get passed along here.
Our ammo is already recharging but it’s not easy to check and see. I can console log these values but it would be nice to have some visual representation of our available projectiles as part of our game screen. I have user interface class here on line 95. I will use it to draw messages and game statuses that player needs to be aware of. This class will be very simple. A constructor will take a game as an argument. So we convert it to a class property as usual. We will define font size and font family. We will need it later to draw some score and game timers.
I will also define color and I set it to white for example. It doesn’t need update method. It just needs a draw method. It will expect context as an argument and here I will draw a small bar for each available projectile. I create a for loop. It will run as many times depending on the amount of ammo we currently have.
I will set fill style to this dot color.
This should actually be outside the for loop like this. For each available projectile, I will draw a small rectangle. So, fill rectangle method. Add coordinates 2050. Width 3 pixels and height 20 pixels. Let’s draw what we have so far and then we can finish it once we can see what we are doing. Same as we did with player and input handle classes, I create an instance of user interface class here on line 116 inside game class constructor. It expects game as an argument. So I pass it this keyword. Now I use this UI property and I call its associated draw method we defined on line 102 and I pass it context. Nice. Right now we are drawing all 20 available projectiles on top of each other here. To make them align next to each other, I just multiply them by the index from the for loop like this. 20 pixels spacing is too much. How about a spacing of 5 pixels
plus 20 pixels left margin like this. That’s better. Now we can see how our ammo is slowly recharging.
I can change the color here on line 100. I can test it and it’s working really well.
It will keep recharging until it reaches max ammo value of 50 and then it should stop.
Yes, we will apply better visuals to our game in a moment. I just want to have a complete functional game skeleton first.
We are missing one last important element in our main game loop, enemies. And some enemies will also work as powerups in this game. We will have multiple enemy types in our game with different visuals and behaviors. Let’s take it step by step to make sure we understand how everything works and how the player projectiles, enemies, and power-ups interact with each other. Enemy class will just contain the main blueprint, the properties and methods shared between all enemy types. We will then extend this class into multiple small subclasses. Each enemy type will have a separate child class that inherits from this main enemy parent class. All enemies will need access to the main game object. So I do this as usual. All enemies will also have the same starting horizontal X coordinate. They will go from right to left starting just behind the right edge of game area. So this gamewith horizontal speed of each enemy will be a random number between minus0.5 and minus2 pixels because I want them to move in minus direction to the left on horizontal xaxis. Marked for deletion will be set to false initially.
Update method will adjust horizontal x coordinate by the amount of speed x value for each animation frame moving enemies from right to left. I check if enemy moved completely off screen all the way behind the left edge of game area. So if xcoordinate of the enemy plus its width is less than zero, we will set its marked for deletion property to true. Draw method will take context as an argument. At first I will just draw red rectangles representing enemies. So fill style red and fill rectangle at enemies x and y coordinates and its width and height like this.
We will have multiple enemy types. Let’s start with the basic simple enemy angler fish. I call that class for example angler one and it extends enemy class from line 86. In this scenario we have a parent enemy class also called a superass and we have child angular class also called a subclass. This is a good example of the second principle of object-oriented programming called inheritance. Angler is a child of parent enemy class and it has access to its methods such as this update and draw as well as these properties. If I call a property or a method on angle class and JavaScript can’t find it on angular, it will automatically travel to the parent enemy class and it will look for it there. Inheritance is used to reduce code repetition. Instead of redeclaring the same methods and properties on each class, I can just declare them once on parent class and all child classes will inherit them automatically. We can also do other things here using a special super keyword. I will show you as we write the code. Angle class will have its own constructor because some properties will be specific only to anglerfish enemies in our game. If I don’t declare constructor on angler class at all, it will automatically use constructor from enemy class from line 87. In this case, I want the code from parent class constructor on line 87 to run. And I want to add some additional properties here. If I just declare constructor on angular, it will completely override parent class constructor. And this code would be ignored. So I have to use a special syntax to kind of merge them. I want first this code to run and I want to add little bit of it here. I do it by calling super which refers to superass parent class constructor. This line of code will make sure that the constructor on the parent gets executed. Now I can add some more properties that will be specific only for angular. Remember that if you want to combine constructors like this, you have to call super first before you use this keyword. Doing it the other way around will give you an error. I have specific sprite sheets ready for our project. So I know that width of a single frame in our angular sprite sheet is 228 pixels and height is 169 pixels. I want vertical y position to start from here and go 90% from the top because we will have some ground graphics in our game environment layer. These are flying fish machines. They need to be above ground. Rectangles and images on canvas are drawn from the top left corner. So, I want the starting vertical position on each enemy to be between zero here and 90% of game height here. But I need to offset it by the actual height of the enemy. Otherwise, they will be drawn too low down here. That’s why I define x coordinate on the parent enemy class on line 89. But I had to wait declaring vertical y-coordinate until I had height of that specific enemy type. It will all work together well. Now you’ll see inside update method on the main game class I will cycle through all enemies and for each enemy object inside enemies array I will call their update method. I also need to define this enene array here. It will hold all currently active enemy objects. I will again use filter method to filter out all enemy objects that have marked for deletion property set to true. The same thing we did with projectiles.
And I will cycle through all enemy objects here. Call their draw method and pass it context. So we have enemy class. We are updating, drawing, and removing old ones. We need to figure out how to add new enemies into our game. I will have a special method on the game class called add enemy. Every time this method is called, it will push one new enemy object inside this enemies array. Notice that I’m not calling the parent enemy class. I’m calling the child angler one class here on line 103. I can see it expects game as an argument. So I pass it this keyword because we are inside that game class.
Now I want to call add enemy in a specific interval. We will use the same technique we used to periodically recharge ammo and it will be much easier because we are already calculating delta time. I will need two helper variables. Enemy timer that will count between zero and enemy interval. I want to add new enemy into our game every 1 second every thousand milliseconds.
Same as we did with recharging projectiles. I say if this enemy timer which starts from zero is more than enemy interval. H let’s go up here and also declare game over property and initially set it to false.
Back here on line 162 I say and at the same time this game over is false because I don’t want to be adding new enemies when the game ended. And we call add enemy method from here. We will also reset enemy timer back to zero so that it can count again. Else meaning enemy timer is less than enemy interval. We will keep increasing enemy timer by delta time which is already being passed to update method here. Nice. We are adding big red enemies to our game. I go up to line 105 and I make the width and also height smaller just for now before we start animating sprites. I will make the entire game wider when we get to later stages. For now, we are just building the main logic loop. We have player that can move and shoot projectiles. We have recharging ammo and there are swarms of enemies coming at us. Nice work if you are following along. As usual, I want to check if enemies are being correctly added and removed from the array. So, I console log this enemies array here. I want to make sure that filter method on line 161 does its job and we don’t have endlessly growing array. It works well. Enemies that moved off screen to the left are being removed. Perfect.
It’s time to add some interactions. I need to check if enemies collide with player and also if projectiles collide with enemies. To save ourselves code repetition, I will create a reusable collision detection method on the main game object. I call for example check collisions. This method will take two arguments, two objects. I will call them rectangle one and rectangle two. And it will return true if they collide and false if they don’t. It will be a reusable function. So we can use it later and pass it player and enemy as rectangle one and two. And we can also pass it enemy and projectile to check if they collide. Let me show you exactly how to do that right now. When checking if two rectangles collide, we compare their X Y width and height in a specific way. So all objects we are comparing need to have X Y width and height properties for this to work. I will return a statement in brackets like this. So if the code in brackets evaluates true or false, it will immediately be returned by this function. Quick and easy to check if two rectangles collide. We need to run four checks. We need to check if horizontal x position of rectangle one is less than horizontal position of rectangle 2 plus its width. If this side is to the left of this side, I will comment out line 196 just for a moment so the movement doesn’t distract us. At the same time, we need to check if horizontal position of rectangle one plus the width of rectangle one is more than horizontal position of rectangle 2. If this side is to the right of this side, if both of these statements are true, we know they’re in the same space on horizontal x-axis, but it still doesn’t mean they collide. They can be far away vertically. Because of that, we need two more checks. We need to check if vertical position of rectangle one is less than vertical position of rectangle two plus the height of rectangle two if this side is above this side. And finally we check if the height of rectangle one plus its y position is more than vertical y position of rectangle two if this side is below this side. If all four of these checks are true, this entire statement will evaluate to true and check collision function will return true. If at least one of these is false, the entire statement will be false. And we know these two rectangles don’t collide. We stay inside the main game class and go to its update method. In this for each call, we cycle through every object inside enemies array from line 141 and we call their update method. As we go through that array one enemy object at a time, we will check collision between player object from line 137
and that particular enemy rectangle. I do this by calling our custom check collision method we just wrote and I pass it player as rectangle one and enemy as rectangle two. If this message returns true, we know we are colliding and I will set marked for deletion on that animator true. I go down to line 200 and I unccomment request animation frame again so that we can test it.
I can see that whenever player collides with an enemy that enemy gets deleted. Perfect. We have some interactions in the same for each method. I will also check each enemy against all currently active projectiles. They are stored inside this. Player.p projectiles and I call for each on that array as well like this. For each enemy inside enemies array, we check against every single projectile in projectiles array. I say if this check collision between projectile from this iteration of this for each and enemy from this iteration from this for each if check collision is true decrease enemy lives by one at the same time set marked for deletion on that projectile that collided to true so it gets deleted then I check if enemy lives are less or equal to zero
And if they are set marked for deletion on that enemy to true as well. And lastly, I will increase score by plus one. Actually, I want each enemy to give different amount of score points when defeated. So, we will increase game score by score property on that enemy. Now, I used three properties that don’t exist yet. I go up to parent enemy class and I declare them here. I set lives to five and score that this enemy rewards will be equal to the number of its lives like this. I will also draw that number on top of each enemy for debugging purposes by calling fill text. Here I pass it this lives from line 92 and X and Y coordinates of that enemy. It’s very small. So I will set fill style to black and font property will be set to 20 pixels Helvetica for now.
I need to define score on the main game class here on line 154. And I will also define winning score. Let’s set it to 10 points for now so it’s easy to test. Every time we increase score here, we check if the current score is more than winning score. And if it is, game over from line 153 is true. I go up to line 130 inside draw method on UI class. In this area, I will write code to display the current score to the user. I call built-in fill text method again and I pass it the text I want to draw. So this.game.score score and I want to draw it at coordinates 2040. I set font property to this do font size from line 126 plus pixels space plus this do font family from line 127. I put a string that says score colon space here plus we will concatenate this.game.score. Let’s see if everything works. All seems to be fine except for the text colors jumping around. I will move this fill style declaration up here and I set this dot color to white.
I can also do other adjustments to the text. For example, shadows. If I want the shadows to apply only to the text and not all the shapes on canvas, I will put it between built-in save and restore canvas methods. Save method of canvas 2D API saves the entire state of canvas at that point in time. That includes settings like stroke style, fill style, line width, global alpha, all shadow settings we will use in a minute as well as other things like cliin region or the current transformation matrix. So whatever we do with scale, translate and rotate canvas methods. Then we can change the state of canvas however we want and we call restore. Canvas restore method restores the most recently saved canvas state. If there is no saved state, this method does nothing. So save and restore only work when used together. I will start applying canvas shadows. If I did that outside save and restore, shadows would get applied to everything including player and enemies. But in our case, these shadows will only affect shapes and text we draw in this area between this safe and this restore. Shadow offset X defines the distance that shadow will be offset horizontally. It can be positive or negative depending on if you want the shadow to be to the left or to the right from the source element. Shadow offset Y defines vertical distance of the shadow. I set shadow color to black. I could also set shadow blur, but I don’t need it at this time. So by wrapping code in save and restore, I make sure that canvas shadows and this fill style property affect only this drawing code and not other shapes and graphics we are drawing on the same canvas element. This is a pretty solid code base. You can use this as a base boiler plate for many different games. Maybe some of you want to make a copy of this project at this state and save it in a different folder so you can experiment with it later and use it as a starting point of a different game project.
Win and lose condition in our game will depend on how many score points can the player get in a specific time window. We are going to handle those game over messages here before we call restore. So if game over property on the main game object is true, we set text align to center. We are going to display message one in larger letters and under that there will be message two and smaller font. What these two messages say will depend on how many score points we manage to get in a specified game time. I check if game score is more than winning score. Message one will say you win
and message two will say well done. So that’s our winning message. Else we display the losing message. So message one is you lost.
Message two is try again next time. Now we actually need to draw these messages on canvas. So font for the first message will be 50 pixels space plus font family. Then I call fill text and we draw message one in the center of the screen. So x coordinate will be game width time 0.5. Vertical y-coordinate will be game height time 0.5.
I copy these two lines of code and I change some things to draw message 2 in smaller letters. 25 pixels here and message two here.
I try to play the game and win. Nice. We get a winning message. I need to create some space between the lines. So, first message will be minus 40 pixels vertically and the second message plus 40 pixels vertically. Let’s see.
Yeah, this is fine for now.
We need a time limit for our game. On the main game object, I create two helper variables. I call them, for example, game time, which will start at 0 milliseconds, and time limit. For testing purposes, time limit will be 5,000 milliseconds, 5 seconds. After 5 seconds, the game will end. And depending on how many score points we manage to get, we will display winning or losing message. In update method, I say if game over is false, take game time from line 181 and increase it by delta time. Delta time is the difference in milliseconds between time stamp from this animation loop and the time stamp from the previous loop. So, it’s the amount of milliseconds between frames. By adding delta time to game time every time we draw a new frame, we are keeping track of how many milliseconds passed since the game started. Game time variable is simply just accumulating milliseconds since the game started. If game time is more than time limit from line 182. So when the game has been running for 5,000 milliseconds, we set game over to true. The final game will run longer. I just use 5 seconds time limit here so it’s easy to test. Up inside UI class, we want to draw game timer on screen. Let’s do it in this area on line 143. I call fill text and the text will say timer colon plus this.game time add coordinates 20 100. Actually the variable we just created was called game time like this. And I need to add space here.
Nice. So we can see game timer. I want to format that value so it looks a bit cleaner. I create a temporary helper variable called formatted time. I want to move the decimal point to show seconds. So this game.game times 0.001.
That works. I also want to display only one digit after the decimal point. So I use built-in two fixed method and I pass it one. Two fixed method formats a number using fixed point notation. We can use it to define the number of digits we want to appear after the decimal point. Perfect. We have game time functionality. I’m getting a back when I get a losing message and then I defeat a couple of remaining enemies and that message switches to the winning message. I want to make sure that after game over message appears, we are no longer able to gain more score points. Down here inside update method on game class, I only want to increase score if game over is false.
I get an error. It’s supposed to be this.ame game over like this. This will fix the bug. We have the base game. We have player, enemies, and projectiles. We have recharging ammo, score, timer, and win and lose condition. You can save this code in a separate folder and use it as a base skeleton for many different games if you want. From this point, I will start adding graphics and features that will be specific to our steampunk alien game. This will be the fun part. Let’s go.
We will be using large detailed images and graphics today. I will put all of them here inside index html. And because all the code in script js is inside event listener for load event, JavaScript will only run after all our images have been fully loaded. This will prevent errors. I will separate our assets in this project into three different categories. Characters, which will include player and enemies. props, which will include things like mechanical parts that fall from enemies when we damage them, and game environment. Let’s start by creating the game world. First, img element with an ID of layer 1, and source will point towards that.png file. You can download all project images in the description. I will put all my images into a project folder I call assets. Our game world will be made out of four separate layers for parallax scrolling effect. layer one, two, three, and four like this. I don’t really want to draw these images on the web page like this. I only want to draw them on canvas with the JavaScript. So, inside style CSS, I set all of them to display none.
In script JS on line 116, we have a custom class I called layer. Its job will be to set up each individual layer object. constructor will expect three arguments coming from the outside game, image and speed modifier. I convert those arguments into class properties like this.
The width of the images we are using is 1,768 pixels and the height is 500 pixels. horizontal x coordinate will start at zero and y will also be zero.
We will need update method to move the background layers from right to left as the game scrolls. If horizontal xcoordinate is less or equal to minus width from line 121, meaning the background image has moved across the screen and is now fully hidden behind the left edge of canvas. We set x back to zero so that it can scroll again. else decrease X by game speed times speed modifier. Each layer object will have different speed modifier to create parallax but all will depend on the main game speed variable so that all four layers can be controlled from one place. Game speed variable doesn’t exist yet. So I create it on the main game class here. Initially I set it to one. Layer class will also need a draw method. It will take context as an argument. I call built-in draw image method. This method needs at least three arguments. Image we want to draw. So this do image from line 119. And where on canvas we want to draw it. So I want to draw it at this.x this.y from lines 123 and 124. So we have our layer class which will handle individual background layers. We will also need background class that will put all four layer objects together to create the game world. Constructor will take the main game object as an argument. I convert it into a class property like this. Here we will grab all four images for each layer with JavaScript. So this image one will be document getelement by id and I pass it layer 1. The ID I give it in index html. This layer 1 property will be an object that holds an instance of layer class from line 116. So new layer like this. On line 117 I can see that layer class constructor expects game. image and speed modifier arguments. So I pass it this game from line 137. This do image one from line 138 and speed modifier will be one for now. Update method will move all layer objects
and draw method will draw all of them. We will hold all layer objects inside this dot layers property. It will be an array. Let’s start with just layer one.
Inside update method, I take this layers and I call for each method. For each layer object call their associated update method we declared on line 126. Draw method will expect context as an argument to specify which canvas we want to draw on. Inside we will do the same thing. call for each on layers array and for each layer object trigger its draw method and pass it that context value because we know that the draw method on line 130 expects that argument. So background will handle all layers to create the game world. Layer class will handle logic for each individual background layer object separately. Now to animate the background I just need to create an instance of background class inside game class constructor. Same as we did before with player and input handler classes. Background class expects game as an argument. So I pass it this keyword because I’m inside that game class. Right now inside update method on game class, I take this new background property and I call its update method. Inside the draw method on game class, I call draw method on the background object and I pass it context along here. Notice that I am drawing background first, then the player. This will make sure that the background is behind the player and doesn’t cover it. I’m getting an error. I go back to my background class and I want to put this layer 1 object into the array. So, I have to use this keyword.
Nice. We are animating layer one of our background. I can change scrolling speed by changing the value I pass as a speed modifier. If I pass it five, the game speed is one because I’m multiplying game speed times speed modifier. 1 * 5 is five. So background will scroll at the speed of five pixels per frame. I will bring all other layers into the project. I bring their images first. Then for each one, I create an instance of layer class
and I put all of them inside this dot layers array.
Okay, now we see all four layers. I give each layer a different speed modifier. These values are passed as speed modifier and they will be multiplied by the current game speed. You can control scrolling speed of each layer individually like this. For now, I will set speed modifier to one on all four layers. You can see that because of line 128, as soon as the layer images slide completely off screen, they reset back to X position of zero so that they can slide again. We are getting this gap. To get a seamless scrolling background effect, we can use a simple trick. We can place a second identical image next to the first one. So at the point where the first image doesn’t fill the entire game area, the second image comes in to fill the gap. The second image will never be fully visible. It will be filling in just for that short period of time before the first image can reset to its starting position again. Reset to the original position happens in one frame. So it’s not visible by naked eye. It will create an illusion that we have one endless seamless background. How do I apply that in code? I simply draw a second identical image. But I need to make sure it is next to the first one. The second image will start where the first image ends. So its horizontal x position will be this. X plus this dowith like this. And that’s it. We have endless parallax background implemented in our game. I can change its scrolling speed by adjusting speed property on the main game object. Or I can change speed modifier values I pass as arguments when I create individual layer objects. I’m getting a small stutter when the background images reset back to position zero. I fix that by removing this else statement here.
Now the reset should be smooth and not noticeable. I can also use small values as speed modifiers. I want the back layer to scroll very slowly. If I use speed modifier 0.2 and base game speed is one, that layer will scroll by 0.2 pixels per frame because we are multiplying game speed by speed modifier. I will try 0.2 0.4 four one because layer three is this ground image so that it should scroll the same speed as the rest of the main game objects and layer four are these items in the foreground so I will make them scroll at 1.5 base game speed the issue we have right now is at layer four these foreground objects should be in front of the player but right now they are behind because in the main animation loop I’m drawing all four background layers before I draw the player there are multiple ways to solve this I’ll use the simplest possible ible solution. I will draw only layer 1, two, and three here as part of the background class and remove layer four to draw it separately later. Because I removed layer 4 from the array, I need to call its update method first. From inside update method on the main game class here on line 226, I’m calling background.update which updates layers 1, 2, and three. And I will also call update on layer four like this. Inside draw method, we are redrawing all elements that make up our game for every animation frame. First, we draw the background which now contains only layer one, two, and three. Then, we draw the player on top of that. Then, we draw UI, then enemies, and on top of everything, we will draw background layer four to make it appear in front of all other game objects. The thing to understand is that since we are drawing everything on the same canvas element, what is drawn first will be drawn behind the thing that is drawn after. Background will be behind the player.
Time to draw the player. We start by bringing player image into the project here in index html. I created this seahorse sprite sheet that you can download and use. We will have basic swimming animation and we will have power up animation row because when the player collects a special overcharged fish, it will absorb its energy. The player’s eyes and chest will light up and player will shoot two lasers instead of one. In script js, I go to line 59 inside player class constructor and I bring the image into the project using get element by ID. Same as we did with background images. In style CSS, I give player image display none.
We are bringing the image into JavaScript project here on line 59. inside the draw method. Currently, we are drawing a black rectangle representing the player. To draw the sprite sheet, I will use built-in canvas draw image method. I pass it the image I want to draw. So, this image from line 59 and X and Y coordinates where I want to draw it. I want to draw it at player’s current X and Y position from lines 54 and 55. You can see we are drawing the entire sprite sheet with all its frames and rows. Draw image method can accept three arguments like this. We can also give it five arguments by adding width and height. In case of a spreadsheet like this, it will just squeeze all frames into an area of one frame. We don’t really want that. What we need is the longest version of draw image method that expects nine arguments and it gives us the most control over the image we want to draw on canvas. Those nine arguments are image we want to draw, source X, source Y, source width, and source height of the area we want to crop out from the source image, and destination X, destination Y, destination width, and destination height to specify where we want to draw that cropped out piece of image on destination canvas. We will need some helper variables as well. Frame X will cycle through the sprite sheet horizontally. Frame Y will determine row of the sprite sheet. In this case, row zero or row one. I need to crop out just one frame from the source spreadsheet image. So source X will be frame X times width of a single frame, which in our case is the width of player. Because I like to size my images to the same size we will draw them in game. When frame X is zero, we display this frame. When frame X is one, 1 * width of 120 pixels is 120. So we will draw this frame. Frame x2 will be 2 * 120, so 240 pixels, this frame, and so on. It will work the same with frame Y vertically. Frame Y times height of the player, which is identical to the height of the sprite sheet frame. Source width is width of a single frame. Source height is height of a single frame. Now we are cropping out just one frame. Perfect. I will animate the sprite sheet by cycling between frames horizontally from zero to max frame. In the case of player sprite sheet, max frame is 37, we count from zero. I will handle sprite animation logic down here. It’s simple. If frame X from line 56 is less than max frame from line 58, increase frame X by one. Else reset frame X back to zero. If you prepare your sprite sheets well in graphics editor, the way I prepared the seahorse for you, you only need very little code to get the animation. If I set frame Y to one, we will see the second row in the sprite sheet. We will use that for power up state. For now, I set it back to zero.
At this point, I no longer need the black rectangle that represents the player. But it is still useful to keep track of that area because it will be player’s hitbox used for collision detection. I replace fill rectangle with stroke rectangle. I remove this fill style declaration. Stroke style will be black by default unless we set it to a different color. I want to have a debug mode in my game. When I press letter D, the game will show hitboxes and maybe other things like enemy hit points. player hitbox is this black rectangle we are outlining. So I only want to draw that if gamedebug is true.
I need to create that property. So down here inside game class constructor I say this debug is true. The last thing I need is to create a switch that will allow the user to toggle debug mode on and off by pressing letter D. To create a toggle with JavaScript is simple. I go up inside our input handler class. And inside key down event listener, I create an else if statement and I check if the key that was pressed is letter D. If it is, I set the new debug property we just created on the game object to its opposite value. So if it’s true, I set it to false. If it’s false, I set it to true. Now when I play my game, I can toggle debug mode on and off by pressing the letter D. At this point, debug mode only shows and hides player hitbox. But we will attach more functionality to it a bit later.
Nice. We are making a lot of progress. Let’s add more characters to the game. The base game loop will have three enemy types. I created multiple animations for each enemy type to give our game more variety, and I will show you how easy it is to swap between them. You can download all art assets in the project files in the section below. I made these character spreadsheets for you, so feel free to keep them for your personal projects. You can modify them and do whatever you want with them. You can credit me if you use it for your personal projects, but only if you want. Also, if you want the base Photoshop files or separate pieces in PNG format for all these characters so that you can animate them and turn them into spreadsheets yourself, let me know. The best free software to create custom sprite animations is Dragon Bones. If you want more advanced features, there is also a paid software called Spine. You don’t need any of that because I’m already giving you complete readytouse sprite sheets. This is just for those of you who want to learn more and create custom animations or different animations than the ones I created for you. So, I’m bringing three new images into the project. Angler one will be the most basic anglerfish enemy type. The sprite sheet has multiple different animations for the same character model, and we will use all of them in a minute. The second character is a different type of angler fish with more gears and chimney. These are steampunk machines that come to life. I gave them a lot of mechanical parts. These two anglerfish enemies will be just a simple basic enemy type that comes at the player trying to eat it. They are not very resistant and they are easy to deal with using our seahorse sentinel lasers. The only advantage they have is that they can come in big numbers sometimes. To deal with that, we have to be smart and use our slowly recharging ammo wisely. We can use the third enemy type to help us. I call this third enemy type lucky fish because it will be an enemy but also a power up at the same time. It will have two different models, different skins that we will use randomly when we create a new one. And the way we use this enemy type to make gameplay more interesting is that the player can do two things when this enemy appears. We can shoot it for a lot of score points or we can collide with it to collect it as a power up. It doesn’t have many lives. So players also have to be careful not to destroy it accidentally before they can collect it and activate the power up. The story is that this is an overcharged fish and when the sentinel seahorse collides with it, it absorbs its light and power to activate its ultimate offensive mode for a short period of time. It will all be represented by graphics to make it clear to the user what’s happening. So here is the parent enemy class and here we have a child angle one class that extends it and inherits from it. I will add this image property and I point it towards the spreadsheet we just included in index. HTML like this. We have three different animations that loop and when we create a new angular fish, we will assign it one of these three randomly by setting this frame Y property to a random number between zero or one or two. So math at random times three wrapped in math floor
like this. Right now we have these small red rectangles representing each anglerfish enemy. Same as I did with player, I replace fill rectangle with stroke rectangle and I remove this fill style declaration.
And also this one to draw enemy sprite sheet. Same as we did with player sprite sheet. I use built-in canvas draw image method. I pass it image I want to draw. So this dot image from line 124. If I just pass it X and Y coordinates, it will draw the entire big spreadsheet with all frames and all rows. I declare frame X that will help us to navigate in the spreadsheet horizontally. Frame Y will navigate vertically.
Max frame will be the maximum horizontal frame which is 37 for this particular sprite sheet. I only want to draw enemy hitboxes when debug mode is active. So this line will run only if this.game.debug is true. I want to draw them at the original full size. So I remove this temporary size modifier we created earlier. If I pass a draw image method additional width and height, we will squeeze all frames into an area of one frame. So I will add four more to define an area I want to crop out. Same as we did with the player. So source X, source Y, source width, and source height. So image I want to draw, four arguments for the area I want to crop out from the source image and four arguments for where to place that cropped out image on destination canvas. Source X will handle horizontal cycling through the sprite sheet and it will be frame X from line 106 times width from line 124. We keep width property on a subclass because it will be different for each enemy type. Source Y will be frame Y from line 107 times height from line 125. Source width will be this dowidth. Source height is this doheight. I’m giving you spreadsheets that are already sized exactly as they will be drawn in the game. So, we don’t have to do any scaling with code here. It’s simpler and it’s more performance efficient to do it this way. The sprite animation itself will be simply going between frame x0 and max frame over and over. We will handle it here inside update method. If frame x from line 106 is less than max frame from line 108, increase frame x by one. else reset frame X back to zero and we are animating our first enemy type. The final game will be wider. I just keep the game area smaller so that I can show you everything on one screen. In style CSS, I hide images with ID angler one, angler two and lucky. I can set the game width to 700 pixels so that the large enemies fit better. You can see that each enemy fish has been randomly assigned one of the three possible animations I prepared for you. It gives our game a nice variety. I needs to account for game scrolling speed when calculating enemy positions. So down here inside update method on enemy class, I say this.x plus equals this. Speedx minus this.game.speed. This way we can have dynamic events that change game speed and enemies will always be correctly positioned in relation to our scrolling game world.
Let’s add the second type of angler fish I created for you. I copy this child class that extends the parent enemy class and I call it angr 2. Width of a single frame is 213 pixels and height is 165 pixels.
For this anime model, I created two different animations. So frame Y can be row zero or row one. So now we have two different enemy types. I want to have a simple functionality where I say something like when you create a new enemy in the game in 50% of cases make that enemy angler one. Otherwise make it anglr 2. We will handle that logic inside add enemy method. We will do it by rolling dice once every time we create a new enemy. So helper variable I call randomize is math.random. math.random declared like this will generate a random number between zero and one. If that random number is less than 0.5, create angler one enemy type.
Else create angler 2.
Up here on line 249, I increase game limit to 15 seconds. So we have some more time to test everything before game over is triggered. Immediately I can see the new angler 2 enemy type and I can see we are playing both animations. Perfect. Giving graphics like this for your game will go a long way to help you create unique stories and adventures for your players. If you are interested how to take drawings of static characters and turn them into animated sprite sheets with free dragon bones software like I did with this seahorse and angler for this class, let me know and I might make a course on that. On enemy class, I have this lives and this score properties. I want each enemy to have different values here. So, I cut it here and I paste it on each enemy type individually. Angler one will have two lives and when we defeat it, we will get two score points. Angler 2 will have three lives and it will give three score points. Quick test to see if everything works. I can see enemy lives and it gives the correct amount of score points. Don’t worry about the color of the numbers right now. We will fix that later.
Let’s create a third enemy type. I copy this code block and I will call this subclass lucky fish for example.
Width will be 99 pixels and height 95.
This fish will have three lives and if destroyed it will award 15 score points. I will give it a property I call type and I will set it to lucky. We will use this to check which enemy type player collided with. Down on line 314 inside add enemy method. I add another else if statement like this. If random number is less than 0.3, we create angular one. Else if it’s less than 0.6, we create angler 2. When the random value is between 0.6 and 1, we create lucky fish using the new class we just wrote. There’s something strange happening with the animation. I go up to lucky fish class to see what’s wrong. I can see that on line 153 I need to point this image property to the correct sprite sheet. And we are animating our special lucky fish power up. There are two models because the sprite sheet has two rows and we are randomizing vertical coordinate with frame Y here on line 154.
When the player collides with one of these special overcharged fish, it enters a power up mode. Let’s write that code now. We start by declaring this. Power up property on the player up here on line 65. Initially, I set it to false. Power up will last for a while and then the player will go back to the normal state. So, we will need another timer. We can use delta time for it. We will have two helper variables called power up timer and power up limit. Power up timer will be counted milliseconds. When it reaches power up limit of 10,000 milliseconds, 10 seconds power up state will end. We will handle that logic here in update method on player class. I say if this power up from line 65 is true, we enter this code block. Inside we check if power up timer is more than power up limit
and if it is we set power up timer back to zero and power up to false. We will also set player sprite sheet to frame Y0. So normal default animation else mean and power up is true and power up timer is not yet higher than power up limit. The player is in power up mode and power up timer is counting milliseconds by adding delta time. We calculated delta time before inside the main animation loop. It’s the difference in milliseconds between time stamp from previous and the current loop. We need to make sure update method expects delta time as an argument on line 69.
And we pass it to this method inside update method on the main game class here on line 286.
So we are counting timer and frame Y is on the second row. So value is one. We count from zero. That will give us a special animation with light coming from chest and eyes. And also tail will animate differently because while in this state we will shoot two lasers, one from the nose and one from the tail at the same time. Two lasers for the price of one ammo. At the same time, we will be recharging ammo by 0.1 per frame. And this will be on top of the regular recharging speed. So ammo will be recharging very fast while in power up mode. So when power up is set to true, we enter this code block. Power up timer is increasing by delta time, counting how many milliseconds we are in power up mode. Frame Y is animating special overcharged animation and ammo is recharging fast. As soon as power up timer increases over the value in power up limit which will happen after 10 seconds 10,000 milliseconds we set power up timer back to zero. We set power up to false and we set animation row back to normal regular floating animation.
Inside update method on game class in this area where we check for collision between player and enemies. We check if type property on that enemy is set to lucky.
If it is, we call a special method on the player object called enter power up. We will write it in a minute. Else if we collided with another enemy that is not lucky fish, we decrease score by one as a penalty to the player. This will encourage player to collide with power up fish and avoid regular enemies to get maximum possible score.
I go up to player class here on line 111. I create that method. So enter power up. Its job will be to set everything up when player enters power up state. I could have used state design pattern, but we don’t have that many player states. So, we can do it simply like this. When we enter power up, we set power up timer back to zero in case we collided with power up fish while we are already in power up state. This will make sure the timer resets and we get full 10 seconds from that second collision. Reset power up property on player object to true. We will also recharge ammo to its maximum possible value.
I test it. I collide with our special overcharged fish and player gets overcharged. We are in power up mode and ammo is instantly refilled and it’s recharging very fast. Perfect. After 10 seconds, power up ends. Ammo stops recharging because we are well over max ammo value and player sprite sheet switched to the basic floating animation. Great. So when we are in power up mode, our sentinel seahorse will shoot defensive lasers from mouth and tail at the same time. It’s overflowing with energy and it can shoot extra free projectiles from tail every time it shoots one from the mouth. We can handle that functionality with JavaScript very easily by declaring additional method I call shoot bottom. In shoot top we handle shooting from the mouth. In shoot bottom we will handle logic for tail lasers. So inside I check if ammo is more than zero and we just push new projectile into projectiles array.
I adjust its horizontal and vertical starting position because I want it to be coming out from the tail. I will call this method from inside shoot top like this. While we are running the code in shoot top, we check if we are in power up mode. And if we are, we execute shoot bottom method as well to add that extra free projectile. I tested lasers are just coming out from Seahorse’s mouth in basic floating state. If we collect the power up fish, we enter power up mode and both shoot top and shoot bottom methods are running. We are shooting two projectiles, but it costs only one ammo. Getting overcharged makes the player very powerful. Maybe we need some bigger bulkier enemies so we get more challenge. I made two types of mechanical whales for player to defeat. Probably you will need these powerups to do so. I want the projectiles to come from behind the player. We are drawing projectiles inside the draw method on player class. If I want them to be drawn behind, I need to make sure we draw projectiles first and the player second. Like this. I want to make it even more visually clear when powerup mode is active. For example, I can make this ammo display up here different color while in power up. We draw all these UI elements in our custom UI class on line 243. Before I draw all these small rectangles representing ammo, I check if power up property on player class is true. If it is, I set fill style to a different color. I want that color to be similar to the glow effect we have on our spreadsheets. So maybe this
it works. The problem is that it also recolors the timer text because we define fill style here. We draw ammo and then we draw the timer. I can keep drawing ammo at the same coordinates in our game and I can move this entire code block further down.
That way this white fill style will apply to timer and the other color will apply only to ammo like this.
Instead of drawing a rectangle representing a laser projectile, we can also use an image. I bring it into the project here in props section in index html. You can download it in the video description or you can use your own image. I give it an ID of projectile. I bring it into the project here on line 38 inside projectile class constructor. This do image equals to document.getelement by ID and I use the ID I just gave it. So right now we are drawing yellow rectangles representing our projectiles. I delete that and instead I will use draw image method. This time it’s simple because the image is already the right size. I just give it three arguments. The image I want to draw and X and Y coordinates where to draw it on canvas.
Nice. We are drawing images. These projectiles can also be animated either using a special sprite sheet or all different kinds of particle effects. We will use spreadsheet to animate projectiles in a bonus section of this course.
Let’s add a web font to make the text look better. Adding a font to a canvas game is very simple. I go to Google fonts and I search for bangers. I like this comic book style. When you have your font, you click select your style here. You can select multiple different fonts like this if you want. Then we go up here to view selected families. If you choose more than one, this code will be adjusted to contain all of them as part of a single declaration. All we need to do to bring the font to our project is to copy these link tags and paste them up here in index html, ideally above the main CSS stylesheet to make sure the fonts are available from there as the code gets executed line by line from top to bottom. I go back to Google fonts website and here it also gives us a readym made CSS font family declaration. I copy it and I put it inside our style CSS file here on canvas element. Now my font is available. So I go to script js. Inside UI class I set this custom font family property we wrote before to bangers. And now all the text drawn by UI class. So score timer and game over messages will use the new font. Let’s adjust the winning text to something less boring. I want to keep the Victorian steampunk explorer theme going for this game. So, winning message will say most wondrous in big letters and with an exclamation mark. Secondary smaller message, we’ll say well done explorer. For the losing message, let’s use another old phrase. I will say blazes with an exclamation mark. It’s a word for when something goes wrong in old English.
Secondary small message will say hm get my repair kit and try again for example. You can come up with your own messages here if you want. Now I have my messages in a new font so I can adjust the sizing and spacing. I want the main message one to be much bigger. I try 150 pixels. Well, that’s way too big. We could also adjust the text size to match the width of canvas, but let’s keep it simple for now and set it to 100 pixels.
H I think I will go with 70 pixels.
I want the messages to be closer together. So instead of 40 pixels offset, I tried 20 for both of them.
I’m happy with the spacing and font size for now.
I can see that we still have the projectile image visible in the top left corner of the browser window. So in style CSS I set it to display none. We can also see the spacing of the loin message. Now looks good. I would also like the little numbers above each enemy that represent their lives to be only visible when debug mode is active. To do that, I go up to enemy class and inside draw method, I only want to call that fill text method if debug mode is active. If debug property on the main game object is true.
Let’s test it.
Yes, I can only see hitboxes and enemy lives when in debug mode. Perfect.
I want to make sure that player cannot leave the screen. Right now, if you press up or down arrow for long enough, player will move completely off screen. Inside update method on player class, we will handle vertical boundaries. Let’s start with the bottom boundary. If this doy vertical position of the player is more than the height of the game minus the height of the player, meaning that the bottom edge of the player is touching the bottom edge of the game area. Make sure we cannot move past this point like this. I actually want the player to disappear halfway so that it can avoid very big dangerous enemies we will add soon. I will do that by multiplying player height times 0.5 here and here. Now we can move half the seahorse’s body outside the screen and then we hit the boundary we just defined. I will do the same thing for the top boundary. Else, if player’s current vertical y-coordinate is less than minus player height time 0.5, meaning that the top half of the seahorse is offcreen, make sure it cannot move any further up like this. I test it. Top boundary works. Bottom boundary works. Perfect.
Up on line 48, we have a particle class. I will use it to create broken parts fallen from enemies every time we hit them and for a big spray of spare parts when we completely destroy an enemy. We will also make those parts bounce from the floor for a specific number of times before they fall off screen. Particle class constructor will need a reference to the main game object. Start in horizontal x coordinate and start in vertical ycoordinate. I convert those arguments into class properties. As usual, we will use a spreadsheet with nine images in a grid. Each particle object will randomly choose one of these images so that we have some variety. This image is document.getelement by ID and ID will be gears. In index html, I bring it into the project here in the props section. ID is gears and file name is gears.png. You can download it in the project section as usual.
In style CSS, I hide it by giving it display none.
Here we are referencing that image with JavaScript. I will need some helper variables to navigate around the sprite sheet. Each particle object, each spare part flying from the enemy will have a random image assigned from these nine available options. Frame X will be a random number between zero and two. So column zero or one or two. Frame Y will be the same. So row zero or one or two. Combination of frame X and frame Y value for each particle object will point to a specific image in the grid. For example, frame X 2, frame Y1 will be here. Sprite size will be the size of individual frame. In this case, frames are squares 50 * 50 pixels. I want each particle to have a different size when drawn on canvas. So we will create size modifier and we will set it to be a random number between 0.5 and one. I will set it to one number after decimal point to reduce artifacts. So particle size will be sprite size multiplied by size modifier to make sure every particle is a different random size when drawn on canvas. I want particles to fall in both directions horizontally from the enemy. So speed x will be a random number between minus3 and + three. If it’s minus, it will move to the left. If it’s plus value, it will move to the right on the positive direction on horizontal x-axis.
Speed y vertical speed will be a random number between zero and minus15. So particles will always start moving upwards on the negative direction on the vertical y-axis before they start being pulled down towards the ground by gravity. I set gravity to 0.5. Let’s see what kind of curved movement we get. We can adjust it later. Marked for deletion will initially be false. I want the [ __ ] wheels and spare parts to rotate as they fall. So I will have an angle property storing rotation angle for each particle separately starting at zero. And the speed of that rotation will also be randomized in this. VA property velocity of angle. Rotation speed will be a random value between minus0.1 and plus0.1 radians per animation frame.
In update method, we will increase the rotation angle by VA. Speed Y will increase by gravity which will give it a nice curve. Let’s say the particle will start moving upwards because starting speed Y is -15. As we increase speed Y by gravity from line 6 to1, that minus5 value goes closer to zero. At that point, when it reaches zero, it will stop moving and it will be at its peak height. As speed Y further increases by gravity value into positive numbers, it increases by 0.5 per animation frame. That particle will start falling down faster and faster until it disappears below the bottom edge of canvas. This dox minus equals speed X to move particles horizontally. this do y plus equals speed y to actually apply that speed y value affected by gravity to the vertical coordinate of each particle. If the particle fell off screen vertically, so its y-coordinate is more than game height plus size of the particle. Or if the game scrolled past the particle, so its x position is less than zero minus particle size. Set its mark for deletion property to true.
Draw method will take context as an argument. We call draw image method. pass it the image we want to draw and XY width and height where to draw it on canvas. Since the particle image is a grid of frames, it’s a sprite sheet. We only want to crop out one individual frame for each particle object. I will also need to specify cropping position. So source X, source Y, source width, and source height. Source X is this frame X from line 54 times this. sprite size from line 56. Source Y is frame Y time sprite size.
Source width and source height will be this dot sprite size like this. We want to crop out a frame 50 * 50 pixels from the source sprite sheet. Then we multiply that by size modifier and we draw it at that scaled size. We will need an array that holds all active particle objects. I will create it here as this particles property on the main game object.
For each particle in that array, we call their update method like this. Use an ES6 arrow function syntax to keep the code cleaner.
After that, I call built-in filter method on that array and I replace that original array with a different one that contains only particles that have marked for deletion property set to false. All the particles with mark for deletion property set to true will be removed.
Inside the draw method, again we call for each on each particle object. We call its draw method and we pass it context as an argument as it expects up here on line 73.
We have our particle class ready. We just need to find a good place in our code to add these particles. I go down to update method on game class. And here where we check for collisions between projectiles and enemies, I create a for loop. This for loop will run 10 times.
And each time it runs it takes this particles array and it will push new particle object inside. I know that particle class constructor expects a reference to the main game object as the first argument. So I pass it this because right now in our code we are inside that game object. Each particle also needs starting X and Y coordinates. So I pass it X and Y of the enemy that just collided with a projectile. I don’t want the particles to come from the top left corner of each enemy. I want them to come from the middle. So, I add enemy width time 0.5 horizontally and enemy height time 0.5 vertically.
I copy this for loop. I actually want 10 particle spare parts to fall from the enemy when it collides with the player and gets destroyed.
down here. I remove the for loop and just push one particle. I test it.
Nice. We have a splash of spare parts coming out of enemy when they are destroyed by colliding with the player. Player in this game is indestructible. We will make that clear by giving it a special animated shield later in the bonus section. I copy this code that adds just one particle. And I actually want to put it here. So that every time projectile collides with enemy, one particle falls out. Nice. So now every time enemy gets hit by a projectile, one particles per part falls. If enemy is destroyed by colliding with the player, 10 particles come out in all directions. I want the particles to bounce off the floor for a specific number of times before they fall off screen. To make this effect even more intense, I go up to our particle class. And here on line 65, I create a helper variable. I call it bounced. And initially I set it to false. I also want to define a margin from the bottom end of canvas from which point the particles will bounce. So I call it for example bottom bounce boundary like this. And I set it to 100 pixels. If particle reaches this point, I want it to bounce. So I will switch its speed Y value to its opposite. If vertical position of the particle is more than game height minus bottom bounce boundary. So somewhere around this point. And at the same time if this dobbounce from line 65 is false. Set bounced to true so that it doesn’t bounce again after this. And set speed Y to its opposite value by multiplying it times 0.5.
So particle is falling. It reaches this point and its current speed is for example + 5. We switch it to minus 2.5 making it bounce moving upwards again. Still line 70 keeps applying gravity. So eventually we reach zero and go into positive numbers making the particle curve towards the ground again and eventually fall off screen. It bounces once sets bounced to true. So this if statement check will fail for the second bounce and particle will just fall off screen.
Maybe I don’t want particles to all bounce from the same vertical point on the ground. How about they bounce from a range between this and this point roughly to match our ground level image. So bottom bounce boundary will be a random value between 60 and 160 pixels from the bottom edge of canvas area. What about if we want to specify how many times each spare part bounces from the ground rather than having them bounce just once? On line 65, I set the initial bounced property to zero. In this check on line 74, I check if bounced is less than two. And each time we bounce, I increase bounced by one. Here on line 75, now each particle bounces twice before it falls off screen.
down here where we create particles. I copy this for loop that creates 10 particles. We create one particle when enemy collides with the projectile. I paste that for loop here to create 10 particles when enemy is destroyed by a projectile. So particles will come out of enemies after three different events. When enemy collides with a player and is destroyed, we get 10 particles. When projectile collides with an enemy, we get one particle. When enemy is destroyed by a projectile, we get 10 particles. So, we know how to create particles when certain events happen in our game. We know how to bounce them off floor and we know how to control how many times they bounce.
We also have this angle property that is increasing by VA on line 69. If you never done rotation on canvas, this part might be a little bit challenging, but I will try to explain. Each particle has a different angle value that’s increasing by VA value that’s also randomized for each particle object. It means that at any point each particle is at different rotation. Their angles have different values. To make sure that rotation angle only affects one specific particle and doesn’t spill out and affect other objects we draw on canvas, I will wrap the entire drawing code between save and restore. Save method takes note of the current canvas state. When we call restore later that restore will look for its associated save method and it will reset all canvas settings back to that point in time. Anything done between save and restore will just affect the code the drawing code in between. After we call restore, everything will be reset to its original state. To rotate something on canvas, we first have to move a rotation center point, the point that is normally considered coordinates 0 on canvas. So the top left corner, we have to translate that point over the object we are rotating. So translate to the current x and y position of the particle. Calling translate like this moved rotation center point from top left corner to this dox this doycoordinates of this particle. Then we call built-in rotate method that takes angle value in radians. I pass it the angle property that is increasing for every animation frame by va here on line 69. Not sure if you can see but particles are currently making very large circles around their x and y coordinates. It is because we are going to another distance of X and Y from these coordinates because of the fact that we translated 0 0 and rotation center point to these coordinates. For all intents and purposes, this dox and this doy between this save and restore in this area of our code is considered point 0 on canvas. And then I’m drawing this particle at this dox this doy from that translated point further and I’m rotating it making it go in larger circles around that center point. I fix it by changing x and y to zero here because position of that particle is already defined on line 81 in translate call.
Now the particles are rotating from their top left corner. So I shift them a little bit by half of their width and half of their height to rotate around the center point of each particle.
I have other classes about rotation. For now, let’s just move on. If you don’t fully understand some things about rotation, don’t worry. It will become more clear as you use these techniques more often in the future. We are getting a lot of particles now. If you are on a very old computer, you can reduce the amounts of particles to get better performance. I don’t have any performance issues on my computer. Let me know if it runs smoothly for you or if you had to decrease the number of particles to get a good frame rate.
The particles don’t move correctly in relation to the ground artwork. I need to go here and include the game scrolling speed in the horizontal position calculation.
H. So, this actually has to be plus
Yes, now it works. Awesome.
Time for a small cleanup. First of all, I want the debug mode to be disabled by default. We can still press letter D on keyboard to toggle it on and off, but when the game first loads, I want player and enemy hitboxes and enemy lives to be hidden by default. We have a bug in our game. When the player collides with any enemy type, it goes into a power up mode. That would make the game too easy. We only want the player to enter power up when it collides with power up fish. And later in the bonus section, there will be another way to enter power up for skilled and fast players who can shoot fast and big moonfish before it comes too close. To make sure the power up works correctly, I just need to fix this comparison operator on line 357. Like this. Now, our Sentinel Seahorse only enters power up if it collides with Lucky Fish. Another bug is when we enter power up, players ammo gets automatically assigned to max ammo. This is meant to refill the ammo. But if the player saved up and has current ammo higher than maximum ammo, entering power up will actually reduce the total amount of ammo they have. I fix it here on line 159 inside enter power up method on the player class. We only refill ammo to max ammo if the current ammo is less than max ammo.
Like this.
That works. I’m using VS Code editor. When I press Ctrl + F, I can look for all console locks. I want to delete them. Let’s keep our code and console clean. Particle spare parts falling from the enemies are not bouncing from the correct area. I want the bounce area range to match the ground artwork a bit closer. Here on line 66 inside particle class constructor, I set bottom bounce boundary to a range between 60 and 140 pixels.
I want to change the order in which we draw our game elements. Because we are drawing everything on a single canvas element, the order in which we call draw methods will determine what is behind and what’s on top. I go down here inside the main draw method on game class. And I want to draw the background first, then UI elements. So score, ammo, timer, and game over messages. Then we draw player, projectiles, particles, and enemies. On top of that, you can play with the draw order here if you want. If you want UI to be on top of everything, for example, just draw it last. I think it looks better like this because it’s a bit 3D or 2 and 1/2D when it’s slotted between the game world and game characters like this. We can do many other things to make the UI feel like a part of the game world. I have lots of ideas. Maybe in the next class I’ll show you more. Game text can be floating, moving, changing colors. It’s easy to do it if you follow my other coding tutorials and you understand canvas animation well.
We have a power up mode. So the game currently is very easy. When we are in power up enemies have no chance. Let’s add a massive enemy type that when defeated splits into five smaller enemies. That should give the player some challenge. Hope you like teeth and tentacles. It will also introduce some tactical decisions into the game. Maybe sometimes it’s better to avoid the enemy altogether if we are low on ammo when we know we can’t deal with the little enemies that come out of it when we destroy the big one. I copy lucky fish class that extends the main parent enemy class. And I renamed that copy hive whale. This is a massive mechanical whale that serves the enemy swarm as a hive vessel containing a lot of aggressive and fast drones. Width is 400 pixels and height is 227 pixels. This image will be looking for an ID of hive whale. I need to go to index html to bring that spreadsheet into the project. You can download it in the project files section. I made just one animation rule for this enemy type. It’s a lot of work animating all these tentacles and creating this spreadsheet. And also it’s already a big image file. image with an ID of hive whale and source is assets/hive whalepng. In style CSS, I give that image display none. Frame Y will be zero because this sprite sheet only has one animation row. It will have 15 lives. It can take a lot of damage. When defeated, it will give player 15 score points. Type is hive like this and it will move very slow. So here I will override the default speed X property from the parent enemy class and I set it to a random number between minus0.2 and minus 1.4 pixels per frame. I want the enemies to be spread a bit more down, not all the way to the bottom of the game area, but maybe 95% of the game height. I do it by replacing this vertical position calculation on angler one, angler 2, lucky fish and hive whale with 0.95 position where enemies can spawn in the game can be anywhere between zero and 95% of game height. We need this extra space. There are some large enemies incoming. We add hive whale into the project down here inside add enemy method on the main game class. I copy this line and I say if randomize is between 0.6 and 0.8 add new hive whale enemy type.
And here they come. I can enable debug mode by pressing letter D to see its massive hitbox and its current lives. We need to hit the whale with 15 projectiles to destroy it. But that’s not all. When destroyed, five smaller, fast, and aggressive drones come out of it, and they will go straight for our Sentinel Seahorse character. Let’s create that enemy type.
Drones are small, fast, and aggressive creatures. They live inside hive wells where they collect resources and feed on whatever the hive will swallows. Let’s make sure that Sentinel Seahorse is not their next meal. I copy this code block and I will name it drone.
This enemy type is different. They are ambush predators and they always hide inside the hive whale ready to jump out and attack their prey. They will not be coming at player from the right side of the screen like the other enemy types do. We will only see them when we destroy a hive whale. For that reason, when we create a new drone using this class, we need to pass it game object as usual, but also we need to pass it the current X and Y position. It will be the current position of the hive while we just destroyed. I convert X and Y into class properties as we always do.
Image will be looking for an element with an ID of a drone.
I bring it into the project here in index html. ID is a drone, source is assets/ dronepng. I created two different animation rows. Each one will be moving differently so we get some variety. Frame Y will be a random number, either row zero or row one. Each drone will have three lives and will award player with three score points if destroyed. I will give it type drone. I’m not sure if I will use this type property for something, but might as well declare it. We might need it later. This enemy can move very fast if it wants to. It will have a random speed range from a minus0.5 to -4.7 pixels per frame.
The way I want this to work is when we destroy hive whale with projectiles, it will spawn five drones. In star CSS, I give the drone image display none. If the hive whale gets destroyed by colliding with the player, there will be no drones because the drones would spawn too close to the player and there will be no time to try and aim and target them properly. It would not be a good gameplay experience having enemies spawn so close to the player. If projectile collides with an enemy and if lives of that enemy are less or equal to zero, that enemy is destroyed. We check if the enemy that was just destroyed has type property set to hive. If it is a hive whale, we take enemies array and push one new drone enemy object inside. If you remember drone class expects game and X and Y position were to appear in the game world. I want it to appear under the destroyed hive whale. So I pass it X and Y of that destroyed enemy. Let’s test it. I destroy a hive whale and we have one drone coming out of it. Nice. Let’s put this code into a for loop that runs five times to create five drones.
We have five drones, but they all appear on top of each other in the top left corner of the hive whale. That’s not ideal. Let’s spread them around a bit. Starting position of the drone will be horizontal X position of the hive whale that was just destroyed plus a random number between zero and the width of the hive whale enemy. Starting vertical position of each drone will be vertical Y position of the hive whale plus a random number between zero and height of the hive whale like this.
Maybe just height time 0.5.
Nice. Now the drones are more spread out. Some move very fast, some move slower. This is good. We get too many particles. I don’t want each drone to burst into 10 particles when destroyed. What if we make the number of particles per parts that fall from enemies when destroyed to be equal to the score that enemy gives?
I also make the same change here on line 383 when enemies get destroyed by colliding with the player. Now, hive whale should burst into 15 particles because when defeated, it gives 15 score points. Each drone will turn into three spare parts. That way, we won’t get a massive flood of 50 [ __ ] and wheels when we destroy five drones.
I want the game to feel good when we score some points and destroy an enemy. I want that enemy to really pop. So, not only we will turn it into spare parts that bounce around, we will also play a dust or fire explosion from a sprite sheet. I have two special sprite sheets for this purpose. You can take them and use them in your other projects as well if you want. Many games need dust and fire effects like this. All art assets for this episode were custom made by me or artists I hired. So there is no copyright. You can use them however you want. Enjoy. We will have the parent explosion class and we will extend it into two child classes. Dust explosion and fire explosion. Parent explosion class will contain methods and properties shared for all explosion types. It will expect game X and Y as arguments because I want the animation to play over the enemy that was just destroyed. So that X and Y position will be passed from the outside each time we create a new explosion object. Both fire and smoke explosion will be single row sprite sheets. I could also have placed them into a single image. We will cover compact sprite sheet animation in a later class, not today. I will cycle through them from left to right from frame x0 to max frame. Sprite height is the height of a single animation frame in the sprite sheet and it will be the same for both explosion types. I made both sprite sheets the same height of 200 pixels. So that property can be on the parent class. It’s a property shared between all explosion types. Sprite width. The width of a single animation frame in the sprite sheet will be different for each type. Because of that, it will sit on each subclass separately. We will define that in a minute. The sprite sheets have only eight frames, so they will animate very fast. I want us to be able to control FPS, frames per second on this animation. And I want that FPS to be independent of animation speed of the rest of the game. We will do that using delta time again. And we will need two helper variables. Timer that will count from zero adding delta time over and over until it reaches interval value. When we reach it, we will serve the next animation frame in the sprite sheet. Interval will be th00and milliseconds, 1 second divided by 15. So the animation will run at 15 fps. I can also put frames per second in a separate property if I want to. We can control FPS by adjusting that property instead. Marked for deletion will be initially set to false. As usual, we will need update method and it will expect delta time value as an argument. We calculate delta time in the main animation loop. Draw method will expect context as an argument to specify which canvas element we want to draw on. Update method will simply animate the sprite sheet by increasing frame X property from line 302. Draw method will draw currently active animation frame cropped out from the sprite sheet. Let’s start by just giving it image we want to draw and X and Y coordinates where to draw it. So this is the parent explosion class. It contains properties and methods shared between all explosion types. We will have two child classes. Smoke explosion and fire explosion. I say class smoke explosion extends explosion. That extends keyword creates a relationship between these two classes and it sets up prototype based inheritance behind the scenes for us. Smoke explosion subclass now has access to properties and methods that sit on explosion superclass. We will also have fire explosion subclass. Constructor will expect game and x and y coordinates. In index html I bring the sprite sheets into the project here in the props section.
IDs will be smoke explosion
and fire explosion.
In style CSS we give both of them display none.
I point the subclass towards that image using get element by ID. I’m placing this image property on the subclass because that image is specific only for this subclass. Parent superclass contains only properties and methods that are shared between all subasses. Sprite width. The width of a single frame will be 200 pixels. Both sprite sheets have eight frames. So max frame will be eight. Rectangles on canvas are drawn from the top left corner. I want the explosion animation rectangle to be coming from the exact center of the enemy rectangle. I can do it like this. I’m going to give it width and height property and I set it to be equal to sprite width and sprite height. I take X and Y properties from the parent class and I place them on the child class. Once we have a width property here, I offset X position by half of its width, moving its center point to the middle of the image horizontally. I will also move the vertical Y position into the middle of the image vertically like this. Down here inside game class constructor, I create an array that will hold all currently active explosion objects. Inside update method, same as we do for particles, I will call for each
and for each explosion object in explosions array. I call its update method. We will also use filter method to remove all explosion objects that have marked for deletion property set to true.
Inside the draw method, I call for each on each explosion object again and I draw them passing it context. Notice I draw explosions after I draw enemies. So they will be drawn on top of enemies. The order in which we call draw methods here matters. We will have two different explosion types. So same as we did with adding different types of enemies to the game. On the main game class, I define a custom method I call add explosion. We will have a variable that will randomize a number between zero and one like this. If that random number is less than one for now
take this explosions array and push new smoke explosion inside I pass it game. So this keyword it will also need a position. So we will take it from the enemy we just collided with. We will add a reference pointing to that entire enemy object here as an argument. X and Y of this explosion will be X and Y properties from that enemy object that is passed here. Let’s try to add it into the game and see what we’ve got so far. Up here on line 419, we check if player collided with an enemy. That enemy will be destroyed and we want to animate an explosion at that position. So I will call this add explosion like this. I pass it the enemy player just collided with and destroyed as an argument. I also want the explosion animation to play when we destroy enemy with projectiles. So here we check if projectile collided with an enemy. If that enemy’s lives are less or equal to zero, it will be destroyed. And we animate a splash of spare parts. We set marked for deletion on that enemy to true. And we will add explosion here as well like this. I test it and we are getting no explosion animations. I don’t see any console errors. Let’s debug it by console logining explosions array from inside add explosions method. Every time we add one, we console log the entire array. I can see that add explosion method runs and creates the console lock for us. But the array remains empty. Let’s see what I did wrong. Probably I made a typo somewhere.
Oh, I see this if statement never runs because there is no this.randomize property. Easy fix. We need to refer to this temporary helper variable like this.
Now I’m getting a console error which is better because this error will guide us to where the problem is. The error says we must call constructor in derived class before accessing this. Now I know the problem is inside smoke explosion class constructor. This is a so-called derived class. When I create a new smoke explosion object, I want the parent class constructor to run first, creating properties and values shared for all child classes. And then I want to add this little bit of code that’s specific only for smoke explosion class. To do that, I have to call the parent class constructor using a special super keyword. I know that parent class constructor expects game X and Y. So, I pass these arguments along. It’s a good lesson for me to remember on a child class we must always call superclass constructor before using this keyword otherwise we will get a reference error. Now when I create a new smoke explosion we create one new blank object. This constructor will run first creating these properties and then these properties will be added on top of that. Smoke explosion will also have access to update and draw methods because of the inheritance that was set up behind the scenes by the special extent keyword we used. We fixed all the bugs and typos. When I test it, I can see that everything works and we are adding smoke explosion into the explosions array and we are drawing the entire sprite sheet at that position. Perfect. Let’s clean it up and make it into a proper sprite animation. Now this dust cloud is a sprite sheet. We are drawing it using built-in draw image method on line 312. As we already did multiple times in this class, we know that to animate a sprite sheet, we need to crop out individual frames and swap through them one by one. We do it by passing draw image method nine arguments. Image we want to draw source X, source Y, source width, and source height to specify area we want to crop out. We want to crop out one spreadsheet frame at a time and destination X, destination Y, destination width, and destination height to tell JavaScript where we want to draw that cropped out image on destination canvas. We already have the values for destination. We just need to specify the crop area. Source X will be frame X from line 300 times sprite width from line 320. Source Y will always be zero because this sprite sheet has only one row of frames. Width of the cropped area will be sprite width. Height will be sprite height. So now because frame X times sprite width determines horizontal cropping coordinate as we increase frame X inside update method the frames will cycle and animate. Now it works but the animation is coming from the top left corner of each enemy and it plays very fast. Also, we are not removing all the explosion objects that already animated and we have an everinccreasing array. I will check if frame X is more than max frame. And if it is, I know that entire animation played. At that point, it’s safe to set marked for deletion property to true. And that explosion object will be removed by filter method we already defined earlier. In the browser console, I can see that the old explosion objects are being correctly removed. Nice. I want the animation to run at 15 frames per second. I do it by creating a periodic event again. Timer will be counting from zero to the value of milliseconds defined in interval. Whenever it reaches that value, it will trigger next frame in the spreadsheet and resets back to zero so that it can count again. So if this timer from line 303 is more than this interval from line 304, increase frame X by one. else keep increasing timer by delta time accumulate in milliseconds until it reaches the interval.
This will serve only the first frame. It’s because after timer reaches interval, it needs to be reset back to zero so that it can count again for the next animation frame. It still doesn’t work. I can see that update method expects delta time as an argument to be used to increase timer value on line 313. I have a feeling I’m not passing it that value when I call it. I go down to line 422
and I pass update method on explosion object delta time. Now we are animating the explosions at 15 frames per second. Structuring your code like this will allow you to set different FPS for different objects in your game. In this case, we did it because player and enemy sprite sheets are optimized for high frame rate, but explosion animation has only eight frames. So, it looks better if we stagger the animation speed. If you want to turn this into a mobile game, you would have to do this for all objects, player and enemies. You would have to delete some frames and stagger their animation because current mobile phones will struggle to serve sprite sheets with so many frames we are using for our characters. If you want lower frame rate spreadsheets for these characters, let me know. so I can easily make them for you. The explosions are animating in the top left corner of each enemy. I want them to be in the middle. So I adjust the initial X coordinate we pass it by half of enemy width
and the vertical coordinate will be adjusted by a half of enemy height like this. This looks good. I’m noticing that we are not showing the first animation frame of our smoke explosion sprite sheet. It’s because we first call the update method that increases frame X by one and then we draw it. I can fix it by calling draw first and then I call update like this.
Now we can see the first sprite frame as well and it stays long enough so we can actually notice it. Nice.
Let’s delete this console lock. We don’t need it anymore. I can change the animation speed of dust clouds by changing the value here in FPS property. When I set it to a very low value, like five frames per second, it becomes obvious that they are not being correctly placed within the game world. They are static, but they should be scrolling along with the game world for the positioning to look right. I fix it by accounting for the current game speed in their horizontal coordinate calculation like this. Now they scroll with the game world and it looks as it should.
I think a good FPS for this sprite sheet is around 30 frames per second.
Yes, I’m happy with this for now.
Let’s create a fire explosion. This time it will be very simple. I copy all the code inside the constructor into the other child class. Image will have ID of fire explosion like this. I actually decided to refactor the fire sprite sheet. And now both smoke and fire sprite sheets have frames 200 * 200 pixels. In that case I can remove sprite wave on child class and I can place it on the parent explosion class because now it’s shared for both explosion types. H in that case I can actually take all these properties and add them on the parent class as well.
I remove them here since they will be automatically inherited. Nice. That’s cleaner. So only the images are specific for each class. All other properties and methods will be shared and inherited from the parent class.
Down here inside add explosion method I say if randomize is less than 0.5 create smoke explosion. Else careful about the brackets and syntax here. else create fire explosion.
Nice work. Now, sometimes we get a fire effect, sometimes we get a smoke effect.
Inside game class constructor, I adjust time limit to 30 seconds. Winning score will be 100. So, the player needs to get at least 100 score points in 30 seconds to see the winning message. I set ammo interval to 350 milliseconds so that ammo recharges a bit faster. And I set enemy interval to 2 seconds. Let’s test it.
We have a bug. I got a winning message and there were some leftover enemies and colliding with them reduced my score below 100 and losing message was displayed instead. I need to make sure that after the winning or losing message appears, colliding with enemies doesn’t affect the score.
I do it here on line 437. Only decrease score when we collide with an enemy if game over is false.
Let’s tune the game into a more playable and challenging state. I make hive whale less likely to appear in our game and lucky fish more likely like this. I give hive whale 20 lives.
Lucky fish will have five lives. Angler 2 will have six lives. Angler one will have five lives. For example, I set game width to,000 pixels. This will make the game easier as we can see enemies coming sooner and we can plan and manage our moves and ammo better. I try to play to get a better idea of how much score I can get in 30 seconds. Okay, the game ended because we reached winning score 100 in 25.1 seconds. Let’s set winning score to 80.
I can strategize now. I can see there is a lucky fish coming that will replenish my ammo. So, I will use as much as my ammo as possible before I get the refill. Now, I get a refill and I can go crazy with my lasers. I will be careful not to hit the lucky fish coming at the bottom so that I can collect it and make my power up state longer.
Okay, it took 24 seconds to get the score over 80. game has a time limit of 30,000 milliseconds. 30 seconds. If the game runs for that amount of time, it will end. We need to get enough score points in that time period. This is just my choice. You don’t have to do this, but I want the game to always run for 30 seconds. I don’t want it to end as soon as we get over the winning score. So, I will comment out line 456. Game will only end when time limit is over 30 seconds. And based on how much score we manage to get, we will see a winning or losing message. There is an element of luck in the game we designed because it is very dependent on which type of enemy spawns. If we get just lucky fish and hive whales, we will get a lot of score points fast. If we get only anglers, it will be very hard to win the game. I gave you all the tools and techniques you need to understand. You can now adjust the game time, enemy health, and winning score yourself to make the game as easy or as difficult as you want it to be.
The game runs well on my gaming PC, but if you have a Chromebook or some very old laptop, you might be experiencing performance issues. I focused this class mostly on handling graphics and animation. There is a whole other set of optimizations we can do to make this run even on the oldest machines, but that’s beyond the scope of this class. Notable optimizations would be for example to draw on multiple canvas elements and only clear parts of canvas that actually update it. Object pooling technique would also have a massive positive effect on the performance. It means that we create a pool of particle and projectile objects and we just reuse them over and over rather than creating new ones and discarding them after one use. For this project, the quick changes you can make to improve performance would be to increase enemy interval property to make enemies spawn slower. If your computer is not keeping up, you can also remove all shadow properties inside UI class. Canvas shadows are still not well supported and optimized in some browsers, especially in Firefox. We can reduce the number of particle spare parts that fall from enemies when damaged. And we can also make sure they only bounce off the floor once or that they don’t bounce at all. If you are still getting performance issues, you can also merge all background layers into one image and just animate that one layer. That will increase performance. massively. Same goes for our spreadsheets. I gave you sprite sheets with 37 animation frames, which looks really good and smooth in motion. But if you struggle to get 60 fps, reducing the number of frames and have each animation to be just around 15 frames, let’s say, and staggering animation speed with delta time like we did with animated explosions would make the performance better as well. I will probably release a version of sprite sheets with lower frame rate for each character so you don’t have to do it manually yourself. Bonus extended lesson where I add more enemy types, animated projectiles, shield, and simple sound design is linked in the video description, but feel free to play with the code and add your own features with the techniques we learned today. Let me know if you finish this project by typing I did it in the comments. If you want more vanilla JavaScript game death, come and build this game with me. I will show you how to use state design pattern for more complex player controls and movement and how to split our code into individual modules. Click like, please. I’ll see you there.