Lecture 10: Strategy Pattern - Enemy Drones

๐Ÿค– Strategy Pattern

Implementing Enemy Drones

Game Programming - CSCI 3213

Spring 2026 - Lecture 10

Oklahoma City University

๐Ÿ“š Learning Objectives

  • Understand the Strategy design pattern
  • Learn the difference between Strategy and State patterns
  • Implement enemy drone behaviors using Strategy pattern
  • Create runtime-swappable attack maneuvers
  • Recognize when to use Strategy vs. other patterns
  • Explore alternative solutions using Unity's animation system

๐ŸŽฏ Enemy Drone Design

Core Concept

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

Characteristics:

  • Weapon: Front-facing laser beam (45ยฐ angle)
  • Intelligence: No AI - pure algorithmic movement
  • Difficulty: Easy alone, challenging in formations
  • Attack Patterns: Three distinct maneuvers
Design Philosophy: Like Goombas in Super Mario Bros - simple, predictable, but dangerous when positioned strategically.

โš”๏ธ Three Attack Maneuvers

1. Bobbing Maneuver

  • Rapid vertical movement (up and down)
  • Continuous laser fire
  • Hard to predict vertical position

2. Weaving Maneuver

  • Rapid horizontal movement (side to side)
  • Limited to track rail distance
  • Forces player to time their pass

3. Fallback Maneuver

  • Moves backward while shooting
  • Can match player's bike speed
  • Limited duration before stopping

๐Ÿ” 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:

  • Context: Route planner
  • Strategies: Fastest route, shortest distance, avoid highways
  • Runtime selection: User chooses which strategy to use

๐Ÿ—๏ธ Strategy Pattern Structure

Key Participants:

  • Context: Uses strategy objects
    • In our case: Drone class
  • Strategy Interface: Common interface for all strategies
    • In our case: IManeuverBehaviour
  • Concrete Strategies: Specific implementations
    • BoppingManeuver
    • WeavingManeuver
    • FallbackManeuver
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

  • Encapsulation:
    • Each algorithm in its own class
    • Avoid long conditional statements
    • Cleaner, more organized code
  • Runtime Flexibility:
    • Swap algorithms dynamically
    • No need to restart or rebuild
    • Responsive to gameplay conditions
  • Open/Closed Principle:
    • Open for extension (new strategies)
    • Closed for modification (existing code)
  • Testing:
    • Test each strategy independently
    • Mock strategies for unit tests

โš ๏ธ Potential Drawbacks

  • Client Awareness:
    • Client must know about all strategies
    • Must understand which to select
    • Increased coupling to strategy types
  • Pattern Confusion:
    • Strategy and State look similar structurally
    • Team members may misunderstand intent
    • Requires clear documentation
  • Class Proliferation:
    • More classes to manage
    • Can be overkill for simple cases
Mitigation: Regular team discussions about architecture choices and pattern usage conventions.

๐Ÿค” When to Use Strategy Pattern

Use Strategy When:

  • โœ… Multiple variants of same behavior needed
  • โœ… Want to assign behavior at runtime
  • โœ… Internal state doesn't dictate behavior changes
  • โœ… Need to accomplish specific task based on runtime criteria

Use State/FSM Instead When:

  • โŒ Behavior changes based on internal state
  • โŒ Need state transitions and history
  • โŒ Object is inherently stateful (e.g., player character)

Other Use Cases for Strategy:

  • Encryption algorithms (platform-dependent)
  • Pathfinding algorithms (A*, Dijkstra, etc.)
  • Spell behaviors in fantasy games
  • Difficulty level implementations

๐Ÿ’ป Implementation: Strategy Interface

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

using UnityEngine;

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

        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

using UnityEngine;
using System.Collections;

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

            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

using UnityEngine;
using System.Collections;

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

            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

using UnityEngine;
using System.Collections;

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

            // 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

๐Ÿ’ป ClientStrategy - Testing the System

using UnityEngine;
using System.Collections.Generic;

namespace Chapter.Strategy
{
    public class ClientStrategy : MonoBehaviour
    {
        private GameObject _drone;
        private List<IManeuverBehaviour> _components =
            new List<IManeuverBehaviour>();

        private void SpawnDrone()
        {
            _drone = GameObject.CreatePrimitive(PrimitiveType.Cube);
            _drone.AddComponent<Drone>();
            _drone.transform.position = Random.insideUnitSphere * 10;

            ApplyRandomStrategies();
        }

๐Ÿ’ป Random Strategy Assignment

        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]);
        }

        void OnGUI()
        {
            if (GUILayout.Button("Spawn Drone"))
                SpawnDrone();
        }
    }
}

๐Ÿงช Testing the Implementation

Steps:

  1. Create new Unity scene
  2. Create empty GameObject
  3. Attach ClientStrategy script
  4. Add all strategy scripts to project
  5. Press Play
  6. Click "Spawn Drone" button
  7. Observe cube executing random maneuver
  8. 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<Drone>();
        _bop = GetComponent<BoppingManeuver>();
        _weave = GetComponent<WeavingManeuver>();

        // 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:

  • DOTween: Powerful tween animation engine
    • Less code, better results
    • Built-in easing functions
    • Sequence chaining
    • http://dotween.demigiant.com
  • Unity Animation System: For complex movements
    • Animator Controller
    • Animation Clips
    • State Machine transitions

Why we used coroutines here:

  • No external dependencies
  • Easy to understand and port
  • Focus on pattern, not animation

๐ŸŽฌ 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:

  • Better quality animations
  • Smooth transitions between maneuvers
  • Visual editing in Animator window
  • Could switch to State pattern if needed

๐Ÿค” Strategy Pattern vs Unity Animator?

Use Strategy Pattern When:

  • โœ… Behavior is algorithmic (pathfinding, damage calculation)
  • โœ… Not purely visual/animated
  • โœ… Need to combine multiple systems
  • โœ… Behavior includes complex logic beyond animation

Use Unity Animator When:

  • โœ… Primarily visual animation
  • โœ… Need smooth transitions
  • โœ… Animation created by artists
  • โœ… State-based with transitions

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:

  • Save File Encryption:
    saveManager.SetEncryptionStrategy(
        platform == PC ? new AES256() : new SimpleXOR()
    );
  • Difficulty Settings:
    enemy.SetBehaviorStrategy(
        difficulty == Hard ? new AggressiveAI() : new PassiveAI()
    );
  • Pathfinding Algorithms:
    navigator.SetPathfindingStrategy(
        mapSize > 1000 ? new AStarStrategy() : new DijkstraStrategy()
    );
  • Combat Systems:
    weapon.SetDamageStrategy(
        target.armor > 50 ? new ArmorPiercingDamage() : new StandardDamage()
    );

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

  • Object Pooling:
    • Don't Instantiate/Destroy drones constantly
    • Recycle drone GameObjects
    • Use Object Pool pattern (covered earlier)
  • Strategy Caching:
    // Cache strategy components
    private Dictionary<Type, IManeuverBehaviour> _strategyCache;
    
    void Start()
    {
        _strategyCache = new Dictionary<Type, IManeuverBehaviour>()
        {
            { typeof(BoppingManeuver), GetComponent<BoppingManeuver>() },
            { typeof(WeavingManeuver), GetComponent<WeavingManeuver>() },
            { typeof(FallbackManeuver), GetComponent<FallbackManeuver>() }
        };
    }
  • Update Optimization:
    • Use coroutines instead of Update() when possible
    • Reduces per-frame overhead

๐Ÿงช Unit Testing Strategies

[Test]
public void BoppingManeuver_MovesVertically()
{
    // Arrange
    var drone = new GameObject().AddComponent<Drone>();
    drone.maxHeight = 10f;
    var strategy = drone.gameObject.AddComponent<BoppingManeuver>();
    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<Drone>();
    var bop = drone.gameObject.AddComponent<BoppingManeuver>();
    var weave = drone.gameObject.AddComponent<WeavingManeuver>();

    // 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:

  • Use Gizmos to show movement paths
  • Color-code drones by strategy type
  • Display strategy name in UI overlay
void OnDrawGizmos()
{
    Gizmos.color = strategyType == BoppingManeuver ? Color.red : Color.blue;
    Gizmos.DrawWireSphere(transform.position, 0.5f);
}

๐ŸŽฎ Real-World Examples

Strategy Pattern in Production Games:

  • Civilization Series:
    • AI strategies (aggressive, defensive, cultural)
    • Switched based on game state
  • StarCraft II:
    • Build order strategies
    • Combat micro strategies
  • Racing Games:
    • AI racing lines (aggressive vs safe)
    • Rubberbanding strategies
  • RPGs:
    • Combat AI (melee, ranged, support)
    • Companion behaviors
Common Theme: Runtime selection of behavior based on context

๐Ÿ“ Summary

What We Learned:

  • โœ… Strategy pattern encapsulates algorithm families in separate classes
  • โœ… Enables runtime behavior selection without state dependency
  • โœ… Perfect for enemy drones with predictable, swappable maneuvers
  • โœ… Similar to State pattern but with different intent
  • โœ… Alternative: Unity Animator for pure animation cases

Key Takeaways:

  • Context class uses strategies through interface
  • Each strategy is independent and testable
  • Easy to extend with new behaviors
  • Consider Unity's native systems for animation
  • Strategy pattern shines for algorithmic behaviors

๐Ÿ’ช 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:

  • Design Patterns (GoF): Original Strategy pattern definition
  • Game Programming Patterns: Game-specific strategy applications
  • Unity Animator Documentation:
    • https://docs.unity3d.com/Manual/AnimationOverview.html
  • DOTween: Professional animation solution
    • http://dotween.demigiant.com

Code Examples:

  • Full implementation in course GitHub repo
  • FPP project folder has working drone example
  • Includes formation spawning and visual effects

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