Advanced JS: Games & Visualizations

# Making a Side Scroller: Hoppy Beaver

This game is a classic 2D "side-scroller": that means that we are looking at it side-on, and the character is just moving forwards or backwards through it. We always want our character in the center of the screen however, so actually, we simulate apparent motion of the character by moving the *background* past the character. It's a trick, but it works!

To start off with, let's just draw the parts that won't show any motion, the blue sky and brown ground:

draw = function() {
background(227, 254, 255);
fill(130, 79, 43);
rect(0, height*0.90, width, height*0.10);
// ...
}


Now, to create the side-scrolling appearance, let's add grass, using the grass image from the image library. One way we could create this moving environment would be to pretend our canvas was 3000 pixels wide, and that's how wide our level was, and draw as many grass blocks to fit those 3000 pixels, moving them over each time. However, that's not very efficient, and in programming games, we tend to care a lot about efficiency. Instead, we are going to "tile" and "snake" the grass images. We'll just draw as many as we need to go across the 400 pixel screen, and then when one falls of the left side of the right screen, we'll immediately stick it back on the right side of the screen, and just continue doing that forever.

To do that, we'll start by initializing an array of our initial positions for the grass blocks:

var grassXs = [];
for (var i = 0; i < 25; i++) {
grassXs.push(i*20);
}

Then, inside our draw loop, we'll draw each of them:

for (var i = 0; i < grassXs.length; i++) {
image(getImage("cute/GrassBlock"), grassXs[i], height*0.85, 20, 20);
}

That looks good for a static scene, but we need this to move! So we can just subtract one from each grass position each time, moving them to the left 1 pixel.

for (var i = 0; i < grassXs.length; i++) {
image(getImage("cute/GrassBlock"), grassXs[i], height*0.85, 20, 20);
grassXs[i] -= 1;
}

Now the grass will be moving, but it'll eventually disappear, as the x values become more and more negative. Remember, we want to "snake" the tiles - we want to wrap them to the right side of the canvas once they drop off the left side. To do that, we'll check if we're sufficiently off screen (remember that our images are drawn from the upper left corner), and set the x value to the canvas width if so:

for (var i = 0; i < grassXs.length; i++) {
image(getImage("cute/GrassBlock"), grassXs[i], height*0.85, 20, 20);
grassXs[i] -= 1;
if (grassXs[i] <= -20) {
grassXs[i] = width;
}
}

Putting it all together, we now have a beaver that looks like it's moving while it's hopping. Magic!

Okay, we have a beaver hopping through a side-scrolling environment. But there's nothing for the beaver to do there! We need to add the sticks for the beaver to hop up and collect.

Let's think a bit about our sticks, as we need to decide how to program them:

• Each stick has an x and y position. We probably want the x positions distributed by some amount (possiby constant or random within a range), and we want the y positions randomized within a range, so that the user has to control the beaver's hop and fall.
• The sticks should have the same apparent movement as the grass, but they should not snake around. Once a stick is off screen, it's gone forever.
• There should be some set amount of sticks per level - at some point, there should stop being sticks.

There's many ways that we could program our sticks, but they seem sufficiently complex, so let's model them with an object, like we modeled our beaver character:

var Stick = function(x, y) {
this.x = x;
this.y = y;
};

Stick.prototype.draw = function() {
fill(89, 71, 0);
rect(this.x, this.y, 5, 40);
};

Then, before our game starts running - like after we initialize our beaver - let's create an array of 40 sticks, with constant offset and random y:

var sticks = [];
for (var i = 0; i < 40; i++) {
sticks.push(new Stick(i * 40 + 300, random(20, 260)));
}

Now we can draw the sticks - similar to how we drew the grass, just without the wrapping around:

for (var i = 0; i < sticks.length; i++) {
sticks[i].draw();
sticks[i].x -= 1;
}

Here it is, with the sticks drawn with that code. Try to hop for them! What happens? Nothing! We'll fix that soon...