Advanced

Classes and OOP

Objects with data *and* behaviour, grouped into families. Learn classes, the `this` keyword, and how the game builds dozens of similar entities from one shape.

What you will see

true 1 true 0 false 0

About 5 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

The classes lesson shows code turning into game mechanics. I run small snippets and watch the exact output, then copy the same pattern the finished game ships.

1

A class bundles data and behavior into one shape. A base Entity stores position, size, and activity; a subclass Player adds jumping. I construct a base entity and a subclass and print their fields so the difference between 'just data' and 'data plus behavior' is concrete.

class Entity { constructor(x, y) { this.x = x; this.y = y; this.active = false } moveRight(px) { this.x += px } } console.log((new Entity(100, 300)).x, (new Entity(100, 300)).y, (new Entity(100, 300)).active)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
100 300 false
An Entity is data, position and an activity flag, with one method. The constructor runs first, setting each field. moveRight is defined but unused here; it shows a class holds both data and the actions that operate on it.
2

super(x, y) in a subclass constructor runs the parent constructor first, then the subclass adds its own fields. This is how a Player gets x, y (from Entity) plus jumpsLeft and sliding. I construct a Player and print shared fields and its own so inheritance is visible.

class Entity { constructor(x, y, w, h) { this.x = x; this.y = y; this.w = w; this.h = h; this.active = false } } class Player extends Entity { constructor(x, y) { super(x, y, 40, 56); this.jumpsLeft = 2 } } let p = new Player(0, 400) console.log(p.w, p.h, p.jumpsLeft, p.active)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
40 56 2 false
super(x, y, 40, 56) runs Entity's constructor, giving the player width 40, height 56, and active false, all inherited fields. Then jumpsLeft = 2 is added. A subclass shares the parent's shape and extends it, so the game builds dozens of similar entities from one class tree.
3

this inside a method refers to the specific object the method runs on. A coin and a spike both have moveLeft, but moveLeft moves THEIR x, not each other's. I build two entities and call moveLeft on just one so the difference between this.x (the caller) and a shared variable is obvious.

class Sprite { constructor(x){ this.x = x } moveLeft(px){ this.x -= px } } let coin = new Sprite(500) let spike = new Sprite(200) coin.moveLeft(100) console.log(coin.x, spike.x)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
400 200
coin.moveLeft(100) subtracts 100 from coin.x only, giving 400, while spike stays at 200. this is bound to whatever is on the left of the dot, the caller. Every entity shares the method but acts on its own data.
4

A class is a template; the constructor is where each instance gets its own fresh values. The game makes a new Player for every run and a new Coin for every pickup. I print two instances to show they start identical but hold independent data, sharing code, not memory.

class Coin { constructor(x){ this.x = x; this.taken = false } } let c1 = new Coin(100) let c2 = new Coin(900) c2.taken = true console.log(c1.x, c1.taken) console.log(c2.x, c2.taken)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
100 false 900 true
c1 and c2 are made from the same class but start at different x and track taken independently. c2.taken = true does not touch c1. A class is shared code; each instance is its own data, that is how the game makes thousands of coins without them interfering.
5

Methods let behavior travel with the data: a Player.jump() only works when jumpsLeft > 0, and takeHit() checks invulnTimer. I write a minimal jump that guards on jumpsLeft and decrement it, printing the result so the method-and-guard pattern is explicit.

class Player { constructor(x){ this.x = x; this.jumpsLeft = 2 } jump() { if (this.jumpsLeft > 0) { this.jumpsLeft--; return true } return false } } let p = new Player(0) console.log(p.jump(), p.jumpsLeft) console.log(p.jump(), p.jumpsLeft) console.log(p.jump(), p.jumpsLeft)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
true 1 true 0 false 0
jump() is a method that reads and mutates this.jumpsLeft. The guard prevents a third jump: once jumpsLeft hits 0 it returns false. The method lives in the class, but each player counts its own jumps, behavior and state stay together.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A class defines a shape and behaviour once; new creates instances that each own their data and run the shared methods.
1 Write a class with a constructor and methods
↓
2 new ClassName(...) runs the constructor to build an instance
↓
3 this inside a method refers to that instance
↓
4 extends copies the parent's shape and adds more
↓
5 Call instance.method() and the right this runs
Why this works
The `this` binding gotcha

this is bound by how the method is called, not by where it's written:

const e = new Entity(10, 20, 40, 56);
const func = e.moveX;   // pull the method off the object
func(5);                // this is NOT e here, it's undefined

When you call a method like e.moveX(5), this = e. If you grab the method away and call it bare, this is lost. In the real game this never happens because methods are always called as object.method().

The same rule applies to arrow functions: an arrow's this is the enclosing scope, not the object. That's why game methods use regular function syntax or arrow methods written on the class body, where this stays bound to the instance.

Why classes matter for games

Without classes you'd write every object by hand, repeating the shape (x, y, w, h, vx, vy) for each of dozens of entities, and every method body typed out again. Add 30 obstacles and you have 30 copies of the same fields.

Classes give you:

  1. One shape, many objects. Define Entity once; new as many as you want.
  2. Behaviour on the object. player.jump(), coin.draw(ctx): the logic lives where the data does.
  3. Inheritance. Ground, coins, hazards, and the player all share Entity, adding only what differs.

The game's entities/ folder follows this exactly: Entity.js is the base, and each subtype overrides update(dt) and draw(ctx). The main loop just calls entity.update(dt) and entity.draw(ctx) on every object, and each class decides what to do, because they all share the same shape.

Examples

The "shape once, objects many" idea

Our game has dozens of similar things: the player, many obstacles, many coins, many ground segments. Each is an object of the same shape but with different behaviour. A class is a stencil, describe the shape once, make fresh objects from it.

class Entity {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.vx = 0;    // velocity (how fast x/y change)
    this.vy = 0;
  }
}

The class keyword says "here's the recipe." Each object built from it is a distinct thing with its own x, y, w, h.

OOP (Object-Oriented Programming) is the idea of objects with data and behaviour, grouped into families. In this game it's the single most important idea.

Building objects with `new`

The keyword new runs the class and hands you a fresh object:

const a = new Entity(10, 20, 40, 56);
const b = new Entity(100, 300, 20, 20);

console.log(a.x, a.w);   // 10 40
console.log(b.x, b.w);   // 100 20

Each new Entity(...) call makes a separate object. Change a.x and b.x stays untouched. The constructor (constructor(...)) is special code that runs once when an object is born, it's where you store the starting data.

Methods are what an object does, functions stored as object properties:

class Entity {
  constructor(x, y, w, h) {
    this.x = x; this.y = y; this.w = w; this.h = h;
  }
  moveX(pxPerFrame) {
    this.x += pxPerFrame;
  }
}

The magic word is this. Inside a method, this is the object the method was called on. Call it on another object and this points to that one, so the same method code works for every object.

Inheritance: a family of classes

Not every Entity moves the same way. The player auto-runs and jumps. A ground segment just sits there and draws itself. But they share the shape.

Inheritance lets a new class copy a parent class's shape and methods, then add its own:

class Ground extends Entity {
  draw(ctx) {          // ground only draws itself
    ctx.fillStyle = '#3a2f28';
    ctx.fillRect(this.x, this.y, this.w, this.h);
  }
}

class Player extends Entity {
  jump() {             // player can jump
    this.vy = CONFIG.JUMP_IMPULSE;
  }
}

const g = new Ground(0, 450, 320, 90);
const p = new Player(120, 300);
p.jump();   // Player has jump(); Ground doesn't

extends copies the parent's constructor and methods, so Ground and Player both get x, y, w, h, vx, vy for free, each adds only what makes it different.

This is exactly the game's hierarchy: Entity is the base, and Player, Coin, hazards, and Ground each extends Entity, adding their own update and draw.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← The Game Loop
Previous Lesson
The Game Loop
Next Lesson
Vectors and Physics
Vectors and Physics →