In my last post, Making <canvas> Art, I introduced the idea of using the HTML5 <canvas> API to create a randomly-generated “northern lights” animation. The effect of the resulting animation is entirely aesthetic in nature. It provides no interactivity beyond the generation of new colors and shapes. In this follow-up post, we’ll use the same technique introduced in the original post, but extend our example to introduce keyboard input and manipulation in order to create a fully interactive game.
We’ll be creating a game I’m calling Space. It’s a simple game in which you use the keyboard to navigate a two-dimensional map of stars and planets. For the sake of brevity, the full script will be made available at the bottom of this post, and I’ll use snippets from the script to explain what’s what.
The game “loop”
The most important element of game development is the game “loop.” This is essentially a function that will continuously repeat as long as the game is being played. Our game loop will run much like our animation loop that we used in the previous post, with a few key additions. Without getting into too much detail, game loop script will look something like this:
With this basic structure in place, we’ll start to add more specific features to our script to handle movement, animation, and player interaction.
The game view
The first challenge is creating a scrollable map on which players can move. Our map will contain objects like planets and stars, but not every star will be visible at any given time. Only those stars that are close to the player will be drawn on the canvas. This is made possible by creating a game view. Because this is a two-dimensional game, we can consider the game view according to x and y axes. It looks something like this:
As you can see, stars 1 and 2 are visible within the game view, but star 3 is not. As the player moves around the map on the x and y axes, the view will be updated to show new map content, based on the location of the player. In our script, we can create the game view with the following additions to our game() function.
Using a combination of game.width, game.height, game.view.x, and game.view.y, we can keep track of our view and always draw the correct objects within the game canvas. As we add movement and objects to our map, we’ll refer back to our game view every time an object is rendered.
Stars, planets, and ships, oh my
With a basic animation and view structure in place, it’s time to populate our game world with the stars, planets, and ships that will make up the final game. It’s best to create a generic entity object that we can use to place all objects in our map. Our entities will store information for each map object, including its position (x and y), size (width and height), orientation, and movement vector.
The entity object will store all information we’ll need to draw objects on the game map. Entities will support position, orientation, and even movement using the v (vector) property. I’ve also included a friction property that will slowly reduce the movement vector over time, so that when players move around the map they don’t float away indefinitely.
To draw game entities in the correct position, we’re using a translate() function to convert our map-relative x and y coordinates into view-relative coordinates. Using this in conjunction with an if statement that checks to see if the entity is currently “in view,” we can draw our game objects only when they appear near the player.
Images and animation
Currently, our game entities can be rendered only as simple rectangles on our game map. Instead of rectangles, we want to use images and even animations to bring our game content to life. We can add this support to our game entities using animation sprites, a common technique used in game development. Our game objects will be rendered using images such as the following image of our player ship.
This single image contains frames that make up the different states of our object. From left to right, the first frame is the static state of our ship, followed by two forward thrust frames, followed by two reverse thrust frames, and finally followed by two turbo frames. We can improve our script by adding support for these sprite animations.
The game.sprite() object allows us to quickly specify a width, height, speed, and direction of our sprite image. Using this function, we can extend our entity() object to include support for sprite images. The game.load() function handles image loading, so that we can preload images before the game begins.
The final addition to our game script is keyboard interaction. With game development, it’s much easier to keep track of both the keyup and keydown events independently. I prefer to create an array of keycodes that can be checked at any time in our script to see if that particular key is currently pressed. We can do this with an addition to our game script:
This function allows us to keep track of every keyboard event. For example, if we want to know at any time if the left-arrow-key is depressed, we can check game.keys, which will return true when depressed, and false or undefined when otherwise. We can put this to good use by running a new function every time our game.update() is executed.
This last piece of our script allows users to manipulate the player object’s orientation, movement vector, and speed using the arrow keys. This allows users to rotate their ship and move about the game world. We’ve even added a speed boost when the shift key and the up arrow are pressed at the same time.
The final product
With a bit of tweaking, we’ve added hundreds of randomly placed stars and planets to our game, creating a small two-dimensional world that we can explore in our ship. Try it out using the arrow keys on your keyboard.