Game Programming - CSCI 3213
Spring 2026 - Lecture 13
Oklahoma City University
Think about traveling internationally with your phone:
Convert the interface of a class into another interface clients expect, allowing incompatible classes to work together.
You have two systems that need to work together, but their interfaces are incompatible. You can't modify one or both systems directly.
IInventorySystemInventorySystemAdapterInventorySystem (third-party)Client โ Target Interface โ Adapter โ Adaptee
Client calls methods on the interface, Adapter translates those calls to the Adaptee's methods.
| Aspect | Adapter Pattern | Facade Pattern |
|---|---|---|
| Intent | Convert interface to match client expectations | Simplify complex subsystem |
| Number of Classes | Usually adapts one class | Wraps multiple classes |
| Interface | Matches existing target interface | Creates new simplified interface |
| Goal | Compatibility between systems | Ease of use |
You've downloaded an excellent inventory system from the Unity Asset Store. It saves player items to a secure cloud backend, but it only supports cloud saves.
Use the Adapter pattern to create InventorySystemAdapter that:
InventorySystemIInventorySystem interfaceusing UnityEngine;
using System.Collections.Generic;
namespace Chapter.Adapter
{
// This is the third-party class we CANNOT modify
public class InventorySystem
{
public void AddItem(InventoryItem item)
{
Debug.Log("Adding item to the cloud");
}
public void RemoveItem(InventoryItem item)
{
Debug.Log("Removing item from the cloud");
}
public List<InventoryItem> GetInventory()
{
Debug.Log(
"Returning inventory list stored in the cloud");
return new List<InventoryItem>();
}
}
}
namespace Chapter.Adapter
{
public enum SaveLocation
{
Local, // Save to local disk
Cloud, // Save to cloud backend
Both // Save to both locations
}
}
using System.Collections.Generic;
namespace Chapter.Adapter
{
// This is the interface our client expects
public interface IInventorySystem
{
void SyncInventories();
void AddItem(
InventoryItem item, SaveLocation location);
void RemoveItem(
InventoryItem item, SaveLocation location);
List<InventoryItem> GetInventory(
SaveLocation location);
}
}
SaveLocation parameterSyncInventories() methodusing UnityEngine;
using System.Collections.Generic;
namespace Chapter.Adapter
{
// Inherits from InventorySystem AND implements IInventorySystem
public class InventorySystemAdapter :
InventorySystem, IInventorySystem
{
private List<InventoryItem> _cloudInventory;
// NEW functionality: Sync local and cloud
public void SyncInventories()
{
var _cloudInventory = GetInventory();
Debug.Log(
"Synchronizing local drive and cloud inventories");
}
// ADAPTED method: AddItem with location choice
public void AddItem(
InventoryItem item, SaveLocation location)
{
if (location == SaveLocation.Cloud)
AddItem(item); // Calls parent method!
if (location == SaveLocation.Local)
Debug.Log("Adding item to local drive");
if (location == SaveLocation.Both)
Debug.Log(
"Adding item to local drive and cloud");
}
AddItem(item) calls the inherited method public void RemoveItem(
InventoryItem item, SaveLocation location)
{
Debug.Log(
"Remove item from local/cloud/both");
}
public List<InventoryItem> GetInventory(
SaveLocation location)
{
Debug.Log(
"Get inventory from local/cloud/both");
return new List<InventoryItem>();
}
}
}
using UnityEngine;
namespace Chapter.Adapter
{
[CreateAssetMenu(
fileName = "New Item",
menuName = "Inventory")]
public class InventoryItem : ScriptableObject
{
// Placeholder class for inventory items
// In production, add:
// - Item name, description, icon
// - Stack size, rarity, value
// - Equipment type, stats, etc.
}
}
using UnityEngine;
namespace Chapter.Adapter
{
public class ClientAdapter : MonoBehaviour
{
public InventoryItem item;
private InventorySystem _inventorySystem;
private IInventorySystem _inventorySystemAdapter;
void Start()
{
// Old vendor system
_inventorySystem = new InventorySystem();
// Our adapted system
_inventorySystemAdapter =
new InventorySystemAdapter();
}
void OnGUI()
{
if (GUILayout.Button("Add item (no adapter)"))
_inventorySystem.AddItem(item);
if (GUILayout.Button("Add item (with adapter)"))
_inventorySystemAdapter.AddItem(
item, SaveLocation.Both);
}
}
}
InventoryItem ScriptableObject asset:
ClientAdapter scriptTest Sword asset into the Item field_inventorySystemAdapter.AddItem(item, SaveLocation.Both);
AddItem(item) (inherited method)
InventorySystem (Adaptee - vendor code)
โ
| inherits
|
InventorySystemAdapter
|
| implements
โ
IInventorySystem (Target - what client expects)
public class InventorySystemAdapter : IInventorySystem
{
// Wrap instead of inherit!
private InventorySystem _inventorySystem;
public InventorySystemAdapter()
{
_inventorySystem = new InventorySystem();
}
public void AddItem(InventoryItem item, SaveLocation location)
{
if (location == SaveLocation.Cloud)
_inventorySystem.AddItem(item); // Delegate call
if (location == SaveLocation.Local)
Debug.Log("Adding item to local drive");
// ... rest of implementation
}
}
using System.IO;
using UnityEngine;
public void AddItem(InventoryItem item, SaveLocation location)
{
if (location == SaveLocation.Cloud || location == SaveLocation.Both)
{
AddItem(item); // Cloud save via parent
}
if (location == SaveLocation.Local || location == SaveLocation.Both)
{
// Real local save implementation
string path = Application.persistentDataPath + "/inventory.json";
var inventory = LoadLocalInventory(path);
inventory.Add(item);
string json = JsonUtility.ToJson(new InventoryList(inventory));
File.WriteAllText(path, json);
Debug.Log($"Saved {item.name} to {path}");
}
}
public void SyncInventories()
{
// Get inventories from both sources
List<InventoryItem> cloudInventory = GetInventory();
List<InventoryItem> localInventory = LoadLocalInventory();
// Merge strategy: Cloud takes precedence
var merged = new HashSet<InventoryItem>(cloudInventory);
foreach (var item in localInventory)
{
if (!merged.Contains(item))
{
// Item exists locally but not in cloud - upload it
AddItem(item, SaveLocation.Cloud);
merged.Add(item);
}
}
// Save merged inventory locally
SaveLocalInventory(merged.ToList());
Debug.Log($"Synced {merged.Count} items between local and cloud");
}
public void AddItem(InventoryItem item, SaveLocation location)
{
if (item == null)
{
Debug.LogError("Cannot add null item to inventory");
return;
}
try
{
if (location == SaveLocation.Cloud || location == SaveLocation.Both)
{
AddItem(item);
}
if (location == SaveLocation.Local || location == SaveLocation.Both)
{
SaveToLocalDisk(item);
}
}
catch (System.Exception e)
{
Debug.LogError($"Failed to add item: {e.Message}");
// Implement rollback if needed
}
}
// Adapter for Vendor A's system
public class VendorAInventoryAdapter : IInventorySystem
{
private VendorAInventory _vendorA;
// ... implement IInventorySystem
}
// Adapter for Vendor B's system
public class VendorBInventoryAdapter : IInventorySystem
{
private VendorBInventory _vendorB;
// ... implement IInventorySystem
}
// Client uses either adapter through same interface
IInventorySystem inventory;
if (useVendorA)
inventory = new VendorAInventoryAdapter();
else
inventory = new VendorBInventoryAdapter();
inventory.AddItem(item, SaveLocation.Both); // Works with both!
using NUnit.Framework;
[Test]
public void Adapter_AddsItemToCloud_WhenCloudLocationSpecified()
{
// Arrange
var adapter = new InventorySystemAdapter();
var item = ScriptableObject.CreateInstance<InventoryItem>();
// Act
adapter.AddItem(item, SaveLocation.Cloud);
// Assert
var inventory = adapter.GetInventory(SaveLocation.Cloud);
Assert.Contains(item, inventory);
}
[Test]
public void Adapter_SyncsInventories_WithoutDuplicates()
{
// Arrange
var adapter = new InventorySystemAdapter();
var item = ScriptableObject.CreateInstance<InventoryItem>();
adapter.AddItem(item, SaveLocation.Both);
// Act
adapter.SyncInventories();
// Assert
var cloud = adapter.GetInventory(SaveLocation.Cloud);
var local = adapter.GetInventory(SaveLocation.Local);
Assert.AreEqual(cloud.Count, local.Count);
}
// Change ONE line:
// IInventorySystem inventory = new InventorySystemAdapter();
IInventorySystem inventory = new InventorySystemV2();
// All client code continues working!
private List<InventoryItem> _cachedInventory;
private float _lastCacheTime;
public List<InventoryItem> GetInventory(SaveLocation location)
{
if (Time.time - _lastCacheTime < 1f)
return _cachedInventory; // Use cache
_cachedInventory = LoadInventoryFromLocation(location);
_lastCacheTime = Time.time;
return _cachedInventory;
}
public void AddItem(InventoryItem item, SaveLocation location)
{
Debug.Log($"[Adapter] AddItem called: {item.name}, " +
$"Location: {location}");
if (location == SaveLocation.Cloud || location == SaveLocation.Both)
{
Debug.Log("[Adapter] Calling vendor's AddItem for cloud save");
AddItem(item);
}
if (location == SaveLocation.Local || location == SaveLocation.Both)
{
Debug.Log("[Adapter] Executing local save logic");
SaveToLocalDisk(item);
}
Debug.Log("[Adapter] AddItem completed successfully");
}
Debug.Assert(inventory is IInventorySystem,
"Inventory must implement IInventorySystem interface!");
ISaveSystem interface with methods:
Save(string key, string data)Load(string key)Delete(string key)Lecture 14: Game Architecture & Systems Design
Thank you! ๐ฎ