Game Programming - CSCI 3213
Spring 2026 - Lecture 11
Oklahoma City University
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.
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.
Every bike comes with a front-mounted laser weapon. Players can purchase attachments to boost weapon stats.
Add new functionality to an existing object without altering it, by creating a decorator class that wraps the original.
Decorator wraps an object and enhances its behavior without changing its structure.
Think of a smartphone:
IWeaponWeaponWeaponDecoratorWeaponAttachment instances| 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 |
Weapon โ StrongWeapon โ StrongFastWeapon โ StrongFastLongRangeWeapon
Need a new class for every combination!
Weapon + Injector + Cooler + Stabilizer (runtime composition)
Traditional Decorator: Relies heavily on constructors
// Standard C# approach
IWeapon weapon = new Weapon();
weapon = new InjectorDecorator(weapon);
weapon = new CoolerDecorator(weapon);
Adapt the pattern to use Unity's ScriptableObject system while preserving Decorator benefits!
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
}
Weapon and WeaponDecorator implement it
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;
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; } }
}
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; } }
}
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; }
}
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; }
}
}
IWeapon (could be Weapon or another decorator!)Weapon โ Injector โ Cooler
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;
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; } }
}
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);
}
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;
}
Notice how we wrap a decorator with another decorator! This is the power of the pattern.
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);
}
}
}
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);
}
}
}
Weapon with config_weapon = new WeaponDecorator(_weapon, Injector)_weapon = new WeaponDecorator( new WeaponDecorator(_weapon, Injector), Cooler)
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();
}
}
TestPanel.cs to combine all test controls into one draggable window.
See the Event Bus lecture for the unified TestPanel implementation.
Add Decorator Pattern keyboard shortcuts and section to your TestPanel.cs.
DrawKeymapWindow():M = Decorate (Modify), N = Reset (uNdo), B = Toggle Fire (Bang)
Range: 10 Strength: 25 Cooldown: 2 Firing Rate: 5 Weapon Firing: False
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!
| 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 |
// Fixed in BikeWeapon
public WeaponAttachment mainAttachment;
public WeaponAttachment secondaryAttachment;
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!
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;
}
}
}
Important Lesson: Design patterns are guidelines, not commandments!
Lesson: Experiment with patterns! Find what works for your engine, your team, and your game.
| 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 |
// 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
}
// 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
}
// Add null checks!
if (mainAttachment && secondaryAttachment) { ... }
[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
}
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.
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!
IArmor interface with properties:
Armor class and ArmorConfig ScriptableObjectArmorDecorator wrapper classArmorUpgrade ScriptableObject with upgrade types:
PlayerArmor MonoBehaviour with 3 upgrade slotsSpatial Partition Pattern: Optimizing large game worlds with spatial data structures
Thank you! ๐ฎ