Game Programming - CSCI 3213
Spring 2026 - Lecture 10
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.
Flying robotic enemies that attack the player with predictable, automated behaviors.
Define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.
Think of a GPS navigation app:
Drone classIManeuverBehaviourBoppingManeuverWeavingManeuverFallbackManeuver| 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 |
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.
namespace Chapter.Strategy
{
public interface IManeuverBehaviour
{
// Execute maneuver on given drone
void Maneuver(Drone drone);
}
}
Drone parameter - important!
Create a new file: Assets/Scripts/Patterns/Strategy/Drone.cs
This is the Context class that uses different strategy implementations.
โ ๏ธ This code goes into your Unity project for Blade Racer.
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;
Add these methods to the Drone class created in 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
}
}
}
public void ApplyStrategy(IManeuverBehaviour strategy)
{
strategy.Maneuver(this);
}
this parameter
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;
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;
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
}
}
}
}
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;
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!
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;
}
}
}
}
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;
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!
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
}
}
}
Unlike Bopping and Weaving, Fallback doesn't loop infinitely - it moves backward once then stops. This creates a "retreat" behavior.
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
}
}
}
Create a new file: Assets/Scripts/Testing/ClientStrategy.cs
This script tests your strategy pattern implementation by spawning drones with random behaviors.
โ ๏ธ This is temporary testing code - you can remove it after testing your implementation.
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();
}
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();
}
}
}
TestPanel.cs to combine all test controls into one draggable window.
See the Event Bus lecture for the unified TestPanel implementation.
Add Strategy Pattern keyboard shortcuts and section to your TestPanel.cs.
DrawKeymapWindow():G = Spawn Drone (Generate)
ClientStrategy scriptpublic 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);
}
}
}
Important: Using coroutines for animation is simplified for this example. In production, consider better alternatives:
animator.SetInteger("ManeuverType", (int)ManeuverType.Bopping);
// 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");
}
}
saveManager.SetEncryptionStrategy(
platform == PC ? new AES256() : new SimpleXOR()
);
enemy.SetBehaviorStrategy(
difficulty == Hard ? new AggressiveAI() : new PassiveAI()
);
navigator.SetPathfindingStrategy(
mapSize > 1000 ? new AStarStrategy() : new DijkstraStrategy()
);
weapon.SetDamageStrategy(
target.armor > 50 ? new ArmorPiercingDamage() : new StandardDamage()
);
// 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.
// 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>() }
};
}
[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);
}
// BAD - old strategy still running!
drone.ApplyStrategy(newStrategy);
// GOOD - stop previous first
StopAllCoroutines();
drone.ApplyStrategy(newStrategy);
public void ApplyStrategy(IManeuverBehaviour strategy)
{
if (strategy == null)
{
Debug.LogError("Null strategy provided!");
return;
}
strategy.Maneuver(this);
}
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);
}
void OnDrawGizmos()
{
Gizmos.color = strategyType == BoppingManeuver ? Color.red : Color.blue;
Gizmos.DrawWireSphere(transform.position, 0.5f);
}
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.
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!
DroneSpawner class that:
Decorator Pattern: Implementing a weapon upgrade system
Thank you! ๐ฎ