If you're seeing this message, it means we're having trouble loading external resources on our website.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

Main content

Forest 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?

  • orange juice squid orange style avatar for user Mr. Granger
    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?
    (46 votes)
    Default Khan Academy avatar avatar for user
  • aqualine ultimate style avatar for user Cole
    i'm probly a very simple Javascript user but i can't and don't get anything of what she is saying.
    (22 votes)
    Default Khan Academy avatar avatar for user
    • piceratops tree style avatar for user JeffGeorgeCINY
      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.
      (44 votes)
  • starky tree style avatar for user vrcoelho
    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 this var sticks = new Stick (" ");?
    How does this sticks.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...
    (16 votes)
    Default Khan Academy avatar avatar for user
    • leafers tree style avatar for user charkittycat
      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 using sticks.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.
      (13 votes)
  • leaf red style avatar for user 𝓐wesomecookie1000 - baack
    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)
    Default Khan Academy avatar avatar for user
  • winston baby style avatar for user Nguyen Do Nguyen Anh
    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)
    Default Khan Academy avatar avatar for user
  • piceratops ultimate style avatar for user Carsten
    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?
    (9 votes)
    Default Khan Academy avatar avatar for user
    • orange juice squid orange style avatar for user Ben S
      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.
      (2 votes)
  • hopper cool style avatar for user Yathartha
    "At ,I am not sure what to do in step 1 of pong".
    (6 votes)
    Default Khan Academy avatar avatar for user
  • mr pants purple style avatar for user Giant Squid
    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!
    (4 votes)
    Default Khan Academy avatar avatar for user
    • starky ultimate style avatar for user menialOfficial
      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)
  • sneak peak blue style avatar for user hanen-khaled
    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)
    Default Khan Academy avatar avatar for user
    • area 52 yellow style avatar for user PT [inactive]
      Stick is a constructor function. That means that if you call it using new, like new Stick(), it will construct a stick object for you.
      Stick takes in two parameters: x and y. The two lines inside the function set the stick's x and y properties to the values passed in. For example, if you write new Stick(100, 200), the constructor will set this.x = 100 and this.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 of Stick. This draw function draws the stick as a rectangle: rect(this.x, this.y, 5, 40). The rectangle's coordinates are this particular stick's x and y. We can call this method on our stick using stick.draw(), which draws the stick at its coordinates: (100, 200).
      (5 votes)
  • purple pi purple style avatar for user imen
    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)
    Default Khan Academy avatar avatar for user
    • old spice man blue style avatar for user Flostin(READ BIO)
      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 subtracts 1 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 of 0 is that you would see the block change positions and it would ruin the illusion of movement.

      Good luck and happy coding!
      (5 votes)