AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Unity Game Loop

skill-nice-wolf-studio-unity-claude-skills-unity-game-loop · by Nice-Wolf-Studio

>

No reviews yet
0 installs
33 views
0.0% view→install

Install

$ agentstack add skill-nice-wolf-studio-unity-claude-skills-unity-game-loop

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-nice-wolf-studio-unity-claude-skills-unity-game-loop)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
6mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Unity Game Loop? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Game Loop & Progression -- Design Translation Patterns

> Prerequisite skills: unity-state-machines (session FSM), unity-game-architecture (events, Service Locator, bootstrap), unity-data-driven (SO configs for difficulty/pacing)

Claude's most common gameplay failure mode is building features in isolation without a unifying loop structure. It will create movement, inventory, combat, and crafting systems that all work independently but never form a cohesive game. The result is a playable sandbox with no progression, no session boundaries, no win/lose conditions, and no hooks for designers to tune pacing or difficulty. These patterns translate design intent ("the player explores, collects, crafts, then survives the night") into code architecture that enforces phase ordering, tracks session state, and exposes tuning surfaces to designers.


PATTERN: Core Loop Scaffolding

DESIGN INTENT: Designer describes the game as "explore, collect, craft, survive night" -- needs a code skeleton that enforces this cycle with extensible phases that run in order.

WRONG (Claude default):

// Systems built independently with no phase concept -- everything runs simultaneously.
// ExplorationManager, InventoryManager, CraftingManager, CombatManager all live in the scene
// and run their Update loops at the same time. There is no concept of "it is now the crafting
// phase" or "exploration has ended." The game is a flat sandbox.
public class GameManager : MonoBehaviour
{
    [SerializeField] private ExplorationManager _exploration;
    [SerializeField] private InventoryManager _inventory;
    [SerializeField] private CombatManager _combat;

    private void Start()
    {
        _exploration.Enable();
        _inventory.Enable();
        _combat.Enable(); // all systems active from frame 1
    }
}

RIGHT:

// GameLoopController manages phase transitions via IGamePhase interface.
// Each phase gets Enter/Tick/Exit. Phases are configured as SO assets.
// Only one phase is active at a time, enforcing the designer's intended cycle.
public interface IGamePhase
{
    /// Name shown in debug UI and logs.
    string PhaseName { get; }

    /// Called when this phase becomes active. Load assets, enable systems.
    Awaitable EnterAsync(CancellationToken ct);

    /// Called every frame while this phase is active.
    void Tick(float deltaTime);

    /// Called when transitioning away. Cleanup, disable systems.
    Awaitable ExitAsync(CancellationToken ct);
}

[CreateAssetMenu(menuName = "Game Loop/Phase Config")]
public class GamePhaseConfig : ScriptableObject
{
    [Tooltip("Display name for this phase")]
    public string phaseName;

    [Tooltip("Assembly-qualified type name of the IGamePhase implementation")]
    public string phaseTypeName;

    [Tooltip("Duration in seconds, 0 = phase decides when to end")]
    public float maxDuration;
}

SCAFFOLD (full implementation):

using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

/// 
/// Drives the core game loop through a sequence of phases.
/// Phases are defined by  assets and cycle in order.
/// 
public class GameLoopController : MonoBehaviour
{
    [SerializeField] private List _phaseConfigs;

    private readonly List _phases = new();
    private int _currentIndex = -1;
    private IGamePhase _currentPhase;
    private bool _transitioning;

    /// Fires when a new phase begins. Arg: phase index.
    public event Action OnPhaseStarted;

    /// Fires when a phase ends. Arg: phase index.
    public event Action OnPhaseEnded;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    static void ResetStatics() { }

    private async void Start()
    {
        foreach (var config in _phaseConfigs)
        {
            var type = Type.GetType(config.phaseTypeName);
            if (type == null)
            {
                Debug.LogError($"Phase type not found: {config.phaseTypeName}");
                continue;
            }
            _phases.Add((IGamePhase)Activator.CreateInstance(type));
        }

        if (_phases.Count > 0)
            await TransitionToPhaseAsync(0);
    }

    private void Update()
    {
        if (_currentPhase != null && !_transitioning)
            _currentPhase.Tick(Time.deltaTime);
    }

    /// Advance to the next phase. Wraps to index 0 after the last phase.
    public async Awaitable AdvancePhaseAsync()
    {
        int next = (_currentIndex + 1) % _phases.Count;
        await TransitionToPhaseAsync(next);
    }

    /// Jump to a specific phase by index.
    public async Awaitable TransitionToPhaseAsync(int index)
    {
        if (_transitioning) return;
        _transitioning = true;

        var ct = destroyCancellationToken;

        if (_currentPhase != null)
        {
            await _currentPhase.ExitAsync(ct);
            OnPhaseEnded?.Invoke(_currentIndex);
        }

        _currentIndex = index;
        _currentPhase = _phases[_currentIndex];

        await _currentPhase.EnterAsync(ct);
        OnPhaseStarted?.Invoke(_currentIndex);

        _transitioning = false;
    }
}

DESIGN HOOK: New phases require only a new IGamePhase class and a GamePhaseConfig SO asset -- no changes to GameLoopController. Designers reorder or add phases by rearranging the SO list in the Inspector.

GOTCHA: Phase transitions must be async (loading assets, fading screens, enabling/disabling systems). Using instant switches causes frame-spike hitches and race conditions when systems check their enabled state mid-transition. Always use Awaitable, never synchronous calls.


PATTERN: Session Lifecycle

DESIGN INTENT: Clear start/end boundaries for a play session -- a roguelike run, a multiplayer match, a puzzle level. Players can restart without a full scene reload.

WRONG (Claude default):

// Gameplay starts in Awake/Start with no session concept.
// "Restarting" means reloading the entire scene, losing any cross-session state.
// Score, timer, and run-specific data are scattered across multiple MonoBehaviours.
public class GameManager : MonoBehaviour
{
    public int score;
    public float timer;

    private void Start()
    {
        score = 0;
        timer = 0;
    }

    public void Restart()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); // nuclear option
    }
}

RIGHT:

// SessionManager owns all session-scoped state. Explicit StartSession/EndSession
// with events. Systems subscribe to session events to init/cleanup. Restart
// without scene reload by ending then starting a new session.
using System;
using System.Threading;
using UnityEngine;

/// 
/// Manages the lifecycle of a single play session (run, match, level attempt).
/// Separates session state from persistent meta progression.
/// 
public class SessionManager : MonoBehaviour
{
    public static SessionManager Instance { get; private set; }

    [SerializeField] private SessionConfig _defaultConfig;

    /// Current session data. Null when no session is active.
    public SessionData CurrentSession { get; private set; }

    /// True while a session is in progress.
    public bool IsSessionActive => CurrentSession != null;

    public event Action OnSessionStarted;
    public event Action OnSessionEnded;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    static void ResetStatics() => Instance = null;

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    /// Begin a new session with the given configuration.
    public void StartSession(SessionConfig config = null)
    {
        config ??= _defaultConfig;
        CurrentSession = new SessionData(config);
        OnSessionStarted?.Invoke(config);
    }

    /// End the current session and publish the result.
    public SessionResult EndSession(bool victory)
    {
        if (CurrentSession == null) return default;

        var result = new SessionResult
        {
            Config = CurrentSession.Config,
            Victory = victory,
            Score = CurrentSession.Score,
            ElapsedTime = CurrentSession.ElapsedTime,
            EndTime = DateTime.UtcNow
        };

        OnSessionEnded?.Invoke(result);
        CurrentSession = null;
        return result;
    }

    /// Restart the session without reloading the scene.
    public void RestartSession()
    {
        var config = CurrentSession?.Config ?? _defaultConfig;
        EndSession(false);
        StartSession(config);
    }
}

SCAFFOLD (full implementation): See references/game-loop-scaffolds.md for SessionConfig, SessionData, SessionResult, and complete integration example.

DESIGN HOOK: SessionConfig SO defines starting conditions (initial lives, time limit, starting inventory). Gameplay systems subscribe to OnSessionStarted / OnSessionEnded to initialize and tear down their per-session state without coupling to the manager.

GOTCHA: Session state (score, timer, run inventory) must be strictly separated from persistent state (meta progression, unlocks, currency). Mixing them causes "restart doesn't reset" bugs where meta values get wiped or session values survive between runs.


PATTERN: Win/Lose Condition Architecture

DESIGN INTENT: Designers iterate rapidly on what constitutes winning or losing per level -- "kill all enemies", "survive 3 minutes", "reach the exit" -- without programmer intervention for each change.

WRONG (Claude default):

// Hardcoded compound condition buried in a manager.
// Adding a new condition means editing this class. Cannot vary per level.
public class LevelManager : MonoBehaviour
{
    private void Update()
    {
        if (enemies.Count == 0 && objectiveCaptured && timer > 30f)
        {
            WinGame(); // hardcoded, untestable, not reusable
        }
        if (playerHealth 
/// A single win condition that can be evaluated at any time.
/// Implement as a ScriptableObject for Inspector assignment.
/// 
public abstract class WinConditionBase : ScriptableObject
{
    /// Human-readable description for the Inspector.
    public abstract string Description { get; }

    /// Initialize this condition for a new session/level.
    public abstract void Initialize();

    /// Returns true when the condition is satisfied.
    public abstract bool IsMet();

    /// Cleanup when the session/level ends.
    public abstract void Teardown();
}

/// 
/// A single lose condition.
/// 
public abstract class LoseConditionBase : ScriptableObject
{
    /// Human-readable description for the Inspector.
    public abstract string Description { get; }

    public abstract void Initialize();
    public abstract bool IsMet();
    public abstract void Teardown();
}

SCAFFOLD (full implementation):

using System;
using System.Collections.Generic;
using UnityEngine;

/// 
/// Evaluates a set of win and lose conditions for the current level.
/// Supports AND (all must be true) and OR (any must be true) composition.
/// 
public class ConditionEvaluator : MonoBehaviour
{
    public enum CompositionMode { AllRequired, AnyRequired }

    [SerializeField] private CompositionMode _winMode = CompositionMode.AllRequired;
    [SerializeField] private List _winConditions;
    [SerializeField] private CompositionMode _loseMode = CompositionMode.AnyRequired;
    [SerializeField] private List _loseConditions;

    private bool _resolved;

    /// Fires when win conditions are satisfied.
    public event Action OnWin;

    /// Fires when lose conditions are satisfied.
    public event Action OnLose;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    static void ResetStatics() { }

    private void OnEnable()
    {
        _resolved = false;
        foreach (var c in _winConditions) c.Initialize();
        foreach (var c in _loseConditions) c.Initialize();
    }

    private void OnDisable()
    {
        foreach (var c in _winConditions) c.Teardown();
        foreach (var c in _loseConditions) c.Teardown();
    }

    /// 
    /// Call this from gameplay events (enemy killed, timer tick, objective captured)
    /// instead of polling every frame.
    /// 
    public void Evaluate()
    {
        if (_resolved) return;

        if (CheckLose())
        {
            _resolved = true;
            OnLose?.Invoke();
            return;
        }

        if (CheckWin())
        {
            _resolved = true;
            OnWin?.Invoke();
        }
    }

    private bool CheckWin()
    {
        if (_winConditions.Count == 0) return false;
        return _winMode == CompositionMode.AllRequired
            ? _winConditions.TrueForAll(c => c.IsMet())
            : _winConditions.Exists(c => c.IsMet());
    }

    private bool CheckLose()
    {
        if (_loseConditions.Count == 0) return false;
        return _loseMode == CompositionMode.AnyRequired
            ? _loseConditions.Exists(c => c.IsMet())
            : _loseConditions.TrueForAll(c => c.IsMet());
    }
}

DESIGN HOOK: Designers drag condition SOs onto the level's ConditionEvaluator in the Inspector. Programmers add new condition types by subclassing WinConditionBase or LoseConditionBase. No evaluator code changes needed.

GOTCHA: Conditions must handle late-join scenarios (multiplayer) and mid-level rule changes. Evaluate on gameplay events (enemy killed, objective captured), not at a single check time. Subscribe gameplay events to call ConditionEvaluator.Evaluate().


PATTERN: Meta Loop Hooks

DESIGN INTENT: Between sessions, persistent progression accumulates -- currency, unlocks, upgrades, account XP. This meta layer gives meaning to individual runs and drives long-term retention.

WRONG (Claude default):

// Meta progression mixed directly into session code.
// XP increments happen inside the combat script. Unlock checks are in the UI.
// Restarting a session wipes currency. Nothing persists.
public class EnemyHealth : MonoBehaviour
{
    public static int totalXP; // static, no reset safety, no persistence

    public void TakeDamage(int dmg)
    {
        hp -= dmg;
        if (hp 
/// A reward granted after a session ends. Implement as ScriptableObject.
/// 
public abstract class MetaRewardBase : ScriptableObject
{
    /// Human-readable description.
    public abstract string Description { get; }

    /// Apply this reward to the meta progression state.
    public abstract void Apply(MetaProgressionState state);
}

/// 
/// Persistent player progression state that survives between sessions.
/// 
[Serializable]
public class MetaProgressionState
{
    public int Currency;
    public int AccountXP;
    public int AccountLevel;
    public List UnlockedItemIds = new();
}

/// 
/// Service that manages meta progression. Registered via Service Locator,
/// not a MonoBehaviour -- survives scene transitions naturally.
/// 
public class MetaProgressionService
{
    private MetaProgressionState _state;

    /// Fires after rewards are applied.
    public event Action OnProgressionUpdated;

    public MetaProgressionState State => _state;

    public MetaProgressionService(MetaProgressionState loadedState = null)
    {
        _state = loadedState ?? new MetaProgressionState();
    }

    /// Process end-of-session rewards.
    public void ProcessSessionResult(SessionResult result, List rewards)
    {
        foreach (var reward in rewards)
        {
            reward.Apply(_state);
        }
        OnProgressionUpdated?.Invoke(_state);
    }

    /// Check whether an item is unlocked.
    public bool IsUnlocked(string itemId) => _state.UnlockedItemIds.Contains(itemId);
}

SCAFFOLD (full implementation): See references/game-loop-scaffolds.md for CurrencyReward, `XPRewa

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.