Lecture 11: Decorator Pattern - Weapon Systems

๐Ÿ”ซ Decorator Pattern

Implementing a Weapon System

Game Programming - CSCI 3213

Spring 2026 - Lecture 11

Oklahoma City University

๐Ÿ“š Learning Objectives

  • Understand the Decorator design pattern
  • Learn how to add functionality without altering objects
  • Implement a customizable weapon attachment system
  • Combine Decorator pattern with Unity ScriptableObjects
  • Create designer-friendly upgrade mechanics
  • Explore when Decorator is better than inheritance

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.

๐ŸŽฏ Weapon System Design

Core Concept

Every bike comes with a front-mounted laser weapon. Players can purchase attachments to boost weapon stats.

Requirements:

  • Customizable: Multiple attachments available
  • Dynamic: Add/remove attachments at runtime
  • Limited Slots: Maximum of 2 expansion slots
  • Stackable: Attachments combine their effects
  • Reversible: Can restore to base weapon
Design Goal: Build weapon variations without modifying base weapon code

๐Ÿ”ง Weapon Attachments

Three Attachment Categories:

  • Injector (Plasma):
    • Amplifies damage capacity
    • Increases weapon strength
    • Perfect for aggressive players
  • Stabilizer:
    • Reduces vibration at high speed
    • Expands weapon range
    • Improves accuracy
  • Cooler (Water-Cooling):
    • Enhances rate of fire
    • Reduces cooldown duration
    • Sustains longer combat

๐Ÿ” Understanding Decorator Pattern

Core Definition

Add new functionality to an existing object without altering it, by creating a decorator class that wraps the original.

Key Concept:

Decorator wraps an object and enhances its behavior without changing its structure.

Real-World Analogy:

Think of a smartphone:

  • Base phone: iPhone (core functionality)
  • Decorators: Case, screen protector, pop socket
  • Each adds functionality without modifying the phone
  • Can add/remove decorators independently
  • Phone works the same with or without decorators

๐Ÿ—๏ธ Decorator Pattern Structure

Key Participants:

  • Component Interface: Defines common interface
    • In our case: IWeapon
  • Concrete Component: Original object
    • In our case: Weapon
  • Decorator Base: Wraps component
    • In our case: WeaponDecorator
  • Concrete Decorators: Specific enhancements
    • In our case: WeaponAttachment instances
Pattern Family: Structural - concerned with object composition

โš–๏ธ Decorator vs Inheritance

Aspect Inheritance Decorator Pattern
Timing Compile-time (static) Runtime (dynamic)
Flexibility Fixed class hierarchy Mix and match behaviors
Combination Limited by class design Unlimited combinations
Reversibility Can't "unextend" a class Can remove decorators

With Inheritance (Static):

Weapon โ†’ StrongWeapon โ†’ StrongFastWeapon โ†’ StrongFastLongRangeWeapon

Need a new class for every combination!

With Decorator (Dynamic):

Weapon + Injector + Cooler + Stabilizer (runtime composition)

โœ… Benefits of Decorator Pattern

  • Runtime Flexibility:
    • Add/remove functionality dynamically
    • Change behavior without restarting
    • Player can customize on-the-fly
  • Alternative to Subclassing:
    • Avoid class explosion (too many subclasses)
    • Overcome limits of single inheritance
    • More flexible than static inheritance
  • Single Responsibility:
    • Each decorator has one job
    • Easy to understand and test
    • Maintainable code
  • Open/Closed Principle:
    • Open for extension (new decorators)
    • Closed for modification (base weapon unchanged)

โš ๏ธ Potential Drawbacks

  • Relationship Complexity:
    • Multiple layers of wrapping can be confusing
    • Hard to track initialization chain
    • Debugging nested decorators is tricky
  • Code Complexity:
    • Many small decorator classes to maintain
    • Can be overkill for simple cases
    • Learning curve for team members
  • Performance:
    • Extra indirection through wrappers
    • More objects in memory
    • Usually negligible, but consider for mobile
Our Mitigation: Use ScriptableObjects as decorators - reduces complexity by making them configurable assets instead of code classes.

๐Ÿค” When to Use Decorator Pattern

Use Decorator When:

  • โœ… Need to add functionality at runtime
  • โœ… Want to avoid inheritance explosion
  • โœ… Behaviors should be composable/stackable
  • โœ… Need to add/remove features dynamically
  • โœ… Extending sealed/final classes

Other Game Use Cases:

  • Collectible Card Games: Buff cards stacked on base cards
  • RPG Equipment: Armor with enchantments and gems
  • Character Abilities: Temporary buffs/debuffs
  • UI Systems: Windows with borders, shadows, animations
  • Audio Processing: Effects chain (reverb, delay, distortion)
Pattern Family: Other structural patterns include Adapter, Bridge, Faรงade, and Flyweight.

๐ŸŽฎ Unity-Specific Challenge

The Constructor Problem

Traditional Decorator: Relies heavily on constructors

// Standard C# approach
IWeapon weapon = new Weapon();
weapon = new InjectorDecorator(weapon);
weapon = new CoolerDecorator(weapon);
Problem: MonoBehaviour and ScriptableObject don't use constructors the same way!
  • Unity manages object lifecycle
  • Initialization in Awake()/Start() callbacks
  • Can't new up MonoBehaviours directly

Our Solution:

Adapt the pattern to use Unity's ScriptableObject system while preserving Decorator benefits!

๐Ÿ’ป Implementation: IWeapon Interface

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/IWeapon.cs
This interface defines the contract for all weapons and decorators.
โš ๏ธ This code goes into your Unity project for Blade Racer.

public interface IWeapon
{
    float Range { get; }
    float Rate { get; }      // Fire rate
    float Strength { get; }  // Damage
    float Cooldown { get; }  // Cool-down duration
}

Why an Interface?

  • Common contract between weapon and decorators
  • Ensures consistent property signatures
  • Allows decorators to wrap weapons seamlessly
  • Both Weapon and WeaponDecorator implement it

๐Ÿ’ป WeaponConfig - Base Configuration

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Configs/WeaponConfig.cs
This ScriptableObject stores base weapon configurations.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;

[CreateAssetMenu(fileName = "NewWeaponConfig",
    menuName = "Weapon/Config", order = 1)]
public class WeaponConfig : ScriptableObject, IWeapon
{
    [Range(0, 60)]
    [Tooltip("Rate of firing per second")]
    [SerializeField] private float rate;

    [Range(0, 50)]
    [Tooltip("Weapon range")]
    [SerializeField] private float range;

    [Range(0, 100)]
    [Tooltip("Weapon strength")]
    [SerializeField] private float strength;

๐Ÿ’ป WeaponConfig - Properties & Metadata

๐Ÿ“ Continuing WeaponConfig.cs

Add these properties and IWeapon implementation to WeaponConfig from Slide 13.

    [Range(0, 5)]
    [Tooltip("Cooldown duration")]
    [SerializeField] private float cooldown;

    public string weaponName;
    public GameObject weaponPrefab;
    public string weaponDescription;

    // IWeapon implementation
    public float Rate { get { return rate; } }
    public float Range { get { return range; } }
    public float Strength { get { return strength; } }
    public float Cooldown { get { return cooldown; } }
}
ScriptableObject Benefit: Create weapon configs as assets, configure in Inspector without code!

๐Ÿ’ป Weapon - The Component

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/Weapon.cs
This is the concrete component that will be decorated.
โš ๏ธ This code goes into your Unity project for Blade Racer.

public class Weapon : IWeapon
{
    private readonly WeaponConfig _config;

    // Constructor - initializes with config
    public Weapon(WeaponConfig weaponConfig)
    {
        _config = weaponConfig;
    }

    // IWeapon implementation - pass through to config
    public float Range { get { return _config.Range; } }
    public float Rate { get { return _config.Rate; } }
    public float Strength { get { return _config.Strength; } }
    public float Cooldown { get { return _config.Cooldown; } }
}
Note: Not a MonoBehaviour! Plain C# class with constructor. This is the object we'll decorate.

๐Ÿ’ป WeaponDecorator - The Wrapper

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/WeaponDecorator.cs
This is the decorator that wraps and enhances weapons.
โš ๏ธ This code goes into your Unity project for Blade Racer.

public class WeaponDecorator : IWeapon
{
    private readonly IWeapon _decoratedWeapon;
    private readonly WeaponAttachment _attachment;

    public WeaponDecorator(
        IWeapon weapon, WeaponAttachment attachment)
    {
        _attachment = attachment;
        _decoratedWeapon = weapon;
    }

    // Enhance properties by adding attachment values
    public float Rate
    {
        get { return _decoratedWeapon.Rate + _attachment.Rate; }
    }

    public float Range
    {
        get { return _decoratedWeapon.Range + _attachment.Range; }
    }

๐Ÿ’ป WeaponDecorator - Complete

๐Ÿ“ Continuing WeaponDecorator.cs

Add these remaining property implementations to WeaponDecorator from Slide 16.

    public float Strength
    {
        get { return _decoratedWeapon.Strength + _attachment.Strength; }
    }

    public float Cooldown
    {
        get { return _decoratedWeapon.Cooldown + _attachment.Cooldown; }
    }
}

The Magic:

  • Wraps an IWeapon (could be Weapon or another decorator!)
  • Doesn't modify wrapped object directly
  • Returns enhanced values by adding attachment bonuses
  • Can chain decorators: Weapon โ†’ Injector โ†’ Cooler

๐Ÿ’ป WeaponAttachment - Designer Assets

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Configs/WeaponAttachment.cs
ScriptableObject that defines weapon attachment properties.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;

[CreateAssetMenu(fileName = "NewWeaponAttachment",
    menuName = "Weapon/Attachment", order = 1)]
public class WeaponAttachment : ScriptableObject, IWeapon
{
    [Range(0, 50)]
    [Tooltip("Increase rate of firing per second")]
    [SerializeField] private float rate;

    [Range(0, 50)]
    [Tooltip("Increase weapon range")]
    [SerializeField] private float range;

    [Range(0, 100)]
    [Tooltip("Increase weapon strength")]
    [SerializeField] private float strength;

๐Ÿ’ป WeaponAttachment - Properties

๐Ÿ“ Continuing WeaponAttachment.cs

Add these properties and IWeapon implementation to WeaponAttachment from Slide 18.

    [Range(0, -5)]  // Negative values reduce cooldown!
    [Tooltip("Reduce cooldown duration")]
    [SerializeField] private float cooldown;

    public string attachmentName;
    public GameObject attachmentPrefab;
    public string attachmentDescription;

    // IWeapon implementation
    public float Rate { get { return rate; } }
    public float Range { get { return range; } }
    public float Strength { get { return strength; } }
    public float Cooldown { get { return cooldown; } }
}
Key Innovation: Instead of code classes for each decorator, we create ScriptableObject assets! Designers can create unlimited attachment types.

๐Ÿ’ป BikeWeapon - MonoBehaviour Client

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Bike/BikeWeapon.cs
MonoBehaviour that uses the decorated weapon system.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine;
using System.Collections;

public class BikeWeapon : MonoBehaviour
{
    public WeaponConfig weaponConfig;
    public WeaponAttachment mainAttachment;
    public WeaponAttachment secondaryAttachment;

    private bool _isFiring;
    private IWeapon _weapon;
    private bool _isDecorated;

    void Start()
    {
        // Initialize base weapon
        _weapon = new Weapon(weaponConfig);
    }

๐Ÿ’ป BikeWeapon - The Magic Method

๐Ÿ“ Continuing BikeWeapon.cs

Add this Decorate() method to the BikeWeapon class from Slide 20.

        public void Decorate()
        {
            // One attachment
            if (mainAttachment && !secondaryAttachment)
            {
                _weapon = new WeaponDecorator(_weapon, mainAttachment);
            }

            // Two attachments - CHAINING!
            if (mainAttachment && secondaryAttachment)
            {
                _weapon = new WeaponDecorator(
                    new WeaponDecorator(_weapon, mainAttachment),
                    secondaryAttachment);
            }

            _isDecorated = !_isDecorated;
        }

Decorator Chaining:

Notice how we wrap a decorator with another decorator! This is the power of the pattern.

๐Ÿ’ป BikeWeapon - Reset Functionality

๐Ÿ“ Continuing BikeWeapon.cs

Add these Reset() and ToggleFire() methods to BikeWeapon.

        public void Reset()
        {
            // Remove all decorators - back to base weapon
            _weapon = new Weapon(weaponConfig);
            _isDecorated = !_isDecorated;
        }

        public void ToggleFire()
        {
            _isFiring = !_isFiring;
            if (_isFiring)
                StartCoroutine(FireWeapon());
        }

        IEnumerator FireWeapon()
        {
            float firingRate = 1.0f / _weapon.Rate;
            while (_isFiring)
            {
                yield return new WaitForSeconds(firingRate);
                Debug.Log("Fire! Strength: " + _weapon.Strength);
            }
        }
    }

๐Ÿ’ป BikeWeapon - Debug GUI

๐Ÿ“ Continuing BikeWeapon.cs

Add this OnGUI() method to BikeWeapon for debugging weapon stats.

        void OnGUI()
        {
            GUI.color = Color.green;

            GUI.Label(new Rect(5, 50, 150, 100),
                "Range: " + _weapon.Range);
            GUI.Label(new Rect(5, 70, 150, 100),
                "Strength: " + _weapon.Strength);
            GUI.Label(new Rect(5, 90, 150, 100),
                "Cooldown: " + _weapon.Cooldown);
            GUI.Label(new Rect(5, 110, 150, 100),
                "Firing Rate: " + _weapon.Rate);
            GUI.Label(new Rect(5, 130, 150, 100),
                "Weapon Firing: " + _isFiring);

            if (mainAttachment && _isDecorated)
                GUI.Label(new Rect(5, 150, 150, 100),
                    "Main: " + mainAttachment.name);

            if (secondaryAttachment && _isDecorated)
                GUI.Label(new Rect(5, 170, 200, 100),
                    "Secondary: " + secondaryAttachment.name);
        }
    }
}

๐Ÿ”„ The Complete Flow

Initialization:

  1. BikeWeapon.Start() creates base Weapon with config
  2. Weapon wraps WeaponConfig ScriptableObject
  3. Base stats: Range=10, Strength=25, Rate=5, Cooldown=2

Decoration (One Attachment):

  1. User clicks "Decorate Weapon" button
  2. BikeWeapon.Decorate() called
  3. _weapon = new WeaponDecorator(_weapon, Injector)
  4. Now accessing _weapon.Strength returns: 25 (base) + 15 (injector) = 40

Decoration (Two Attachments - Chained!):

  1. _weapon = new WeaponDecorator(
  2.   new WeaponDecorator(_weapon, Injector),
  3.   Cooler)
  4. Inner decorator adds Injector stats
  5. Outer decorator adds Cooler stats on top
  6. Final: Strength = 25 + 15 + 0 = 40, Rate = 5 + 0 + 10 = 15

๐Ÿงช Testing the System

Setup Steps:

  1. Create new Unity scene
  2. Create empty GameObject, add BikeWeapon component
  3. Create WeaponConfig asset:
    • Assets โ†’ Create โ†’ Weapon โ†’ Config
    • Name: "BaseLaser"
    • Set: Rate=5, Range=10, Strength=25, Cooldown=2
  4. Create Attachment assets:
    • Assets โ†’ Create โ†’ Weapon โ†’ Attachment
    • "Injector": Strength +15
    • "Cooler": Rate +10, Cooldown -1
    • "Stabilizer": Range +20
  5. Assign assets to BikeWeapon in Inspector
  6. Add ClientDecorator script (next slide)

๐Ÿ’ป ClientDecorator - Test UI

๐Ÿ“ File Structure Note - TESTING CODE

Create a new file: Assets/Scripts/Testing/ClientDecorator.cs
Test client that demonstrates the decorator pattern in action.
โš ๏ธ This is temporary testing code - you can remove it after testing your implementation.

using UnityEngine;

public class ClientDecorator : MonoBehaviour
{
    private BikeWeapon _bikeWeapon;
    private bool _isWeaponDecorated;

    void Start()
    {
        _bikeWeapon = (BikeWeapon)
            FindObjectOfType(typeof(BikeWeapon));
    }

    void OnGUI()
    {
        if (!_isWeaponDecorated)
            if (GUILayout.Button("Decorate Weapon"))
            {
                _bikeWeapon.Decorate();
                _isWeaponDecorated = !_isWeaponDecorated;
            }

        if (_isWeaponDecorated)
            if (GUILayout.Button("Reset Weapon"))
            {
                _bikeWeapon.Reset();
                _isWeaponDecorated = !_isWeaponDecorated;
            }

        if (GUILayout.Button("Toggle Fire"))
            _bikeWeapon.ToggleFire();
    }
}
๐Ÿ’ก 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 Decorator Pattern

๐Ÿ“ Evolving TestPanel.cs

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

// Add to TestPanel fields private bool _decoratorExpanded = true; private BikeWeapon _bikeWeapon; private bool _isDecorated; void Start() { // ... existing code ... _bikeWeapon = FindFirstObjectByType<BikeWeapon>(); } // Add to Update() - Decorator Pattern shortcuts if (_bikeWeapon != null) { if (Input.GetKeyDown(KeyCode.M)) // Modify { _bikeWeapon.Decorate(); _isDecorated = true; } if (Input.GetKeyDown(KeyCode.N)) // uNdo { _bikeWeapon.Reset(); _isDecorated = false; } if (Input.GetKeyDown(KeyCode.B)) // Bang! _bikeWeapon.ToggleFire(); }
// Add DrawDecoratorSection() method void DrawDecoratorSection() { if (_bikeWeapon == null) return; GUI.backgroundColor = new Color(0.5f, 0f, 0.5f); // Purple _decoratorExpanded = GUILayout.Toggle( _decoratorExpanded, "โ–ผ Decorator Pattern", "button"); GUI.backgroundColor = Color.white; if (_decoratorExpanded) { GUILayout.BeginVertical("box"); if (!_isDecorated) { if (GUILayout.Button("Decorate (M)")) { _bikeWeapon.Decorate(); _isDecorated = true; } } else { if (GUILayout.Button("Reset (N)")) { _bikeWeapon.Reset(); _isDecorated = false; } } if (GUILayout.Button("Toggle Fire (B)")) _bikeWeapon.ToggleFire(); GUILayout.EndVertical(); } }
Keymap Update: Add to DrawKeymapWindow():
M = Decorate (Modify), N = Reset (uNdo), B = Toggle Fire (Bang)

๐Ÿ“Š Expected Behavior

Before Decoration:

Range: 10
Strength: 25
Cooldown: 2
Firing Rate: 5
Weapon Firing: False

After Decoration (Injector + Cooler):

Range: 10          (no change)
Strength: 40       (25 + 15 from Injector)
Cooldown: 1        (2 + -1 from Cooler)
Firing Rate: 15    (5 + 10 from Cooler)
Weapon Firing: False
Main: Injector
Secondary: Cooler

Click "Toggle Fire" to see firing rate in action!

๐ŸŽจ Designer-Friendly Workflow

Creating New Attachments (No Code!):

  1. Right-click in Project: Create โ†’ Weapon โ†’ Attachment
  2. Name it: "TurboCharger"
  3. Configure in Inspector:
    • Rate: +15 (fires faster)
    • Range: 0 (no change)
    • Strength: -5 (trade-off for speed)
    • Cooldown: -2 (cools faster)
    • Attachment Name: "Turbo Charger"
    • Description: "High speed, low power"
  4. Assign prefab: Drag 3D model
  5. Done! No programming required
Power of ScriptableObjects: Designers become content creators without touching code!

๐Ÿ”€ Combination Possibilities

With 3 Attachments:

Combo Result Playstyle
None Base weapon Balanced
Injector +Strength Power
Cooler +Rate, -Cooldown Rapid fire
Stabilizer +Range Sniper
Injector + Cooler +Strength, +Rate Aggressive
Injector + Stabilizer +Strength, +Range Long-range power
Cooler + Stabilizer +Rate, +Range Sustained assault
With 10 attachments: 1 + 10 + 45 = 56 combinations! (With 2 slots)

๐Ÿ”ง Extending Beyond 2 Slots

Current Limitation: Hardcoded 2 Slots

// Fixed in BikeWeapon
public WeaponAttachment mainAttachment;
public WeaponAttachment secondaryAttachment;

Better Approach: Dynamic Slots

public class BikeWeapon : MonoBehaviour
{
    public WeaponConfig weaponConfig;
    public List<WeaponAttachment> attachments;
    public int maxSlots = 2;

    public void Decorate()
    {
        _weapon = new Weapon(weaponConfig);

        // Chain all attachments
        foreach (var attachment in attachments.Take(maxSlots))
        {
            _weapon = new WeaponDecorator(_weapon, attachment);
        }
    }
}

Now easily configurable to 3, 4, or more slots!

๐Ÿ”„ Alternative: Without Decorator

Simple Iteration Approach:

public class SimpleWeaponSystem : MonoBehaviour
{
    public WeaponConfig baseWeapon;
    public List<WeaponAttachment> attachments;

    private float totalRange, totalStrength, totalRate, totalCooldown;

    public void ApplyAttachments()
    {
        // Start with base
        totalRange = baseWeapon.Range;
        totalStrength = baseWeapon.Strength;
        totalRate = baseWeapon.Rate;
        totalCooldown = baseWeapon.Cooldown;

        // Add each attachment
        foreach (var att in attachments)
        {
            totalRange += att.Range;
            totalStrength += att.Strength;
            totalRate += att.Rate;
            totalCooldown += att.Cooldown;
        }
    }
}

Why Use Decorator Then?

  • Decorator provides structured, repeatable pattern
  • Chaining capability (can wrap decorators)
  • Maintains interface consistency
  • Follows established design principles

๐ŸŽญ Pattern Experimentation

Important Lesson: Design patterns are guidelines, not commandments!

Our Implementation is "Unorthodox":

  • Mixes C# constructors with Unity ScriptableObjects
  • Uses assets instead of pure code classes
  • Adapts pattern to Unity's lifecycle
  • Prioritizes designer workflow over "purity"

Why This is OK:

  • โœ… Preserves core pattern benefits (wrapping, chaining)
  • โœ… Adds practical value (no-code asset creation)
  • โœ… Solves real problem (weapon customization)
  • โœ… Maintainable and scalable

Lesson: Experiment with patterns! Find what works for your engine, your team, and your game.

โš–๏ธ Decorator vs Simple: Trade-offs

Aspect Decorator Pattern Simple Addition
Complexity Higher (wrapper classes) Lower (just loops)
Flexibility Can chain, wrap recursively Linear addition only
Testability Test each decorator alone Test entire system
Learning Curve Requires pattern knowledge Straightforward
Extensibility Add new decorators easily Modify existing code
Decision Point: Use Decorator when system will grow complex. Use simple approach for small, stable systems.

๐ŸŒ Beyond Weapons

Other Decorator Use Cases:

  • Character Customization:
    • Base character + clothing + accessories + buffs
    • Each layer decorates appearance and stats
  • Spell System (Magic Game):
    • Base spell + modifiers (area, duration, power)
    • Infinite spell variations from combinations
  • Vehicle Upgrades (Racing):
    • Base car + engine upgrade + tire upgrade + turbo
    • Each affects different performance aspects
  • UI System:
    • Base window + border + shadow + animation
    • Each decorator adds visual/functional layer
  • Damage System:
    • Base damage + elemental + critical + armor penetration
    • Chain damage modifiers

โšก Performance Tips

Optimization Strategies:

  • Cache Decorated Values:
    // Instead of calculating every frame:
    float strength = _weapon.Strength;  // Traverses decorator chain
    
    // Cache when decorators change:
    private float _cachedStrength;
    public void Decorate() {
        // Apply decorators...
        _cachedStrength = _weapon.Strength;  // Calculate once
    }
  • Limit Decorator Depth:
    • 2-3 layers: negligible performance impact
    • 10+ layers: consider caching
    • Our 2-slot system: perfectly fine
  • ScriptableObject Benefits:
    • Shared data across instances (memory efficient)
    • No runtime allocation (created as assets)
    • Better than creating classes at runtime

๐Ÿšซ Common Mistakes to Avoid

  1. Modifying Wrapped Object Directly:
    // BAD - breaks pattern intent
    public class WeaponDecorator : IWeapon {
        public WeaponDecorator(IWeapon weapon) {
            weapon.Strength = 100;  // โŒ Don't modify!
        }
    }
    
    // GOOD - return modified values
    public float Strength {
        get { return _weapon.Strength + 50; }  // โœ“ Enhance
    }
  2. Forgetting Interface Consistency:
    • Decorator must implement same interface as component
    • Otherwise can't wrap/chain properly
  3. Not Handling null Attachments:
    // Add null checks!
    if (mainAttachment && secondaryAttachment) { ... }
  4. Overcomplicating Simple Systems:
    • If you only have 1-2 attachments total, pattern may be overkill

๐Ÿงช Unit Testing Decorators

[Test]
public void Decorator_EnhancesWeaponStats()
{
    // Arrange
    var config = ScriptableObject.CreateInstance<WeaponConfig>();
    config.strength = 25;
    var weapon = new Weapon(config);

    var attachment = ScriptableObject.CreateInstance<WeaponAttachment>();
    attachment.strength = 15;

    // Act
    IWeapon decorated = new WeaponDecorator(weapon, attachment);

    // Assert
    Assert.AreEqual(40, decorated.Strength); // 25 + 15
}

[Test]
public void MultipleDecorators_Stack()
{
    // Arrange
    var weapon = CreateBaseWeapon(strength: 25);
    var injector = CreateAttachment(strength: 15);
    var cooler = CreateAttachment(rate: 10);

    // Act
    IWeapon result = new WeaponDecorator(
        new WeaponDecorator(weapon, injector),
        cooler);

    // Assert
    Assert.AreEqual(40, result.Strength);  // 25 + 15 + 0
    Assert.AreEqual(15, result.Rate);      // 5 + 0 + 10
}

๐Ÿ“ Summary

What We Learned:

  • โœ… Decorator pattern adds functionality without altering objects
  • โœ… Works by wrapping objects in decorator classes
  • โœ… Perfect for weapon attachments and equipment upgrades
  • โœ… More flexible than inheritance (runtime, reversible)
  • โœ… Can chain decorators for unlimited combinations
  • โœ… Adapted pattern to use Unity ScriptableObjects
  • โœ… Enabled designer-friendly asset creation

Key Takeaways:

  • Interface consistency is crucial (IWeapon)
  • Decorators wrap, don't modify directly
  • Pattern experimentation is encouraged
  • Balance pattern benefits vs code complexity
  • ScriptableObjects make patterns more practical

Gaming History Moment ๐Ÿ•น๏ธ

Pokรฉmon Mania (1996-2000)

When Pokรฉmon Red and Blue launched in 1998 (North America), they introduced a revolutionary progression system. Your starter Pokรฉmon began basic, but through battles it would level up, learn new moves, hold items, and eventually evolve. A Charmander could become Charmeleon, then Charizard - each form adding capabilities without losing the base stats.

But the real genius was the item system. Give Pikachu a Light Ball - now it has double attack! Add a Focus Band - 10% chance to survive knockout! Equip Leftovers - passive health regeneration! Each held item decorated the Pokรฉmon with new abilities while keeping its core identity. You could swap items anytime, creating millions of possible combinations from 151 Pokรฉmon.

Connection to Decorator Pattern

Pokรฉmon's held item system IS the Decorator Pattern! Each Pokรฉmon is a base weapon (like our BikeWeapon), and held items are decorators that wrap functionality around it. Light Ball = RangeDecorator, Leftovers = RegenDecorator, Focus Band = SurvivalDecorator. The base Pokรฉmon stats stay intact, but decorators add layers of behavior. Just like our weapon attachments, you can equip/unequip items at runtime, stack effects, and create infinite combinations while maintaining the core IPokรฉmon interface!

Learn More: The Pokรฉmon Company: Official History | Satoshi Tajiri: Pokรฉmon Creator Interview

๐Ÿ’ช Practice Exercise

Implement an Armor Decoration System

Requirements:

  1. Create IArmor interface with properties:
    • Defense (int)
    • Weight (float)
    • Durability (float)
  2. Create Armor class and ArmorConfig ScriptableObject
  3. Create ArmorDecorator wrapper class
  4. Create ArmorUpgrade ScriptableObject with upgrade types:
    • Reinforcement: +Defense, +Weight
    • Lightweight Plating: -Weight, -Defense
    • Hardening: +Durability, +Weight
  5. Create PlayerArmor MonoBehaviour with 3 upgrade slots
  6. Test different upgrade combinations
Bonus: Add visual representation - show different armor pieces on character model when upgraded!

โ“ Questions?

Common Questions:

  • Q: Can I remove individual decorators from the middle?
    • A: Not easily with this implementation. Would need to rebuild chain. Better: store attachments in list, rebuild when changed.
  • Q: What if I want decorators to interact with each other?
    • A: Pass additional context in constructor, or use different pattern (Chain of Responsibility)
  • Q: Can decorators have different types of enhancement?
    • A: Yes! Some can multiply, others add, others replace values entirely
  • Q: When should I use Decorator vs Composite pattern?
    • A: Decorator adds behavior, Composite handles tree structures of objects

Next Lecture:

Spatial Partition Pattern: Optimizing large game worlds with spatial data structures

Thank you! ๐ŸŽฎ