Game Programming - CSCI 3213
Spring 2026 - Lecture 14
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.
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.
Create a new file: FuelPump.cs
Subsystem component that manages fuel consumption.
using UnityEngine;
using System.Collections;
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);
}
}
Create a new file: Assets/Scripts/Subsystems/CoolingSystem.cs
This subsystem manages engine cooling for the bike.
โ ๏ธ This code goes into your Unity project for Blade Racer.
using UnityEngine;
using System.Collections;
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;
}
Add this coroutine method to the CoolingSystem class from the previous slide.
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();
}
}
Create a new file: Assets/Scripts/Subsystems/TurboCharger.cs
This subsystem manages turbo boost functionality for the bike.
โ ๏ธ This code goes into your Unity project for Blade Racer.
using UnityEngine;
using System.Collections;
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
}
}
Create a new file: BikeEngine.cs
Facade class that simplifies interaction with engine subsystems.
using UnityEngine;
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);
}
}
Create a new file: Assets/Scripts/Testing/ClientFacade.cs
This script tests your Facade pattern implementation with GUI buttons.
โ ๏ธ This is temporary testing code - you can remove it after testing your implementation.
using UnityEngine;
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();
}
}
TestPanel.cs to combine all test controls into one draggable window.
See the Event Bus lecture for the unified TestPanel implementation.
Add Facade Pattern keyboard shortcuts and section to your TestPanel.cs.
DrawKeymapWindow():O = Turn On, I = Turn Off, U = Toggle Turbo
// 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!
}
}
In 1988, Sony and Nintendo partnered to create a CD-ROM add-on for the SNES called the "Play Station." At 1991 CES, Sony announced the partnership. The next day, Nintendo publicly announced they'd partnered with Philips instead, humiliating Sony on the world stage. Sony executives wanted to abandon gaming entirely, but engineer Ken Kutaragi convinced them to proceed alone.
The result was the PlayStation (1994). Its secret weapon? A simple, unified development environment. While Saturn and N64 had complex multi-chip architectures requiring assembly code, PlayStation offered a clean C library facade. Developers called simple functions like LoadTexture() or PlayAudio(), and Sony's SDK handled the messy hardware details. This facade made development so easy that third-party devs flocked to PlayStation, giving it 102 million sales.
PlayStation's SDK was a Facade Pattern triumph! Behind the scenes, the hardware had complex subsystems - graphics chips, sound processors, CD-ROM controllers - each with intricate APIs. Sony's SDK provided a facade: simple, high-level functions that hid this complexity. Just like our BikeEngineFacade wraps FuelSystem, CoolingSystem, and TurboSystem into one simple interface, PlayStation's SDK wrapped GPU, SPU, and CD subsystems. The facade didn't limit power - experts could still access low-level APIs - it just made common tasks simple!
CombatFacade with methods:
Attack() - costs stamina, uses weaponDefend() - costs stamina, reduces damageTakeDamage(float amount) - applies armor, affects healthRest() - restore stamina fasterThank you! ๐ฎ