Object Oriented Programming
Object Oriented Programming- CS111 Review
| Back | Index | Next |
|---|---|---|
| Summary | Index | Control Structures |
Object-Oriented Programming
Click a concept to expand details.
Object-Oriented Programming in Heist.exe
OOP organizes code into classes — blueprints that bundle data and behavior together. In Heist.exe, Gem and Guard are both classes that inherit from engine parents and override specific behaviors to make collectibles and bouncing enemies work.
flowchart TD
%% Define styles for clean UML look
classDef classNode fill:#2d3748,stroke:#4a5568,stroke-width:2px,color:#fff,font-family:monospace;
Coin["<table border='0' cellpadding='6' cellspacing='0'><tr><td><b>Coin</b></td></tr><tr><td align='left' style='border-top: 1px solid #4a5568; border-bottom: 1px solid #4a5568;'>+ value<br/>+ collected</td></tr><tr><td align='left'>+ update()<br/>+ collect()</td></tr></table>"]:::classNode
Gem["<table border='0' cellpadding='6' cellspacing='0'><tr><td><b>Gem</b></td></tr><tr><td align='left' style='border-top: 1px solid #4a5568; border-bottom: 1px solid #4a5568;'>+ permanentlyCollected<br/>+ gemImage</td></tr><tr><td align='left'>+ draw()<br/>+ collect()<br/>+ update()</td></tr></table>"]:::classNode
Enemy["<table border='0' cellpadding='6' cellspacing='0'><tr><td><b>Enemy</b></td></tr><tr><td align='left' style='border-top: 1px solid #4a5568; border-bottom: 1px solid #4a5568;'>+ position<br/>+ velocity</td></tr><tr><td align='left'>+ collisionChecks()<br/>+ handleCollisionEvent()<br/>+ stayWithinCanvas()</td></tr></table>"]:::classNode
Guard["<table border='0' cellpadding='6' cellspacing='0'><tr><td><b>Guard</b></td></tr><tr><td align='left' style='border-top: 1px solid #4a5568; border-bottom: 1px solid #4a5568;'>+ playerDestroyed<br/>+ initialVelocityY</td></tr><tr><td align='left'>+ update()<br/>+ stayWithinCanvas()<br/>+ handleCollisionEvent()</td></tr></table>"]:::classNode
Player["<table border='0' cellpadding='6' cellspacing='0'><tr><td><b>Player</b></td></tr><tr><td align='left' style='border-top: 1px solid #4a5568; border-bottom: 1px solid #4a5568;'>+ keypressConfig</td></tr><tr><td align='left'>+ handleKeyInput()<br/>+ destroy()</td></tr></table>"]:::classNode
%% Inheritance Relationships
Gem -->|Inherits| Coin
Guard -->|Inherits| Enemy
Core OOP Concepts
Writing Classes — define a blueprint that bundles related data and behavior into a reusable template
Exactly what it is: A class is a code template that specifies what fields (data) instances will have and what methods (functions) they can perform. When you write a class, you’re defining a blueprint — not creating an actual object yet. The actual objects come later when you instantiate the class.
From Heist.exe: Guard.js (lines 3-7) declares a class that inherits from Enemy. It declares two fields — playerDestroyed and velocity.y — and three methods — update(), stayWithinCanvas(), and handleCollisionEvent(). Every Guard object created from this class will have these exact same fields and methods, but each Guard maintains its own independent state.
// Guard.js — lines 3-10: Class definition with inherited and custom fields
import Enemy from '@assets/js/GameEnginev1.1/essentials/Enemy.js';
class Guard extends Enemy {
constructor(data = null, gameEnv = null) {
super(data, gameEnv); // Inherit parent fields
this.velocity.y = -3; // Custom field: initial upward speed
this.playerDestroyed = false; // Custom field: collision flag
}
update() { ... } // Method: called every frame
stayWithinCanvas() { ... } // Method: enforces boundary rules
handleCollisionEvent() { ... } // Method: responds to collisions
}
export default Guard;
Similarly, Gem.js declares a class that inherits from Coin with its own fields (permanentlyCollected, gemImage) and overridden methods (draw(), collect(), update()).
Methods & Parameters — pass data into methods so each call can customize behavior without rewriting the code
Exactly what it is: A method is a function that belongs to a class and operates on that class’s data. Parameters are inputs that the method accepts, allowing the caller to customize what that method does. Instead of writing separate code for each scenario, you write one method that accepts parameters and adapts.
From Heist.exe: Guard.js constructor (lines 4-8) accepts data and gameEnv as parameters. It uses these parameters to call super() (passing them to the parent Enemy class), which sets up position and sprite configuration. Then it adds Guard-specific setup: this.velocity.y = -3.
The same pattern works for Gem.js (lines 4-13): it accepts the same data and gameEnv parameters, but before calling super(), it wraps and merges the incoming data into a gemData object with default values. This means each Gem can be customized at spawn time (different position, different value), but all Gems share identical logic.
// Guard.js — lines 4-8: Constructor uses parameters to customize setup
constructor(data = null, gameEnv = null) {
super(data, gameEnv); // Parameters passed to parent
this.velocity.y = -3; // Guard-specific customization
this.playerDestroyed = false; // Guard-specific customization
}
// Gem.js — lines 4-13: Parameters merged and passed to parent
constructor(data = null, gameEnv = null) {
const gemData = {
id: 'gem',
value: 5,
SCALE_FACTOR: 10,
hitbox: { widthPercentage: 0.8, heightPercentage: 0.8 },
...data // Merge caller's customizations
};
super(gemData, gameEnv); // Complete config passed to parent
this.permanentlyCollected = false;
}
The key idea: Without parameters, you’d have to write separate constructors for every possible configuration. With parameters, one constructor flexibly handles infinite variations.
Instantiation & Objects — execute a class to create a live, independent object in memory with its own state
Exactly what it is: Instantiation is the moment you create an actual, usable object from a class blueprint. Before instantiation, a class is just a template — it doesn’t exist in memory. Instantiation uses the new keyword to allocate memory, run the constructor, and return a real object. Each instantiation is independent: two Guard objects from the same class are separate entities with different positions, different velocities, and different collision states.
From Heist.exe: HeistL2.js (lines 8-150+) defines a this.classes array containing configuration objects. Each entry specifies which class to instantiate and what data to pass. When the game engine processes this array, it executes something like new Guard(sprite_data_guard1, gameEnv) for each entry — creating real, independent Guard objects:
// HeistL2.js — lines 8-150+: Configuration objects for instantiation
class HeistL2 {
constructor(gameEnv) {
this.gameEnv = gameEnv;
// Configuration objects bundled and ready for instantiation
const sprite_data_guard1 = {
id: 'Guard1',
name: 'guard1',
src: path + "/images/projects/heist-exe/heist-guard.png",
SCALE_FACTOR: 10,
INIT_POSITION: { x: 220, y: 300 },
// ... more properties
};
const sprite_data_guard2 = {
id: 'Guard2',
name: 'guard2',
src: path + "/images/projects/heist-exe/heist-guard.png",
SCALE_FACTOR: 10,
INIT_POSITION: { x: 520, y: 300 }, // Different position
// ... more properties
};
// this.classes array tells the engine what to instantiate
this.classes = [
{ class: GameEnvBackground, data: image_data_bg },
{ class: HeistPlayer, data: sprite_data_mc },
{ class: Gem, data: gem_data_1 },
{ class: Gem, data: gem_data_2 },
{ class: Guard, data: sprite_data_guard1 }, // Instantiate guard1
{ class: Guard, data: sprite_data_guard2 }, // Instantiate guard2 — separate object
{ class: Barrier, data: border_top },
// ... 11 more entries
];
}
}
// Game engine iterates this.classes and instantiates:
// const guard1 = new Guard(sprite_data_guard1, gameEnv); // Independent object #1
// const guard2 = new Guard(sprite_data_guard2, gameEnv); // Independent object #2
// Even though both are Guard instances from the same class,
// they have completely different state:
// guard1.position = { x: 220, y: 300 }
// guard2.position = { x: 520, y: 300 } ← Different position
// guard1.velocity.y = -3
// guard2.velocity.y = -3
// guard1.playerDestroyed = false
// guard2.playerDestroyed = false ← Independent state
The key idea: The class Guard is like a cookie cutter — it defines the shape. Instantiation is pressing that cutter into dough to make actual cookies. Each cookie is separate, even though they’re all made from the same cutter.
Method Overriding — child class replaces a parent’s method with a custom version that runs instead
Exactly what it is: When you inherit from a parent class, you get all its methods. But sometimes you need different behavior than what the parent provides. Method overriding lets you write a new method in the child class with the exact same name as the parent’s method. When code calls that method on a child instance, it runs the child’s version, not the parent’s. The parent’s version is completely replaced — unless you explicitly call it with super().
From Heist.exe: This override calls super() after custom logic to run both the child’s push-out code and the parent’s direction updates. HeistPlayer.js (lines 19-54) overrides handleCollisionReaction() to implement custom push-out logic based on collision touch points. It checks which sides of the player are touching the barrier and adjusts position and velocity accordingly. After all the custom logic, it calls super.handleCollisionReaction(other) to ensure the engine’s own collision handling still runs (like updating direction).
// heistPlayer.js — lines 19-54: Override with custom logic + super() call
class HeistPlayer extends Player {
handleCollisionReaction(other) {
try {
const touchPoints = this.collisionData?.touchPoints?.this;
if (touchPoints && other) {
// Custom push-out logic — heist-specific barrier handling
const barrierLeft = other.x ?? other.position?.x ?? 0;
const barrierTop = other.y ?? other.position?.y ?? 0;
const barrierRight = barrierLeft + (other.width ?? 0);
const barrierBottom = barrierTop + (other.height ?? 0);
const BUFFER = 1;
// Horizontal push-out
if (touchPoints.right && !touchPoints.left) {
this.position.x = barrierLeft - this.width - BUFFER;
this.velocity.x = 0;
} else if (touchPoints.left && !touchPoints.right) {
this.position.x = barrierRight + BUFFER;
this.velocity.x = 0;
}
// Vertical push-out
if (touchPoints.bottom && !touchPoints.top) {
this.position.y = barrierTop - this.height - BUFFER;
this.velocity.y = 0;
} else if (touchPoints.top && !touchPoints.bottom) {
this.position.y = barrierBottom + BUFFER;
this.velocity.y = 0;
}
}
} catch (_) {}
// Still call super so the engine's own logic runs (direction updates, etc.)
super.handleCollisionReaction(other);
}
}
The key idea: Overriding lets child classes customize behavior without duplicating code. Gem.js overrides update() to short-circuit if permanentlyCollected is true. Guard.js overrides it to add bounce physics. Same method name, completely different implementations.
Constructor Chaining — call the parent’s constructor first with super(), then add child-specific setup
Exactly what it is: Every class constructor runs automatically when an object is instantiated. Constructor chaining means calling super() at the beginning of the child constructor to run the parent’s full initialization first, ensuring all parent state is ready. Then, after super() returns, you add child-specific initialization. This guarantees that parent infrastructure is always set up before child customization runs.
From Heist.exe: Guard.js (lines 4-8) chains constructors by calling super() first, which runs Enemy’s full setup — setting position, sprite, collision system, velocity. Then Guard adds its own initialization:
// Guard.js — lines 4-8: Parent setup first, then child customization
class Guard extends Enemy {
constructor(data = null, gameEnv = null) {
super(data, gameEnv); // ← Run Enemy constructor FIRST
this.velocity.y = -3; // Then add Guard customization
this.playerDestroyed = false; // Then add Guard customization
}
}
// Execution order:
// 1. super(data, gameEnv) runs Enemy.constructor()
// → this.position is set
// → this.sprite is loaded
// → this.collisionData is initialized
// → this.velocity = { x, y } is created (velocity.y probably 0 from parent)
// 2. this.velocity.y = -3 overwrites the parent's velocity.y with Guard-specific value
// 3. this.playerDestroyed = false initializes the Guard-specific flag
// 4. Constructor completes; Guard object is fully initialized and ready to use
More complex example — Gem.js: The chain includes data merging before calling super():
// Gem.js — lines 4-14: Merge data, parent setup, then child customization
class Gem extends Coin {
constructor(data = null, gameEnv = null) {
// Assemble the complete config BEFORE calling super()
const gemData = {
id: 'gem',
value: 5,
SCALE_FACTOR: 10,
hitbox: { widthPercentage: 0.8, heightPercentage: 0.8 },
...data // Merge caller's customizations
};
super(gemData, gameEnv); // ← Run Coin constructor with complete config
this.permanentlyCollected = false; // ← Then add Gem-specific customization
this.gemImage = new Image(); // ← Load gem-specific image
this.gemImage.src = gameEnv.path + '/images/projects/heist-exe/gem.png';
}
}
// Execution order:
// 1. gemData object is assembled with merged properties
// 2. super(gemData, gameEnv) runs Coin.constructor(gemData, gameEnv)
// → this.value is set to 5 (from gemData)
// → this.collected is initialized (from parent Coin)
// → Parent collision system is set up
// 3. this.permanentlyCollected = false adds Gem-specific persistence flag
// 4. this.gemImage is loaded with the gem sprite
// 5. Constructor completes; Gem object inherits all Coin functionality + Gem customization
Why this matters: Without constructor chaining, you’d have to duplicate all parent initialization code in the child class. With super(), you inherit the parent’s setup once and extend it. If the parent changes, your child automatically gets the new setup without any code changes.
| Concept | Purpose | Heist.exe Example |
|---|---|---|
| Writing Classes | Reusable code blueprint | Guard.js, Gem.js class definitions |
| Methods & Parameters | Customizable behavior | Constructor accepts data and gameEnv |
| Instantiation & Objects | Live objects in memory | HeistL2 creates Guard instances |
| Method Overriding | Custom child behavior | Guard overrides update() and stayWithinCanvas() |
| Constructor Chaining | Safe parent initialization | super() runs parent setup first |