Purpose: The EnergyRing class manages a system of multiple Ring objects that spawn sequentially, creating a wave or pulse effect. This class demonstrates composition (has-a relationship) by containing and coordinating multiple Ring instances with shared properties.
Visual Example
Figure 1: Creating several EnergyRing objects with various sequential rings, showing the cascade effect where each ring follows the previous one at regular intervals
Key Object-Oriented Programming Concepts
Composition: EnergyRing contains an array of Ring objects (has-a relationship)
Aggregation: Manages a collection of related objects with a common purpose
Delegation: EnergyRing delegates drawing and updating to individual Ring objects
State Management: Tracks complex state (spawn timing, frame counting, continuous mode)
Conditional Logic: Different behaviors for continuous vs. normal mode
Separation of Concerns: Ring handles individual behavior, EnergyRing handles coordination
Composition Design Pattern: Notice how EnergyRing doesn't duplicate the code from Ring. Instead, it creates and manages Ring objects, reusing their functionality. This is a powerful OOP technique that promotes code reuse and maintainability.
Complete EnergyRing Class Source Code
// ============================================
// ENERGYRING CLASS (Sequential Pulse System)
// ============================================
// This class creates and manages a system of n sequential Ring objects
// All rings share the same color and settings, creating a unified energy pulse effect
// Rings are spawned at regular intervals based on the first ring's progress
class EnergyRing {
// Constructor - creates an energy ring system with n sequential pulses
// Parameters:
// x: horizontal position on canvas
// y: vertical position on canvas
// n: number of sequential rings to create (default = 5)
// settings: object containing control panel settings (hue, saturation, brightness, thickness, etc.)
constructor(x, y, n = 5, settings = null) {
this.x = x; // X position for all rings
this.y = y; // Y position for all rings
this.n = n; // Total number of rings to spawn (in continuous mode, this is per cycle)
this.rings = []; // Array to hold all Ring objects
// Store the settings that will be used for all rings in this system
// If no settings provided, use default values
this.settings = settings || {
hue: 51,
saturation: 100,
brightness: 100,
initialThickness: 2,
finalThickness: 50,
maxDiameter: 400,
speed: 2,
continuousMode: false
};
// Track which rings have been spawned in the current cycle
// We start by spawning only the first ring
// Additional rings spawn as the first ring progresses
this.ringsSpawned = 0;
// In continuous mode, track total rings spawned across all cycles
this.totalRingsSpawned = 0;
// Track frames since last spawn for continuous mode timing
// This provides consistent spawn intervals regardless of ring removal
this.framesSinceLastSpawn = 0;
// Immediately create the first ring to start the sequence
this.spawnRing();
}
// Spawn a new ring with the shared settings
// This method is called initially for the first ring,
// then called repeatedly by update() as previous rings progress
spawnRing() {
// In continuous mode, always spawn new rings
// In normal mode, only spawn up to n rings
if (this.settings.continuousMode || this.ringsSpawned < this.n) {
// Create a new Ring at the center position with shared settings
this.rings.push(new Ring(this.x, this.y, this.settings));
// Increment the counter to track how many rings we've created in this cycle
this.ringsSpawned++;
this.totalRingsSpawned++;
}
}
// Update method - called each frame to manage all rings in this system
update() {
// Update all existing rings in this energy system
for (let ring of this.rings) {
ring.update();
}
// Increment frame counter for timing
this.framesSinceLastSpawn++;
// Check if we should spawn the next ring in the sequence
if (this.rings.length > 0) {
if (this.settings.continuousMode) {
// In continuous mode, use frame-based timing for consistent spawning
// Calculate how many frames should pass between each ring spawn
// This is based on: (total frames for one ring to complete) / (number of rings per cycle)
let framesForFullExpansion = this.settings.maxDiameter / this.settings.speed;
let framesPerRing = framesForFullExpansion / this.n;
// Spawn a new ring when enough frames have passed
if (this.framesSinceLastSpawn >= framesPerRing) {
this.spawnRing();
this.framesSinceLastSpawn = 0; // Reset the counter
}
} else {
// In normal (non-continuous) mode, use progress-based spawning
// Get the progress of the very first ring (index 0)
// This is our "leader" ring that determines when others spawn
let firstRingProgress = this.rings[0].getProgress();
// Calculate the threshold for spawning the next ring
// When n=5 and we've spawned 1 ring, next spawns at 1/5 progress (0.2)
// When n=5 and we've spawned 2 rings, next spawns at 2/5 progress (0.4)
// When n=5 and we've spawned 3 rings, next spawns at 3/5 progress (0.6)
// When n=5 and we've spawned 4 rings, next spawns at 4/5 progress (0.8)
// This creates evenly-spaced sequential pulses
let spawnThreshold = this.ringsSpawned / this.n;
// If the first ring has reached or passed the threshold, spawn the next ring
// Only in normal mode - don't spawn more than n rings
if (firstRingProgress >= spawnThreshold && this.ringsSpawned < this.n) {
this.spawnRing();
}
}
}
// Clean up rings that have finished expanding
// Loop backwards so we can safely remove items while iterating
for (let i = this.rings.length - 1; i >= 0; i--) {
if (this.rings[i].isFinished()) {
this.rings.splice(i, 1);
}
}
}
// Display method - draws all rings in this energy system
display() {
// Simply display each ring that exists in this system
for (let ring of this.rings) {
ring.display();
}
}
// Check if this entire energy ring system has finished
// Returns true only when:
// 1. All n rings have been spawned (not in continuous mode)
// 2. All spawned rings have finished expanding and been removed
// In continuous mode, this never returns true because rings keep spawning
isFinished() {
// In continuous mode, never finish
if (this.settings.continuousMode) {
return false;
}
// In normal mode, finish when all rings are spawned and removed
return this.ringsSpawned >= this.n && this.rings.length === 0;
}
}
Instance Variables (Properties)
Property
Type
Description
x
Number
X coordinate for all rings in this system
y
Number
Y coordinate for all rings in this system
n
Number
Total number of rings to spawn per cycle
rings
Array<Ring>
Collection of Ring objects being managed (composition)
settings
Object
Configuration object shared by all rings
ringsSpawned
Number
Count of rings spawned in current cycle
totalRingsSpawned
Number
Total count across all cycles (for continuous mode)
framesSinceLastSpawn
Number
Frame counter for timing in continuous mode
Methods (Behaviors)
Method
Returns
Description
constructor(x, y, n, settings)
-
Initializes the energy ring system and spawns the first ring
spawnRing()
void
Creates a new Ring object and adds it to the rings array
update()
void
Updates all rings, manages spawning timing, and removes finished rings
display()
void
Delegates display to each Ring in the collection
isFinished()
Boolean
Returns true when all rings are spawned and removed (false in continuous mode)
Example Usage
// Create settings object with energy ring configuration
let energySettings = {
hue: 200, // Blue color
saturation: 80,
brightness: 90,
initialThickness: 2,
finalThickness: 40,
maxDiameter: 500,
speed: 3,
continuousMode: false // Normal mode: spawn 5 rings then stop
};
// Create a new EnergyRing instance at position (300, 250)
// with 5 sequential rings
let myEnergyRing = new EnergyRing(300, 250, 5, energySettings);
// In the animation loop:
function draw() {
background(0, 0, 94);
myEnergyRing.update(); // Update all rings and spawn timing
myEnergyRing.display(); // Draw all rings
// Check if energy ring system is finished
if (myEnergyRing.isFinished()) {
console.log("All rings have completed!");
}
}
Relationship Between Ring and EnergyRing
Has-A Relationship: EnergyRing has Ring objects (stored in the rings array).
Why use composition?
Code reuse: Ring's behavior doesn't need to be rewritten
Single Responsibility: Ring handles individual animation, EnergyRing handles coordination
Flexibility: Can easily change how rings are spawned without modifying Ring class
Modularity: Each class has a clear, focused purpose