follow
Dungeon Data
A blog about game development, game design, and game mechanics

Jan 17, 2019 ∙ 9 min read

The base for a side-scroller prototype

A straightforward base for testing games on the web

Prototypes are a quick way to try out new ideas. When prototyping you’re focusing on the core of the intended experience; cutting corners wherever possible. The objective is to have a testable proof of concept as fast as possible.

The web is a compelling platform for prototyping 2D games. Some reasons for that are:

  1. It runs a very permissive scripting language (javascript), there’s no need for compilers or special software, just open the file on the browser.
  2. It comes with a good graphics engine and simple to use APIs.
  3. Sharing your results is easy. Any person with a computer and a browser can test your game. No need to download and run executables or to compile to different operational systems.
  4. The web platform limitations help to make the prototype very focused.

I’m going to walk you through how to set up a simple base for a side-scroller game, and with that show how little code is needed to start having some interesting results. I’m gonna cut down a lot of corners, so be aware that several techniques used here are not resilient for “production ready” works. Our objective will be to do a character that can walk and jump across the screen.

Use the keyboard (←↑→) to move the character

You will need some base knowledge in HTML, CSS and javascript to better follow this tutorial. They are a quite big topic on their own, If you’re new to those or want to fill some missing gap, take a look at the guides and docs of the Mozilla developer page.

To help you follow along, you can download the files from this tutorial using this link.

Bootstrapping

We’re going to put the whole code in one HTML file. When prototyping, go with the simplest approach first, you should break down things in multiple files only when the complexity of the code is enough to justify the work. It’s incredible how barebones our html file can be; modern browsers will add any missing required tags. Because of that, our HTML can be only the canvas and the script tag.

<canvas style="width: 100%; image-rendering: pixelated;"></canvas>
<script>
    // the javascript code goes here
</script>

You can already open the file on your browser; it won’t display anything because the canvas tag is empty. But as we add more code to the file, you can reload the page and see the changes.

We’re going for a pixel-art style, so we set image-rendering: pixelated to turn off any anti-aliasing during the scaling of the canvas. The canvas works the same as an image, it has its own pixel resolution, but it can be scaled using CSS when displayed on the page. So next, we’re going to define that pixel resolution and get the reference of the rendering context.

width = 300
height = 150

canvas = document.querySelector("canvas")
canvas.width = width
canvas.height = height

ctx = canvas.getContext("2d")

Loading the sprite image

For creating the sprite, I used the findings from the “Side-scrolling character sprites” post. It’s a simple base, comprised of two frames for the walking state, and one frame for jumping state. The sprite sheet image we’re going to load contains those three frames side-by-side.

The sprite image for our character comprising of two walking frames and one jumping frame.

After putting our sprite on the same folder as our .html file, we load it using the following code.

playerImg = new Image()
playerImg.src = "player.png"

An interesting simplification here is that for our code we don’t need to know when the image has been fully loaded. If the image is not loaded yet, the render code will not error, it will only not draw the image. But because we’re going to update the screen at ~60fps, as soon as the image is available it will appear on the screen.

Keyboard events

Now we move into doing some wrapping around the keyboard events. There are two reasons for that. First, the onkeydown callback was supposed to be repeatedly called while any keyboard key is down, but in practice, its fire rate is not constant. It gets called the first time a key is down, then there’s a delay, and after that, it’s called at a constant rate until no key is down anymore—we need to handle that inconsistency.

Second, the meat of our code will be inside the game loop; where we process the game logic and render the game. For handling the game logic, It’s easier to know which key is pressed and which key is not pressed, instead of responding to then on the event’s callback.

Here’s our simple wrapper. We start with a key object, which will hold information about which keys are down. The onkeydown and onkeyup callbacks update the data on the key object.

key = {}

document.onkeydown = function(evt){
    evt.preventDefault()
    if (evt.key == "ArrowLeft") key.left = true
    if (evt.key == "ArrowRight") key.right = true
    if (evt.key == "ArrowUp") key.up = true
}
document.onkeyup = function(evt){
    if (evt.key == "ArrowLeft") key.left = false
    if (evt.key == "ArrowRight") key.right = false
    if (evt.key == "ArrowUp") key.up = false
}

Player data

Next, we have the player data. We will hold the information that we need to render the player character inside the following object. That data will be used to determine where the player character is and how to render it. The canvas axis runs left to right and top to bottom, starting from the top left corner of the screen. Because of that, the initial position of our character on that data object is in the middle of the top of the screen.

player = {
    velocity: {x: 0, y: 0},
    position: {x: width/2, y: 0},
    width: 22, height: 24,
    isOnGround: false,
    isWalking: false,
    isFacingLeft: false,
    walkCycle: 0,
    walkFrame: 0,
    frame: 0
}

The player width and height here are equivalent to the size of each of the frames of our sprite image. We’re going to use this data both for the hit-box of the character, as for knowing how to slice and render the sprite to the screen.

The game loop

The main logic of the game, as well as its render code, is processed inside of the game loop. The loop is a function that is called over and over, each time it runs it produces one of the frames of the game. The browser has a very nice method, called requestAnimationFrame, to help us perform the calls to the game loop function. The requestAnimationFrame callback is executed before the next browser repaint, it’s usually in sync with the display refresh rate, it’s capped at 60fps, and it’s not called when the page is not on focus. We can rely on it to handle the pacing of updates to our prototype without needing to care about any extra setups.

To implement it the last part of our script will look like this:

function gameLoop(){
    // the rest of the game logic and render goes here
    window.requestAnimationFrame(gameLoop)
}
window.requestAnimationFrame(gameLoop)

The drawing methods

The gameLoop function will have two main sections, the first is to react to the inputs and process the game logic, and the second to render the game into the canvas element. I will start talking about the render code, as it will allow us to see what’s happening on the game while we code the rest of its logic. In this tutorial, we only have the character being rendered, so our render code will be quite simple. All the render is done with calls to the 2D drawing context that we got from the canvas—you can take a look at its whole API here.

ctx.fillStyle = "hsl(0,0%,90%)"
ctx.fillRect(0, 0, width, height)

ctx.save()
ctx.translate(player.position.x, player.position.y)
if (player.isFacingLeft){
    ctx.scale(-1, 1)
}
ctx.drawImage(playerImg, 
    player.frame, 0, player.width, player.height, 
    -player.width/2, 0, player.width, player.height)
ctx.restore()

First, we paint a gray rectangle over the whole canvas, beyond acting as the background for the game it also resets the canvas. We then translate the transformation matrix to where we’re going to draw the player. We do this to make it easy to flip the image of the player. If the player is facing left we scale the transformation matrix by -1 on the x-axis so that the following drawing calls get flipped horizontally.

We then draw the sprite of the character. As I showed before, the image we loaded comes with three different frames side-by-side. The canvas drawImage method conveniently allow us to define which part of the image are we drawing to which part of the canvas (take a look at the previous link to understand better how this works).

We save and restore the drawing context so that we can reset the matrix transformations.

Movement logic

Now let’s give some movement to our character. We will start by adding gravity and having the bottom of the canvas—our floor—stop the player. On the following code, we are increasing the y velocity of the player every frame, up to a terminal velocity. Every frame we increase the y position of the player by its velocity. If the character crosses the ground, we reset the velocity, set its isOnGround variable to true, and move him to just before he hits the ground.

player.velocity.y += 0.5
if (player.velocity.y > 4) player.velocity.y = 4

player.position.y += player.velocity.y
if (player.position.y > height - player.height) {
    player.velocity.y = 0
    player.isOnGround = true
    player.position.y = height - player.height
}

To make the character walk, we check if either the arrow left or the arrow right is pressed and then update the player variables accordingly. The walkCycle and walkFrame variables are used to help cycle through the walking frames, animating the character.

if (key.right) {
    player.isFacingLeft = false
    player.position.x += 2
}
if (key.left) {
    player.isFacingLeft = true
    player.position.x += -2
}

if (key.left || key.right) {
    player.isWalking = true
    player.walkCycle += 1
    if (player.walkCycle > 8) {
        player.walkCycle = 0
        player.walkFrame += player.width
    }
    if (player.walkFrame > player.width) player.walkFrame = 0
} else {
    player.isWalking = false
}

To make the character jump we add a negative velocity to its y-axis if the up key is pressed and if he is on the ground.

if (key.up && player.isOnGround) {
    player.velocity.y = -8
    player.isOnGround = false
}

Lastly, we update the player.frame variable according to the state of the player. We use this variable to define where to start cutting the character sprite sheet.

player.frame = 0
if (player.isWalking) player.frame = player.walkFrame
if (!player.isOnGround) player.frame = player.width * 2

There we have it, a simple base to start developing 2D game prototypes using web technologies. You can download the files used in this tutorial here.

Receive new posts on your e-mail:
No spam. Unsubscribe at any time.
NEXT POST →

The game mechanics of Resident Evil 2 Remake

Why the game is mainly about opening doors
← PREVIOUS POST

A minimal template for 2D side-scroller sprites

Analyzing NES and SNES game characters