Lecture 10: Strategy Pattern - Enemy Drones

๐Ÿค– Strategy Pattern

Implementing Enemy Drones

Game Programming - CSCI 3213

Spring 2026 - Lecture 10

๐Ÿ“š Learning Objectives

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.

๐ŸŽฏ Enemy Drone Design

Core Concept

Flying robotic enemies that attack the player with predictable, automated behaviors.

Characteristics:

Design Philosophy: Like Goombas in Super Mario Bros - simple, predictable, but dangerous when positioned strategically.

โš”๏ธ Three Attack Maneuvers

1. Bobbing Maneuver

2. Weaving Maneuver

3. Fallback Maneuver

๐Ÿ” Understanding Strategy Pattern

Core Concept

Define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.

Key Benefits:

  • Select behavior at runtime
  • No need to know object's internal state
  • Algorithms encapsulated in separate classes
  • Easy to add new strategies without modifying context

Real-World Analogy

Think of a GPS navigation app:

๐Ÿ—๏ธ Strategy Pattern Structure

Key Participants:

Pattern Type: Behavioral - focuses on how objects interact and distribute responsibility.

โš–๏ธ Strategy vs State Pattern

Aspect Strategy Pattern State Pattern
Intent Select algorithm at runtime Change behavior when state changes
Selection Client decides which strategy Object changes its own state
Awareness Strategies don't know about each other States often trigger transitions
Use Case Different ways to do the same thing Different behaviors in different states
For our drones: We use Strategy because behavior is assigned at spawn time, not based on internal state changes.

โœ… Benefits of Strategy Pattern

โš ๏ธ Potential Drawbacks

Mitigation: Regular team discussions about architecture choices and pattern usage conventions.

๐Ÿค” When to Use Strategy Pattern

Use Strategy When:

Use State/FSM Instead When:

Other Use Cases for Strategy:

๐Ÿ’ป Implementation: Strategy Interface

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/Strategy/IManeuverBehaviour.cs
This interface defines the contract that all drone maneuver strategies must implement.
โš ๏ธ This code goes into your Unity project for Blade Racer.

public interface IManeuverBehaviour { // Execute maneuver on given drone void Maneuver(Drone drone); }

Key Points:

  • Single method that all strategies must implement
  • Accepts Drone parameter - important!
  • Strategies can access drone's public properties
  • Simple, focused interface
Why pass Drone? Strategies need access to drone properties like speed, maxHeight, weavingDistance, etc.

๐Ÿ’ป Drone Class - Context (1/2)

๐Ÿ“ File Structure Note - PRODUCTION CODE

Modify existing file: Assets/Scripts/Enemies/Drone.cs
Add the fields and methods below to your Object Pool drone โ€” do not create a new file.
โš ๏ธ This code goes into your Unity project for Blade Racer.

โš ๏ธ Don't Create a Duplicate Drone Class
You already have Assets/Scripts/Enemies/Drone.cs from the Object Pool lecture. Unity compiles all scripts into one assembly, so a second class Drone anywhere in the project will cause a compile error. Instead, extend the drone you already built: add the laser ray fields, weavingDistance, fallbackDistance, ApplyStrategy(), and swap Update() to draw the debug ray. Your existing OnSpawn() and OnDespawn() pool methods stay untouched.
using UnityEngine; public class Drone : MonoBehaviour { // Ray parameters (for laser weapon) private RaycastHit _hit; private Vector3 _rayDirection; private float _rayAngle = -45.0f; private float _rayDistance = 15.0f; // Movement parameters (public for strategies) public float speed = 1.0f; public float maxHeight = 5.0f; public float weavingDistance = 1.5f; public float fallbackDistance = 20.0f;

๐Ÿ’ป Drone Class - Context (2/2)

๐Ÿ“ Continuing Drone.cs

Add these methods to Assets/Scripts/Enemies/Drone.cs from the previous slide.

void Start() { // Set up laser direction _rayDirection = transform.TransformDirection(Vector3.back) * _rayDistance; _rayDirection = Quaternion.Euler(_rayAngle, 0.0f, 0f) * _rayDirection; } // THE STRATEGY PATTERN CORE! public void ApplyStrategy(IManeuverBehaviour strategy) { strategy.Maneuver(this); } // Update draws raycast for debugging void Update() { Debug.DrawRay(transform.position, _rayDirection, Color.blue); // ... raycast hit detection } }

๐ŸŽฏ The Strategy Pattern Core

public void ApplyStrategy(IManeuverBehaviour strategy) { strategy.Maneuver(this); }

What makes this powerful:

  1. Drone accepts any strategy that implements IManeuverBehaviour
  2. Drone doesn't know how the strategy works internally
  3. Drone only knows the interface (Maneuver method)
  4. Strategy gets full access to drone via this parameter
  5. Behavior can be changed at runtime by passing different strategy
Separation of Concerns: Drone handles core properties and weapon; strategies handle movement patterns.

๐Ÿ’ป BoppingManeuver - Concrete Strategy

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/Strategy/BoppingManeuver.cs
First concrete strategy implementation - makes drones bob up and down.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; using System.Collections; public class BoppingManeuver : MonoBehaviour, IManeuverBehaviour { public void Maneuver(Drone drone) { StartCoroutine(Bopple(drone)); } IEnumerator Bopple(Drone drone) { float time; bool isReverse = false; float speed = drone.speed; Vector3 startPosition = drone.transform.position; Vector3 endPosition = startPosition; endPosition.y = drone.maxHeight;

๐Ÿ’ป BoppingManeuver - Animation Loop

๐Ÿ“ Continuing BoppingManeuver.cs

Add this animation loop to the Execute() coroutine in the previous slide.

while (true) // Infinite loop! { time = 0; Vector3 start = drone.transform.position; Vector3 end = (isReverse) ? startPosition : endPosition; // Lerp from start to end over time while (time < speed) { drone.transform.position = Vector3.Lerp(start, end, time / speed); time += Time.deltaTime; yield return null; } yield return new WaitForSeconds(1); // Pause at top/bottom isReverse = !isReverse; // Reverse direction } } }

๐Ÿ’ป WeavingManeuver - Concrete Strategy

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/Strategy/WeavingManeuver.cs
Second concrete strategy - makes drones weave side to side.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; using System.Collections; public class WeavingManeuver : MonoBehaviour, IManeuverBehaviour { public void Maneuver(Drone drone) { StartCoroutine(Weave(drone)); } IEnumerator Weave(Drone drone) { float time; bool isReverse = false; float speed = drone.speed; Vector3 startPosition = drone.transform.position; Vector3 endPosition = startPosition; endPosition.x = drone.weavingDistance; // Horizontal!

๐Ÿ’ป WeavingManeuver - Animation Loop

๐Ÿ“ Continuing WeavingManeuver.cs

Add this animation loop to the Weave() coroutine in the previous slide.

while (true) { time = 0; Vector3 start = drone.transform.position; Vector3 end = (isReverse) ? startPosition : endPosition; while (time < speed) { drone.transform.position = Vector3.Lerp(start, end, time / speed); time += Time.deltaTime; yield return null; } yield return new WaitForSeconds(1); isReverse = !isReverse; } } }
Note: Very similar to BoppingManeuver, but moves on X axis instead of Y

๐Ÿ’ป FallbackManeuver - Concrete Strategy

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Patterns/Strategy/FallbackManeuver.cs
Third concrete strategy - makes drones retreat backward.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; using System.Collections; public class FallbackManeuver : MonoBehaviour, IManeuverBehaviour { public void Maneuver(Drone drone) { StartCoroutine(Fallback(drone)); } IEnumerator Fallback(Drone drone) { float time = 0; float speed = drone.speed; Vector3 startPosition = drone.transform.position; Vector3 endPosition = startPosition; endPosition.z = drone.fallbackDistance; // Backward!

๐Ÿ’ป FallbackManeuver - Animation

๐Ÿ“ Continuing FallbackManeuver.cs

Add this movement code to the Fallback() coroutine in the previous slide.

// NOT an infinite loop - runs once! while (time < speed) { drone.transform.position = Vector3.Lerp(startPosition, endPosition, time / speed); time += Time.deltaTime; yield return null; } // Then stops - limited duration } }

Key Difference:

Unlike Bopping and Weaving, Fallback doesn't loop infinitely - it moves backward once then stops. This creates a "retreat" behavior.

๐ŸŽฏ Why Encapsulate Each Maneuver?

Imagine without Strategy Pattern:

public class Drone : MonoBehaviour { public enum ManeuverType { Bopping, Weaving, Fallback } public ManeuverType currentManeuver; void Update() { if (currentManeuver == ManeuverType.Bopping) { // 50 lines of bopping code } else if (currentManeuver == ManeuverType.Weaving) { // 50 lines of weaving code } else if (currentManeuver == ManeuverType.Fallback) { // 50 lines of fallback code } } }
Problems: 150+ line Drone class, hard to test, hard to maintain, violates Single Responsibility Principle

๐Ÿ’ป TestPanel - Drone Spawning (1/2)

๐Ÿ“ Evolving TestPanel.cs

Modify existing file: Assets/Scripts/Testing/TestPanel.cs
Add drone spawning directly to TestPanel โ€” no separate ClientStrategy file needed.
โš ๏ธ This code goes into your Unity project for Blade Racer.

// Add at the top of TestPanel.cs (with other usings) using System.Collections.Generic; // Add to TestPanel fields public GameObject dronePrefab; // Drag your Drone prefab here in the Inspector private bool _strategyExpanded = true; private GameObject _drone; private List<IManeuverBehaviour> _components = new List<IManeuverBehaviour>(); // Add to TestPanel โ€” spawns a drone with a random strategy private void SpawnDrone() { _drone = Instantiate(dronePrefab); _drone.transform.position = Random.insideUnitSphere * 10; ApplyRandomStrategies(); }

๐Ÿ’ป TestPanel - Drone Spawning (2/2)

๐Ÿ“ Continuing TestPanel.cs

Add ApplyRandomStrategies() to TestPanel โ€” called by SpawnDrone() from the previous slide.

// Add to TestPanel private void ApplyRandomStrategies() { // Add all three strategies as components _components.Add( _drone.AddComponent<WeavingManeuver>()); _components.Add( _drone.AddComponent<BoppingManeuver>()); _components.Add( _drone.AddComponent<FallbackManeuver>()); // Pick one randomly int index = Random.Range(0, _components.Count); // Apply the selected strategy _drone.GetComponent<Drone>(). ApplyStrategy(_components[index]); }

๐ŸŽฎ TestPanel - GUI Section & Keyboard Shortcut

๐Ÿ“ Continuing TestPanel.cs

Add the keyboard shortcut to Update() and the collapsible GUI section to your TestPanel.cs.

// Add to Update() - Strategy Pattern shortcut // G = Generate/Spawn drone if (Input.GetKeyDown(KeyCode.G)) SpawnDrone();
// Add DrawStrategySection() method void DrawStrategySection() { GUI.backgroundColor = new Color(1f, 0.5f, 0f); // Orange _strategyExpanded = GUILayout.Toggle( _strategyExpanded, "โ–ผ Strategy Pattern", "button"); GUI.backgroundColor = Color.white; if (_strategyExpanded) { GUILayout.BeginVertical("box"); if (GUILayout.Button("Spawn Drone (G)")) SpawnDrone(); GUILayout.EndVertical(); } }
Keymap Update: Add to DrawKeymapWindow():
G = Spawn Drone (Generate)

๐Ÿงช Testing the Implementation

Steps:

  1. Create new Unity scene
  2. Create empty GameObject
  3. Attach TestPanel script (if not already in your scene)
  4. Add all strategy scripts to project
  5. Drag your Drone prefab into the dronePrefab field on TestPanel
  6. Press Play
  7. Click "Spawn Drone (G)" in the TestPanel Strategy section, or press G
  8. Observe your drone executing a random maneuver
  9. Spawn multiple drones to see variety
What you'll see: Cubes representing drones moving up/down (bobbing), side/side (weaving), or backward (fallback) with blue debug rays showing laser direction.

๐Ÿ”„ Switching Strategies at Runtime

public class DynamicDrone : MonoBehaviour { private Drone _drone; private BoppingManeuver _bop; private WeavingManeuver _weave; void Start() { _drone = GetComponent(); _bop = GetComponent(); _weave = GetComponent(); // Start with bopping _drone.ApplyStrategy(_bop); } void Update() { // Switch to weaving when player gets close if (PlayerDistance() < 10f) { StopAllCoroutines(); // Stop current strategy _drone.ApplyStrategy(_weave); } } }

โš ๏ธ Coroutine Animation Caveat

Important: Using coroutines for animation is simplified for this example. In production, consider better alternatives:

Better Alternatives:

Why we used coroutines here:

๐ŸŽฌ Alternative: Unity Animation System

Professional Approach:

  1. Animator creates: Attack maneuver animations in Maya/Blender
  2. Import: Animation clips into Unity
  3. Animator Controller: Set up state machine
    • States: Idle, Bopping, Weaving, Fallback
    • Parameters: ManeuverType (int)
  4. Code: Simple parameter setting
    animator.SetInteger("ManeuverType", (int)ManeuverType.Bopping);

Benefits:

๐Ÿค” Strategy Pattern vs Unity Animator?

Use Strategy Pattern When:

Use Unity Animator When:

Hybrid Approach:

// Strategy determines WHAT to do public class SmartAttackStrategy : IManeuverBehaviour { public void Maneuver(Drone drone) { // Calculate best attack based on player position // Then trigger appropriate animation drone.animator.SetTrigger("ExecuteCalculatedAttack"); } }

๐ŸŒ Beyond Drone Maneuvers

Real-World Game Applications:

๐ŸŽฏ Drone Formation Strategy

Making Simple Enemies Challenging:

Strategic Placement:

  • The Gauntlet:
    • 3 weaving drones in a row
    • Player must time passage carefully
  • The Retreat Squad:
    • 2 fallback drones ahead of player
    • Player can't catch them easily
  • Vertical Challenge:
    • Bobbing drones at different heights
    • Forces vertical navigation
  • Mixed Formation:
    • Combine all three types
    • Unpredictable challenge
Design Lesson: Predictable + Strategic Placement = Challenging

๐Ÿ”ง Adding New Maneuvers

Easy Extension:

// 1. Create new strategy class public class CirclingManeuver : MonoBehaviour, IManeuverBehaviour { public void Maneuver(Drone drone) { StartCoroutine(Circle(drone)); } IEnumerator Circle(Drone drone) { float radius = 3f; float angle = 0f; Vector3 center = drone.transform.position; while (true) { angle += drone.speed * Time.deltaTime; float x = center.x + Mathf.Cos(angle) * radius; float z = center.z + Mathf.Sin(angle) * radius; drone.transform.position = new Vector3(x, center.y, z); yield return null; } } }

That's it! No changes to Drone class needed.

โšก Performance Tips

Optimization Strategies:

๐Ÿงช Unit Testing Strategies

[Test] public void BoppingManeuver_MovesVertically() { // Arrange var drone = new GameObject().AddComponent(); drone.maxHeight = 10f; var strategy = drone.gameObject.AddComponent(); float startY = drone.transform.position.y; // Act strategy.Maneuver(drone); yield return new WaitForSeconds(2f); // Let coroutine run // Assert Assert.AreNotEqual(startY, drone.transform.position.y); Assert.LessOrEqual(drone.transform.position.y, drone.maxHeight); } [Test] public void Drone_CanSwitchStrategies() { // Arrange var drone = new GameObject().AddComponent(); var bop = drone.gameObject.AddComponent(); var weave = drone.gameObject.AddComponent(); // Act drone.ApplyStrategy(bop); yield return new WaitForSeconds(1f); drone.ApplyStrategy(weave); // Switch! // Assert - Should not throw, should execute smoothly Assert.IsNotNull(drone); }

๐Ÿšซ Common Mistakes to Avoid

  1. Forgetting to Stop Previous Coroutine:
    // BAD - old strategy still running! drone.ApplyStrategy(newStrategy); // GOOD - stop previous first StopAllCoroutines(); drone.ApplyStrategy(newStrategy);
  2. Not Handling Null Strategies:
    public void ApplyStrategy(IManeuverBehaviour strategy) { if (strategy == null) { Debug.LogError("Null strategy provided!"); return; } strategy.Maneuver(this); }
  3. Hardcoding Strategy References:
    • Use dependency injection or service locator
    • Don't tightly couple client to specific strategy types
  4. Overusing Strategy for Simple Cases:
    • If you only have 1-2 behaviors, direct calls may be clearer

๐Ÿ› Debugging Strategies

Add Logging:

public void Maneuver(Drone drone) { Debug.Log($"[{GetType().Name}] Starting maneuver on {drone.name}"); StartCoroutine(Bopple(drone)); } public void ApplyStrategy(IManeuverBehaviour strategy) { Debug.Log($"[Drone] Applying strategy: {strategy.GetType().Name}"); strategy.Maneuver(this); }

Visual Debugging:

void OnDrawGizmos() { Gizmos.color = strategyType == BoppingManeuver ? Color.red : Color.blue; Gizmos.DrawWireSphere(transform.position, 0.5f); }

๐ŸŽฎ Real-World Examples

Strategy Pattern in Production Games:

Common Theme: Runtime selection of behavior based on context

๐Ÿ“ Summary

What We Learned:

Key Takeaways:

Gaming History Moment ๐Ÿ•น๏ธ

Console Wars: Sega vs Nintendo (1989-1995)

In the early 1990s, Sega and Nintendo employed radically different competitive strategies. Nintendo focused on quality control, family-friendly games, and their established Mario brand. Sega chose an aggressive, edgy strategy - "Genesis does what Nintendon't!" They targeted teenagers, emphasized speed and attitude with Sonic, and pursued sports licenses Nintendo ignored.

Both companies could swap strategies mid-battle. Sega released Sonic Spinball (family-friendly pinball) when their edgy approach plateaued. Nintendo countered with Mortal Kombat (with blood code!) abandoning their clean image temporarily. Each company's "context" (market position) stayed the same, but they swapped "strategies" (marketing approaches) based on what worked.

Connection to Strategy Pattern

The Console Wars demonstrate the Strategy Pattern perfectly! Both companies (contexts) could employ different competitive strategies (algorithms) without changing their core identity. Just as our Drone context switches between FallbackManeuver, WeavingManeuver, and BoppingManeuver strategies at runtime, Sega and Nintendo switched between "Family Friendly," "Edgy Teen," and "Sports Focus" strategies based on market conditions. The strategy changes, but the company stays the same!

Learn More: Console Wars by Blake J. Harris | Console Wars (Documentary)

๐Ÿ’ช Practice Exercise

Implement a Dynamic Drone Spawner

Requirements:

  1. Create a DroneSpawner class that:
    • Spawns drones in formations
    • Assigns strategies based on position:
      • Front drones: FallbackManeuver
      • Side drones: WeavingManeuver
      • Center drones: BoppingManeuver
  2. Add difficulty scaling:
    • Easy: Spawn 3 drones
    • Medium: Spawn 5 drones
    • Hard: Spawn 8 drones
  3. Implement object pooling for drones
  4. Add visual indicators for each strategy type
Bonus: Create a "Boss Drone" that switches strategies every 10 seconds

๐Ÿ“š Additional Resources

Further Reading:

Code Examples:

Next Lecture:

Decorator Pattern: Implementing a weapon upgrade system

โ“ Questions?

Common Questions:

  • Q: Can a drone have multiple strategies at once?
    • A: Not by default, but you could create a CompositeStrategy that combines multiple behaviors
  • Q: How do I smooth transitions between strategies?
    • A: Lerp between end of old position and start of new, or use Unity Animator
  • Q: Should strategies be MonoBehaviours?
    • A: Not required! Can be plain C# classes. We used MonoBehaviour for coroutines.
  • Q: What if I want state-based drone behavior?
    • A: Then use State pattern or FSM instead of Strategy

Thank you! ๐ŸŽฎ