Game Programming - CSCI 3213
Spring 2026 - Lecture 14
Oklahoma City University
Think about what happens when you turn the ignition key:
Turn the key. ๐
Provide a unified, simplified interface to a set of interfaces in a subsystem, making the subsystem easier to use.
Your game has multiple complex, interacting systems. Clients need to use these systems but shouldn't need to understand their intricate interactions.
| Aspect | Facade Pattern | Adapter Pattern |
|---|---|---|
| Intent | Simplify complex interface | Convert incompatible interface |
| Interface | Creates NEW simplified interface | Adapts EXISTING interface to match target |
| Number of Classes | Wraps multiple subsystems | Usually adapts one class |
| Goal | Ease of use | Compatibility |
BikeEngineFuelPumpCoolingSystemTurboChargerClientFacade - doesn't know about subsystems
Client โ Facade.TurnOn() โ FuelPump.Start()
โ CoolingSystem.Start()
โ Internal orchestration
Simulate a motorcycle engine with realistic component interactions, but expose a simple interface for gameplay.
using UnityEngine;
using System.Collections;
namespace Chapter.Facade
{
public class FuelPump : MonoBehaviour
{
public BikeEngine engine;
public IEnumerator burnFuel;
void Start()
{
burnFuel = BurnFuel();
}
IEnumerator BurnFuel()
{
while (true)
{
yield return new WaitForSeconds(1);
engine.fuelAmount -= engine.burnRate;
if (engine.fuelAmount <= 0.0f)
{
engine.TurnOff();
yield return 0;
}
}
}
void OnGUI()
{
GUI.color = Color.green;
GUI.Label(new Rect(100, 40, 500, 20),
"Fuel: " + engine.fuelAmount);
}
}
}
using UnityEngine;
using System.Collections;
namespace Chapter.Facade
{
public class CoolingSystem : MonoBehaviour
{
public BikeEngine engine;
public IEnumerator coolEngine;
private bool _isPaused;
void Start()
{
coolEngine = CoolEngine();
}
public void PauseCooling()
{
_isPaused = !_isPaused;
}
public void ResetTemperature()
{
engine.currentTemp = 0.0f;
}
IEnumerator CoolEngine()
{
while (true)
{
yield return new WaitForSeconds(1);
if (!_isPaused)
{
// Cool toward ideal temperature
if (engine.currentTemp > engine.minTemp)
engine.currentTemp -= engine.tempRate;
if (engine.currentTemp < engine.minTemp)
engine.currentTemp += engine.tempRate;
}
else
{
// Paused = heating up!
engine.currentTemp += engine.tempRate;
}
// Overheat protection
if (engine.currentTemp > engine.maxTemp)
engine.TurnOff();
}
}
using UnityEngine;
using System.Collections;
namespace Chapter.Facade
{
public class TurboCharger : MonoBehaviour
{
public BikeEngine engine;
private bool _isTurboOn;
private CoolingSystem _coolingSystem;
public void ToggleTurbo(CoolingSystem coolingSystem)
{
_coolingSystem = coolingSystem;
if (!_isTurboOn)
StartCoroutine(TurboCharge());
}
IEnumerator TurboCharge()
{
_isTurboOn = true;
_coolingSystem.PauseCooling(); // DISABLE COOLING!
yield return new WaitForSeconds(engine.turboDuration);
_isTurboOn = false;
_coolingSystem.PauseCooling(); // RE-ENABLE COOLING
}
}
}
using UnityEngine;
namespace Chapter.Facade
{
public class BikeEngine : MonoBehaviour
{
// Engine parameters (exposed to subsystems)
public float burnRate = 1.0f;
public float fuelAmount = 100.0f;
public float tempRate = 5.0f;
public float minTemp = 50.0f;
public float maxTemp = 65.0f;
public float currentTemp;
public float turboDuration = 2.0f;
private bool _isEngineOn;
private FuelPump _fuelPump;
private TurboCharger _turboCharger;
private CoolingSystem _coolingSystem;
void Awake()
{
// Create subsystem components
_fuelPump = gameObject.AddComponent<FuelPump>();
_turboCharger = gameObject.AddComponent<TurboCharger>();
_coolingSystem = gameObject.AddComponent<CoolingSystem>();
}
void Start()
{
// Wire up subsystem references
_fuelPump.engine = this;
_turboCharger.engine = this;
_coolingSystem.engine = this;
}
// SIMPLIFIED PUBLIC INTERFACE
public void TurnOn()
{
_isEngineOn = true;
StartCoroutine(_fuelPump.burnFuel);
StartCoroutine(_coolingSystem.coolEngine);
}
public void TurnOff()
{
_isEngineOn = false;
_coolingSystem.ResetTemperature();
StopCoroutine(_fuelPump.burnFuel);
StopCoroutine(_coolingSystem.coolEngine);
}
public void ToggleTurbo()
{
if (_isEngineOn)
_turboCharger.ToggleTurbo(_coolingSystem);
}
void OnGUI()
{
GUI.color = Color.green;
GUI.Label(
new Rect(100, 0, 500, 20),
"Engine Running: " + _isEngineOn);
}
}
}
using UnityEngine;
namespace Chapter.Facade
{
public class ClientFacade : MonoBehaviour
{
private BikeEngine _bikeEngine;
void Start()
{
_bikeEngine = gameObject.AddComponent<BikeEngine>();
}
void OnGUI()
{
if (GUILayout.Button("Turn On"))
_bikeEngine.TurnOn();
if (GUILayout.Button("Turn Off"))
_bikeEngine.TurnOff();
if (GUILayout.Button("Toggle Turbo"))
_bikeEngine.ToggleTurbo();
}
}
}
// Client needs to know EVERYTHING
var fuelPump = gameObject.AddComponent<FuelPump>();
var coolingSystem = gameObject.AddComponent<CoolingSystem>();
var turboCharger = gameObject.AddComponent<TurboCharger>();
fuelPump.engine = this;
coolingSystem.engine = this;
turboCharger.engine = this;
StartCoroutine(fuelPump.burnFuel);
StartCoroutine(coolingSystem.coolEngine);
// ... and on and on ...
// Client just uses the engine
_bikeEngine.TurnOn();
ClientFacade script// 1. Create new subsystem
public class NitroInjector : MonoBehaviour
{
public BikeEngine engine;
public void InjectNitro()
{
// Massive speed boost!
// Huge fuel consumption!
// Temperature spike!
}
}
// 2. Update facade
public class BikeEngine : MonoBehaviour
{
private NitroInjector _nitroInjector;
void Awake()
{
// ... existing code ...
_nitroInjector = gameObject.AddComponent<NitroInjector>();
}
public void ActivateNitro()
{
if (_isEngineOn)
_nitroInjector.InjectNitro();
}
}
public class BikeController : MonoBehaviour
{
private BikeEngine _engine;
private Rigidbody _rb;
public float normalSpeed = 10f;
public float turboMultiplier = 2f;
void Start()
{
_engine = GetComponent<BikeEngine>();
_rb = GetComponent<Rigidbody>();
_engine.TurnOn();
}
void Update()
{
// Can't move if engine is off
if (!_engine.IsEngineOn)
{
_rb.velocity = Vector3.zero;
return;
}
// Normal movement
float speed = normalSpeed;
// Turbo boost!
if (Input.GetKeyDown(KeyCode.Space))
_engine.ToggleTurbo();
if (_engine.IsTurboActive)
speed *= turboMultiplier;
// Move bike...
_rb.velocity = transform.forward * speed;
}
}
using UnityEngine;
using UnityEngine.UI;
public class EngineUI : MonoBehaviour
{
public BikeEngine engine;
public Slider fuelGauge;
public Slider tempGauge;
public Text statusText;
public Image turboIndicator;
void Update()
{
// Update fuel gauge
fuelGauge.value = engine.fuelAmount / 100f;
// Update temperature gauge
tempGauge.value = (engine.currentTemp - engine.minTemp) /
(engine.maxTemp - engine.minTemp);
// Color temperature gauge based on danger
if (tempGauge.value > 0.8f)
tempGauge.fillRect.GetComponent<Image>().color = Color.red;
else if (tempGauge.value > 0.5f)
tempGauge.fillRect.GetComponent<Image>().color = Color.yellow;
else
tempGauge.fillRect.GetComponent<Image>().color = Color.green;
// Update status
statusText.text = engine.IsEngineOn ? "ENGINE ON" : "ENGINE OFF";
// Turbo indicator
turboIndicator.enabled = engine.IsTurboActive;
}
}
// BAD - Facade hiding spaghetti code
public void DoEverything()
{
// 500 lines of messy logic
}
// GOOD - Facade coordinating clean subsystems
public void StartGame()
{
_levelLoader.Load();
_playerSpawner.Spawn();
_uiManager.ShowHUD();
}
// BAD - Exposing subsystem details
public FuelPump GetFuelPump() { return _fuelPump; }
// GOOD - Exposing only needed info
public float GetFuelPercent() { return fuelAmount / maxFuel; }
using NUnit.Framework;
using UnityEngine;
[Test]
public void BikeEngine_TurnOn_StartsFuelBurning()
{
// Arrange
var go = new GameObject();
var engine = go.AddComponent<BikeEngine>();
float initialFuel = engine.fuelAmount;
// Act
engine.TurnOn();
yield return new WaitForSeconds(2f);
// Assert
Assert.Less(engine.fuelAmount, initialFuel);
}
[Test]
public void BikeEngine_ToggleTurbo_IncreasesTemperature()
{
// Arrange
var go = new GameObject();
var engine = go.AddComponent<BikeEngine>();
engine.TurnOn();
float initialTemp = engine.currentTemp;
// Act
engine.ToggleTurbo();
yield return new WaitForSeconds(1f);
// Assert
Assert.Greater(engine.currentTemp, initialTemp);
}
[Test]
public void BikeEngine_OutOfFuel_StopsEngine()
{
// Arrange
var go = new GameObject();
var engine = go.AddComponent<BikeEngine>();
engine.fuelAmount = 1f;
engine.TurnOn();
// Act
yield return new WaitForSeconds(2f);
// Assert
Assert.IsFalse(engine.IsEngineOn);
}
public void TurnOn()
{
Debug.Log("[BikeEngine] TurnOn() called");
_isEngineOn = true;
Debug.Log("[BikeEngine] Starting FuelPump coroutine");
StartCoroutine(_fuelPump.burnFuel);
Debug.Log("[BikeEngine] Starting CoolingSystem coroutine");
StartCoroutine(_coolingSystem.coolEngine);
Debug.Log("[BikeEngine] Engine is now running");
}
void OnDrawGizmos()
{
if (_isEngineOn)
Gizmos.color = Color.green;
else
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, 1f);
}
// Only create subsystems when needed
private FuelPump _fuelPump;
public FuelPump FuelPump
{
get
{
if (_fuelPump == null)
_fuelPump = gameObject.AddComponent<FuelPump>();
return _fuelPump;
}
}
// Don't update every frame
private float _updateInterval = 0.1f;
private float _nextUpdate = 0f;
void Update()
{
if (Time.time >= _nextUpdate)
{
UpdateEngine();
_nextUpdate = Time.time + _updateInterval;
}
}
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private AudioSystem _audio;
private InputSystem _input;
private SaveSystem _save;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
// Initialize subsystems
_audio = new AudioSystem();
_input = new InputSystem();
_save = new SaveSystem();
}
// Simplified facade methods
public void PlaySound(string name) => _audio.Play(name);
public float GetAxis(string name) => _input.GetAxis(name);
public void SaveGame() => _save.Save();
}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Presentation Layer (UI) โ
โ - Buttons, Menus, HUD โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ Facade Layer (Managers) โ โ Simplified Interface
โ - GameManager โ
โ - LevelManager โ
โ - PlayerManager โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ Business Logic Layer โ
โ - Game rules, systems โ
โ - Physics, AI, progression โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ Data Layer โ
โ - Save files, databases โ
โ - Assets, configuration โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Wrap existing messy system
public class LegacyInventoryFacade
{
private OldMessyInventory _legacy = new OldMessyInventory();
public void AddItem(string item)
{
// Simple interface hides complexity
_legacy.DoComplicatedThingToAddItem(item, true, null, 0);
}
}
// Change all client code to use facade
// Before: OldMessyInventory inventory;
// After: LegacyInventoryFacade inventory;
// Replace implementation, keep interface
public class LegacyInventoryFacade
{
private NewCleanInventory _new = new NewCleanInventory();
public void AddItem(string item)
{
_new.Add(item); // Much better!
}
}
CombatFacade with methods:
Attack() - costs stamina, uses weaponDefend() - costs stamina, reduces damageTakeDamage(float amount) - applies armor, affects healthRest() - restore stamina fasterThank you! ๐ฎ