Provides zero-allocation C# patterns for performance-critical game systems like object pooling and timers.

Install

mkdir -p .claude/skills/game-patterns && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/2214" && unzip -o skill.zip -d .claude/skills/game-patterns && rm skill.zip

Installs to .claude/skills/game-patterns

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Zero-GC game patterns with JEngine using modern C# 9+. Triggers on: game loop, spawn system, wave spawner, cooldown, ability timer, damage over time, DoT, health regen, bullet pool, enemy pool, object pool pattern, projectile system, combat system, zero allocation, no GC, performance optimization
297 charsno explicit “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Implement zero-GC game loops
  • Create pooled object systems
  • Manage asynchronous ability timers
  • Build damage-over-time systems
  • Optimize memory usage with JEngine

How it works

The agent uses JAction and JObjectPool to manage state objects as reference types, preventing garbage collection during high-frequency game events.

Inputs & outputs

You give it
game system requirements
You get back
zero-allocation C# code implementation

When to use game-patterns

  • Creating object pools for bullets or enemies
  • Implementing zero-allocation ability cooldowns
  • Building high-performance game loops

About this skill

Game Patterns with JEngine

Zero-GC patterns using JAction + JObjectPool with modern C# 9+ features.

Principles

  1. Async-First: Always use ExecuteAsync() (non-blocking)
  2. Zero-GC: Use static lambdas + reference type state + object pooling
  3. Modern C#: Target-typed new, init properties, Span<T>, in parameters

State Classes for Zero-GC

JAction state parameters must be reference types to avoid boxing. Pool these state objects for true zero-GC:

// Reusable state for abilities
public sealed class AbilityState
{
    public bool CanUse { get; set; } = true;
    public float Cooldown { get; init; }
}

// Reusable state for DoT effects
public sealed class DoTState
{
    public IDamageable Target { get; set; }
    public float DamagePerTick { get; set; }
    public int TicksRemaining { get; set; }

    public void Reset()
    {
        Target = null;
        DamagePerTick = 0;
        TicksRemaining = 0;
    }
}

// Reusable state for timers
public sealed class TimerState
{
    public float Duration { get; set; }
    public float Elapsed { get; set; }
    public Action OnComplete { get; set; }

    public void Reset()
    {
        Duration = 0;
        Elapsed = 0;
        OnComplete = null;
    }
}

Combat Patterns

Ability with Cooldown (Zero-GC)

public sealed class AbilitySystem
{
    private readonly AbilityState _state = new() { Cooldown = 2f };

    public async UniTaskVoid UseAbility()
    {
        if (!_state.CanUse) return;
        _state.CanUse = false;

        PerformAbility();

        using var action = await JAction.Create()
            .Delay(_state.Cooldown)
            .Do(static s => s.CanUse = true, _state)
            .ExecuteAsync();
    }
}

Damage Over Time (Zero-GC Pooled State)

public static class DoTSystem
{
    public static async UniTaskVoid Apply(
        IDamageable target,
        float damagePerTick,
        int ticks,
        float interval)
    {
        var state = JObjectPool.Shared<DoTState>().Rent();
        state.Target = target;
        state.DamagePerTick = damagePerTick;

        using var action = await JAction.Create()
            .Repeat(
                static s => s.Target?.TakeDamage(s.DamagePerTick),
                state,
                count: ticks,
                interval: interval)
            .ExecuteAsync();

        state.Reset();
        JObjectPool.Shared<DoTState>().Return(state);
    }
}

Combo System (Zero-GC)

public sealed class ComboState
{
    public int Count;
    public JAction ResetAction;
}

public sealed class ComboSystem : IDisposable
{
    private readonly ComboState _state = new();

    public int ComboCount => _state.Count;

    public void OnHit()
    {
        _state.ResetAction?.Cancel();
        _state.ResetAction?.Dispose();
        _state.Count++;

        _state.ResetAction = JAction.Create()
            .Delay(1.5f)
            .Do(static s => s.Count = 0, _state);

        _ = _state.ResetAction.ExecuteAsync();
    }

    /// <summary>
    /// Call from OnDestroy to prevent callbacks on destroyed objects.
    /// </summary>
    public void Dispose()
    {
        _state.ResetAction?.Cancel();
        _state.ResetAction?.Dispose();
        _state.ResetAction = null;
    }
}

Spawning Patterns

Wave Spawner (Async)

public sealed class WaveSpawner
{
    private readonly EnemySpawner _spawner;

    // Use ReadOnlyMemory<T> for async (ReadOnlySpan<T> is a ref struct, invalid in async)
    // Access .Span inside the loop for zero-allocation iteration
    public async UniTask RunWaves(ReadOnlyMemory<WaveConfig> waves, CancellationToken ct = default)
    {
        foreach (var wave in waves.Span)
        {
            using var action = await JAction.Create()
                .Delay(wave.StartDelay)
                .Do(() => SpawnWave(wave.EnemyCount))
                .WaitUntil(() => ActiveCount == 0, timeout: 120f)
                .ExecuteAsync();

            if (action.Cancelled || ct.IsCancellationRequested) break;
        }
    }

    private void SpawnWave(int count)
    {
        for (var i = 0; i < count; i++)
        {
            var pos = GetSpawnPosition(i);
            _spawner.Spawn(in pos);
        }
    }
}

Pooled Spawner (Generic, Zero-GC)

public interface IPoolable
{
    void OnSpawn();
    void OnDespawn();
}

public sealed class PooledSpawner<T> where T : class, IPoolable, new()
{
    private readonly JObjectPool<T> _pool;

    public PooledSpawner(int maxSize = 64)
    {
        _pool = new(
            maxSize,
            onRent: static obj => obj.OnSpawn(),
            onReturn: static obj => obj.OnDespawn());
    }

    public T Spawn() => _pool.Rent();
    public void Despawn(T obj) => _pool.Return(obj);
    public void Prewarm(int count) => _pool.Prewarm(count);
    public int PooledCount => _pool.Count;
}

Resource Patterns

Health Regeneration (Zero-GC)

public sealed class RegenState
{
    public float Current;
    public float Max;
    public float PerTick;
    public JAction Action;

    public void Cleanup()
    {
        Action?.Cancel();
        Action?.Dispose();
        Action = null;
    }
}

public static class RegenSystem
{
    public static void Start(RegenState state, float hpPerSecond)
    {
        state.Cleanup();
        state.PerTick = hpPerSecond * 0.1f;

        state.Action = JAction.Create()
            .RepeatWhile(
                static s => s.Current = MathF.Min(s.Current + s.PerTick, s.Max),
                static s => s.Current < s.Max,
                state,
                frequency: 0.1f);

        _ = state.Action.ExecuteAsync();
    }

    /// <summary>
    /// Stop regeneration. Call Cleanup() from OnDestroy to fully dispose.
    /// </summary>
    public static void Stop(RegenState state) => state.Cleanup();
}

Projectile Patterns

Bullet Manager (Zero-GC)

public sealed class Bullet
{
    public Vector3 Position;
    public Vector3 Velocity;
    public float Damage;
    public bool Active;

    public void Reset()
    {
        Position = default;
        Velocity = default;
        Damage = 0;
        Active = false;
    }
}

public sealed class BulletManager
{
    public static BulletManager Instance { get; private set; }

    private readonly JObjectPool<Bullet> _pool = new(
        maxSize: 500,
        onReturn: static b => b.Reset());

    public void Initialize()
    {
        Instance = this;
        _pool.Prewarm(200);
    }

    public Bullet Fire(in Vector3 pos, in Vector3 dir, float speed, float damage)
    {
        var bullet = _pool.Rent();
        bullet.Position = pos;
        bullet.Velocity = dir * speed;
        bullet.Damage = damage;
        bullet.Active = true;
        return bullet;
    }

    public void Return(Bullet b) => _pool.Return(b);
}

Auto-Return After Lifetime (Zero-GC)

public sealed class BulletLifetimeState
{
    public Bullet Bullet;
    public BulletManager Manager;

    public void Reset()
    {
        Bullet = null;
        Manager = null;
    }
}

public static async UniTaskVoid FireWithLifetime(
    BulletManager manager,
    in Vector3 pos,
    in Vector3 dir,
    float speed,
    float damage,
    float lifetime)
{
    var bullet = manager.Fire(in pos, in dir, speed, damage);

    var state = JObjectPool.Shared<BulletLifetimeState>().Rent();
    state.Bullet = bullet;
    state.Manager = manager;

    using var action = await JAction.Create()
        .Delay(lifetime)
        .Do(static s => s.Manager?.Return(s.Bullet), state)
        .ExecuteAsync();

    state.Reset();
    JObjectPool.Shared<BulletLifetimeState>().Return(state);
}

UI Patterns

Delayed Tooltip (Zero-GC)

public sealed class TooltipState
{
    public JAction Action;
    public Action ShowCallback;
    public Action HideCallback;

    public void Reset()
    {
        Action = null;
        ShowCallback = null;
        HideCallback = null;
    }
}

public sealed class TooltipTrigger : IDisposable
{
    private readonly TooltipState _state = new();

    public void OnPointerEnter(Action show, Action hide)
    {
        _state.Action?.Cancel();
        _state.Action?.Dispose();

        _state.ShowCallback = show;
        _state.HideCallback = hide;

        _state.Action = JAction.Create()
            .Delay(0.5f)
            .Do(static s => s.ShowCallback?.Invoke(), _state);

        _ = _state.Action.ExecuteAsync();
    }

    public void OnPointerExit()
    {
        _state.Action?.Cancel();
        _state.Action?.Dispose();
        _state.Action = null;
        _state.HideCallback?.Invoke();
    }

    /// <summary>
    /// Call from OnDestroy to prevent callbacks on destroyed objects.
    /// </summary>
    public void Dispose()
    {
        _state.Action?.Cancel();
        _state.Action?.Dispose();
        _state.Reset();
    }
}

Typewriter Effect (Zero-GC)

public sealed class TypewriterState
{
    public string FullText;
    public int CurrentIndex;
    public Action<string> OnUpdate;
    public StringBuilder Builder;

    public void Reset()
    {
        FullText = null;
        CurrentIndex = 0;
        OnUpdate = null;
        Builder = null;
    }
}

public static async UniTask TypeText(string content, float charDelay, Action<string> onUpdate)
{
    var state = JObjectPool.Shared<TypewriterState>().Rent();
    var sb = JObjectPool.Shared<StringBuilder>().Rent();

    state.FullText = content;
    state.CurrentIndex = 0;
    state.OnUpdate = onUpdate;
    state.Builder = sb;

    using var action = await JAction.Create()
        .Repeat(
            static s =>
            {
                if (s.CurrentIndex < s.FullText.Length)
                {
                    s.Builder.Append(s.FullText[s.CurrentIndex++]);
                    s.OnUpdate?.Invoke(s.Builder.ToString());
                }
            },
            state,


---

*Content truncated.*

When not to use it

  • Non-game development projects

Prerequisites

JEngine frameworkC# 9+ environment

Limitations

  • Requires strict adherence to reference-type state management

How it compares

It enforces zero-allocation patterns by pooling state objects, whereas standard C# approaches often create frequent short-lived objects.

Compared to similar skills

game-patterns side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
game-patterns (this skill)96moNo flagsAdvanced
dev_unity_unreal06moNo flagsAdvanced
csharp-developer432moNo flagsAdvanced
csharp-pro94moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry