Skip to content
Development
Skill

/scriptable-objects

ScriptableObject architecture patterns — event channels, variable references, runtime sets, factory pattern, data containers. The backbone of data-driven Unity architecture.

From plugin
everything-claude-unity
2442 skills20 agents27 commands
Install
$ npx -y skills add XeldarAlz/everything-claude-unity --skill scriptable-objects --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/scriptable-objects

Context preview

The summary Claude sees to decide when to auto-load this skill.

ScriptableObject architecture patterns — event channels, variable references, runtime sets, factory pattern, data containers. The backbone of data-driven Unity architecture.

SKILL.md

scriptable-objects.SKILL.md
name: scriptable-objects
description: "ScriptableObject architecture patterns — event channels, variable references, runtime sets, factory pattern, data containers. The backbone of data-driven Unity architecture."
alwaysApply: true

ScriptableObject Architecture

ScriptableObjects (SOs) are Unity's most powerful architectural tool. They are asset-based data containers that live outside scenes, enabling data-driven design, loose coupling, and designer-friendly workflows.

Pattern 1: Data Container

The simplest and most common use — define game data as assets.

[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game/Weapon Definition")]
public sealed class WeaponDefinition : ScriptableObject
{
    [Header("Identity")]
    [SerializeField] private string _displayName;
    [SerializeField] private Sprite _icon;
    [TextArea]
    [SerializeField] private string _description;

    [Header("Stats")]
    [SerializeField] private float _damage = 10f;
    [SerializeField] private float _fireRate = 0.5f;
    [SerializeField] private int _ammoCapacity = 30;
    [SerializeField] private GameObject _prefab;

    public string DisplayName => _displayName;
    public Sprite Icon => _icon;
    public float Damage => _damage;
    public float FireRate => _fireRate;
    public int AmmoCapacity => _ammoCapacity;
    public GameObject Prefab => _prefab;
}

**Use for:** Items, abilities, enemy configs, level data, audio events, UI themes.

Pattern 2: Event Channel

Zero-coupling communication between systems. No direct references needed.

[CreateAssetMenu(fileName = "NewVoidEvent", menuName = "Events/Void Event")]
public sealed class VoidEventChannel : ScriptableObject
{
    private System.Action _onRaised;

    public void Raise()
    {
        _onRaised?.Invoke();
    }

    public void Subscribe(System.Action listener)
    {
        _onRaised += listener;
    }

    public void Unsubscribe(System.Action listener)
    {
        _onRaised -= listener;
    }
}

// Generic version for typed events
[CreateAssetMenu(fileName = "NewIntEvent", menuName = "Events/Int Event")]
public sealed class IntEventChannel : ScriptableObject
{
    private System.Action<int> _onRaised;

    public void Raise(int value) => _onRaised?.Invoke(value);
    public void Subscribe(System.Action<int> listener) => _onRaised += listener;
    public void Unsubscribe(System.Action<int> listener) => _onRaised -= listener;
}

**Usage:**

// Publisher (e.g., ScoreSystem)
[SerializeField] private IntEventChannel _onScoreChanged;
_onScoreChanged.Raise(newScore);

// Subscriber (e.g., ScoreUI) — wire the SAME SO asset in inspector
[SerializeField] private IntEventChannel _onScoreChanged;
private void OnEnable() => _onScoreChanged.Subscribe(UpdateDisplay);
private void OnDisable() => _onScoreChanged.Unsubscribe(UpdateDisplay);

Pattern 3: Variable Reference

Inspector-tweakable values that can be shared or overridden per instance.

[System.Serializable]
public sealed class FloatReference
{
    [SerializeField] private bool _useConstant = true;
    [SerializeField] private float _constantValue;
    [SerializeField] private FloatVariable _variable;

    public float Value => _useConstant ? _constantValue : _variable.Value;
}

[CreateAssetMenu(fileName = "NewFloatVar", menuName = "Variables/Float")]
public sealed class FloatVariable : ScriptableObject
{
    [SerializeField] private float _value;
    public float Value { get => _value; set => _value = value; }
}

**Use for:** Player health, move speed, gravity — values that designers tweak and multiple systems read.

Pattern 4: Runtime Set

Track all active instances of a type without FindObjectsOfType.

[CreateAssetMenu(fileName = "NewRuntimeSet", menuName = "Sets/Transform Set")]
public sealed class TransformRuntimeSet : ScriptableObject
{
    private readonly List<Transform> _items = new();

    public IReadOnlyList<Transform> Items => _items;
    public int Count => _items.Count;

    public void Add(Transform item)
    {
        if (!_items.Contains(item))
        {
            _items.Add(item);
        }
    }

    public void Remove(Transform item)
    {
        _items.Remove(item);
    }
}

// Usage: enemies register themselves
public sealed class Enemy : MonoBehaviour
{
    [SerializeField] private TransformRuntimeSet _enemySet;

    private void OnEnable() => _enemySet.Add(transform);
    private void OnDisable() => _enemySet.Remove(transform);
}

Pattern 5: Factory Configuration

[CreateAssetMenu(fileName = "NewSpawnConfig", menuName = "Game/Spawn Config")]
public sealed class SpawnConfiguration : ScriptableObject
{
    [SerializeField] private GameObject _prefab;
    [SerializeField] private int _poolSize = 10;
    [SerializeField] private float _spawnRate = 2f;
    [SerializeField] private float _spawnRadius = 15f;

    public GameObject Prefab => _prefab;
    public int PoolSize => _poolSize;
    public float SpawnRate => _spawnRate;
    public float SpawnRadius => _spawnRadius;
}

Anti-Patterns

1. **Mutable state not reset between plays** — SOs persist across play sessions in Editor. If you write to an SO at runtime, it keeps the value when you stop playing. Use `OnEnable` or `OnDisable` to reset, or use a separate runtime copy.

2. **SO as singleton** — Don't use `Resources.Load` to access SOs globally. Use `[SerializeField]` references or DI.

3. **Too much logic in SOs** — SOs are data and events. Keep complex logic in systems/services.

4. **Circular SO references** — SO A references SO B which references SO A. Can cause infinite loops in serialization.

Read more
Ships witheverything-claude-unity

The ultimate Claude Code toolkit for Unity game development. A production-ready, plug-and-play system that gives Claude Code deep Unity expertise — from writing performant C# to building scenes, profiling performance, and triggering iOS/Android builds — all

Get the whole plugin

Other skills on everything-claude-unity.