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

๐ŸŽฏ 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

namespace Chapter.Decorator
{
    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

using UnityEngine;

namespace Chapter.Decorator
{
    [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

        [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

namespace Chapter.Decorator
{
    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

namespace Chapter.Decorator
{
    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

        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

using UnityEngine;

namespace Chapter.Decorator
{
    [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

        [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

using UnityEngine;
using System.Collections;

namespace Chapter.Decorator
{
    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

        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

        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

        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

using UnityEngine;

namespace Chapter.Decorator
{
    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();
        }
    }
}

๐Ÿ“Š 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

๐Ÿ’ช 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! ๐ŸŽฎ