Writing Classes with Inheritance

Requirement

Create minimum 2 custom character classes extending base classes to demonstrate understanding of object-oriented design and inheritance.

Evidence

I created three custom classes that extend base game engine classes, each adding specialized functionality:


1. Ghost.js - Extends Enemy

Location: _projects/games/castle-game/levels/Ghost.js

import Enemy from '@assets/js/GameEnginev1.1/essentials/Enemy.js';
import Player from '@assets/js/GameEnginev1.1/essentials/Player.js';
import showDeathScreen from './DeathScreen.js';

class Ghost extends Enemy {
    constructor(data, gameEnv) {
        super(data, gameEnv);
        this.followSpeedFactor = data?.followSpeedFactor ?? 0.4;
        this.followStopDistance = data?.followStopDistance ?? 8;
        this._hasTriggeredDeath = false;
    }

    getPlayer() {
        return this.gameEnv?.gameObjects?.find(obj => obj instanceof Player) || null;
    }

    followPlayer(player) {
        if (!player) return;

        const ghostCenter = this.getCenter();
        const playerCenter = typeof player.getCenter === 'function'
            ? player.getCenter()
            : { x: player.x || 0, y: player.y || 0 };

        const dx = playerCenter.x - ghostCenter.x;
        const dy = playerCenter.y - ghostCenter.y;
        const distance = Math.hypot(dx, dy);

        if (distance <= this.followStopDistance) {
            this.velocity.x = 0;
            this.velocity.y = 0;
            return;
        }
        // ... rest of method
    }
}

export default Ghost;

How it meets the requirement:

  • Extends Enemy: class Ghost extends Enemy inherits all Enemy class functionality
  • Custom Constructor: Calls super(data, gameEnv) to initialize parent class
  • Specialized Methods: Adds followPlayer() method for AI behavior specific to Ghost enemies
  • Type Checking: Uses instanceof Player for polymorphic behavior

2. DeathBarrier.js - Extends Barrier

Location: _projects/games/castle-game/levels/DeathBarrier.js

import Barrier from '@assets/js/GameEnginev1.1/essentials/Barrier.js';
import showDeathScreen from './DeathScreen.js';

class DeathBarrier extends Barrier {
    constructor(data, gameEnv) {
        super(data, gameEnv);
        this._hasTriggeredDeath = false;
        // Store the level start time - all barriers share this for grace period
        if (!DeathBarrier.levelStartTime) {
            DeathBarrier.levelStartTime = new Date();
        }
    }

    update() { // Checks for collisions, if triggered then uses the below code
        super.update();

        if (this._hasTriggeredDeath) return; // if the death is already triggered no need to trigger it again

        const player = this.gameEnv?.gameObjects?.find(obj => obj.constructor?.name === 'Player');
        if (!player || !player.canvas || !this.canvas) return;

        this.isCollision(player);

        if (this.collisionData?.hit) {
            // Check grace period from level start time
            const timeSinceLevelStart = new Date() - DeathBarrier.levelStartTime;
            if (timeSinceLevelStart < 500) {
                console.log('[MazeDebug] Grace period active:', timeSinceLevelStart, 'ms');
                return; // we added a grace period because there was an error where the barrier would kill the player before they could even play
            }
            // ... rest of method
        }
    }

    // Reset level start time when level restarts
    static resetLevelStartTime() {
        DeathBarrier.levelStartTime = new Date();
    }
}

export default DeathBarrier;

How it meets the requirement:

  • Extends Barrier: class DeathBarrier extends Barrier inherits collision detection from Barrier
  • Custom Constructor: Uses super(data, gameEnv) and adds static state management
  • Method Override: Overrides update() to add custom collision response
  • Static Method: Implements resetLevelStartTime() as a static utility method

3. SpriteSheetCoin.js - Extends Coin

Location: _projects/games/castle-game/levels/SpriteSheetCoin.js

import Coin from '@assets/js/GameEnginev1.1/Coin.js';

/**
 * SpriteSheetCoin extends the basic Coin class to support image/spritesheet rendering.
 * Instead of a colored circle, you can now display any image asset (gem, star, etc).
 */
class SpriteSheetCoin extends Coin {
	constructor(data = null, gameEnv = null) {
		super(data, gameEnv);
		
		this.spriteImagePath = data?.spriteImagePath || null;
		this.spriteImage = null;
		this.isImageLoaded = false;
		this.spriteFrames = data?.spriteFrames || { rows: 1, columns: 1, frameIndex: 0 };
		this.fallbackToCircle = data?.fallbackToCircle !== false; // Default true
		
		// Load the image if provided
		if (this.spriteImagePath) {
			this.loadImage();
		}
	}

	/**
	 * Load the sprite image asynchronously
	 */
	loadImage() {
		const img = new Image();
		img.onload = () => {
			this.spriteImage = img;
			this.isImageLoaded = true;
			console.log(`SpriteSheetCoin image loaded: ${this.spriteImagePath}`);
		};
		img.onerror = () => {
			console.warn(`Failed to load SpriteSheetCoin image: ${this.spriteImagePath}`);
			this.isImageLoaded = false;
		};
		img.src = this.spriteImagePath;
	}

	/**
	 * Draw the coin using spritesheet if available, otherwise fall back to colored circle
	 */
	draw() {
		if (!this.ctx) return;
		
		// Clear the canvas
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
		
		if (this.collected) return;
		
		// Draw sprite image if loaded
		if (this.isImageLoaded && this.spriteImage) {
			this.drawSpriteImage();
		} else if (this.fallbackToCircle) {
			// Fall back to the original colored circle
			this.drawCircle();
		}
	}
}

export default SpriteSheetCoin;

How it meets the requirement:

  • Extends Coin: class SpriteSheetCoin extends Coin adds rendering functionality to collectibles
  • Specialized Constructor: Calls super(data, gameEnv) and adds spritesheet-specific properties
  • Method Override: Overrides draw() method to render sprites instead of colored circles
  • Async Loading: Implements loadImage() for asynchronous asset loading
  • Fallback Logic: Includes graceful degradation with fallbackToCircle option

Summary

All three custom classes demonstrate: ✅ Inheritance - Each extends a base game engine class
Constructor Chaining - All call super() to initialize parents
Specialized Behavior - Each adds unique methods for specific game mechanics
Code Reuse - Inherit common functionality (collision, rendering, etc.) from base classes
Polymorphism - Each behaves differently despite extending engine classes