Writing Classes

  • Create minimum 2 custom character classes extending base classes
  • Code review: Player.js, NPC.js, Enemy.js,
  • Found in any level

Level 1: Character Classes

Challenge

2 Classes Demo

Lines: 1 Characters: 0
Game Status: Not Started

Methods & Parameters

  • Implement methods with parameters (e.g., collisionHandler(other, direction))
  • Code review: Method signatures with 2+ parameters

Possibly battle bus in level 3??

Instantiation & Objects

  • Instantiate game objects in GameLevel configuration
  • Code review: GameLevel setup objects

Level 1: The constructer creates data objects

Inheritance (Basic)

  • Create class hierarchy with 2+ levels (e.g., GameObject → Character → Player)
  • Code review: extends keyword, inheritance chain

Challenge

Wall repetition, rather than individually placing them

Lines: 1 Characters: 0
Game Status: Not Started

Challenge

Step Counter Demo

Lines: 1 Characters: 0
Game Status: Not Started

Method Overriding

  • Override parent methods (update(), draw(), handleCollision())
  • Code review: Polymorphic implementations

Challenge

Step Counter Demo

Lines: 1 Characters: 0
Game Status: Not Started
  • Or additionally, we use gravity.
     update() {
          super.update();
          if(!this.moved){
              if (this.gravity) {
                      this.time += 1;
                      this.velocity.y += 0.5 + this.acceleration * this.time;
                  }
              }
          else{
              this.time = 0;
          }
          }
    

Constructor Chaining

  • Use super() to chain constructors
  • Code review: super(data, gameEnv) calls
    constructor(data = null, gameEnv = null) {
          super(data, gameEnv);
          this.interact = data?.interact; // Interact function
          this.currentQuestionIndex = 0;
          this.alertTimeout = null;
          this.isInteracting = false; // Flag to track if currently interacting
          this.handleKeyDownBound = this.handleKeyDown.bind(this);
          this.handleKeyUpBound = this.handleKeyUp.bind(this);
          this.bindInteractKeyListeners();
    

CONTROL STRUCTURES

Iteration

  • Use loops for game object arrays, animation frames
  • Code review: for, forEach, while loops

Level 1: Wall Classes

  • repetition of walls
    const wallClasses = mazeWalls.map(wall => ({ ... }));
    
%%js

// GAME_RUNNER: Wall repetition, rather than individually placing them

// Import for GameRunner
import GameControl from '/assets/js/GameEnginev1/essentials/GameControl.js';
// Level Code
import GameEnvBackground from '/assets/js/GameEnginev1/essentials/GameEnvBackground.js';
import Player from '/assets/js/GameEnginev1/essentials/Player.js';
import Barrier from '/assets/js/GameEnginev1.1/essentials/Barrier.js'

class CustomLevel {
  constructor(gameEnv) {
    const path = gameEnv.path;
    const width = gameEnv.innerWidth;
    const height = gameEnv.innerHeight;
    
    const bgData = {
        name: 'custom_bg',
        src: path + "/images/gamebuilder/bg/clouds.jpg",
        pixels: { height: 720, width: 1280 }
    };
    const playerData = {
      id: 'playerData',
            src: path + "/images/gamebuilder/sprites/kirby.png",
            SCALE_FACTOR: 5,
            STEP_FACTOR: 1000,
            ANIMATION_RATE: 50,
            INIT_POSITION: { x: 100, y: 300 },
            pixels: { height: 36, width: 569 },
            orientation: { rows: 1, columns: 13 },
            down: { row: 0, start: 0, columns: 3 },
            downRight: { row: 0, start: 0, columns: 3, rotate: Math.PI/16 },
            downLeft: { row: 0, start: 0, columns: 3, rotate: -Math.PI/16 },
            left: { row: 0, start: 0, columns: 3 },
            right: { row: 0, start: 0, columns: 3 },
            up: { row: 0, start: 0, columns: 3 },
            upLeft: { row: 0, start: 0, columns: 3, rotate: Math.PI/16 },
            upRight: { row: 0, start: 0, columns: 3, rotate: -Math.PI/16 },
            hitbox: { widthPercentage: 0, heightPercentage: 0 },
            keypress: { up: 87, left: 65, down: 83, right: 68 }
            };
    const mazeWalls = [
            { x: 0, y: 0, width: width, height: 20 },
            { x: 0, y: height - 20, width: width, height: 20 },
            { x: width * 0.2, y: 0, width: 20, height: height * 0.6 },
            { x: width * 0.4, y: height * 0.4, width: 20, height: height * 0.6 },
            { x: width * 0.6, y: 0, width: 20, height: height * 0.6 },
            { x: width * 0.8, y: height * 0.4, width: 20, height: height * 0.6 }
        ];

        const wallClasses = mazeWalls.map(wall => ({
            class: Barrier,
            data: { id: "wall_" + Math.random(), x: wall.x, y: wall.y, width: wall.width, height: wall.height, visible: false }
        }));

    this.classes = [
      { class: GameEnvBackground, data: bgData },
      { class: Player, data: playerData },
      ...wallClasses
    ];
  }
}
export const gameLevelClasses = [CustomLevel];
export { GameControl };

Conditionals

  • Implement collision detection, state transitions
  • Code review: if/else, nested conditions

Level 2: Message

if (steps > STEP_GOAL) {
message.textContent = "You didn't make it...";
gameOver = true;
}

Nested Conditions

  • Complex game logic (e.g., power-up + collision + direction)
  • Code review: Multi-level conditionals

Level 2: Teleporting Garrett

reaction: function() {
    if (!this.teleported) return; // Level 1
    if (window.currentSteps <= window.stepGoal) { // Level 2
        alert("Win");
    } else {
        alert("Loss");
    }
}

DATA TYPES

Numbers

  • Position, velocity, score tracking
  • Code review: Numeric properties

    Level 2: Step Counter

    const STEP_GOAL = 200;
    steps++;
    

Strings

  • Character names, sprite paths, game states
  • Code review: String manipulation

    Level 2: Message

    message.textContent = "You didn't make it to Garrett in time!";
    window.location.href = "timmycounter.html";
    

    Booleans

  • Flags (isJumping, isPaused, isVulnerable)
  • Code review: Boolean logic

Level 2: Music!

let musicStarted = false;
let gameOver = false;
this.teleported = true;

Arrays

  • Game object collections, level data
  • Code review: Array operations

Level 1: Player Data

this.classes = [      { class: GameEnvBackground, data: bgData },
      { class: Player, data: playerData },
      { class: Npc, data: npcData1 },
      { class: Npc, data: npcData2 },
      { class: Npc, data: npcData3 },
      { class: Barrier, data: dbarrier_1 }
];

];

Objects (JSON)

  • Configuration objects, sprite data
  • Code review: Object literals

Level 1: Any NPC or Player data, EX is from Garrett’s data

const npcData1 = {
            id: 'Garrett The Popcorn Man',
            greeting: 'Hi! I\'m Garrett!',
            src: path + "/images/gamebuilder/sprites/GarettThePopcornMan.png",
            SCALE_FACTOR: 1,
            ANITION_RATE: 50,
            INIT_POSITION: { x: 650, y: 540 },
            pixels: { height: 523, width: 477 },
            orientation: { rows: 1, columns: 1 },
            down: { row: 0, start: 0, columns: 1 },
            hitbox: { widthPercentage: 0.1, heightPercentage: 0.2 },
            dialogues: [
                "Welcome to Timmy's Fun World! I'm Garrett! Oh, and by the way, be wary of that circus tent, the Invisible Maze lies within...  Want some popcorn?",
            ],
            reaction: function() { if (this.dialogueSystem) { this.showReactionDialogue(); } else { console.log(this.greeting); } },
            interact: function() { if (this.dialogueSystem) { this.showRandomDialogue(); } }
        };

OPERATORS

Mathematical

  • Physics calculations (gravity, velocity, collision)
  • Code review: +, -, *, / in physics

Level 2: Step Counter

%%js

// GAME_RUNNER: Step Counter Demo

// Import for GameRunner
import GameControl from '/assets/js/GameEnginev1/essentials/GameControl.js';
// Level Code
import GameEnvBackground from '/assets/js/GameEnginev1/essentials/GameEnvBackground.js';
import Player from '/assets/js/GameEnginev1/essentials/Player.js';
import Barrier from '/assets/js/GameEnginev1.1/essentials/Barrier.js'

class CustomLevel {
  constructor(gameEnv) {
    const path = gameEnv.path;
    const width = gameEnv.innerWidth;
    const height = gameEnv.innerHeight;
    
    const bgData = {
        name: 'custom_bg',
        src: path + "/images/gamebuilder/bg/clouds.jpg",
        pixels: { height: 720, width: 1280 }
    };
    const playerData = {
      id: 'playerData',
            src: path + "/images/gamebuilder/sprites/kirby.png",
            SCALE_FACTOR: 5,
            STEP_FACTOR: 1000,
            ANIMATION_RATE: 50,
            INIT_POSITION: { x: 100, y: 300 },
            pixels: { height: 36, width: 569 },
            orientation: { rows: 1, columns: 13 },
            down: { row: 0, start: 0, columns: 3 },
            downRight: { row: 0, start: 0, columns: 3, rotate: Math.PI/16 },
            downLeft: { row: 0, start: 0, columns: 3, rotate: -Math.PI/16 },
            left: { row: 0, start: 0, columns: 3 },
            right: { row: 0, start: 0, columns: 3 },
            up: { row: 0, start: 0, columns: 3 },
            upLeft: { row: 0, start: 0, columns: 3, rotate: Math.PI/16 },
            upRight: { row: 0, start: 0, columns: 3, rotate: -Math.PI/16 },
            hitbox: { widthPercentage: 0, heightPercentage: 0 },
            keypress: { up: 87, left: 65, down: 83, right: 68 }
            };
    window.addEventListener("load", () => {
            // 1. Browser Alert - This acts as the "user interaction" needed to play audio
            alert("Catch me if you can! -Garrett");

            // 2. Play music immediately after clicking 'OK'
            music.play().catch(err => console.log("Audio waiting for interaction:", err));

            const STEP_GOAL = 300;
            window.currentSteps = 0;
            window.stepGoal = STEP_GOAL;

            this.createLeaderboardUI();

            const hud = document.createElement("div");
            hud.style.cssText = "position:fixed; bottom:20px; left:50%; transform:translateX(-50%); z-index:10000;";
            document.body.appendChild(hud);

            const stepCounterEl = document.createElement("div");
            stepCounterEl.style.cssText = "color:white; font-size:26px; font-family:Arial; background:rgba(0,0,0,0.85); padding:10px 18px; border-radius:10px; border: 2px solid #ffd700;";
            stepCounterEl.textContent = "Steps: 0 / " + STEP_GOAL;
            hud.appendChild(stepCounterEl);

            document.addEventListener("keydown", (e) => {
                const movementKeys = [87, 65, 83, 68];
                if (movementKeys.includes(e.keyCode)) {
                    window.currentSteps++;
                    stepCounterEl.textContent = `Steps: ${window.currentSteps} / ${STEP_GOAL}`;
                    
                    if (window.currentSteps > STEP_GOAL * 0.8) {
                        stepCounterEl.style.color = "#ff4d4d";
                    }
                }
            });
        });

    this.classes = [
      { class: GameEnvBackground, data: bgData },
      { class: Player, data: playerData },
    ];
  }
}
export const gameLevelClasses = [CustomLevel];
export { GameControl };
%%js

// GAME_RUNNER: Step Counter Demo

// Import for GameRunner
import GameControl from '/assets/js/GameEnginev1/essentials/GameControl.js';
// Level Code
import GameEnvBackground from '/assets/js/GameEnginev1/essentials/GameEnvBackground.js';
import Player from '/assets/js/GameEnginev1/essentials/Player.js';
import Barrier from '/assets/js/GameEnginev1.1/essentials/Barrier.js'

class CustomLevel {
  constructor(gameEnv) {
    const path = gameEnv.path;
    const width = gameEnv.innerWidth;
    const height = gameEnv.innerHeight;
    
    const bgData = {
        name: 'custom_bg',
        src: path + "/images/gamebuilder/bg/clouds.jpg",
        pixels: { height: 720, width: 1280 }
    };
    const playerData = {
      id: 'playerData',
            src: path + "/images/gamebuilder/sprites/kirby.png",
            SCALE_FACTOR: 5,
            STEP_FACTOR: 1000,
            ANIMATION_RATE: 50,
            INIT_POSITION: { x: 100, y: 300 },
            pixels: { height: 36, width: 569 },
            orientation: { rows: 1, columns: 13 },
            down: { row: 0, start: 0, columns: 3 },
            downRight: { row: 0, start: 0, columns: 3, rotate: Math.PI/16 },
            downLeft: { row: 0, start: 0, columns: 3, rotate: -Math.PI/16 },
            left: { row: 0, start: 0, columns: 3 },
            right: { row: 0, start: 0, columns: 3 },
            up: { row: 0, start: 0, columns: 3 },
            upLeft: { row: 0, start: 0, columns: 3, rotate: Math.PI/16 },
            upRight: { row: 0, start: 0, columns: 3, rotate: -Math.PI/16 },
            hitbox: { widthPercentage: 0, heightPercentage: 0 },
            keypress: { up: 87, left: 65, down: 83, right: 68 }
            };
    window.addEventListener("load", () => {
            // 1. Browser Alert - This acts as the "user interaction" needed to play audio
            alert("Catch me if you can! -Garrett");

            // 2. Play music immediately after clicking 'OK'
            music.play().catch(err => console.log("Audio waiting for interaction:", err));

            const STEP_GOAL = 300;
            window.currentSteps = 0;
            window.stepGoal = STEP_GOAL;

            this.createLeaderboardUI();

            const hud = document.createElement("div");
            hud.style.cssText = "position:fixed; bottom:20px; left:50%; transform:translateX(-50%); z-index:10000;";
            document.body.appendChild(hud);

            const stepCounterEl = document.createElement("div");
            stepCounterEl.style.cssText = "color:white; font-size:26px; font-family:Arial; background:rgba(0,0,0,0.85); padding:10px 18px; border-radius:10px; border: 2px solid #ffd700;";
            stepCounterEl.textContent = "Steps: 0 / " + STEP_GOAL;
            hud.appendChild(stepCounterEl);

            document.addEventListener("keydown", (e) => {
                const movementKeys = [87, 65, 83, 68];
                if (movementKeys.includes(e.keyCode)) {
                    window.currentSteps++;
                    stepCounterEl.textContent = `Steps: ${window.currentSteps} / ${STEP_GOAL}`;
                    
                    if (window.currentSteps > STEP_GOAL * 0.8) {
                        stepCounterEl.style.color = "#ff4d4d";
                    }
                }
            });
        });

    this.classes = [
      { class: GameEnvBackground, data: bgData },
      { class: Player, data: playerData },
    ];
  }
}
export const gameLevelClasses = [CustomLevel];
export { GameControl };

String Operations

  • Path concatenation, text display
  • Code review: Template literals, concatenation

Level 1: Background Paths

src: path + "/images/gamebuilder/sprites/kirby.png",
src: path + "/images/gamebuilder/bg/TimmyGreatBg.png",
stepCounterEl.textContent = "Steps: " + steps + " / " + STEP_GOAL;

Boolean Expressions

  • Compound conditions in game logic
  • Code review: &&,   , !

Level 1/2: Game Logic

if (gameOver && e.keyCode === 82) {
    steps = 0;

```.js if (!this.listenerAdded) { this.listenerAdded = true; // … logic }

// From GameLevelTimmyfuncounter.js if (!musicStarted) { music.play(); musicStarted = true; }


#  INPUT/OUTPUT
### Keyboard Input
- Arrow keys, space, WASD controls using event listeners
- Testing: Key event handlers respond correctly
```.js
setTimeout(() => {
                playerRef = gameEnv.gameObjects.find(obj => obj.id === 'playerData');
            }, 500);

            document.addEventListener("keydown", (e) => {

                const movementKeys = [87,65,83,68];
                if (movementKeys.includes(e.keyCode)) {

                    steps++;
                    window.currentSteps = steps;