Summary of "Unite Austin 2017 - Game Architecture with Scriptable Objects"
Main ideas & lessons
1) Why “game architecture” needs a bigger picture
- Developers can usually handle small problems (algorithms, C#), but often lack an overall architecture: how many small parts come together into a cohesive, maintainable game.
- The talk proposes an approach built around Scriptable Objects as “glue” for modular systems.
2) Core “pillars” for architecture (Ryan Hipple’s operating principles)
The speaker frames these as decision-making rules for how a game should be structured:
-
Modular
- Systems should be able to communicate, but not through hard/direct references that tightly couple them.
- Prefer designs where pieces can be reassembled into new combinations without rewiring everything.
-
Editable
- Make the game data-driven so designers/artists can change values via the Unity Inspector and/or runtime.
- Enable “emergent design”: designers can assemble proven small components into new mechanics without waiting on new code features.
-
Debuggable
- Add tooling so you can view and inspect state (especially in the Inspector).
- Never consider a feature “done” without a plan for debugging it.
- A stated rule: “Never fix a bug you don’t understand”—understand root cause, then resolve it.
3) Scene and prefab design rules
-
Scenes as clean slates
- Avoid “transient data” carrying across scene loads.
- Don’t rely on leftover objects; each scene load should break cleanly (reduce hacks and fragile conditional logic like checking scene names).
-
Prefer prefabs that work on their own
- Prefabs should contain their own functionality like LEGO bricks.
- This helps team workflows (fewer merge conflicts in scenes; changes occur at prefab/component level).
4) Data-driven architecture using Scriptable Objects
Scriptable Object basics (primer)
In Unity, a ScriptableObject:
- Is a serializable class asset-like object.
- Does not have a
TransformorGameObject. - Is similar to
MonoBehaviourin the underlying engine type system.
It can be used:
- As a referenced asset (typically a
.assetfile). - Or created at runtime via
CreateInstance. - Multiple scriptable objects can be stored in a single asset file as well (mentioned as an advanced technique).
Common uses (before architecture)
- Massive game configuration data
- Inventory-style lists of items
- Enemy stats sharing
- Many visual prefabs can reference shared enemy stat data
- Audio collections
- An asset references multiple clips and can return one (e.g., random selection)
5) A major architectural enemy: Singleton
The speaker argues that Singletons cause widespread “dependency nightmares” in larger projects.
Benefits that motivate Singleton usage (acknowledged):
- Quick access to “global” functionality (e.g.,
PlayerManager.instance.PlayerName). - Persistent state via
DontDestroyOnLoad.
Problems called out (why it becomes dangerous):
- Rigid connections (break modularity; systems depend on each other being present).
- Reduced polymorphism (harder to swap specialized variants like testing/debug implementations).
- Harder testing (can’t easily inject alternatives).
- Global state issues across scenes (state persists when it shouldn’t).
- Race conditions (init order problems).
- CO-OP limitations (single instance can require revisiting design when there are multiple players).
6) Replacement concept: reduce global state via dependency inversion & injection
- Use dependency injection / inversion of control: objects receive what they need rather than locating it globally.
- In Unity, the Inspector can function as a dependency injector:
- Prefabs/components can be configured with references in editor time.
- Emphasis on Single Responsibility:
- Components should do one thing to support modularity, testing, editability, and reuse.
Detailed methodology & patterns presented
A) Replace “shared data via Singleton” with shared ScriptableObject variables
Goal: multiple systems react to the same value without direct coupling.
Pattern: Shared value variable (float example)
- Create a ScriptableObject variable type (e.g.,
FloatVariable). - Systems reference the ScriptableObject variable asset.
- Components read/write the variable.
- Example architecture:
- Player “Health” system decreases
HPScriptableObject variable. - Health UI prefab reads the same
HPvariable and updates visuals. - Audio/other systems can also subscribe to HP changes (without knowing about the player).
- Player “Health” system decreases
Extra layer: make inspector-friendly “references”
A wrapper pattern:
- Instead of exposing raw floats everywhere, expose a FloatReference / serializable reference.
- Designers can choose:
- Use a constant value, OR
- Use the shared scriptable variable value.
- This scales inspector configuration and keeps values in sync across systems.
Demo behaviors shown
- A player collider damages the player by subtracting from HP.
- A health meter updates in real time from the shared HP variable.
- A “low health” warning sound changes behavior as HP changes:
- Compare HP to a threshold/curve
- Modify heartbeat pitch/mixer based on HP
- Key property: systems keep working even if the player object is not in the scene, because the data/state is held by the ScriptableObject in memory.
B) When you don’t want “state polling,” use event architectures
Goal: represent “something happened” as an event message rather than continuously checking values.
Why event architecture fits architecture goals
- Modular AI & reuse
- Prefabs can respond to events without caring where they came from.
- Debuggability
- You can trigger events from the editor to confirm behavior.
- Reduced per-frame checking
- Avoid constant “did something change?” queries.
UnityEvent limitations (called out)
UnityEvent is useful for serialized function calls (including editor wiring), but not ideal as a fully modular event system because:
- It relies on rigid bindings (button must know exactly which object/function responds).
- Serialization limits and type constraints for inspector-editable arguments.
- Potential performance/garbage concerns if invoked frequently (string-based resolution noted).
- Not robust for fully decoupled prefab composition.
Custom event system using Scriptable Objects (“GameEvent”)
Pattern overview (ScriptableObject-driven events):
- GameEvent = ScriptableObject holding a listener list
- When
Raise()is called:- It iterates through listeners (looping backward to safely remove listeners during dispatch).
- Calls
OnEventRaised(...)via UnityEvent inside listener or through a structured callback.
- When
- GameEventListener = component (ModelBehaviour) that:
- References a GameEvent asset in the Inspector.
- Registers/unregisters itself when enabled/disabled.
- Exposes a UnityEvent response to define what happens locally.
Debugging advantage:
- Events can be raised from the Inspector to reproduce behavior.
Demo event flow:
- Player damage raises
playerDamagedevent. - A responder spawns particles and plays a sound via a GameEventListener response.
- Player death raises
playerKilledevent. - A responder disables the appropriate objects (example “fix” shown):
- Rather than editing code paths broadly, add a listener so only the sound object turns off when the player dies.
- Emphasizes late-stage QA friendliness: small prefab changes without retesting everything.
C) Replace “Singleton manager collections” with runtime sets (ScriptableObject-based registries)
Problem being solved: managers often need lists of scene entities (enemies, renderers, mini-map items), but Singleton + scene startup ordering can create race conditions and rigid dependencies.
Pattern: RuntimeSet
- A ScriptableObject “runtime set” stores a list of objects.
- Objects add themselves when enabled and remove themselves when disabled.
- A manager/system can read the list from the ScriptableObject asset whenever needed.
Benefits claimed
- Avoids race conditions (data exists when referenced; no manual initialization required).
- More flexible than Unity tags:
- Tags are string-based and error-prone.
- Tags are also limited (single tag per object).
- More performant/clean than frequent searches like
FindObjectsOfType.
Demo behaviors
- Cubes register into an Enabled/Disabled runtime set.
- A “disabler” system disables a random item from that set and can be extended into targeting logic (enemy targeting example).
- UI text/mini-map style observer reads the list size and displays it.
D) Replace code-driven enums with data-driven ScriptableObjects
Reason:
- Enums are code-driven and often hard to extend safely as games grow.
- Serialized enums are index-based; deleting/reordering can break saved references.
- Enums can’t carry extra data cleanly (often leading to lookup tables).
ScriptableObject “element” system
- Elements (rock/paper/scissors example) are ScriptableObject assets.
- Each element asset stores relationships/outcomes (e.g., what it beats).
- Designers can add new elements (example: “dynamite”) by creating new assets and wiring references in the Inspector.
- The architecture avoids enum index breakage and supports growth.
E) Put “systems” into Scriptable Objects too (not just data)
The speaker notes people often view ScriptableObjects as “data buckets,” but argues they can include methods and behavior.
A ScriptableObject-based system can be referenced like Unity’s audio mixer approach:
- The asset defines behavior/configuration.
- Other prefabs reference the asset, reducing scene coupling.
Inventory example architecture
- Inventory is a ScriptableObject asset containing:
- A master list of all possible items
- One ScriptableObject per item
- A lookup mechanism (mentioned: validate/populate via callbacks or asset events)
- Different scenes can use different inventory assets (e.g., tutorial inventory vs full inventory) without changing code.
- Player references the inventory asset; UI prefabs reference it too.
- NPCs can add items to the inventory by interacting with the inventory asset rather than requiring player/scene objects to exist.
Key takeaways / “what to adopt”
- Design for modularity: avoid hard references that create rigid coupling.
- Design for editability and designer empowerment: use ScriptableObjects and inspector-driven wiring to enable emergent design.
- Design for debuggability: plan debug views; build event/data tooling that makes runtime behavior observable.
- Replace Singleton-heavy architectures with:
- Shared ScriptableObject variables for state
- ScriptableObject-driven event assets for “something happened”
- ScriptableObject RuntimeSets for scene entity registration
- ScriptableObject-based assets for extensible “enums” and complex systems
Speakers / sources featured
- Ryan Hipple (speaker; principal engineer at Shell Games / Shell Game Studio)
- Richard Fine (referenced/credited for prior talks; his ScriptableObject/event-related guidance is mentioned as a source the speaker learned from)
Category
Educational
Share this summary
Is the summary off?
If you think the summary is inaccurate, you can reprocess it with the latest model.