The EnergyRing Class

Back to Energy Rings Animation

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

EnergyRing Instance 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 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?