Main content
Course: Computer programming - JavaScript and the web > Unit 4
Lesson 4: Making a side scroller: Hoppy BeaverForest environment
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 off 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 (possibly 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 are 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...
Want to join the conversation?
- I've noticed that you guys started using "height" and "width" to define parameters inside shapes. Are those referring the to height and width of the canvass?(52 votes)
- Every Processing.js environment (like KA) can count on two predefined variables,
width
andheight
that describe the dimensions of the canvas, aka the sketch.(56 votes)
- i'm probly a very simple Javascript user but i can't and don't get anything of what she is saying.(24 votes)
- If none of this is making sense to you, then double back and do the Intro to Javascript course. Do ALL the challenges and projects, Skip nothing. You can burn through the whole thing in a couple of evenings, and then you will be ready for this Advanced JS course.
This course assumes you've got everything in the Intro course down; if you skimmed that, or skipped it entirely, you're going to be lost in this course. There is simply no substitute for practice with the basics before you start to get tricky.(49 votes)
- Hi, could someone help understand this part of the code?
var sticks = [];
for (var i = 0; i < 40; i++) {
sticks.push(new Stick (i * 40 + 300, random(20, 260) ) );
}
I don't understand how was a new 'Stick' created with this line... Shouldn't we use something like thisvar sticks = new Stick (" ");
?
How does thissticks.push( new Stick (x,y) );
do the same job?
I get the push statement but how can it push a new Stick inside that array? Sorry if it is a bit confused...(17 votes)- Remember, an array is storing multiple variables, so by adding an object to it, it's like using
var
to create a new variable, except inside the array.
So usingsticks.push(new Stick());
is still creating a new stick object. It just isn't making it a separate variable with a name as you're used to.(14 votes)
- i feel like this is just giving me code, and not teacthing me how to do it. How would i learn to do this?(13 votes)
- You learn to do this by starting right from the beginning: Intro to JS. Do all lessons, challenges and quizzes to learn programming. When you feel you get stucked, repeat and try again. There is no better way to understand the basics.(9 votes)
- It doesn't show anything on my canvas even though I pass the chalenge. Here is my code:
var player1Y = height/2;
var player2Y = height/2;
var player1Score = 0;
var player2Score = 0;
var ball;
var gameStarted = false;
var t = 0;
//Constants
var PAUSE_TIME = 60;
var PLAYER_MOVE_SPEED = 2;
var BALL_SPEED = 2.5;
var PADDLE_HEIGHT = 100;
var PADDLE_WIDTH = 20;
angleMode = "degrees";
var Ball = function(position, speed) {
this.position = position;
this.speed = speed || BALL_SPEED;
this.radius = 12;
this.resetVelocity = function() {
this.theta = random(0, 360);
this.velocity = new PVector(
this.speed*cos(this.theta), -this.speed*sin(this.theta));
player2Y = height/2;
};
this.resetVelocity();
this.draw = function() {
fill(255, 255, 255);
noStroke();
ellipse(this.position.x, this.position.y,
this.radius*2, this.radius*2);
};
this.collideWithPaddle = function(x, y) {
if (this.position.x - this.radius < x + PADDLE_WIDTH/2 &&
this.position.x + this.radius > x - PADDLE_WIDTH/2) {
if (dist(0, this.position.y, 0, y) <
PADDLE_HEIGHT/2 + this.radius) {
if (this.position.x > x) {
this.position.x = x +
this.radius + PADDLE_WIDTH/2;
}
else if (this.position.x < x) {
this.position.x = x -
this.radius - PADDLE_WIDTH/2;
}
this.velocity.mult(new PVector(-1, 1));
}
}
};
this.update = function() {
//Handle wall collisions
if (this.position.x < 0) {
player2Score++;
this.position = new PVector(width/2, height/2);
gameStarted = false;
this.resetVelocity();
}
else if (this.position.x > width) {
player1Score++;
this.position = new PVector(width/2, height/2);
gameStarted = false;
this.resetVelocity();
}
if (this.position.y < 0) {
this.position.y = 0;
this.velocity.mult(new PVector(1, -1));
}
else if (this.position.y > height) {
this.position.y = height;
this.velocity.mult(new PVector(1, -1));
}
//Handle paddle collisions
this.collideWithPaddle(20, player1Y);
this.collideWithPaddle(width-20, player2Y);
this.position.add(this.velocity);
};
};
ball = new Ball(new PVector(width/2, height/2));
var drawScores = function() {
var s;
fill(255, 255, 255);
textSize(20);
s = "Player 1: " + player1Score;
text(s, width*0.25-textWidth(s)/2, 25);
s = "Player 2: " + player2Score;
text(s, width*0.75-textWidth(s)/2, 25);
};
var updatePlayer2 = function() {
if (abs(player2Y-ball.position.y) < PLAYER_MOVE_SPEED){
player2Y = ball.position.y;
}
else if (player2Y-ball.position.y >= PLAYER_MOVE_SPEED) {
player2Y -= PLAYER_MOVE_SPEED;
}
else if (player2Y-ball.position.y <= PLAYER_MOVE_SPEED) {
player2Y += PLAYER_MOVE_SPEED;
}
};
//Move the player up
var movePlayerUp = function() {
player1Y -= PLAYER_MOVE_SPEED;
};
//Move the player down
var movePlayerDown = function() {
player1Y += PLAYER_MOVE_SPEED;
};
var drawPlayers = function() {
//Constrain the player movement
player1Y = constrain(player1Y, 0, height);
rectMode(CENTER);
fill(255, 255, 255);
rect(20, player1Y, PADDLE_WIDTH, PADDLE_HEIGHT);
rect(width-20, player2Y, PADDLE_WIDTH, PADDLE_HEIGHT);
};
draw = function() {
//Control Player 1
//Draw the environment
background(0, 0, 0);
updatePlayer2();
drawPlayers();
drawScores();
stroke(255, 255, 255);
line(width/2, 0, width/2, height);
//Draw the ball
ball.draw();
if (!gameStarted) {
t++;
if (t >= PAUSE_TIME) {
t = 0;
gameStarted = true;
}
return;
}
ball.update();
};
draw = function() {
if(!gameStarted){
t++;
if(t >= PAUSE_TIME) {
t = 0;
gameStarted = true;
}
return;
}
if (keyIsPressed) {
if (keyCode === UP) {
movePlayerUp();
} else if (keyCode === DOWN) {
movePlayerDown();
}
}
ball.update();
};(6 votes)- Since you assign two different values to the
draw
variable, you may as well delete the first attempt since the second assignment prevails. As such, there is no code that invokes a ball's draw method.(3 votes)
- Org! I felt like it was a mega jump from "Intro to JS" to "Advanced JS: Games & Visualizations" and incorporates difficult concepts suddenly. Is there something that I am missing?(10 votes)
- Yes, it is a big jump. My recommendation is to study other people's programs a lot. This helps extend your knowledge of computer programming a lot. If you want to study a certain program for a while...make a spin-off. It is easy and you can have it for as long as you want.(1 vote)
- I did "Intro to JS" and I understood everything. I felt confident and moved on to "Advanced JS: Games and Visualizations" and now I'm totally lost. I get confused by every article I read, and none of the code makes sense. I've only gotten this far because of the hints offered in the challenges. I try reading answers to people's questions, but they just confuse me even more. HELP!(6 votes)
- I also recently finished the Intro course and I can see what you mean that this one jumps forward a lot at once. What I have found helped a lot is to go back to the Intro course and when a new skill is introduced or a small challenge it had you do, think of a way you could make it cooler to challenge yourself. Then, you can ask questions or google to figure out how to make it inot what you imagined. This will really solidify the skills. For example, on the number analizer challenge that told you if a number was positive, negative, or zero, I took mine and added identifiers for even and odd also. I also made it so that it generated a new random whole number every time you click the screen. It forced me to learn a couple new things and also practice with other skills from the intro course without the prompts to use as a crutch. It's not a race to get through the courses, so taking your time and creating your own opportunities to practice is key. Also browse other people's projects for inspiration and practicing reading other people's code to see how things work together. You can change random numbers to see what changes on the screen to really figure each value out. I hope this is helpful! Good luck!(2 votes)
- "At13:05,I am not sure what to do in step 1 of pong".(6 votes)
- Just add an if function (if up arrow pressed move up, if down arrow pressed move down)(1 vote)
- i can't understand if (grassXs[i] <= -20)
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;
}
}(2 votes)- Remember the Make it Rain project? If you haven't done it, then I highly recommend checking it out. It is just like the code you have, but with raindrops instead of grass.
In the case of this code, there is an array of grass blocks that are each 20 pixels wide and 20 pixels apart. This loop draws each one and subtracts1
from them so that they all move to the left. If any of them are all the way to the left, then set its position all the way to the right and start the cycle all over again. The reason that the conditional statement uses-20
instead of0
is that you would see the block change positions and it would ruin the illusion of movement.
Good luck and happy coding!(6 votes)
- ive been kind of struggling with objects so im not super sure what exactly this is doing or why its there can someone explain?
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);
};(2 votes)Stick
is a constructor function. That means that if you call it usingnew
, likenew Stick()
, it will construct a stick object for you.Stick
takes in two parameters:x
andy
. The two lines inside the function set the stick's x and y properties to the values passed in. For example, if you writenew Stick(100, 200)
, the constructor will setthis.x = 100
andthis.y = 200
. Now your stick object has x = 100 and y = 200, and you can access those properties like so:var stick = new Stick(100, 200); // create a Stick
println(stick.x); // print out our stick's x: 100
println(stick.y); // print out our stick's y: 200
// we can modify the stick's properties:
stick.x += 100; // move the stick 100 to the right
The next line adds a draw function to the Stick constructor's prototype, which is also called a method ofStick
. This draw function draws the stick as a rectangle:rect(this.x, this.y, 5, 40)
. The rectangle's coordinates arethis
particular stick'sx
andy
. We can call this method on our stick usingstick.draw()
, which draws the stick at its coordinates: (100, 200).(5 votes)