본문으로 건너뛰기
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

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

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의 스킬 모두 보기

이 스킬의 문제 신고