Game Programming - CSCI 3213
Spring 2026 - Lecture 10
Oklahoma City University
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 |
namespace Chapter.Strategy
{
public interface IManeuverBehaviour
{
// Execute maneuver on given drone
void Maneuver(Drone drone);
}
}
Drone parameter - important!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;
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 parameterusing 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;
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
}
}
}
}
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!
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;
}
}
}
}
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!
// 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
}
}
}
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();
}
}
}
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);
}
DroneSpawner class that:
Decorator Pattern: Implementing a weapon upgrade system
Thank you! ๐ฎ