agentsclimarketplace

Uw scriptable object arch

Skill IdoCohen560/claude-unity-game-studio/unity-ai-workflow/.claude/skills/uw-scriptable-object-arch

Generate ScriptableObject-based systems including data containers, event channels, and runtime sets. Use when creating game data (weapons, enemies, items, abilities), cross-system events, or live object tracking. Triggers on requests like "create weapon data", "add an event system", "make a runtime set", "decouple these systems", "create item database", "add a damage event", "track active enemies", "create a config SO", "add game settings data", or any task involving ScriptableObject data containers, event-driven communication, or runtime object tracking. Reads ProjectConfig.yaml -> architecture_pattern to determine SO-first vs DI-first context.From its SKILL.md

Install
npx -y skills add IdoCohen560/claude-unity-game-studio --skill uw-scriptable-object-arch

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.8 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it

ScriptableObject Architecture

Generate SO patterns for data, events, and runtime collections.

Before You Start

  1. Read docs/ProjectConfig.yaml for:
    • architecture_pattern — "so-first" (default, this skill is primary) or "di-first" (SOs still hold data, but events become Commands — see uw-dependency-injection).
    • mcp.unity_mcp — if true, call refresh_unity after creating files.
  2. Read docs/TDD_Template.md for data architecture decisions.
  3. Read docs/NAMING_CONVENTIONS.md for SO file naming (PascalCase: SwordData.asset).
  4. Ensure the feature has an .asmdef — create with uw-unity-feature-scaffold if needed.

Three Patterns

1. Data Container

Store game-tunable values in a ScriptableObject. Designers edit them in the Inspector without touching code.

using UnityEngine;

[CreateAssetMenu(fileName = "New WeaponData", menuName = "Game/Data/Weapon Data")]
public class WeaponData : ScriptableObject
{
    [Header("Combat")]
    [SerializeField] private float _damage = 10f;
    [SerializeField] private float _fireRate = 0.5f;
    [SerializeField] private float _range = 15f;

    [Header("Ammo")]
    [SerializeField] private int _maxAmmo = 30;
    [SerializeField] private float _reloadTime = 1.5f;

    [Header("Feel")]
    [SerializeField] private ShakeProfile _hitShakeProfile;
    [SerializeField] private AudioClip _fireSound;
    [SerializeField] private GameObject _muzzleFlashPrefab;

    // Read-only properties — game code reads, designers tune in Inspector
    public float Damage => _damage;
    public float FireRate => _fireRate;
    public float Range => _range;
    public int MaxAmmo => _maxAmmo;
    public float ReloadTime => _reloadTime;
    public ShakeProfile HitShakeProfile => _hitShakeProfile;
    public AudioClip FireSound => _fireSound;
    public GameObject MuzzleFlashPrefab => _muzzleFlashPrefab;
}

2. Event Channel

Decouple systems by broadcasting events through a ScriptableObject asset. Any system can subscribe without knowing who raises the event.

Parameterless event:

using System;
using UnityEngine;

[CreateAssetMenu(fileName = "New GameEvent", menuName = "Game/Events/Game Event")]
public class GameEvent : ScriptableObject
{
    private event Action _onRaise;

    public void Raise() => _onRaise?.Invoke();
    public void Subscribe(Action listener) => _onRaise += listener;
    public void Unsubscribe(Action listener) => _onRaise -= listener;
}

Typed event channels for events that carry data:

using System;
using UnityEngine;

[CreateAssetMenu(fileName = "New IntEvent", menuName = "Game/Events/Int Event")]
public class IntEvent : ScriptableObject
{
    private event Action<int> _onRaise;

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

[CreateAssetMenu(fileName = "New FloatEvent", menuName = "Game/Events/Float Event")]
public class FloatEvent : ScriptableObject
{
    private event Action<float> _onRaise;

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

Usage:

// Publisher (doesn't know who listens)
[SerializeField] private IntEvent _onScoreChanged;
_onScoreChanged.Raise(newScore);

// Subscriber (doesn't know who publishes)
[SerializeField] private IntEvent _onScoreChanged;
private void OnEnable() => _onScoreChanged.Subscribe(UpdateScoreUI);
private void OnDisable() => _onScoreChanged.Unsubscribe(UpdateScoreUI);

3. Runtime Set

Track live objects (active enemies, collectibles, spawned projectiles) without FindObjectsOfType. Objects register themselves on spawn and unregister on destroy.

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "New RuntimeSet", menuName = "Game/Sets/Runtime Set")]
public class RuntimeSet<T> : ScriptableObject
{
    private readonly List<T> _items = new();
    public IReadOnlyList<T> Items => _items;
    public int Count => _items.Count;

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

    public void Remove(T item) => _items.Remove(item);

    private void OnDisable() => _items.Clear(); // Prevent stale refs across play sessions
}

Concrete sets (needed because Unity can't serialize open generics):

[CreateAssetMenu(fileName = "New EnemySet", menuName = "Game/Sets/Enemy Set")]
public class EnemyRuntimeSet : RuntimeSet<EnemyController> { }

Self-registering pattern:

public class EnemyController : MonoBehaviour
{
    [SerializeField] private EnemyRuntimeSet _activeEnemies;

    private void OnEnable() => _activeEnemies.Add(this);
    private void OnDisable() => _activeEnemies.Remove(this);
}

Naming Convention

PatternNamingExample
Data Container{Type}DataWeaponData, EnemyData
Event Channel{Action}EventOnDeathEvent, ScoreChangedEvent
Typed Event{Type}EventIntEvent, FloatEvent, StringEvent
Runtime Set{Type}Set or {Type}RuntimeSetEnemySet, CollectibleRuntimeSet

After Setup

  • Add game feel: Use uw-game-feel-integrator — SO data containers pair well with feel parameters (ShakeProfile, tween durations stored in the SO).
  • Write tests: Use uw-unity-test-runner — test event channels (subscribe, raise, verify callback) and runtime sets (add, remove, clear) in EditMode tests.
  • Custom inspectors: Use uw-unity-editor-tools for designer-friendly SO inspectors (preview panels, validation).
  • Graduate to DI: If the project outgrows SO events, use uw-dependency-injection — SO data stays, events become Commands.

When to Consider DI Instead

If your project needs any of these, check ProjectConfig.yaml -> architecture_pattern and consider switching to di-first with the uw-dependency-injection skill:

  • Cross-feature orchestration beyond simple events (Command pattern)
  • Constructor injection for testability with mock services
  • Scoped lifetimes (per-scene, per-level services)
  • Factory patterns with automatic dependency resolution

DI + SO coexist: In DI-first projects, ScriptableObjects still hold data — they're injected into the container as instances. Events are replaced by Commands.

Rules

  • Always use [CreateAssetMenu] on every ScriptableObject.
  • Always use [SerializeField] private + public read-only properties for data fields.
  • Use Action/Action<T> delegates for events, not UnityEvents (better performance, no boxing).
  • Runtime sets must clear on OnDisable() to prevent stale references across play sessions.
  • Always unsubscribe from events in OnDisable — matching every Subscribe with an Unsubscribe.
  • All SO code must live inside an .asmdef.
  • SO file names use PascalCase (per NAMING_CONVENTIONS.md): SwordData.asset.
  • If ProjectConfig.yaml -> mcp.unity_mcp is true, call refresh_unity after creating files.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.