OOP - Writing Classes (Inheritance)
How I implement custom classes extending base classes
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 Enemyinherits 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 Playerfor 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 Barrierinherits 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 Coinadds 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
fallbackToCircleoption
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