本文へ移動
cccskills
無料GitHub で公開

tools-unity-unitask

UniTask async/await patterns for Unity including cancellation, lifecycle binding, and coroutine interop.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md12.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

UniTask Async/Await for Unity

Overview

UniTask provides efficient async/await support for Unity with zero allocation, proper cancellation, and Unity lifecycle integration.

When to Use

  • Async operations in Unity (loading, networking, delays)
  • Replacing coroutines with async/await
  • Managing cancellation in MonoBehaviours
  • Async initialization patterns
  • Parallel async operations

Installation

// manifest.json
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"

Basic Patterns

Simple Async Method

public async UniTask LoadGameAsync()
{
    await UniTask.Delay(1000); // 1 second delay
    await LoadAssetsAsync();
    await InitializeSystemsAsync();
}

Async with Return Value

public async UniTask<PlayerData> LoadPlayerAsync()
{
    var json = await File.ReadAllTextAsync(path);
    return JsonUtility.FromJson<PlayerData>(json);
}

Fire-and-Forget (Use Carefully!)

// ForgetTask() suppresses warnings but loses error handling
LoadGameAsync().Forget();

// Better: Use UniTaskVoid for true fire-and-forget
public async UniTaskVoid StartBackgroundTask()
{
    try
    {
        await DoWorkAsync();
    }
    catch (Exception ex)
    {
        Debug.LogException(ex);
    }
}

Cancellation Patterns

Basic CancellationToken

public class GameLoader : MonoBehaviour
{
    private CancellationTokenSource _cts;
    
    private void OnEnable()
    {
        _cts = new CancellationTokenSource();
        LoadAsync(_cts.Token).Forget();
    }
    
    private void OnDisable()
    {
        _cts?.Cancel();
        _cts?.Dispose();
        _cts = null;
    }
    
    private async UniTask LoadAsync(CancellationToken ct)
    {
        await UniTask.Delay(1000, cancellationToken: ct);
        // Work here...
    }
}

Destroy CancellationToken (Preferred for MonoBehaviour)

public class Enemy : MonoBehaviour
{
    private async UniTaskVoid Start()
    {
        // Automatically cancelled when GameObject destroyed
        var ct = this.GetCancellationTokenOnDestroy();
        
        while (!ct.IsCancellationRequested)
        {
            await UniTask.Delay(1000, cancellationToken: ct);
            Patrol();
        }
    }
}

Linked Cancellation

public async UniTask DoWorkAsync(CancellationToken externalCt)
{
    // Link external token with destroy token
    var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
        externalCt,
        this.GetCancellationTokenOnDestroy()
    );
    
    try
    {
        await LongRunningTask(linkedCts.Token);
    }
    finally
    {
        linkedCts.Dispose();
    }
}

Timeout

// Timeout with exception
await task.Timeout(TimeSpan.FromSeconds(5));

// Timeout without exception
var (hasValue, result) = await task.TimeoutWithoutException(TimeSpan.FromSeconds(5));
if (!hasValue)
{
    Debug.Log("Operation timed out");
}

// Timeout with CancellationTokenSource
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await task.AttachExternalCancellation(cts.Token);

Unity Lifecycle Integration

Waiting for Frames

// Wait one frame
await UniTask.Yield();

// Wait for end of frame
await UniTask.WaitForEndOfFrame();

// Wait for fixed update
await UniTask.WaitForFixedUpdate();

// Wait multiple frames
await UniTask.DelayFrame(10);

// Wait for specific player loop
await UniTask.Yield(PlayerLoopTiming.PreUpdate);

Player Loop Timing Options

// Available timings:
PlayerLoopTiming.Initialization
PlayerLoopTiming.EarlyUpdate
PlayerLoopTiming.FixedUpdate
PlayerLoopTiming.PreUpdate
PlayerLoopTiming.Update
PlayerLoopTiming.PreLateUpdate
PlayerLoopTiming.PostLateUpdate
PlayerLoopTiming.TimeUpdate

Waiting for Conditions

// Wait until condition is true
await UniTask.WaitUntil(() => player.IsReady);

// Wait while condition is true
await UniTask.WaitWhile(() => isLoading);

// Wait until with cancellation
await UniTask.WaitUntil(
    () => player.IsReady,
    cancellationToken: ct
);

// Wait until with timeout
await UniTask.WaitUntil(() => player.IsReady)
    .Timeout(TimeSpan.FromSeconds(10));

Waiting for Unity Events

// Wait for button click
await button.OnClickAsync();

// Wait for trigger enter
var other = await gameObject.OnTriggerEnterAsync();

// Wait for collision
var collision = await gameObject.OnCollisionEnterAsync();

// Wait for animation event
await animator.WaitForAnimationEvent("AttackHit");

Parallel Operations

WhenAll (Wait for All)

// Wait for all tasks to complete
var results = await UniTask.WhenAll(
    LoadTexturesAsync(),
    LoadAudioAsync(),
    LoadConfigAsync()
);

// With different return types
var (textures, audio, config) = await UniTask.WhenAll(
    LoadTexturesAsync(),
    LoadAudioAsync(),
    LoadConfigAsync()
);

WhenAny (Wait for First)

// Wait for first to complete
var (winIndex, result1, result2) = await UniTask.WhenAny(
    TryServerAAsync(),
    TryServerBAsync()
);

// Use winner
if (winIndex == 0)
{
    UseResult(result1);
}

Throttling Parallel Operations

// Process with limited concurrency
var semaphore = new SemaphoreSlim(3); // Max 3 concurrent

await UniTask.WhenAll(items.Select(async item =>
{
    await semaphore.WaitAsync();
    try
    {
        await ProcessItemAsync(item);
    }
    finally
    {
        semaphore.Release();
    }
}));

Coroutine Interop

Convert Coroutine to UniTask

// Wrap existing coroutine
await MyCoroutine().ToUniTask();

// With cancellation
await MyCoroutine().ToUniTask(cancellationToken: ct);

// From IEnumerator
IEnumerator LegacyCoroutine()
{
    yield return new WaitForSeconds(1);
}

await LegacyCoroutine().ToUniTask();

Convert UniTask to Coroutine

// For APIs that require coroutines
StartCoroutine(MyUniTask().ToCoroutine());

Async Trigger Components

// Add async triggers to GameObjects
var trigger = gameObject.GetAsyncTriggerEnterTrigger();

// Wait for trigger
var other = await trigger.OnTriggerEnterAsync();

Resource Loading

Addressables Integration

public async UniTask<T> LoadAssetAsync<T>(string address, CancellationToken ct)
{
    var handle = Addressables.LoadAssetAsync<T>(address);
    
    try
    {
        return await handle.ToUniTask(cancellationToken: ct);
    }
    catch (OperationCanceledException)
    {
        Addressables.Release(handle);
        throw;
    }
}

Scene Loading

public async UniTask LoadSceneAsync(string sceneName, CancellationToken ct)
{
    await SceneManager.LoadSceneAsync(sceneName)
        .ToUniTask(cancellationToken: ct);
}

// With progress
public async UniTask LoadSceneWithProgressAsync(string sceneName, IProgress<float> progress)
{
    await SceneManager.LoadSceneAsync(sceneName)
        .ToUniTask(progress: progress);
}

Asset Bundle Loading

public async UniTask<AssetBundle> LoadBundleAsync(string url, CancellationToken ct)
{
    var request = UnityWebRequestAssetBundle.GetAssetBundle(url);
    
    await request.SendWebRequest().ToUniTask(cancellationToken: ct);
    
    if (request.result != UnityWebRequest.Result.Success)
    {
        throw new Exception(request.error);
    }
    
    return DownloadHandlerAssetBundle.GetContent(request);
}

Error Handling

Try-Catch Pattern

public async UniTask SafeLoadAsync()
{
    try
    {
        await LoadDataAsync();
    }
    catch (OperationCanceledException)
    {
        // Expected when cancelled - don't log as error
        Debug.Log("Load cancelled");
    }
    catch (Exception ex)
    {
        Debug.LogException(ex);
        ShowErrorUI();
    }
}

SuppressCancellationThrow

// Returns (isCancelled, result) instead of throwing
var (cancelled, result) = await LoadAsync()
    .SuppressCancellationThrow();

if (cancelled)
{
    return; // Graceful exit
}

ProcessResult(result);

Exception Handling in WhenAll

// Collect all exceptions
try
{
    await UniTask.WhenAll(tasks);
}
catch (AggregateException ae)
{
    foreach (var ex in ae.InnerExceptions)
    {
        Debug.LogException(ex);
    }
}

Progress Reporting

IProgress<float>

public async UniTask LoadWithProgressAsync(IProgress<float> progress, CancellationToken ct)
{
    var items = await GetItemsAsync();
    
    for (int i = 0; i < items.Count; i++)
    {
        await ProcessItemAsync(items[i], ct);
        progress?.Report((float)(i + 1) / items.Count);
    }
}

// Usage
var progress = new Progress<float>(p => loadingBar.value = p);
await LoadWithProgressAsync(progress, ct);

Custom Progress

public struct LoadProgress
{
    public string CurrentItem;
    public int Loaded;
    public int Total;
    public float Percent => (float)Loaded / Total;
}

public async UniTask LoadAsync(IProgress<LoadProgress> progress)
{
    var items = await GetItemsAsync();
    
    for (int i = 0; i < items.Count; i++)
    {
        progress?.Report(new LoadProgress
        {
            CurrentItem = items[i].Name,
            Loaded = i + 1,
            Total = items.Count
        });
        
        await ProcessItemAsync(items[i]);
    }
}

UniTask vs Task

When to Use UniTask

// Unity main thread operations
await UniTask.Delay(1000);              // Use UniTask
await UniTask.Yield();                   // Use UniTask
await SceneManager.LoadSceneAsync(s);    // Use UniTask

// Fire and forget in Unity
DoWorkAsync().Forget();                  // UniTask only

When to Use Task

// Pure .NET operations (no Unity API)
await File.ReadAllTextAsync(path);       // Task is fine
await Task.Run(() => HeavyComputation()); // Task for thread pool

// Converting
var result = await task.AsUniTask();     // Task to UniTask
var task = unitask.AsTask();             // UniTask to Task

Common Pitfalls

Pitfall 1: Missing CancellationToken

// BAD: No cancellation, leaks when object destroyed
public async UniTaskVoid Start()
{
    await UniTask.Delay(10000);
    DoSomething(); // May crash if destroyed
}

// GOOD: Use destroy token
public async UniTaskVoid Start()
{
    await UniTask.Delay(10000, cancellationToken: destroyCancellationToken);
    DoSomething();
}

Pitfall 2: Forget Without Error Handling

// BAD: Exceptions silently swallowed
DoWorkAsync().Forget();

// GOOD: Handle errors
async UniTaskVoid DoWorkSafe()
{
    try
    {
        await DoWorkAsync();
    }
    catch (Exception ex) when (!(ex is OperationCanceledException))
    {
        Debug.LogException(ex);
    }
}
DoWorkSafe().Forget();

Pitfall 3: Blocking on Main Thread

// BAD: Blocks main thread
var result = LoadAsync().GetAwaiter().GetResult();

// GOOD: Await properly
var result = await LoadAsync();

Pitfall 4: Unnecessary Allocations

// BAD: Allocates closure
await UniTask.Delay(1000).ContinueWith(_ => DoSomething());

// GOOD: Just await
await UniTask.Delay(1000);
DoSomething();

Best Practices

  1. Always use CancellationToken for MonoBehaviour async methods
  2. Use GetCancellationTokenOnDestroy() for automatic cleanup
  3. Handle OperationCanceledException separately from other exceptions
  4. Prefer UniTask over Task for Unity operations
  5. Use WhenAll for parallel operations
  6. Report progress for long-running operations
  7. Use SuppressCancellationThrow for optional cancellation handling
  8. Avoid Forget() without proper error handling

Performance Tips

  • UniTask is struct-based (zero allocation when awaited directly)
  • Avoid unnecessary .AsTask() conversions
  • Use UniTask.Yield() instead of await UniTask.Delay(0)
  • Pool CancellationTokenSource for high-frequency operations
  • Use UniTaskCompletionSource for custom async patterns

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

adopt

無料

Brownfield onboarding — audits existing project artifacts for template format compliance (not just existence), classifies gaps by impact, and produces a numbered migration plan. Run this when joining an in-progress project or upgrading from an older template version. Distinct from /project-stage-detect (which checks what exists) — this checks whether what exists will actually work with the template's skills.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Validates completeness and consistency of the project architecture against all GDDs. Builds a traceability matrix mapping every GDD technical requirement to ADRs, identifies coverage gaps, detects cross-ADR conflicts, verifies engine compatibility consistency across all decisions, and produces a PASS/CONCERNS/FAIL verdict. The architecture equivalent of /design-review.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

art-bible

無料

Guided, section-by-section Art Bible authoring. Creates the visual identity specification that gates all asset production. Run after /brainstorm is approved and before /map-systems or any GDD authoring begins.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Audits game assets for compliance with naming conventions, file size budgets, format standards, and pipeline requirements. Identifies orphaned assets, missing references, and standard violations.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Generate per-asset visual specifications and AI generation prompts from GDDs, level docs, or character profiles. Produces structured spec files and updates the master asset manifest. Run after art bible and GDD/level design are approved, before production begins.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

IdoCohen560 のスキルをすべて見る

このスキルの問題を報告する