JS Background lesson
Review the YAML, HTML, and JavaScript code for the background and player. Provide comments and feedback on each section.
Background Game
A background and flying object start the brain thinking…
- Can we add keys movements?
- How could we make another object?
- How does something like this example turn into a game like Flappy Bird?
Learning Goals
The goals of this challenge are much simpler than the brainstorming mentioned above.
- Can we determine which files are required to run the game and move them into our personal student repository?
- Can we identify the different sections of code and know the different comment requirements? (YAML, HTML, JavaScript)
- Can we locate and comment on the key code blocks in the game? As you do this, you may ask yourself:
- What do we see when running the game?
- Can we relate each code block to the visual elements it creates?
Code Overview
Frontmatter Images
These lines are YAML key-value pairs used to define the images for the main objects in the game: the center object and the moving background.
sprite: /images/platformer/sprites/flying-ufo.png
background: /images/platformer/backgrounds/alien_planet1.jpg
Move code to personal Repo
Obtain code and assets for the game using the VSCode terminal.
Obtain Code
Transfer markdown file from GitHub OCS repo to VSCode workspace.
mkdir -p hacks
wget https://raw.githubusercontent.com/Open-Coding-Society/pages/refs/heads/main/hacks/background.md -O hacks/background.md
Obtain Assets
Transfer assets referenced in the YAML section of the code to your VSCode workspace.
mkdir -p images/platformer/sprites
wget https://raw.githubusercontent.com/Open-Coding-Society/pages/refs/heads/main/images/platformer/sprites/flying-ufo.png -O images/platformer/sprites/flying-ufo.png
mkdir -p images/platformer/backgrounds
wget https://raw.githubusercontent.com/Open-Coding-Society/pages/refs/heads/main/images/platformer/backgrounds/alien_planet1.jpg -O images/platformer/backgrounds/alien_planet1.jpg
Modify Code
Open each file in VSCode to verify the transfer. It is easiest to click on them in Source Control
and then open them in Explorer
.
Verify the location of the files and make minor changes to the YAML
in the markdown file.
- hacks/background.md
- frontmatter changes
- change “opencs” to “base”
- remove 1st forward slashes in the image names
- script/javascript changes
- change name of object list from
objects
togameObjects
, there are two locations to change. This name is for clarity of ready code.
- change name of object list from
- frontmatter changes
- images/platformer/backgrounds/flying-ufo.png
- images/platformer/backgrounds/alien_planet.jpg
Canvas of the Game
Now that you have the game running, let’s talk about the code.
In order for anything to show up on your screen in the first place, we have to create a canvas
for everything to be drawn on. The canvas
is defined in HTML
.
<canvas id="world"></canvas>
The code in the game needs to connect to the Document Object Model (DOM) element for the canvas. We have named the HTML element’s id world
, and we will define a GameWorld
class to manage the canvas
.
We’re going to need to declare variables inside the GameWorld class using the keyword this
.
this.canvas = document.getElementById("world");
// document.getElementById("world") finds the <canvas id="world"> element in the DOM.
this.ctx = canvas.getContext('2d');
// every canvas has a drawing context. for example, getContext('2d') returns the 2D drawing API (methods like drawImage, fillRect, clearRect).
// canvas is the DOM element; ctx is what draws pixels
Sizing the Canvas
Toward the bottom of the JavaScript <script>
tag, just before the closing </script>
tag, you will find the GameWorld
class. We have defined this code to manage everything that writes to the canvas, aka game world.
We need to setup the GameWorld
- The
canvas
is setup to match window screen. Look at all thethis.canvas
commands. - The
ctx
is used to draw into the canvas - The
game objects
are assigned to thethis.gameObjects
array. These game pieces are inserted into the GameWorld. - In object oriented programming language…
- The
GameWorld
has-aBackground
- The
GameWorld
has-aPlayer
- The
class GameWorld {
static gameSpeed = 5;
constructor(backgroundImg, spriteImg) {
this.canvas = document.getElementById("world");
this.ctx = this.canvas.getContext('2d');
this.width = window.innerWidth;
this.height = window.innerHeight;
this.canvas.width = this.width;
this.canvas.height = this.height;
this.canvas.style.width = `${this.width}px`;
this.canvas.style.height = `${this.height}px`;
this.canvas.style.position = 'absolute';
this.canvas.style.left = `0px`;
this.canvas.style.top = `${(window.innerHeight - this.height) / 2}px`;
this.objects = [
new Background(backgroundImg, this),
new Player(spriteImg, this)
];
Here are a couple of definitions that could be commented in the above code block.
canvas.width
, canvas.height
: set the drawing buffer size (pixel resolution of the canvas).
canvas.style.width
, canvas.style.height
: set the CSS size (how big it appears on screen).
They set both equal so the drawing area matches the visible size.
Game Objects
In our game, everything that appears on the screen (like the alien planet background or the UFO sprite) can be thought of as a game object.
Instead of writing separate code for each image, we create a class that acts like a blueprint. This blueprint is defined in the class GameObject
class GameObject {
constructor(image, width, height, x = 0, y = 0, speedRatio = 0) {
this.image = image; // what picture to draw
this.width = width; // how wide to draw it
this.height = height; // how tall to draw it
this.x = x; // where it is horizontally
this.y = y; // where it is vertically
this.speedRatio = speedRatio;
this.speed = gameSpeed * this.speedRatio; // how fast it moves
}
update() {
// gets filled in by subclasses (like Background)
}
draw(ctx) {
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
}
}
Instead of hardcoding the background or sprite separately, we can now make them instances of the GameObject
class. Look for the Background
and Player
classes in the <script>
code.
Since the Background
and Player
classes use the keyword extends in their definition, they inherit
the properties of GameObject
. Inheritance is a key to OOP programming and using OOP language we say.
- The
Background
is-aGameObject
- The
Player
is-aGameObject
Background Object
Our background is a special game object: it has to move sideways forever to look like the player is moving. To make it seamless, we draw two copies of the background image side-by-side. As one scrolls off the screen, the other one takes its place.
class Background extends GameObject {
constructor(image, gameWorld) {
// Fill entire canvas
super(image, gameWorld.width, gameWorld.height, 0, 0, 0.1);
}
update() {
this.x = (this.x - this.speed) % this.width;
}
draw(ctx) {
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
ctx.drawImage(this.image, this.x + this.width, this.y, this.width, this.height);
}
}}
The standard part of Background
is it uses a super
call to GameObject
to define it’s this
attributes like this.x, this.y, etc.
The special part is that it overrides
or customizes the update()
and draw()
methods for its unique behavior.
update()
moves the background a little bit every frame.
The % this.width (modulo) makes sure it “wraps around” so the background never disappears.
draw()
paints two backgrounds so there’s no empty gap.
The Game Loop (Animation)
The most important part of a game is the gameLoop
that keeps running continuously. This is where things get updated and redrawn, frame after frame.
gameLoop() {
this.ctx.clearRect(0, 0, this.width, this.height);
for (const obj of this.objects) {
obj.update();
obj.draw(this.ctx);
}
requestAnimationFrame(this.gameLoop.bind(this));
}
start() {
this.gameLoop();
}
clearRect
wipes the screen so old frames so they don’t overlap.
for
cycles through every gameObject we defined with the GameWorld constructor.
update
typically changes object positions.
draw
places the image onto the canvas according to recent updates.
requestAnimationFrame
tells the browser to “do this again on the next frame.”
That’s what makes the background look alive — it’s being redrawn 60 times per second!
OOP Model for Game
GameObject is the base class. Background and Player are subclasses (is-a relationship). GameWorld has-a Background and Player (composition).
classDiagram
GameWorld "1" o-- "many" GameObject : has-many
GameObject <|-- Background : is-a
GameObject <|-- Player : is-a
class GameWorld {
+GameObject[] gameObjects
+gameLoop()
+start()
}
class GameObject {
+image
+width
+height
+x
+y
+speedRatio
+speed
+update()
+draw(ctx)
}
class Background {
+update()
+draw(ctx)
}
class Player {
+draw(ctx)
}
Hacks (Objective Assessment)
The Player object has both standard
and special
methods in relation to GameObject
. Be sure to make comments on both the standard and unique behaviors.
Discuss Player Object update
class Player extends GameObject {
// code omitted
update() {
// Comment on what is going on here
}
}
Make comments throughout the Game code
Commenting is key to understanding. The code has been spaced with blank lines to separate distinct sections. Make comments in every section, working with your trio. Try to do this individually and then in live share. Use Chat to help verify your comments, but your teacher will expect you to be able to explain them.
Here are comment styles that will work in JavaScript:
- Single line or end of line comment
//
- Multi line commets starting line
/*
ending line*/
Make your own scene
There are backgrounds and sprites in images/platform. Locate them or find your own. Make a change to the Background and Sprite by modifying the YAML according to your located files.