Lecture 9: Visitor Pattern - Power-Ups

๐ŸŽฎ Visitor Pattern

Implementing Power-Ups

Game Programming - CSCI 3213

Spring 2026 - Lecture 9

Oklahoma City University

๐Ÿ“š Learning Objectives

  • Understand the Visitor design pattern
  • Learn when and why to use the Visitor pattern
  • Implement a power-up system using Visitor pattern
  • Combine Visitor pattern with Unity ScriptableObjects
  • Create designer-friendly power-up configuration

Developer's Note: "Never Panic Early"

โš ๏ธ What to Expect During Implementation

As we implement this pattern, we'll be creating multiple files in a specific order. You will see errors in Unity and your IDE until all files are complete.

How to Handle Development Errors

  • Don't Ignore: Note the errors - they're telling you something
  • Don't Panic: These are expected until all files are created
  • Stay Calm: Follow the implementation order and errors will resolve

As Apollo 13's Fred Haise said: "Never Panic Early"

This is a critical skill for software development. Note problems, but stay focused on the implementation path.

๐Ÿ•น๏ธ Power-Ups: A Gaming Staple

Classic Examples

  • Pac-Man (1980): Power Pellets - temporary invincibility
  • Super Mario Bros: Mushrooms - size and durability boost
  • Sonic: Shield, Speed Shoes, Invincibility
  • Modern Games: Complex multi-attribute boosts
Key Difference: Power-ups activate immediately on contact, while items are collected and activated later by player choice.

๐ŸŽฏ Design Requirements

Power-Up Specifications

  • Granularity: Boost multiple properties simultaneously
    • Example: "Protector" - Shield durability + Weapon strength
  • Permanence: Effects stack until maximum values reached
    • No temporal expiration
    • Benefits accumulate across pickups
  • Designer-Friendly: No code required to create new power-ups
  • Scalable: Easy to add new boost types

๐Ÿ” Understanding Visitor Pattern

Core Concept

A Visitable object permits a Visitor to operate on specific elements of its structure.

Key Benefit: Add new functionality to objects without modifying them

Real-World Analogy

Imagine a power-up flowing through a bike like an electric current:

  • Power-up enters the bike's structure
  • Visits each component (engine, shield, weapon)
  • Enhances visited components
  • Original bike code remains unchanged

๐Ÿ—๏ธ Pattern Structure

Key Participants

// 1. Visitor Interface
public interface IVisitor
{
    void Visit(BikeShield shield);
    void Visit(BikeEngine engine);
    void Visit(BikeWeapon weapon);
}

// 2. Visitable Interface
public interface IBikeElement
{
    void Accept(IVisitor visitor);
}
Note: Each visitable type gets its own Visit() method

โœ… Benefits of Visitor Pattern

  • Open/Closed Principle:
    • Open for extension (new visitors)
    • Closed for modification (existing elements)
  • Single Responsibility:
    • Visitable objects hold data
    • Visitor objects define behaviors
  • Centralized Logic:
    • All power-up logic in one place
    • Easy to maintain and modify

โš ๏ธ Potential Drawbacks

  • Accessibility Issues:
    • Visitors may need access to private fields
    • Requires exposing more public properties
  • Structural Complexity:
    • More complex than simpler patterns (Singleton, State)
    • Requires understanding of Double Dispatch concept
  • Learning Curve:
    • Considered one of the harder patterns to grasp
    • Team members need pattern familiarity

๐Ÿ”„ Double Dispatch

What is Double Dispatch?

A mechanism that delegates method calls to different concrete methods based on the runtime types of two objects.

In our power-up system:

  1. PowerUp visits BikeController
  2. BikeController dispatches visitor to components
  3. Each component calls correct Visit() method
  4. Method resolution uses types of both visitor and visited
Don't worry: You don't need to fully understand Double Dispatch to use the Visitor pattern effectively!

๐Ÿ’ป Implementation: Visitor Interface

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/IVisitor.cs
Interface that defines the visitor operations for each bike element type.
โš ๏ธ This code goes into your Unity project for Blade Racer.

public interface IVisitor
{
    // One Visit method per visitable element
    void Visit(BikeShield bikeShield);
    void Visit(BikeEngine bikeEngine);
    void Visit(BikeWeapon bikeWeapon);
}
Pattern Rule: You need one Visit() method for each type of visitable element in your system.

๐Ÿ’ป Implementation: Visitable Interface

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/IBikeElement.cs
Interface for bike elements that can be visited by power-ups.
โš ๏ธ This code goes into your Unity project for Blade Racer.

public interface IBikeElement
{
    // Entry point for visitor
    void Accept(IVisitor visitor);
}
Key Point: The Accept() method is the gateway that allows visitors to access the object's internal structure.

๐Ÿ’ป PowerUp ScriptableObject (1/2)

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Items/PowerUp.cs
ScriptableObject that implements the visitor pattern to apply effects to bike elements.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;

[CreateAssetMenu(fileName = "PowerUp", menuName = "PowerUp")]
public class PowerUp : ScriptableObject, IVisitor
{
    public string powerupName;
    public GameObject powerupPrefab;
    public string powerupDescription;

    [Tooltip("Fully heal shield")]
    public bool healShield;

    [Range(0.0f, 50f)]
    [Tooltip("Boost turbo settings up to 50 mph")]
    public float turboBoost;

    [Range(0.0f, 25)]
    [Tooltip("Boost weapon range up to 25 units")]
    public int weaponRange;

๐Ÿ’ป PowerUp ScriptableObject (2/2)

    [Range(0.0f, 50f)]
    [Tooltip("Boost weapon strength up to 50%")]
    public float weaponStrength;

    // Visitor methods on next slides...
}
ScriptableObject Benefits:
  • Create power-ups from Asset menu
  • Configure in Inspector (no code!)
  • Designer-friendly workflow

๐Ÿ’ป Visiting BikeShield

๐Ÿ“ Continuing PowerUp.cs

Add this Visit method to the PowerUp class from Slide 12.

public void Visit(BikeShield bikeShield)
{
    if (healShield)
        bikeShield.health = 100.0f;
}

What happens here?

  • PowerUp receives reference to BikeShield
  • Checks if healShield is enabled
  • If yes, restores shield to full health
  • Simple, focused behavior

๐Ÿ’ป Visiting BikeWeapon

๐Ÿ“ Continuing PowerUp.cs

Add this Visit method to the PowerUp class (second of three Visit methods).

public void Visit(BikeWeapon bikeWeapon)
{
    // Boost range (respect max)
    int range = bikeWeapon.range += weaponRange;
    if (range >= bikeWeapon.maxRange)
        bikeWeapon.range = bikeWeapon.maxRange;
    else
        bikeWeapon.range = range;

    // Boost strength by percentage (respect max)
    float strength = bikeWeapon.strength +=
        Mathf.Round(bikeWeapon.strength * weaponStrength / 100);

    if (strength >= bikeWeapon.maxStrength)
        bikeWeapon.strength = bikeWeapon.maxStrength;
    else
        bikeWeapon.strength = strength;
}

๐Ÿ’ป Visiting BikeEngine

๐Ÿ“ Continuing PowerUp.cs

Add this Visit method to the PowerUp class (third and final Visit method).

public void Visit(BikeEngine bikeEngine)
{
    float boost = bikeEngine.turboBoost += turboBoost;

    // Ensure non-negative
    if (boost < 0.0f)
        bikeEngine.turboBoost = 0.0f;

    // Respect maximum
    if (boost >= bikeEngine.maxTurboBoost)
        bikeEngine.turboBoost = bikeEngine.maxTurboBoost;
}
Pattern Observation: Each Visit() method encapsulates the specific logic for modifying one component type.

๐Ÿ’ป BikeController - The Coordinator

๐Ÿ“ File Structure Note - PRODUCTION CODE

Modify existing file: Assets/Scripts/Controllers/BikeController.cs
Add IBikeElement implementation and Accept method to coordinate visitor pattern.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;
using System.Collections.Generic;

public class BikeController : MonoBehaviour, IBikeElement
{
    private List<IBikeElement> _bikeElements =
        new List<IBikeElement>();

    void Start()
    {
        _bikeElements.Add(
            gameObject.AddComponent<BikeShield>());
        _bikeElements.Add(
            gameObject.AddComponent<BikeWeapon>());
        _bikeElements.Add(
            gameObject.AddComponent<BikeEngine>());
    }

๐Ÿ’ป BikeController - Accept Method

    public void Accept(IVisitor visitor)
    {
        // Forward visitor to all bike elements
        foreach (IBikeElement element in _bikeElements)
        {
            element.Accept(visitor);
        }
    }
}

The Flow:

  1. Bike collides with power-up
  2. PowerUp calls BikeController.Accept()
  3. BikeController forwards visitor to all components
  4. Each component's Visit() method is called

๐Ÿ’ป BikeShield - Visitable Component

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Bike/BikeShield.cs
Bike shield component that can be visited by power-ups.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;

public class BikeShield : MonoBehaviour, IBikeElement
{
    public float health = 50.0f; // Percentage

    public float Damage(float damage)
    {
        health -= damage;
        return health;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this); // Double dispatch!
    }

    // OnGUI for debug display...
}

๐Ÿ’ป BikeWeapon - Visitable Component

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Bike/BikeWeapon.cs
Bike weapon component that can be visited by power-ups.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;

public class BikeWeapon : MonoBehaviour, IBikeElement
{
    [Header("Range")]
    public int range = 5;
    public int maxRange = 25;

    [Header("Strength")]
    public float strength = 25.0f;
    public float maxStrength = 50.0f;

    public void Fire()
    {
        Debug.Log("Weapon fired!");
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

๐Ÿ’ป BikeEngine - Visitable Component

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Bike/BikeEngine.cs
Bike engine component that can be visited by power-ups.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;

public class BikeEngine : MonoBehaviour, IBikeElement
{
    public float turboBoost = 25.0f; // mph
    public float maxTurboBoost = 200.0f;
    private bool _isTurboOn;
    private float _defaultSpeed = 300.0f;

    public float CurrentSpeed
    {
        get {
            if (_isTurboOn)
                return _defaultSpeed + turboBoost;
            return _defaultSpeed;
        }
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

๐Ÿ’ป Making Pickups Collectable

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Items/Pickup.cs
Pickup object that applies power-ups to the bike on collision.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;

public class Pickup : MonoBehaviour
{
    public PowerUp powerup;

    private void OnTriggerEnter(Collider other)
    {
        // Check if player bike entered trigger
        if (other.GetComponent<BikeController>())
        {
            // Apply power-up
            other.GetComponent<BikeController>().Accept(powerup);

            // Destroy pickup
            Destroy(gameObject);
        }
    }
}
Setup: Attach to GameObject with Collider (IsTrigger = true)

๐Ÿงช Testing the System

Quick Test Steps:

  1. Create a new Unity scene
  2. Add an empty GameObject
  3. Attach the ClientVisitor test script (next slide)
  4. Create 3 PowerUp assets via Assets/Create/PowerUp
  5. Configure each PowerUp in Inspector:
    • Shield PowerUp: healShield = true
    • Engine PowerUp: turboBoost = 25
    • Weapon PowerUp: weaponRange = 5, weaponStrength = 10
  6. Drag PowerUp assets to ClientVisitor component
  7. Press Play and test buttons

๐Ÿ’ป ClientVisitor Test Script

๐Ÿ“ File Structure Note - TESTING CODE

Create a new file: Assets/Scripts/Testing/ClientVisitor.cs
Test client that applies power-ups via GUI buttons.
โš ๏ธ This is temporary testing code - you can remove it after testing your implementation.

using UnityEngine;

public class ClientVisitor : MonoBehaviour
{
    public PowerUp enginePowerUp;
    public PowerUp shieldPowerUp;
    public PowerUp weaponPowerUp;

    private BikeController _bikeController;

    void Start()
    {
        _bikeController =
            gameObject.AddComponent<BikeController>();
    }

    void OnGUI()
    {
        if (GUILayout.Button("PowerUp Shield"))
            _bikeController.Accept(shieldPowerUp);
        if (GUILayout.Button("PowerUp Engine"))
            _bikeController.Accept(enginePowerUp);
        if (GUILayout.Button("PowerUp Weapon"))
            _bikeController.Accept(weaponPowerUp);
    }
}
๐Ÿ’ก Alternative: When you have multiple test scripts with overlapping GUI buttons, consider using TestPanel.cs to combine all test controls into one draggable window. See the Event Bus lecture for the unified TestPanel implementation.

๐ŸŽฎ Updating TestPanel for Visitor Pattern

๐Ÿ“ Evolving TestPanel.cs

Add Visitor Pattern keyboard shortcuts and section to your TestPanel.cs.

// Add to TestPanel fields private bool _visitorExpanded = true; public PowerUp shieldPowerUp; public PowerUp enginePowerUp; public PowerUp weaponPowerUp; // Add to Update() - Visitor Pattern shortcuts if (_bikeController != null) { if (Input.GetKeyDown(KeyCode.V)) _bikeController.Accept(shieldPowerUp); if (Input.GetKeyDown(KeyCode.E)) _bikeController.Accept(enginePowerUp); if (Input.GetKeyDown(KeyCode.W)) _bikeController.Accept(weaponPowerUp); }
// Add DrawVisitorSection() method void DrawVisitorSection() { if (_bikeController == null) return; GUI.backgroundColor = Color.red; _visitorExpanded = GUILayout.Toggle( _visitorExpanded, "โ–ผ Visitor Pattern", "button"); GUI.backgroundColor = Color.white; if (_visitorExpanded) { GUILayout.BeginVertical("box"); if (GUILayout.Button("Shield PowerUp (V)")) _bikeController.Accept(shieldPowerUp); if (GUILayout.Button("Engine PowerUp (E)")) _bikeController.Accept(enginePowerUp); if (GUILayout.Button("Weapon PowerUp (W)")) _bikeController.Accept(weaponPowerUp); GUILayout.EndVertical(); } }
Keymap Update: Add to DrawKeymapWindow():
V = Shield PowerUp, E = Engine PowerUp, W = Weapon PowerUp

๐Ÿ”„ The Complete Flow

Sequence of Events:

  1. Collision Detected: Bike enters pickup trigger
  2. Accept Called: Pickup calls BikeController.Accept(powerup)
  3. Distribution: BikeController forwards to all elements
  4. Visit Shield: powerup.Visit(bikeShield) called
    • Shield health restored if configured
  5. Visit Engine: powerup.Visit(bikeEngine) called
    • Turbo boost increased if configured
  6. Visit Weapon: powerup.Visit(bikeWeapon) called
    • Range and strength boosted if configured
  7. Cleanup: Pickup GameObject destroyed

๐ŸŽจ Designer-Friendly Workflow

Creating New Power-Ups (No Code!)

  1. Right-click in Project window
  2. Create โ†’ PowerUp
  3. Name it (e.g., "SuperBoost")
  4. Select the asset in Inspector
  5. Configure properties:
    • Power-Up Name: "Super Boost"
    • Description: "Massive speed and weapon upgrade"
    • Turbo Boost: 50
    • Weapon Range: 10
    • Weapon Strength: 25
  6. Assign to Pickup prefab
  7. Done! No programming required

๐Ÿ”ง Extending the System

Adding New Visitable Components

// 1. Create new component
public class BikeArmor : MonoBehaviour, IBikeElement
{
    public float armorRating = 0f;
    public float maxArmor = 100f;

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// 2. Add Visit method to IVisitor interface
void Visit(BikeArmor bikeArmor);

// 3. Implement in PowerUp class
public void Visit(BikeArmor bikeArmor)
{
    bikeArmor.armorRating += armorBoost;
    if (bikeArmor.armorRating > bikeArmor.maxArmor)
        bikeArmor.armorRating = bikeArmor.maxArmor;
}

โฑ๏ธ Optional: Temporary Effects

Adding Duration to Power-Ups

public class PowerUp : ScriptableObject, IVisitor
{
    public float duration = 0f; // 0 = permanent

    // Store original values
    private Dictionary<IBikeElement, object> _originalValues;

    public void Visit(BikeEngine bikeEngine)
    {
        if (duration > 0)
        {
            // Store original
            _originalValues[bikeEngine] = bikeEngine.turboBoost;

            // Apply boost
            bikeEngine.turboBoost += turboBoost;

            // Schedule revert
            StartCoroutine(RevertAfterDuration(bikeEngine));
        }
    }

    IEnumerator RevertAfterDuration(BikeEngine engine)
    {
        yield return new WaitForSeconds(duration);
        engine.turboBoost = (float)_originalValues[engine];
    }
}

๐ŸŽญ Pattern Variations

Note on Implementation

Our implementation modifies visited objects' properties. This technically breaks a "purist" rule of the Visitor pattern.

Classic Visitor: Performs operations on elements without changing them (e.g., calculating totals, generating reports)

Our Approach: Uses Visitor structure to traverse and modify elements

Why this is okay:

  • Game development requires practical solutions
  • The traversal structure is what matters most
  • We maintain separation of concerns
  • System remains extensible and maintainable

๐Ÿค” When to Use Visitor Pattern

Good Use Cases:

  • Operating on complex object structures
  • Need to add operations without modifying classes
  • Operations are related but conceptually separate
  • Object structure rarely changes, but operations do

Avoid When:

  • Object structure changes frequently (adding new types)
  • Simple operations on simple structures
  • Team unfamiliar with pattern complexity
  • Simpler patterns (Strategy, Command) would suffice

โš–๏ธ Visitor vs Other Patterns

Pattern Use Case Key Difference
Strategy Swap algorithms at runtime One object, many behaviors
Command Encapsulate requests as objects Focuses on actions, not traversal
Visitor Operate on object structure Traverses composite structures
Visitor shines when: You have a stable structure (bike components) but want to add many different operations (various power-up types).

Gaming History Moment ๐Ÿ•น๏ธ

Weird Consoles: Coleco Telstar Arcade (1977)

Before standardized controllers, console makers experimented wildly. The Coleco Telstar Arcade (1977) was a triangular console with three completely different detachable controllers: a steering wheel for racing games, a light gun for shooting games, and paddle controllers for Pong-style games. Each controller was a separate physical device that plugged into the same console.

The console had to handle input from radically different controller types - rotational input from the wheel, analog paddles, and point-and-click from the light gun. The system needed to "visit" each controller type and extract input in completely different ways, then translate that to game actions. This modularity allowed one console to support multiple game genres with specialized controls.

Connection to Visitor Pattern

The Telstar Arcade's controller system is like the Visitor Pattern! The console (visitor) needs to interact with different controller types (visitable elements) - steering wheel, light gun, paddles - each requiring different input handling. Just as our PowerUpVisitor visits different bike components (Engine, Shield, Weapon) with type-specific logic, the Telstar "visited" different controllers with controller-specific input parsing. The Visitor Pattern lets you add new operations (games) without modifying the controllers themselves!

Learn More: Coleco Telstar Arcade (Old Computers Museum) | Retro Gaming Consoles: Coleco

๐ŸŒ Real-World Applications

Beyond Power-Ups:

  • Damage Systems:
    • Different damage types visit different armor components
    • Fire damage, ice damage, physical damage
  • Status Effects:
    • Poison, slow, stun effects visit character stats
    • Each effect has unique impact on different attributes
  • Buff/Debuff Systems:
    • Temporary or permanent stat modifications
    • Stackable or unique effects
  • Rendering Pipelines:
    • Shader visitors operate on mesh components
    • Post-processing effects visit render targets

โšก Performance Considerations

Optimization Tips:

  • Cache Component References:
    • Store _bikeElements list once in Start()
    • Avoid repeated GetComponent() calls
  • Object Pooling for Pickups:
    • Don't Destroy() - recycle instead
    • Reduces garbage collection overhead
  • Conditional Visiting:
    • Skip components if powerup doesn't affect them
    • Check flags before calling Visit()
// Example: Conditional visiting
if (powerup.turboBoost > 0)
    element.Accept(powerup); // Only visit if needed

๐Ÿšซ Common Mistakes to Avoid

  1. Forgetting to Implement All Visit Methods:
    • IVisitor requires one method per visitable type
    • Compiler will catch this, but plan ahead
  2. Not Checking Maximum Values:
    • Always clamp boosted values to maximums
    • Prevents game-breaking stat inflation
  3. Tight Coupling in Visit Methods:
    • Don't access deep nested properties
    • Use public interface of visited objects
  4. Overusing the Pattern:
    • Not every system needs Visitor
    • Simple direct calls may be clearer

๐Ÿงช Testing Strategy

Unit Test Scenarios:

[Test]
public void PowerUp_BoostsWeaponRange()
{
    // Arrange
    var weapon = new BikeWeapon { range = 5, maxRange = 25 };
    var powerup = CreatePowerUp(weaponRange: 10);

    // Act
    powerup.Visit(weapon);

    // Assert
    Assert.AreEqual(15, weapon.range);
}

[Test]
public void PowerUp_RespectsMaximumValues()
{
    // Arrange
    var engine = new BikeEngine { turboBoost = 180, maxTurboBoost = 200 };
    var powerup = CreatePowerUp(turboBoost: 50);

    // Act
    powerup.Visit(engine);

    // Assert - Should cap at max, not exceed
    Assert.AreEqual(200, engine.turboBoost);
}

๐Ÿ› Debugging Tips

Add Logging to Track Visits:

public void Visit(BikeWeapon bikeWeapon)
{
    Debug.Log($"[PowerUp] Visiting weapon: " +
              $"Range {bikeWeapon.range} โ†’ {bikeWeapon.range + weaponRange}");

    // Apply boost...
}

public void Accept(IVisitor visitor)
{
    Debug.Log($"[{GetType().Name}] Accepting visitor: {visitor}");
    visitor.Visit(this);
}

Visual Debugging in Unity:

  • Use OnGUI() to display current stat values
  • Add particle effects when power-up applied
  • Color-code UI elements based on boost level

๐Ÿ“ Summary

What We Learned:

  • โœ… Visitor pattern allows operations on object structures without modifying them
  • โœ… Perfect for power-up systems with multiple component types
  • โœ… Combines well with ScriptableObjects for designer-friendly workflows
  • โœ… Separates data (visitable components) from behavior (visitors)
  • โœ… Follows Open/Closed and Single Responsibility principles

Key Takeaways:

  • One Visit() method per visitable type
  • Accept() method is the entry point
  • BikeController coordinates the visiting process
  • ScriptableObjects enable no-code power-up creation

๐Ÿ’ช Practice Exercise

Implement a "Debuff" System

Using the Visitor pattern, create a debuff system that weakens the bike:

Requirements:

  1. Create a Debuff class that implements IVisitor
  2. Debuff should reduce properties instead of increasing them:
    • Reduce turbo boost by percentage
    • Damage shield by fixed amount
    • Decrease weapon range
  3. Make it a ScriptableObject for easy configuration
  4. Create a "DebuffZone" trigger that applies debuff when entered
  5. Add visual feedback (red particle effect?)
Bonus: Make debuffs temporary using coroutines

๐Ÿ“š Additional Resources

Further Reading:

  • Design Patterns (Gang of Four): Original Visitor pattern description
  • Unity ScriptableObjects Documentation: Deep dive into asset-based design
  • Game Programming Patterns: Game-specific pattern applications

Code Examples:

  • Full implementation in course GitHub repo
  • FPP project folder has complete racing game example
  • Includes working pickups and visual effects

Next Lecture:

Strategy Pattern: Implementing AI behaviors for enemy drones

โ“ Questions?

Common Questions:

  • Q: Can a power-up visit only specific components?
    • A: Yes! Just check conditions before calling Visit() methods
  • Q: How do I make effects stackable or unique?
    • A: Track applied power-ups in a list, check before applying
  • Q: Can visitors access private fields?
    • A: Only through public interface - may need to expose properties
  • Q: Is this pattern overkill for simple power-ups?
    • A: Possibly! Use direct modification for 1-2 simple cases

Thank you! ๐ŸŽฎ