CS111 Evidence: Object-Oriented Programming
Object-Oriented Programming (OOP) Evidence
🔹 1. Writing Classes
- 📌 Requirement: Create at least 2 custom classes.
- 📝 Files:
Bullet.js,RedRidingMusic.js, andSplineBarrier.js.
- What it is: A class is a blueprint. It groups related data and functions together into one neat object instead of using separate, messy variables.
- How I used it: The
Bulletclass groups position and speed properties so the game can spawn many bullets easily. I also built separate classes to manage background music tracks and path-based collision boundaries. - Why it helps: Each object tracks its own data independently. This cleans up the global scope and stops unrelated parts of the game from breaking each other.
class Bullet {
// Sets up a new bullet with position, speed, and size
constructor(data) {
this.x = data.x; // Where the bullet is horizontally
this.y = data.y; // Where the bullet is vertically
this.velocity = data.velocity || { x: 0, y: 0 }; // How fast it moves
this.gameEnv = data.gameEnv;
this.shooter = data.shooter; // Who shot it (player or enemy)
this.direction = data.direction || 'down';
this.width = 20; // Bullet width
this.height = 20; // Bullet height
this.lifetime = 3000; // Despawn timer (3 seconds)
this.creationTime = Date.now();
this.destroyed = false; // True when the bullet hits something or expires
}
}
class RedRidingMusic {
// Handles the background music settings
constructor() {
this.audio = null; // Holds the actual music file
this.started = false;
this.isPlaying = false; // Makes sure music doesn't play twice at once
this.endpoint = 'https://itunes.apple.com/search?term=little+red+riding+hood&entity=song&limit=5';
this.userActivated = false;
this.createToggleButton(); // Creates the play/pause button on screen
}
}
class SplineBarrier {
// Creates boundaries on the map so characters can't walk off track
constructor(leftPoints, rightPoints, gameEnv) {
this.leftControlPoints = leftPoints;
this.rightControlPoints = rightPoints;
this.gameEnv = gameEnv;
this.ctx = gameEnv.ctx;
// Turns a few points into a smooth line for the borders
const leftSamples = this.sampleSpline(this.leftControlPoints, 1000);
const rightSamples = this.sampleSpline(this.rightControlPoints, 1000);
// Connects the left and right borders to make a complete boundary wall
this.polygon = [...leftSamples, ...rightSamples.reverse()];
}
}
🔹 2. Methods with Parameters
- 📌 Requirement: Create methods that take at least 2 parameters.
- 📝 Examples:
checkInZone(player, zone)andcheckPlayerWolfCollision(player, wolf).
- What it is: Parameters are placeholders for information given to a function. They let a function accept different data every time it runs instead of doing the exact same thing forever.
- How it works: Collision methods accept two game elements at the same time (such as the player and a wolf object). The function reads both positions to determine if they overlap on the screen.
- Why it helps: It prevents duplicate code. A single collision function can compare any target against any obstacle or zone dynamically, keeping the project organized and modular.
// EVIDENCE: METHODS WITH PARAMETERS
class GameLevelRedRidingHood2 {
// Method accepts two objects to calculate if a player is standing inside a zone
checkInZone(player, zone) {
if (!player?.position) return false;
// Compares the player boundaries against the target zone boundaries
return (
player.position.x + player.width > zone.x &&
player.position.x < zone.x + zone.width &&
player.position.y + player.height > zone.y &&
player.position.y < zone.y + zone.height
);
}
// Method accepts two objects to calculate if the player hits an active wolf enemy
checkPlayerWolfCollision(player, wolf) {
if (!player?.position || !wolf?.position) return false;
// Custom size adjustments to make the hitting area feel accurate on screen
const wolfPadding = 60;
const playerPadding = 10;
// Compares coordinates of both elements to check for any overlapping spaces
return (
player.position.x + playerPadding < wolf.position.x + wolf.width - wolfPadding &&
player.position.x + player.width - playerPadding > wolf.position.x + wolfPadding &&
player.position.y + playerPadding < wolf.position.y + wolf.height - wolfPadding &&
player.position.y + player.height - playerPadding > wolf.position.y + wolfPadding
);
}
}
🔹 3. Object Instantiation
- 📌 Requirement: Create game objects dynamically within a game level configuration.
- 📝 Examples: Using
new GameEnvBackground(...)andnew ShooterPlayer(...).
- What it is: Instantiation means bringing a class blueprint to life. Using the
newkeyword builds an actual, active copy of that class in memory. - How it works: When the game level loads, the code runs commands to build the level pieces. It creates the map background using the background class and sets up the user's character using the player class.
- Why it helps: It keeps level generation flexible. Instead of coding every level by hand, data settings can tell the system exactly which objects to build on the fly when the player starts a new stage.
// EVIDENCE: OBJECT INSTANTIATION
class GameLevelRedRidingHood3 {
constructor(gameEnv) {
this.gameEnv = gameEnv;
let width = gameEnv.innerWidth;
let height = gameEnv.innerHeight;
// Configuration settings for the level background asset
const image_data_forest = {
name: 'forest',
src: gameEnv.path + "/images/projects/red-riding/lrrh-lvl3-bg-clipped.png",
pixels: { height: 580, width: 1038 }
};
// Instantiates the background object using the 'new' keyword
this.background = new GameEnvBackground(image_data_forest, gameEnv);
// Configuration settings for the main player sprite assets and sizing
const sprite_data_red = {
id: 'RedRidingHood',
src: gameEnv.path + "/images/projects/red-riding/Finalred.png",
SCALE_FACTOR: 6,
STEP_FACTOR: 800,
INIT_POSITION: { x: width / 2 - 50, y: height - 100 },
pixels: { height: 144, width: 192 },
orientation: { rows: 3, columns: 4 }
};
// Instantiates the interactive player object using the 'new' keyword
this.player = new ShooterPlayer(sprite_data_red, gameEnv);
}
}
🔹 4. Class Hierarchy & Inheritance
- 📌 Requirement: Create a parent class and a child class that inherits its features.
- 📝 Example:
ShooterPlayerextends the basePlayerclass.
- What it is: Inheritance lets a child class copy properties and behaviors from a parent class automatically using the
extendskeyword. - How it works: The
ShooterPlayerclass inherits from the generalPlayerclass. This gives it immediate access to basic movement and animations without writing that code all over again. - Why it helps: It stops you from rewriting duplicate code. Shared features stay in the parent class, making the child class smaller and easier to read since it only focuses on unique additions like ammo and shooting controls.
// EVIDENCE: CLASS HIERARCHY AND INHERITANCE
import Player from '@assets/js/GameEnginev1.1/essentials/Player.js';
// ShooterPlayer is a child class that copies features from the parent Player class
class ShooterPlayer extends Player {
constructor(data, gameEnv) {
// super() calls the parent class constructor to set up standard player features
super(data, gameEnv);
// Adds unique properties specific only to a shooting character
this.bullets = []; // Tracks active projectiles fired by this player
this.shootCooldown = data.shootCooldown || 500; // Minimum wait time between shots in milliseconds
this.lastShotTime = 0; // Tracks the timestamp of the last shot to manage the cooldown
this.facing = 'up'; // Tracks the looking direction so bullets fly the right way
}
}
🔹 5. Polymorphism & Method Overriding
- 📌 Requirement: Change how an inherited method works in a child class.
- 📝 Example: Modifying the
update()method inside theShooterPlayerclass.
- What it is: Overriding happens when a child class replaces or adds to a function it got from its parent class. The function keeps the exact same name, but runs new code.
- How it works: The child class runs its own
update()function. It usessuper.update()to keep the parent's basic walking mechanics, but immediately adds extra steps to move and clean up flying bullets. - Why it helps: It keeps the game loop simple. The main engine can tell every single object to
update()without needing to care about what kind of character it is or what special actions they are doing.
// EVIDENCE: POLYMORPHISM AND METHOD OVERRIDING
class ShooterPlayer extends Player {
// Overrides the parent class update method to add shooting logic
update() {
// Run the parent class update first to handle standard walking and animations
super.update();
// Extra custom step: Check all active bullets and remove old ones
this.bullets = this.bullets.filter(bullet => {
const age = Date.now() - bullet.creationTime; // Calculates how long the bullet has been flying
// If the bullet has been active longer than its allowed lifetime, mark it for deletion
if (age > bullet.lifetime) {
bullet.destroyed = true;
return false; // Removes it from the active bullets array
}
return true; // Keeps it in the array if it's still fresh
});
}
}
🔹 6. Encapsulation & Access Modifiers
- 📌 Requirement: Protect variable values from being changed incorrectly by outside code.
- 📝 Examples: Using method functions like
setX()andsetY()to filter values.
- What it is: Encapsulation hides data inside an object. It stops outside scripts from changing variables directly, protecting the internal stability of the program.
- How it works: Instead of letting other classes edit coordinates directly, specialized setting methods are used. These methods check the new values first to ensure they stay within valid screen boundaries.
- Why it helps: It stops bugs from spreading. Bad values are caught immediately inside the specific methods instead of breaking other parts of the game engine later on.
// EVIDENCE: ENCAPSULATION AND ACCESS CONTROL
class Npc extends Character {
/**
* General patrol movement within defined walking area (bouncing behavior)
* This method encapsulates the movement logic and protects the character
* from walking off the designated boundaries of the custom map.
*/
patrol() {
// Use moveDirection and speed, defaulting if not set
if (!this.moveDirection) this.moveDirection = { x: 1, y: 1 };
if (!this.speed) this.speed = 1;
// Update position based on direction and speed
this.position.x += this.moveDirection.x * this.speed;
this.position.y += this.moveDirection.y * this.speed;
// Encapsulation checking: Bounce off left/right boundaries and update sprite direction safely
if (this.position.x <= this.walkingArea.xMin) {
this.position.x = this.walkingArea.xMin;
this.moveDirection.x = 1;
this.direction = 'right';
}
if (this.position.x + this.width >= this.walkingArea.xMax) {
this.position.x = this.walkingArea.xMax - this.width;
this.moveDirection.x = -1;
this.direction = 'left';
}
// Encapsulation checking: Bounce off top/bottom boundaries safely
if (this.position.y <= this.walkingArea.yMin) {
this.position.y = this.walkingArea.yMin;
this.moveDirection.y = 1;
}
if (this.position.y + this.height >= this.walkingArea.yMax) {
this.position.y = this.walkingArea.yMax - this.height;
this.moveDirection.y = -1;
}
}
}