Game Character Factory with Prototype Pattern
This demo shows how to build an efficient game entity spawning system using the Prototype pattern, demonstrating complex object cloning with nested structures, collections, and runtime mutations.
What it demonstrates
- Prototype registry - store and retrieve named character templates
- Deep cloning - create independent copies of complex objects
- Mutation chains - customize clones at creation time
- Performance - avoid expensive initialization via cloning
- Independence verification - clones don't affect originals
Where to look
- Code:
src/PatternKit.Examples/PrototypeDemo/PrototypeDemo.cs - Tests:
test/PatternKit.Tests/Creational/Prototype/PrototypeTests.cs - Generator: Prototype Generator Documentation
Quick start
using PatternKit.Creational.Prototype;
// Define your domain model
public sealed class GameCharacter
{
public required string Id { get; set; }
public required string Name { get; set; }
public required string Class { get; set; }
public int Level { get; set; } = 1;
public CharacterStats Stats { get; set; } = new();
public Equipment Equipment { get; set; } = new();
public static GameCharacter DeepClone(in GameCharacter source) => new()
{
Id = $"{source.Id}-{Guid.NewGuid():N}"[..16],
Name = source.Name,
Class = source.Class,
Level = source.Level,
Stats = source.Stats.Clone(),
Equipment = source.Equipment.Clone()
};
}
// Create a prototype registry
var factory = Prototype<string, GameCharacter>
.Create()
.Map("warrior", CreateWarriorPrototype(), GameCharacter.DeepClone)
.Map("mage", CreateMagePrototype(), GameCharacter.DeepClone)
.Build();
// Clone with optional mutations
var warrior = factory.Create("warrior");
var customWarrior = factory.Create("warrior", c =>
{
c.Name = "Sir Galahad";
c.Level = 25;
});
The Problem
In game development, you often need to spawn many similar entities (NPCs, enemies, projectiles) with:
- Complex initialization - nested objects (stats, equipment, abilities)
- Expensive setup - loading assets, computing derived values
- Variations needed - similar but not identical instances
- Performance critical - spawn hundreds per frame
Traditional solutions have drawbacks:
// ❌ Factory functions - run full initialization every time
public GameCharacter CreateWarrior()
{
return new GameCharacter
{
Id = Guid.NewGuid().ToString(),
Name = "Warrior",
Stats = new CharacterStats { Health = 150, Strength = 15, /* ... */ },
Equipment = new Equipment { Weapon = "Sword", /* ... */ },
Abilities = ["Slash", "Block", "Charge"],
// ... expensive initialization
};
}
// ❌ Inheritance - class explosion for variants
public class Warrior : GameCharacter { }
public class EliteWarrior : Warrior { }
public class BossWarrior : Warrior { }
// ... one class per variant
The Solution: Prototype Pattern
The Prototype pattern solves this by:
- Pre-configuring base prototypes once
- Cloning them (cheap memory copy)
- Mutating the clone as needed
// ✅ Configure once
var warriorPrototype = CreateWarriorPrototype();
// ✅ Clone many times (fast)
var w1 = warriorPrototype.Clone();
var w2 = warriorPrototype.Clone();
// ✅ Customize each clone
w1.Name = "Goblin Fighter #1";
w2.Name = "Goblin Fighter #2";
Domain Model
Character Statistics
public sealed class CharacterStats
{
public int Health { get; set; }
public int Mana { get; set; }
public int Strength { get; set; }
public int Agility { get; set; }
public int Intelligence { get; set; }
public CharacterStats Clone() => new()
{
Health = Health,
Mana = Mana,
Strength = Strength,
Agility = Agility,
Intelligence = Intelligence
};
}
Equipment System
public sealed class Equipment
{
public string Weapon { get; set; } = "Fists";
public string Armor { get; set; } = "Cloth";
public List<string> Accessories { get; set; } = [];
public Equipment Clone() => new()
{
Weapon = Weapon,
Armor = Armor,
Accessories = new List<string>(Accessories) // New list, same items
};
}
Game Character
public sealed class GameCharacter
{
public required string Id { get; set; }
public required string Name { get; set; }
public required string Class { get; set; }
public int Level { get; set; } = 1;
public CharacterStats Stats { get; set; } = new();
public Equipment Equipment { get; set; } = new();
public List<string> Abilities { get; set; } = [];
public Dictionary<string, int> Resistances { get; set; } = new();
// Deep clone - recursively clones all nested objects
public static GameCharacter DeepClone(in GameCharacter source) => new()
{
Id = $"{source.Id}-{Guid.NewGuid():N}"[..16], // Generate new ID
Name = source.Name,
Class = source.Class,
Level = source.Level,
Stats = source.Stats.Clone(), // Clone nested object
Equipment = source.Equipment.Clone(), // Clone nested object
Abilities = new List<string>(source.Abilities), // Clone collection
Resistances = new Dictionary<string, int>(source.Resistances) // Clone dict
};
}
Key points:
DeepClonecreates truly independent copies- Each nested object is also cloned (not just the reference)
- Collections are copied (new collection, elements may be shared if immutable)
- IDs are regenerated to ensure uniqueness
Base Prototypes
Warrior Prototype
public static GameCharacter CreateWarriorPrototype() => new()
{
Id = "warrior-base",
Name = "Warrior",
Class = "Warrior",
Level = 1,
Stats = new CharacterStats
{
Health = 150,
Mana = 30,
Strength = 15,
Agility = 8,
Intelligence = 5
},
Equipment = new Equipment
{
Weapon = "Iron Sword",
Armor = "Chainmail",
Accessories = ["Shield"]
},
Abilities = ["Slash", "Block", "Charge"],
Resistances = new Dictionary<string, int>
{
["Physical"] = 20,
["Magic"] = -10
}
};
Design:
- High health and strength
- Low intelligence and mana
- Physical resistance, magic weakness
Mage Prototype
public static GameCharacter CreateMagePrototype() => new()
{
Id = "mage-base",
Name = "Mage",
Class = "Mage",
Level = 1,
Stats = new CharacterStats
{
Health = 80,
Mana = 150,
Strength = 5,
Agility = 7,
Intelligence = 18
},
Equipment = new Equipment
{
Weapon = "Oak Staff",
Armor = "Silk Robes",
Accessories = ["Spellbook", "Amulet"]
},
Abilities = ["Fireball", "Ice Shield", "Teleport"],
Resistances = new Dictionary<string, int>
{
["Physical"] = -10,
["Magic"] = 25
}
};
Design:
- High mana and intelligence
- Low health and strength
- Magic resistance, physical weakness
Rogue Prototype
public static GameCharacter CreateRoguePrototype() => new()
{
Id = "rogue-base",
Name = "Rogue",
Class = "Rogue",
Level = 1,
Stats = new CharacterStats
{
Health = 100,
Mana = 60,
Strength = 10,
Agility = 16,
Intelligence = 10
},
Equipment = new Equipment
{
Weapon = "Twin Daggers",
Armor = "Leather Vest",
Accessories = ["Lockpicks", "Smoke Bombs"]
},
Abilities = ["Backstab", "Stealth", "Poison"],
Resistances = new Dictionary<string, int>
{
["Physical"] = 5,
["Magic"] = 5,
["Poison"] = 50
}
};
Design:
- High agility for speed/evasion
- Balanced stats
- Poison immunity
Prototype Registry Setup
Basic Character Factory
public static Prototype<string, GameCharacter> CreateCharacterFactory()
{
return Prototype<string, GameCharacter>
.Create()
// Register base class prototypes
.Map("warrior", CreateWarriorPrototype(), GameCharacter.DeepClone)
.Map("mage", CreateMagePrototype(), GameCharacter.DeepClone)
.Map("rogue", CreateRoguePrototype(), GameCharacter.DeepClone)
// Register elite variants with pre-configured mutations
.Map("elite-warrior", CreateWarriorPrototype(), GameCharacter.DeepClone)
.Mutate("elite-warrior", c =>
{
c.Name = "Elite Warrior";
c.Level = 10;
c.Stats.Health *= 2;
})
.Map("elite-mage", CreateMagePrototype(), GameCharacter.DeepClone)
.Mutate("elite-mage", c =>
{
c.Name = "Archmage";
c.Level = 15;
c.Stats.Mana *= 3;
})
// Register boss variant
.Map("boss-dragon-knight", CreateWarriorPrototype(), GameCharacter.DeepClone)
.Mutate("boss-dragon-knight", c =>
{
c.Name = "Dragon Knight";
c.Level = 50;
c.Stats.Health = 5000;
c.Stats.Strength = 80;
c.Equipment.Weapon = "Dragonbone Greatsword";
c.Equipment.Armor = "Dragon Scale Plate";
c.Resistances["Fire"] = 100;
})
// Set default fallback
.Default(CreateWarriorPrototype(), GameCharacter.DeepClone)
.Build();
}
Key features:
- Named templates - retrieve by key ("warrior", "mage", etc.)
- Pre-configured mutations - elite variants have baked-in transformations
- Default fallback - returned if key not found
- Fluent API - chain configuration calls
Single Prototype Spawner
For mass-spawning many copies of the same entity:
public static Prototype<GameCharacter> CreateNpcSpawner(GameCharacter baseNpc)
{
return Prototype<GameCharacter>
.Create(baseNpc, GameCharacter.DeepClone)
.Build();
}
Usage Scenarios
Scenario 1: Clone Base Classes
var factory = CreateCharacterFactory();
var warrior = factory.Create("warrior");
Console.WriteLine(warrior);
// Output: Warrior (Lv.1 Warrior) - HP:150 MP:30 STR:15 AGI:8 INT:5
var mage = factory.Create("mage");
Console.WriteLine(mage);
// Output: Mage (Lv.1 Mage) - HP:80 MP:150 STR:5 AGI:7 INT:18
What happens:
- Registry looks up "warrior" key
- Calls
GameCharacter.DeepClone()on the stored prototype - Returns independent copy
- Each clone gets a new unique ID
Scenario 2: Clone with Runtime Mutations
var customWarrior = factory.Create("warrior", c =>
{
c.Name = "Sir Galahad";
c.Level = 25;
c.Stats.Strength += 20;
c.Equipment.Weapon = "Excalibur";
c.Abilities.Add("Holy Strike");
});
Console.WriteLine(customWarrior);
// Output: Sir Galahad (Lv.25 Warrior) - HP:150 MP:30 STR:35 AGI:8 INT:5
// Weapon: Excalibur | Armor: Chainmail
// Abilities: Slash, Block, Charge, Holy Strike
What happens:
- Clone the base "warrior" prototype
- Apply the mutation lambda to the clone
- Return the customized instance
Benefits:
- Start from a base template
- Customize only what's needed
- No need to set every property manually
Scenario 3: Clone Pre-configured Elite Variants
var eliteWarrior = factory.Create("elite-warrior");
Console.WriteLine(eliteWarrior);
// Output: Elite Warrior (Lv.10 Warrior) - HP:300 MP:30 STR:15 AGI:8 INT:5
var archmage = factory.Create("elite-mage");
Console.WriteLine(archmage);
// Output: Archmage (Lv.15 Mage) - HP:80 MP:450 STR:5 AGI:7 INT:18
What happens:
- Elite variants are stored with pre-applied mutations
- When you clone "elite-warrior", you get the mutated version
- No runtime mutation cost - it's baked into the prototype
When to use:
- Common variations you spawn frequently
- Avoid repeating the same mutations
- Performance optimization
Scenario 4: Clone Boss Characters
var boss = factory.Create("boss-dragon-knight");
Console.WriteLine(boss);
// Output: Dragon Knight (Lv.50 Warrior) - HP:5000 MP:30 STR:80 AGI:8 INT:5
// Weapon: Dragonbone Greatsword | Armor: Dragon Scale Plate
// Resistances: Physical:20%, Magic:-10%, Fire:100%
Design pattern:
- Bosses are heavily modified variants
- Start from a base class prototype (Warrior)
- Transform stats, equipment, abilities
- Still benefit from cloning mechanism
Scenario 5: Mass Spawn NPCs
// Create a single prototype
var goblinPrototype = new GameCharacter
{
Id = "goblin",
Name = "Goblin",
Class = "Monster",
Level = 3,
Stats = new CharacterStats
{
Health = 30,
Mana = 10,
Strength = 8,
Agility = 12,
Intelligence = 3
},
Equipment = new Equipment { Weapon = "Rusty Dagger", Armor = "Rags" },
Abilities = ["Scratch", "Flee"]
};
var spawner = CreateNpcSpawner(goblinPrototype);
// Spawn many with slight variations
Console.WriteLine("Spawning 5 goblins with variations...");
for (int i = 0; i < 5; i++)
{
var goblin = spawner.Create(g =>
{
g.Name = $"Goblin #{i + 1}";
g.Stats.Health += Random.Shared.Next(-5, 10); // Randomize
});
Console.WriteLine($"[{goblin.Id[..8]}] {goblin.Name} HP:{goblin.Stats.Health}");
}
// Output:
// [a7b2c3d4] Goblin #1 HP:27
// [e5f6g7h8] Goblin #2 HP:35
// [i9j0k1l2] Goblin #3 HP:32
// [m3n4o5p6] Goblin #4 HP:29
// [q7r8s9t0] Goblin #5 HP:38
Performance benefits:
- Clone operation is O(1) for value types, O(n) for collections
- No expensive initialization logic
- No asset loading or computation
- Can spawn hundreds per frame
Scenario 6: Verify Clone Independence
var original = factory.Create("warrior");
var clone = factory.Create("warrior", c => c.Name = "Clone Warrior");
Console.WriteLine($"Original: {original.Name}"); // "Warrior"
Console.WriteLine($"Clone: {clone.Name}"); // "Clone Warrior"
Console.WriteLine($"Are same object? {ReferenceEquals(original, clone)}"); // False
Console.WriteLine($"Original unchanged? {original.Name == "Warrior"}"); // True
// Verify deep independence
clone.Stats.Health = 999;
Console.WriteLine($"Original health: {original.Stats.Health}"); // Still 150
Console.WriteLine($"Clone health: {clone.Stats.Health}"); // 999
clone.Abilities.Add("New Ability");
Console.WriteLine($"Original abilities: {original.Abilities.Count}"); // Still 3
Console.WriteLine($"Clone abilities: {clone.Abilities.Count}"); // 4
What this proves:
- Clones are separate instances (different references)
- Mutations to the clone don't affect the original
- Nested objects (Stats, Equipment) are also independent
- Collections (Abilities, Resistances) are independent
Before/After Comparison
Before: Manual Creation
// ❌ Manual creation - verbose, error-prone, expensive
public void SpawnEnemies()
{
for (int i = 0; i < 100; i++)
{
var goblin = new GameCharacter
{
Id = Guid.NewGuid().ToString(),
Name = $"Goblin {i}",
Class = "Monster",
Level = 3,
Stats = new CharacterStats
{
Health = 30,
Mana = 10,
Strength = 8,
Agility = 12,
Intelligence = 3
},
Equipment = new Equipment
{
Weapon = "Rusty Dagger",
Armor = "Rags",
Accessories = []
},
Abilities = ["Scratch", "Flee"],
Resistances = new Dictionary<string, int>()
};
// ... expensive initialization
LoadAssets(goblin);
ComputeDerivedStats(goblin);
_entities.Add(goblin);
}
}
Problems:
- 🐌 Slow - runs full initialization 100 times
- 📝 Verbose - lots of repetitive code
- 🐛 Error-prone - easy to miss a property
- 🚫 Not reusable - can't easily create variants
After: Prototype Pattern
// ✅ Prototype pattern - fast, concise, flexible
public void SpawnEnemies()
{
// Configure prototype once
var goblinPrototype = CreateGoblinPrototype();
LoadAssets(goblinPrototype); // Load once
ComputeDerivedStats(goblinPrototype); // Compute once
var spawner = CreateNpcSpawner(goblinPrototype);
// Clone 100 times (fast)
for (int i = 0; i < 100; i++)
{
var goblin = spawner.Create(g => g.Name = $"Goblin {i}");
_entities.Add(goblin);
}
}
Benefits:
- ⚡ Fast - clone is much cheaper than initialization
- 📦 Concise - configuration in one place
- ✅ Safe - single source of truth
- 🔄 Reusable - clone with variations easily
Pattern Benefits Demonstrated
1. Fast Object Creation
Cloning is significantly faster than running initialization logic:
// Initialization: O(complexity)
var character = CreateAndInitializeCharacter(); // Slow
// Cloning: O(size)
var clone = prototype.Create(); // Fast
When initialization is expensive:
- Loading assets from disk
- Database queries
- Complex computations
- Network calls
Cloning just copies memory - much faster.
2. Named Prototype Registry
Store common configurations with semantic names:
factory.Create("warrior"); // Get a warrior
factory.Create("elite-warrior"); // Get elite variant
factory.Create("boss-dragon-knight"); // Get boss
Benefits:
- Self-documenting - name describes what you get
- Centralized - all prototypes in one place
- Easy to extend - add new variants without changing calling code
3. Mutation Chains
Customize clones at creation time:
var custom = factory.Create("warrior", c =>
{
c.Name = "Custom Name";
c.Level = 25;
c.Stats.Strength *= 2;
});
Benefits:
- Fluent - readable customization
- Flexible - override any property
- Composable - chain multiple mutations
4. Deep Cloning Ensures Independence
Clones don't share mutable state with the original:
var c1 = factory.Create("warrior");
var c2 = factory.Create("warrior");
c1.Stats.Health = 999;
Console.WriteLine(c2.Stats.Health); // Still 150, not affected
Critical for:
- Game entities (independent behavior)
- Undo/redo systems (save/restore state)
- Concurrent processing (no shared mutations)
5. Runtime Flexibility
Create variants without defining new classes:
// No need for EliteWarrior, BossWarrior classes
// Just mutate the base prototype
factory.Map("custom-variant", basePrototype, clone)
.Mutate("custom-variant", c => { /* custom logic */ });
Reduces:
- Class explosion
- Inheritance hierarchies
- Coupling between types
Use Cases
Game Development
- Enemy spawning - clone enemy templates
- Particle systems - clone particle configurations
- Item generation - clone base items with modifications
- Character presets - clone character templates
Document Processing
- Document templates - clone base documents
- Form pre-filling - clone form configurations
- Report generation - clone report structures
Configuration Management
- Environment configs - clone base configs per environment
- User preferences - clone default preferences
- Feature flags - clone flag sets
Testing
- Test fixtures - clone base test data
- Mock objects - clone mock configurations
- Test scenarios - clone scenario templates
Performance Notes
Cloning is Fast
For the game character example:
- Prototype creation: Once per base type (~5-10 types)
- Cloning cost: O(n) where n = number of properties + collection sizes
- vs. Initialization: Often 10-100x faster when initialization involves I/O
Memory Efficiency
// Prototype stored once in registry
var prototype = CreateWarriorPrototype(); // ~1KB memory
// Each clone is independent but shallow for immutable data
var clone1 = prototype.Clone(); // ~1KB (deep copy)
var clone2 = prototype.Clone(); // ~1KB (deep copy)
// Strings are interned, so "Warrior" string is shared (but immutable)
When NOT to Use Prototype
❌ Simple value objects - just use constructors:
var point = new Point(x: 10, y: 20); // Simpler than cloning
❌ Immutable types - use with expressions (records):
var p2 = p1 with { X = 20 }; // Built-in cloning
❌ Very large objects - cloning might be expensive:
// If object is 100MB, cloning copies 100MB
// Consider lazy loading or copy-on-write strategies
Code Generator Support
The Prototype pattern generator can automate clone method generation:
using PatternKit.Generators.Prototype;
[Prototype]
public partial class GameCharacter
{
public required string Id { get; set; }
public required string Name { get; set; }
[PrototypeStrategy(PrototypeCloneStrategy.Clone)]
public CharacterStats Stats { get; set; } = new();
[PrototypeStrategy(PrototypeCloneStrategy.Clone)]
public Equipment Equipment { get; set; } = new();
[PrototypeStrategy(PrototypeCloneStrategy.Clone)]
public List<string> Abilities { get; set; } = [];
}
// Generated automatically:
// public GameCharacter Clone() { /* implementation */ }
Benefits:
- No manual clone method writing
- Configurable per-member strategies
- Compile-time diagnostics for unsafe cloning
- Supports classes, structs, records
See Prototype Generator Documentation for details.
Key Takeaways
✅ Use prototypes when:
- Object creation is expensive (I/O, computation)
- You need many similar but not identical instances
- Runtime flexibility is important (no new classes)
- You want a registry of common configurations
✅ Remember:
- Deep clone for independence (copy nested objects)
- Shallow clone may share references (understand implications)
- Mutations apply to the clone, not the prototype
- Verify clone independence in tests
✅ Performance wins:
- Initialization once, clone many times
- Memory copy is faster than logic execution
- Registry lookup is O(1)
Run the Demo
# From the repo root
dotnet build PatternKit.slnx -c Debug
dotnet run --project src/PatternKit.Examples --framework net9.0
# Select "Prototype Demo" from the menu
Further Reading
- Prototype Generator - Automated clone method generation
- PatternKit.Core.Creational.Prototype - Runtime prototype API
- Gang of Four Design Patterns - Original pattern catalog