# Unity Design System

> Use when designing, implementing, reviewing, or refactoring Unity game architecture and reusable gameplay/UI systems. Trigger on Unity design system, Unity architecture, ScriptableObject architecture, data-driven gameplay, EventBus, ServiceLocator, bootstrap, domain module layout, phase/state machines, save/load, reset flows, editor tooling, runtime state safety, god object cleanup, or requests to identify good/bad Unity codebase patterns and turn them into maintainable standards.

- Skill: `fauzanazz/unity-design-system` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add fauzanazz/unity-design-system`
- Raw SKILL.md: https://api.skillmd.com/api/skills/fauzanazz/unity-design-system/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: fauzanazz (https://skillmd.com/u/fauzanazz)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/fauzanazz/unity-design-system

---


# Unity Design System

Use this skill to turn a Unity project into a maintainable design system: clear authoring data,
explicit runtime state, small domain modules, deterministic startup, typed events, focused services,
and editor-safe content workflows.

Prefer patterns that fit Unity's inspector and scene workflow without hiding dependencies behind
global state. Treat every large MonoBehaviour or service as a liability until its responsibilities
are visible and bounded.

## Workflow

1. Inspect local instructions, project structure, service/bootstrap entry points, representative
   domain modules, UI screens, save/reset code, and tests.
2. Separate observed patterns into:
   - **Standards to keep**: repeatable patterns that reduce coupling or improve authoring safety.
   - **Patterns to repair**: repeated smells that increase runtime risk or maintenance cost.
3. If implementing, change the smallest slice that establishes the standard. Do not redesign every
   domain at once.
4. Validate with focused tests for plain C# logic, Unity editor tests where needed, and a scene/play
   smoke check for lifecycle or UI changes.

## Core Standards

- Use `ScriptableObject` for authoring data and content definitions.
- Keep runtime state in runtime objects, services, scene instances, or save payloads, not shared assets.
- Split each game domain by reason to change: data, events, interfaces, pure logic, save data,
  authoring assets, and service orchestration.
- Keep services as orchestration boundaries. Put calculations, validation, matching, and state
  transitions in plain C# classes.
- Use typed events for cross-system notifications and UI updates, not for opaque command chains.
- Make required dependencies visible through serialized references, constructors, or one composition
  root. Treat missing required dependencies as startup errors.
- Flag any MonoBehaviour or service over 200 LOC, or any class with more than 5 public methods, for
  extraction unless there is a clear Unity lifecycle reason.
- Prefer deletion over speculative abstraction. Add interfaces only when variants share a real
  lifecycle.

## Domain Module Template

For gameplay domains such as brewing, inventory, economy, customer, recipe, audio, progression,
processing, combat, cards, enemies, or reset, prefer this shape:

```text
Scripts/<Domain>/
├── Data/               # Enums, IDs, small value types
├── Events/             # Typed event structs for requests and notifications
├── Interface/          # Narrow provider/service interfaces
├── Logic/              # Pure rules, validators, matchers, registries, calculators
├── SaveData/           # Serializable save payloads and reset category metadata
├── ScriptableObjects/  # Authoring data and config assets
└── Service/            # Unity lifecycle adapter and orchestration boundary
```

Rules:
- Do not force empty folders for tiny domains, but use the same mental split.
- Domain event files should be easy to find; do not hide public event structs at the bottom of UI
  or scene controller files.
- Service methods should read like orchestration: validate, update runtime object, publish events,
  call save, update optional presentation hooks.

## Authoring Data

Use `ScriptableObject` assets for ingredients, recipes, cards, actions, enemies, processing stations,
shop upgrades, audio clips, scenes, difficulty, tutorial pages, boss definitions, and config values.

Rules:
- Asset classes expose mostly read-only properties.
- Asset classes do not mutate per-run state.
- Use `[CreateAssetMenu]`, `[Header]`, `[Tooltip]`, `[Range]`, and `OnValidate` to make authoring safe.
- Store stable content IDs on definitions; use runtime IDs for spawned instances.
- Prefer explicit serialized references for required config over scattered `Resources.Load` strings.

Good shape:

```csharp
public sealed class IngredientData : ScriptableObject
{
    [SerializeField] private IngredientId id;
    [SerializeField] private string displayName;
    [SerializeField] private MaterialEffectProfile effectProfile;

    public IngredientId Id => id;
    public string DisplayName => displayName;
    public MaterialEffectProfile EffectProfile => effectProfile;

    private void OnValidate()
    {
        if (id == IngredientId.None)
            Debug.LogWarning($"[{nameof(IngredientData)}] {name} has no id.");
    }
}
```

## Runtime State

Create runtime wrappers for stateful gameplay concepts such as active brew sessions, customer queues,
card stacks, cooldowns, EX state, current health, active modifiers, selected phase, and spawned enemy
handles.

Rules:
- A `ScriptableObject` should not own fields like `IsActive`, `IsEx`, `CooldownTimer`, current stack
  count, current HP, or active scene references.
- Runtime data can reference its definition, but the definition should not reference runtime state.
- Reset runtime state through constructors, explicit reset services, or reset orchestrators.
- Plain runtime classes should be testable without loading scenes.

Good shape:

```csharp
public sealed class BrewSession
{
    private readonly RawBrewTotals totals = new();

    public BrewingPhase Phase { get; private set; } = BrewingPhase.NoIngredient;
    public MaterialEffectProfile RawProfile => totals.TotalProfile;

    public bool TryAddMaterial(ProcessedMaterial material)
    {
        if (Phase != BrewingPhase.NoIngredient)
            return false;

        return totals.TryAdd(material);
    }
}
```

## Pure Logic First

Put deterministic rules in plain C# classes or structs:

- Validators: `ValidateAddMaterial`, `ValidateStartBrew`, `ValidatePlacement`.
- Calculators: price, quality, score, XP, cooldown.
- Matchers: customer request match, recipe match, target selection.
- State helpers: stabilizers, phase transition rules, runtime statistics.
- Registries: maps from stable IDs to authoring assets.

Rules:
- Keep Unity side effects out of rule code unless the rule is intrinsically visual or physics-bound.
- Return structured results, not just booleans, when UI/debug needs failure reasons.
- Write focused NUnit tests for these classes before adding scene-level tests.

## Services And Bootstrap

Use one composition root to instantiate persistent services, register them, load persistent scenes,
then initialize services in deterministic order.

Rules:
- A service may own runtime state and coordinate side effects, but should not contain every business
  rule for the domain.
- Keep `ServiceLocator` calls near composition roots, UI adapters, and service boundaries. Avoid
  `ServiceLocator.Get<T>()` inside plain logic, modifiers, or data objects.
- Prefer interfaces for provider contracts when consumers only need a small surface.
- Provide explicit initialization priorities only when order matters.
- Unregister or clear static service registries during teardown or test reset.
- Missing required services should fail fast. Optional polish dependencies can be nullable.

Service shape:

```csharp
public sealed class BrewingService : InitializableServiceBase<BrewingService>, IBrewingProvider
{
    private BrewingSession session;

    public override Task Initialize()
    {
        session = new BrewingSession();
        EventBus.Subscribe<RequestStartBrewEvent>(OnStartRequested);
        return Task.CompletedTask;
    }

    protected override void OnDestroy()
    {
        EventBus.Unsubscribe<RequestStartBrewEvent>(OnStartRequested);
        base.OnDestroy();
    }
}
```

## Event System

Use typed event structs for broad notifications such as phase changes, UI readiness, item usage,
achievement unlocks, score changes, combat transitions, save/reset progress, and service readiness.

Rules:
- Subscribe in `OnEnable`/`Initialize`; unsubscribe in the paired lifecycle method.
- Use request events for user/system intent and result events for state changes.
- Keep event definitions in domain event files.
- Avoid duplicated event names in different scopes unless there is a namespace-level reason.
- Avoid chains where event A publishes B publishes C without one workflow owner.
- Event bus debugging should expose subscriber counts and recent publish history.
- Isolate subscriber exceptions if a failure in one listener should not block the rest.

## UI System

UI renders state, publishes intent, and delegates rules to services or pure logic.

Rules:
- Use reusable UI base components only for repeated mechanics: show/hide, modal pause, tabs,
  carousel, buttons, drag zones, effect displays.
- Split large screens into presenters/controllers for pagination, action buttons, effect previews,
  drop zones, animation, and service adapters.
- UI may cache services on enter/awake, but it should prefer provider interfaces when possible.
- If UI registers as a visual provider, it must unregister on close/exit/disable.
- Use a reference-counted pause manager for modal UI.
- Avoid `GameObject.Find`, tag lookup, and `Camera.main` in frequently used UI paths.

## Save And Reset

Centralize persistence behind generic save payloads and explicit reset orchestration.

Rules:
- Every save payload derives from a base save type with explicit filename and reset policy.
- Async save/load returns `Task`. Reserve `async void` for Unity event entrypoints only.
- Do not fire-and-forget important saves during reset, scene changes, or quit flows.
- Separate platform IO, serialization, encryption/obfuscation, registry, and reset selection if the
  save manager grows large.
- Reset should delete matching save payloads, reset in-memory services by priority, and publish
  progress/completion events.
- Treat hardcoded keys and fixed IVs as obfuscation only; do not describe them as strong security.

## Editor Tooling

Build small editor tools for repetitive production tasks:

- Event bus debugger.
- Service/bootstrap debugger.
- Bootstrap inspector with testing overrides.
- Selective save reset tools.
- Asset generators and validators for catalogs, recipes, cutscenes, scenes, audio, or content IDs.

Rules:
- Put editor-only code under `Editor/` or wrap it in `#if UNITY_EDITOR`.
- Validate selected assets before generating output.
- Make generated folders deterministic and safe to overwrite only after confirmation.

## Anti-Patterns To Fix

### God MonoBehaviours Or Services

Smell:
- One class handles input, pagination, state restore, service lookups, animation, audio, VFX,
  validation, saving, debug, and business rules.

Fix:
- Extract validators, presenters, factories, runtime state objects, and side-effect services.
- Keep MonoBehaviours as Unity adapters/orchestrators.

### Hidden Global Dependencies

Smell:
- Frequent `ServiceLocator.Get<T>()`, `.Instance`, `FindObjectOfType`, `FindAnyObjectByType`,
  `GameObject.Find`, `FindGameObjectWithTag`, or `Camera.main` inside gameplay logic.

Fix:
- Wire dependencies at bootstrap, through serialized fields, or constructors.
- Use interfaces for plain C# services and pass them through constructors.
- Cache optional lookups once at initialization.

### Static Lifecycle Leaks

Smell:
- Static service/event registries have no unregister or test reset path.
- Services keep references to destroyed scene UI or visual providers.

Fix:
- Add explicit `Unregister`, `ClearAll`, or test teardown paths.
- Pair every register/subscribe with unregister/unsubscribe.
- Prefer weak/event ownership only when the lifecycle is genuinely shared.

### Event Spaghetti

Smell:
- Events are used as internal method calls, commands have no owner, or behavior requires tracing many
  publish/subscribe hops.

Fix:
- Use one orchestrator per workflow.
- Use events for notifications and UI updates.
- Keep request/result event pairs explicit.

### `Resources.Load` Drift

Smell:
- Config and content dependencies are loaded through scattered string paths.

Fix:
- Prefer serialized config references in bootstrap assets or service prefabs.
- If `Resources.LoadAll` is used for catalogs, keep paths centralized in one registry and validate
  loaded assets.

### Async Fire-And-Forget

Smell:
- Important saves, resets, or scene transitions are hidden behind `async void`.

Fix:
- Return `Task` from service APIs and await at workflow boundaries.
- Keep `async void` only at Unity event entrypoints, with local exception handling.

### Soft Failure For Required Wiring

Smell:
- Required dependencies log a warning and continue with permissive behavior.

Fix:
- Validate required references in `Awake`/`OnValidate`.
- Disable the component or throw a clear startup error when required wiring is absent.

### Duplicate Or Ambiguous Types

Smell:
- Two event structs/classes share the same name in different scopes without clear namespacing.

Fix:
- Use domain-prefixed names such as `SettingsOpenRequestedEvent` or place all related events in one
  namespace/file.

### Half-Built Abstractions

Smell:
- Interfaces or enum branches exist but implementations are empty or no-op.

Fix:
- Delete unused abstraction until needed, or complete behavior with focused tests/demo scene.

## Review Checklist

- [ ] Domain modules separate data, events, interfaces, logic, save data, assets, and services.
- [ ] Authoring data is in `ScriptableObject`; runtime state is not.
- [ ] Plain logic covers calculations, validation, matching, and phase transitions.
- [ ] Required dependencies are visible and validated.
- [ ] Services initialize from one composition root in deterministic order.
- [ ] ServiceLocator usage is confined to boundaries and supports cleanup.
- [ ] Event subscriptions are paired and event definitions are discoverable.
- [ ] UI renders state and publishes intent; gameplay rules live outside UI.
- [ ] Visual providers and listeners unregister on lifecycle exit.
- [ ] Factories create runtime instances and do not mutate shared assets.
- [ ] Large MonoBehaviours/services are split by responsibility.
- [ ] Save/load has awaitable lifecycle paths and clear reset categories.
- [ ] `Resources.Load` strings are centralized or replaced by serialized references.
- [ ] Debug logs are gated or removed from hot runtime paths.
- [ ] Editor tooling exists for repetitive data/setup work.

## Output Style

When asked to review a Unity codebase, lead with findings ordered by risk. Include file references,
why each pattern matters, and the smallest repair path. When asked to implement a design system,
make the standard real in one representative domain first, then leave a repeatable template for the
next domains.

