Install
$ agentstack add skill-devdavv-unity-ai-workflow-uw-scriptable-object-arch ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
ScriptableObject Architecture
Generate SO patterns for data, events, and runtime collections.
Before You Start
- Read
docs/ProjectConfig.yamlfor:
architecture_pattern—"so-first"(default, this skill is primary) or"di-first"(SOs still hold data, but events become Commands — seeuw-dependency-injection).mcp.unity_mcp— iftrue, callrefresh_unityafter creating files.
- Read
docs/TDD_Template.mdfor data architecture decisions. - Read
docs/NAMING_CONVENTIONS.mdfor SO file naming (PascalCase:SwordData.asset). - Ensure the feature has an
.asmdef— create withuw-unity-feature-scaffoldif 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 _onRaise;
public void Raise(int value) => _onRaise?.Invoke(value);
public void Subscribe(Action listener) => _onRaise += listener;
public void Unsubscribe(Action listener) => _onRaise -= listener;
}
[CreateAssetMenu(fileName = "New FloatEvent", menuName = "Game/Events/Float Event")]
public class FloatEvent : ScriptableObject
{
private event Action _onRaise;
public void Raise(float value) => _onRaise?.Invoke(value);
public void Subscribe(Action listener) => _onRaise += listener;
public void Unsubscribe(Action 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 : ScriptableObject
{
private readonly List _items = new();
public IReadOnlyList 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 { }
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
| Pattern | Naming | Example | |---------|--------|---------| | Data Container | {Type}Data | WeaponData, EnemyData | | Event Channel | {Action}Event | OnDeathEvent, ScoreChangedEvent | | Typed Event | {Type}Event | IntEvent, FloatEvent, StringEvent | | Runtime Set | {Type}Set or {Type}RuntimeSet | EnemySet, 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-toolsfor 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/Actiondelegates 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 everySubscribewith anUnsubscribe. - All SO code must live inside an
.asmdef. - SO file names use PascalCase (per
NAMING_CONVENTIONS.md):SwordData.asset. - If
ProjectConfig.yaml -> mcp.unity_mcpistrue, callrefresh_unityafter creating files.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: devdavv
- Source: devdavv/unity-ai-workflow
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.