ピックアップ記事
コラム
設計
- Unity におけるアーキテクチャの予備知識
- Unity - 設計・実装のコツ
- Unity - 設計パターンへの応用
- 『MVP4U』の概念のポイント
- Unityと協調するためのアーキテクチャ『MVP4U』
- Clean Architecture 達人に学ぶソフトウェアの構造と設計 (概要)
- エントリーポイントを探してUnityの森を彷徨う
- IoCとService LocatorとDIの関係
- Unityを取り巻くアーキテクチャ 第1部 「MVxとLayered Circle」
- Unity - R3 を使った Pure C# のModel層プロジェクトの作成
- Unity - View に interface を使って実装を差し替える
- MRTK3 を使ったHoloLens2、Meta Quest3、MagicLeap2のマルチデバイスプロジェクト環境の構築
- ドメイン駆動設計(DDD)がゲーム開発に向かない理由
- REST API がゲーム開発に向かない理由
- UnityにおけるViewとModelの境界線
- UnityにおけるDI依存症への警告
技術
- Unity Unit Test(単体テスト)入門
- Unity - Timeline 解体新書
- Unity - Addressable Assets System
- Unity - Editor 拡張のチートシート
- Unity と C# のバージョン
- C# の新機能
- C# - Path を操作する
- Unity - Unityを外部プロセスとしてWPFでホストする(Unity as a Library)(2)
- Unity - Unityを外部プロセスとしてWPFでホストする(Unity as a Library)
- .NET - WebGLをWPFでホストする(WebView2 で Unity as a Library)
- Unity - UnityをUWPでホストする(Unity as a Library)
- WPF - XAML ほかの覚書
- 投げっぱなしのタスク処理(async void、UniTask.Void、Forget)
その他
Unityゲーム開発における実践的な立体的設計アプローチ
※ChatGPTとの壁打ちの雑メモ。
雑すぎて人に読んでもらうにはわからなそうなので、遂行するため一旦閉じ。
Unity - CancellationTokenの生成・使い方完全ガイド
UniTask(Cysharp製)で非同期処理を行う際に欠かせない CancellationToken の使い方を、生成方法・基本構文・応用・GC削減テクニック・ベストプラクティスまで、完全網羅で解説します。
🗂 目次
- 🗂 目次
- ✅ CancellationToken の生成・取得手段【完全リスト】
- 🔧 基本的な使い方一覧
- 🚀 応用的な使い方一覧
- 🧪 実践
- 🏆 ベストプラクティス
- 🛠 トラブルシューティング:CancellationToken 利用時のよくある問題と対策
- ❌ 1. CancellationTokenSource が使い回されていて、即キャンセルされる
- ❌ 2. CreateLinkedTokenSource() の使いすぎによる GC 発生
- ❌ 3. CancellationToken.None がキャンセルされると誤解している
- ❌ 4. UnsafeRegister() の解除忘れによるリーク
- ❌ 5. OperationCanceledException を補足せずクラッシュ
- ❌ 6. LinkedTokenSource を再利用して例外になる
- ❌ 7. MissingReferenceException: DestroyCancellation token should be called...
- ✅ デバッグ支援Tips
- 参考
✅ CancellationToken の生成・取得手段【完全リスト】
1. 明示的に生成する
1-1. 通常の CancellationTokenSource
var cts = new CancellationTokenSource(); var ct = cts.Token;
1-2. タイムアウト付き CancellationTokenSource
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var ct = cts.Token;
1-3. CancelAfterSlim を使ったキャンセル予約
var cts = new CancellationTokenSource(); cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); var ct = cts.Token;
2. UniTask 独自の生成方法
2-1. TimeoutController による生成
var tc = new TimeoutController(); var ct = tc.Timeout(TimeSpan.FromSeconds(5)); tc.Reset(); // 再利用可能にする
2-2. 外部 CancellationTokenSource を渡して管理
var cts = new CancellationTokenSource(); var tc = new TimeoutController(cts);
3. Unity のライフサイクルと連動した生成
3-1. GameObject 破棄時
var ct = this.destroyCancellationToken; // Unity 2022.2+ var ct = this.GetCancellationTokenOnDestroy(); // 全バージョン対応
4. システムイベント起因の生成
4-1. アプリ終了時
var ct = Application.exitCancellationToken;
5. 複数トークンの合成生成
5-1. CancellationTokenSource.CreateLinkedTokenSource
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct1, ct2, ct3); var ct = linkedCts.Token;
6. 外部から受け取るトークン
6-1. メソッド引数で受け取る
private async UniTask ExecuteAsync(CancellationToken ctParam)
{
await SomeTask(ctParam);
}
7. 特殊な固定トークン
7-1. CancellationToken.None
await UniTask.Delay(5000, cancellationToken: CancellationToken.None);
🔧 基本的な使い方一覧
| 項目 | 説明 |
|---|---|
cts.Cancel() |
任意のタイミングでキャンセルを発行する。 |
cts.Token |
CancellationToken を取得して非同期処理に渡す。 |
ct.ThrowIfCancellationRequested() |
キャンセル済みなら即座に例外を投げる。 |
await UniTask.XYZ(..., cancellationToken: ct) |
非同期タスク中にトークンを渡し、キャンセル可能にする。 |
サンプルコード
cts.Cancel()
var cts = new CancellationTokenSource(); button.onClick.AddListener(() => cts.Cancel());
cts.Token
var cts = new CancellationTokenSource(); CancellationToken ct = cts.Token;
ct.ThrowIfCancellationRequested()
void DoWork(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
Debug.Log("キャンセルされていないので処理継続");
}
await UniTask.Delay(..., cancellationToken: ct)
var cts = new CancellationTokenSource();
try
{
await UniTask.Delay(5000, cancellationToken: cts.Token);
}
catch (OperationCanceledException)
{
Debug.Log("処理がキャンセルされました");
}
🚀 応用的な使い方一覧
| 応用技法 | 説明 |
|---|---|
CreateLinkedTokenSource(...) |
複数トークンをまとめ、どれかがキャンセルされたら全体を中断。 |
TimeoutController.Reset() |
タイムアウトトークンを再利用するために呼び出す。 |
GetCancellationTokenOnDisable() |
GameObject が無効化されたときに自動キャンセル。 |
Application.exitCancellationToken |
アプリ終了時にキャンセルが発生。 |
SceneManager.activeSceneChanged + cts.Cancel() |
シーン変更時に手動キャンセル。 |
EditorApplication.quitting + cts.Cancel() |
Unity Editor 終了時にキャンセル処理。 |
CancellationToken.None |
キャンセル不可な処理。 |
CancellationToken.Register |
キャンセル発生時の処理を登録。 |
| DOTS / TaskScheduler連携 | 外部スレッドやジョブにキャンセルを反映。 |
サンプルコード
CreateLinkedTokenSource(...)
var cts1 = new CancellationTokenSource(); var cts2 = new CancellationTokenSource(); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token); await UniTask.Delay(10000, cancellationToken: linkedCts.Token);
TimeoutController.Reset()
var tc = new TimeoutController();
var ct = tc.Timeout(TimeSpan.FromSeconds(5));
try
{
await UniTask.Delay(10000, cancellationToken: ct);
}
catch (OperationCanceledException)
{
Debug.Log("タイムアウトによりキャンセルされました");
}
tc.Reset();
GetCancellationTokenOnDisable()
await UniTask.Delay(3000, cancellationToken: this.GetCancellationTokenOnDisable());
Application.exitCancellationToken
await UniTask.Delay(5000, cancellationToken: Application.exitCancellationToken);
SceneManager.activeSceneChanged
var cts = new CancellationTokenSource();
SceneManager.activeSceneChanged += (oldScene, newScene) => {
cts.Cancel();
};
await SomeAsyncTask(cts.Token);
EditorApplication.quitting
#if UNITY_EDITOR var cts = new CancellationTokenSource(); EditorApplication.quitting += () => cts.Cancel(); await UniTask.Delay(5000, cancellationToken: cts.Token); #endif
CancellationToken.None
await UniTask.Delay(3000, cancellationToken: CancellationToken.None);
CancellationToken.Register
var cts = new CancellationTokenSource(); cts.Token.Register(() => { // キャンセル発生時の処理 });
DOTS/Task連携(擬似例)
var cts = new CancellationTokenSource();
NativeArray<bool> cancelFlag = new NativeArray<bool>(1, Allocator.Persistent);
JobHandle job = new CancelableJob { cancelFlag = cancelFlag }.Schedule();
UniTask.Void(async () =>
{
await UniTask.Delay(3000, cancellationToken: cts.Token);
cancelFlag[0] = true;
});
🧪 実践
キャンセル処理とタイムアウトの組み合わせ
public async UniTask RunTaskWithFlexibleCancellation(CancellationToken externalToken)
{
var manualCts = new CancellationTokenSource();
var timeoutCts = new CancellationTokenSource();
timeoutCts.CancelAfterSlim(TimeSpan.FromSeconds(10));
var destroyToken = this.GetCancellationTokenOnDestroy();
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
manualCts.Token, timeoutCts.Token, destroyToken, externalToken
);
try
{
await UniTask.Delay(30000, cancellationToken: linkedCts.Token);
}
catch (OperationCanceledException ex)
{
if (timeoutCts.IsCancellationRequested)
Debug.Log("タイムアウトでキャンセル");
else if (destroyToken.IsCancellationRequested)
Debug.Log("破棄でキャンセル");
else if (externalToken.IsCancellationRequested)
Debug.Log("親トークンでキャンセル");
else
Debug.Log("手動キャンセル");
}
}
♻️ 再利用可能な設計で GC 削減(Stackベース)
static class CtsPool
{
private static readonly Stack<CancellationTokenSource> pool = new();
public static CancellationTokenSource Rent() =>
pool.Count > 0 ? pool.Pop() : new CancellationTokenSource();
public static void Return(CancellationTokenSource cts)
{
if (cts.IsCancellationRequested)
cts.Dispose();
else
{
cts.Reset();
pool.Push(cts);
}
}
}
🏆 ベストプラクティス
ObjectPool と UnsafeRegister によるゼロアロケーション設計
この設計は、UniTask の作者でもある Cysharp の開発者が解説している公式記事:
👉 CancellationTokenとゼロアロケーション(neue.cc, 2022年7月13日)
の解説です。
🎯 このコードの目的
CancellationTokenSourceをObjectPoolで再利用し GCを抑制UnsafeRegisterにより 複数トークンを軽量に統合- 発生源に応じた 適切な例外の再スロー
- 明示的な Dispose管理とプール返却
📄 実装コード(整形済み)
class Client : IDisposable
{
readonly TimeSpan timeout;
readonly ObjectPool<CancellationTokenSource> timeoutTokenSourcePool;
readonly CancellationTokenSource clientLifetimeTokenSource;
public TimeSpan Timeout { get; }
public Client(TimeSpan timeout)
{
this.Timeout = timeout;
this.timeoutTokenSourcePool = ObjectPool.Create<CancellationTokenSource>();
this.clientLifetimeTokenSource = new CancellationTokenSource();
}
public async Task SendAsync(CancellationToken cancellationToken = default)
{
var timeoutTokenSource = timeoutTokenSourcePool.Get();
CancellationTokenRegistration externalCancellation = default;
if (cancellationToken.CanBeCanceled)
{
externalCancellation = cancellationToken.UnsafeRegister(static state =>
{
((CancellationTokenSource)state!).Cancel();
}, timeoutTokenSource);
}
var clientLifetimeCancellation = clientLifetimeTokenSource.Token.UnsafeRegister(static state =>
{
((CancellationTokenSource)state!).Cancel();
}, timeoutTokenSource);
timeoutTokenSource.CancelAfter(Timeout);
try
{
await SendCoreAsync(timeoutTokenSource.Token);
}
catch (OperationCanceledException ex) when (ex.CancellationToken == timeoutTokenSource.Token)
{
if (cancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException(ex.Message, ex, cancellationToken);
}
else if (clientLifetimeTokenSource.IsCancellationRequested)
{
throw new OperationCanceledException("Client is disposed.", ex, clientLifetimeTokenSource.Token);
}
else
{
throw new TimeoutException($"The request was canceled due to the configured Timeout of {Timeout.TotalSeconds} seconds elapsing.", ex);
}
}
finally
{
externalCancellation.Dispose();
clientLifetimeCancellation.Dispose();
if (timeoutTokenSource.TryReset())
{
timeoutTokenSourcePool.Return(timeoutTokenSource);
}
}
}
async Task SendCoreAsync(CancellationToken cancellationToken)
{
// 通信処理など
}
public void Dispose()
{
clientLifetimeTokenSource.Cancel();
clientLifetimeTokenSource.Dispose();
}
}
🔍 処理解説(要点まとめ)
| 処理 | 解説 |
|---|---|
ObjectPool.Get() / Return() |
再利用によってGC削減 |
UnsafeRegister(...) |
複数トークンのキャンセルを1つに束ねる高速API |
CancelAfter(...) |
タイムアウト機能 |
TryReset() |
再利用可否チェック(.NET 6+) |
Dispose() の明示管理 |
メモリリーク防止・登録解除保証 |
✅ メリット
⚠️ 注意点
TryReset()は .NET 6 以上限定UnsafeRegisterを使う場合は 登録解除(Dispose)を必ず行う- Unityで使用する場合は、**
ObjectPoolを拡張導入(NuGetなど)**が必要 - UniTask 環境なら CancelAfterSlim へ差し替えると軽量化
🛠 トラブルシューティング:CancellationToken 利用時のよくある問題と対策
❌ 1. CancellationTokenSource が使い回されていて、即キャンセルされる
原因:
cts.Cancel() 呼び出し後に CancellationTokenSource をそのまま再利用している。
解決策:
if (cts.IsCancellationRequested)
{
cts.Dispose();
cts = new CancellationTokenSource();
}
❌ 2. CreateLinkedTokenSource() の使いすぎによる GC 発生
原因:
頻繁に CreateLinkedTokenSource() を呼び出すことで GC アロケーションが蓄積される。
解決策:
UnsafeRegister()による手動連携で GC を抑制。- 再利用可能なキャンセル設計に切り替える。
❌ 3. CancellationToken.None がキャンセルされると誤解している
原因:
CancellationToken.None は「絶対にキャンセルされない固定トークン」であり、キャンセル機能は無い。
解決策:
中断可能な処理には必ず CancellationTokenSource.Token を使用する。
❌ 4. UnsafeRegister() の解除忘れによるリーク
原因:
UnsafeRegister() で登録したキャンセルリスナーを .Dispose() せずに放置。
解決策:
var reg = ct.UnsafeRegister(...);
try
{
await SomeAsyncTask(ct);
}
finally
{
reg.Dispose();
}
❌ 5. OperationCanceledException を補足せずクラッシュ
原因:
非同期処理中にキャンセルされたとき、例外補足をしておらず unhandled exception に。
解決策:
try
{
await SomeAsyncTask(ct);
}
catch (OperationCanceledException)
{
Debug.Log("キャンセルされました");
}
❌ 6. LinkedTokenSource を再利用して例外になる
原因:
CreateLinkedTokenSource() で作成したトークンは1回限りの使い切り用。再利用は未サポート。
解決策:
- 処理ごとに新規で生成し、終わったら
Dispose()。 - 頻繁な再利用が必要な場合は
UnsafeRegister方式へ移行。
❌ 7. MissingReferenceException: DestroyCancellation token should be called...
エラーメッセージ:
UnityEngine.MissingReferenceException: DestroyCancellation token should be called atleast once before destroying the monobehaviour object
原因:
非同期処理の中で、すでに破棄された MonoBehaviour の destroyCancellationToken を参照した。
再現コード(NGパターン):
await UniTask.Delay(1000); // この時点で this が破棄済み var ct = this.destroyCancellationToken; // → MissingReferenceException
解決策(OKパターン):
var ct = this.destroyCancellationToken; // 事前にキャッシュ await UniTask.Delay(1000, cancellationToken: ct);
補足と参考リンク:
destroyCancellationTokenは アクセス前に初期化が必要。- Unity 2022.2 以降で導入された仕様のため、古い環境では
GetCancellationTokenOnDestroy()を使うと安全。 - 内部実装: 👉 MonoBehaviour.destroyCancellationToken の実装(Unity公式)
✅ デバッグ支援Tips
| テクニック | 効果 |
|---|---|
ct.IsCancellationRequested をログ出力 |
キャンセル発生のタイミング調査に有効 |
Debug.LogException(e) |
発生した例外のスタックトレースと原因を把握 |
.TryReset() で CTS 再利用可否を判定 |
GC抑制しつつ安全に再利用可能 |
このセクションは、UniTask + CancellationToken のトラブルを未然に防ぎ、再発を抑えるための実践的なチェックリストです。 特に「非同期処理+Unityのライフサイクル」はバグの温床となるため、上記の設計・パターンで安定性を確保することが重要です。
参考
- 【C#】async/awaitのキャンセル処理まとめ #Unity - Qiita
- 【Unity】コルーチンからUniTaskに乗り換えるときに気をつけるべきところ #Unity - Qiita
- UniTask 処理のタイムアウトの書き方 まとめ #C# - Qiita
- neue cc - C#のasync/await再考, タイムアウト処理のベストプラクティス, UniTask v2.2.0
- neue cc - async/awaitのキャンセル処理やタイムアウトを効率的に扱うためのパターン&プラクティス
- Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
Unity - 横断的な例外処理
UniRxやUniTaskなどを使った場合の横断的な例外処理についてのメモ書き。
基本的にUniRxでのExceptionは、ストリームが壊れないように堅牢な方向に機能が実装されている。ストリームの内から外へ例外を搬出するのは楽ではない。そういう閉じた世界(例えば画面単位など)で使うのがベターそう。
SubjectのSubscribeのonErrorコールバック引数
- ストリームで起きたエラーが入ってくる
- Subscribeで起きたエラーは入らない
.Subscribe(
_ => throw new Exception(),
e => Debug.Log("ここには来ない"));
前段にDoをかます
- 後段のSubscribeのonErrorに流れる
.Do(_ => throw new Exception()) .Subscribe( _ => {}, e => Debug.Log("ここには来る"));
- ただし、
Do(async _ => xxx)と非同期にしてると流れない - 間にObserveOnMainThreadしてもダメ
.Do(async _ => throw new Exception()) .Subscribe( _ => {}, e => Debug.Log("ここには来ない"));
ストリームからのthrow
throwしてもストリームの外には伝搬しない- 外で
UniTaskCompletionSourceで状態待機してる場合は、completed.TrySetException(e)で外のタスクに例外を起こさせることができる
.Do(_ => throw new Exception()) .Subscribe( _ => {}, e => _completed.TrySetException(e));
ReactivePropertyのストリーム
- Subjectと同じ
MessageBrokerでPub/Sub
- 使い方と使うとこ絞れば便利そう
// Pub MessageBroker.Default.Publish(new AbortApplicationException()); // Sub MessageBroker.Default.Receive<AbortApplicationException>() .Subscribe(_ => { Debug.LogError("MessageBroker.AbortApplicationException"); GoToTitle(); }) .AddTo(this);
Lopp + while のステートマシン
- 例外が自然に伝搬するのでこれが楽そう
private async UniTask MainLoop() { while(current != State.End) { switch(current) { // 各ステートの処理 case State.Start: await Hoge(); current = State.Next; break; } await UniTask.Yield(); } }
AsyncReactiveProperty
WaitAsyncだとタイミングで取りこぼして停止してしまう
private async UniTask MainLoop() { while(current.Value != State.End) { // 取りこぼして停止する可能性がある var next = await current.WaitAsync(); switch(next) { // 各ステートの処理 case State.Start: await Hoge(); current = State.Next; break; } } }
- UniTask.Linqで
Queueを挟んでメソッドチェーンであれば、例外の伝搬もされる
private async UniTask MainLoop() { await current .WithoutCurrent() // 初期値無視 .Queue() // 取りこぼし防止 .ForEachAwaitAsync(async x => { switch(x) { // 各ステートの処理 throw new Exception(); } }); }
UnhandledExceptionっぽいやつ
- 動かなかったり扱いが難しそうだったりで使えないかも
AppDomain.CurrentDomain.UnhandledException += (_, args) => Debug.LogError("AppDomain.CurrentDomain.UnhandledException " + args.ExceptionObject); UniTaskScheduler.UnobservedTaskException += exception => Debug.LogError("UniTaskScheduler.UnobservedTaskException " + exception); Application.logMessageReceivedThreaded += (condition, trace, type) => Debug.Log($"Application.logMessageReceivedThreaded {type}: {condition} ({trace})");
参考
- neuecc/UniRx: Reactive Extensions for Unity
- 【Unity】【UniRx】UniRxで例外を取り扱う方法まとめ - LIGHT11
- UniRxアンチパターン集 #Unity - Qiita
- UniRxのディープなつまずきの紹介 - Cluster Tech Blog
- UniRxのMessageBrokerが便利という話 #Unity - Qiita
- 【C#】ZeroMessenger – .NET/Unity向けの軽量メッセージングライブラリ - Annulus Games
- Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
- 【UniTask】UniTaskCompletionSourceを使って好きなタイミングで結果を確定させるUniTaskを生成する(ついでにUniTask.Voidの紹介) - はなちるのマイノート
- 【UniRx, UniTask】ReactivePropertyをawaitしたらどうなるか(awaitした後ReactivePropertyの値が変わるまで) - はなちるのマイノート
- UniTask Ver2 、UniTaskAsyncEnumerableまとめ #C# - Qiita
- UniTaskがパワーアップ!『UniTask v2』を使おう! - HackMD
Unity - UnityWebRequestをToUniTaskしてキャンセルするとAbortが呼ばれるようになってる
メモ書き
2018年の記事。Abortが呼ばれなかった様子。
[UniRx.Async] UnityWebRequestAsyncOperationConfiguredAwaiter周辺で困った話 #UniTask - Qiita
その話のIssue。
UnityWebRequest cancellation is not call UnityWebRequest.Abort · Issue #361 · neuecc/UniRx
Cycharpに移管されてAbortが呼ばれるよう更新されてる。
ことの発端はサーバーで"context canceled"が発生する話。
API呼び出しててキャンセルすることはあまり意識しないけど、恐らくCancellationTokenが発火して起きてるんじゃなかろうか。(未検証)
Rider - Tips
目次
Tips
どこでも検索 (Search Everywhere)
ショートカットキーは、 Shift 2回 もしくは Ctrl + ,。
使い方は以下を参考に。
- どこでも検索 | JetBrains Rider ドキュメント
- 【Rider】Shiftを2回押すことで"どこでも検索(Search Everywhere)"というあらゆるものを検索できる検索ボックスを開く - はなちるのマイノート
スコープとファイルカラー
検索ダイアログ一般
Ctrl + Shift + F (複数ファイル内検索)や Shift 2回 (どこでも検索)で表示される検索ダイアログについて。
主な検索範囲は以下のような感じ。

- ソリューション
- ソリューションに含まれるファイル。「非ソリューション項目を含める」にチェックを入れるとLibraryやobjなども含まれる。
- プロジェクト
- 特定のプロジェクト配下。
- ディレクトリ
- 特定のディレクトリ配下。
- スコープ
- すべての場所
- 上記ソリューションの「非ソリューション項目を含める」にチェックしたときと同じ。
- ソリューション
- 上記ソリューションの「非ソリューション項目を含める」にチェックしなかったときと同じ。
- プロジェクトとライブラリ
- Libraryやobjの配下が含まれる。すべての場所と同じかも?
- 最近表示したファイル
- 最近変更したファイル
- 開いているファイル
- その他
- 独自に定義したスコープ内。
- すべての場所
注意が必要なのは、ヘッダーバーに「◯◯以上」一致と出力されている場合、検索結果がすべて表示しきれていない。さらに、この画面では表示される結果や並び順も変動する。 「◯◯以上」と言われず件数が断言されている場合はすべて表示されているものと思われる。
「◯◯以上」一致の場合は、ダイアログ右下の「検索ウィンドウで開く」をクリックすると、検索ウィンドウに完全な検索結果が表示される。
![]()
検索対象に含まれるのはインデックスが作成されているものとなる。 「インデックスなし」となっている場合、いくら検索しても結果に含まれないので注意すること。

インデックスの作成や停止のコマンドは、エクスプローラーでプロジェクトやフォルダ、ファイルを右クリックするとツール内に表示される。

このコマンドは注意が必要で、プロジェクトやフォルダ、ファイルごとに設定を保持しているせいか、コマンドを実行しても上手く切り替わらないことが多い。
「1回目のコマンドでフォルダ配下の各要素が持ってるフラグを消して、2回目で作成を開始する」みたいな?挙動をしている。停止する時は3回位実行しないと反映されない。
個別に設定してると理由(わけ)がわからなくなって混乱するのでかなり危険である。 プロジェクトを指定した設定はいじらず、ルートフォルダに対してだけ実行するのが無難な気がする。
スコープ(Scope)
外部アセットや、サブプロジェクトをUPMで読み込んでいたりすると、検索する際にターゲットを絞れず余計な結果がノイズにのることがある。そんな時に便利なのがスコープの定義。
検索ダイアログの一番右にあるタブが「スコープ」。

「すべての場所」のプルダウンを開くと定義したスコープが選べる。デフォルトでは未定義なので選択するものがない。 隣りにある「…」もしくは設定から、外観&振る舞い > スコープ でスコープの設定画面を開ける。

「+」ボタンから定義を追加する。

インデックスを追加していなければデフォルトだとUnityプロジェクトのフォルダ配下が表示されている。

SamplePackageのRuntimeフォルダのインデックスを追加すると次のような表示になる。

スコープに含めるディレクトリを選択して、「再帰的に含める」を押せば「パターン」に設定される。 ここでも注意が必要で、インデックスを追加した場合のトップのディレクトリはパターンで表現できない。同じ階層に同じディレクトリ名(例えばAssets)がいると出し別けれない。
以下では、Runtime を選択して「再帰的に含める」を押したのに、パターンが file:*/ になって SentryTest2 まで含まれている。

登録しておくと便利そうなスコープは以下の通り。

- Current
- 現在Unityプロジェクト
file:Assets//*
- SubProject
- インデックスで追加したサブプロジェクト(ディレクトリ)のみを含む
- AllProjects
- 上記のCurrentとSubProjectをすべて含む
これで検索する時にスコープのプルダウンで選べるようになる。

ファイルカラー
設定で、外観&振る舞い > ファイルカラー からスコープに色を設定できる。

追加ボタンを押すと、次のようなスコープに対して設定できる。色が選べるように見えるが、どれを選択してもパレットが出てきてゼロから色を選択することになるのでバグってそう。

「エディタータブで使用する」をチェックすると開いているタブに対して、「プロジェクトビューで使用する」をチェックするとエクスプローラーや検索ダイアログなどに対して、色が表示されるようになる。 検索ダイアログだけ設定することができない、検索結果ウィンドウには反映されない、など、かゆい所がかゆい使い勝手かもしれない。。

使い所
検索ダイアログの表示に関しては以下の設定ができる。デフォルトだと、最近使用したファイルと機械学習によって出力が変化する。

また検索ダイアログでは、結果の一覧にディレクトリやプロジェクトなどが表示されないし、フィルタもできないので結構不便である。
そこで「プログラムを調査する」など、検索を長時間使い続ける場面では、いちいち設定を弄る必要はあるもののスコープと色付けが有効である。 以下のようにスコープ毎に色が分かれるので、ぱっとみてどこのスコープのファイルか一目瞭然になる。

参考
Unity - Sentry を利用する
目次
検証環境
- Unity 6000.0.34f1
- Sentry Unity 3.0.2
導入
公式ドキュメント。
SentryのWebページから、Settings > Projects から特定のProjectを選択 > Client Keys(DSN) のページを開いて、DSNをコピー
パッケージマネージャーからGit URLでパッケージを追加する
https://github.com/getsentry/unity.git#3.0.2メニュー > Tools > Sentry で構成画面を開く
DSNを設定する
後はエラーなどを発生させるとレポートが送信される。 Webページに反映されるには、数秒程度のラグがある。
基本
Unity SDKは .NET SDK をベースに構築されている。
.NET SDK では、SentrySdk.Init を使ってコードベースで初期化している。
Unityでは、構成画面を開くと Resources/Sentry/SentryOptions.asset (ScriptableObject)が生成され、それを元に自動で初期化される。
構成画面
スクロールバーとか表示されないので、見切れてる項目には注意が必要。
Core

- 基本的には、DSNさえ設定すれば動く
Enrichment

- Stacktrace For Logs にチェックを入れると、スタックトレースを送信してくれる
- スタックトレースが長すぎると、too longという例外を吐くので注意
- Attach Screenshot にチェックを入れると、送信する際にスクショを添付してくれる
- 契約プラン毎に月の容量があるので注意
Transport

Advanced

Options Config

- Option Config Script を設定すると送信前にタグを追加するなどの処理を仕込める
public class SentryOptionConfiguration : SentryOptionsConfiguration { public override void Configure(SentryUnityOptions options) { // Here you can programmatically modify the Sentry option properties used for the SDK's initialization options.SetBeforeSend(@event => { @event.SetTag("lib", "SentryTest"); return @event; }); } }
Debug Symbols

プログラム
初期化
.NET には初期化のために SentrySdk.Init が用意されている。
Unityは基本構成画面から生成されるScriptableObjectを使うことになるが、SentryUnity.Initメソッドも一応用意されている。
SentryUnity.Init(options => { options.Dsn = HoloearthUnityDsn; });
ただし、ドキュメントには明記されておらず、サンプルコードの一部にSentrySdkとSentryUnityが混在しているので混乱する。(改善要望提案中)
動きも若干怪しい所があるため、使う際は注意が必要である。
// こちらは機能する options.AttachStacktrace = true; // こちらは機能しない options.AttachScreenshot = true;
Screenshotに関しては、自分でEventProcessorを追加することでスクショが送信されるようになることは確認できた。
options.AttachScreenshot = true; options.ScreenshotQuality = ScreenshotQuality.Low; options.AddEventProcessor(new ScreenshotEventProcessor(options));
スコープ
Scopeと呼ばれる設定の箱が用意されており、定義したり破棄したりできるみたい。
スコープには、イベントとともに送信される有用な情報が保持されます。たとえば、コンテキストやパンくずリストはスコープに保存されます。スコープがプッシュされると、親スコープからすべてのデータが継承され、ポップされるとすべての変更が元に戻されます。
SentrySdk.ConfigureScope(scope => { scope.User.Id = "12345"; scope.User.Username = "yotiky"; scope.User.Email = "example@example.com"; scope.Environment = "dev"; scope.SetTag("GroupId", "67890"); scope.SetTag("GroupName", "hoge"); });
ドキュメントには、ログアウト時にユーザーの設定を解除するときのサンプルとして以下が掲載されているが、個別にすべてを初期化しないとダメなのか、読み取れず。しなきゃいけない気もする。
SentrySdk.ConfigureScope(scope => { scope.User = new SentryUser(); });
その他、色々カスタマイズするできるっぽいので以下参照。とても読みやすいとは言えないけれど。。
パッケージの分離
コアライブラリにSentryを設定して、アプリケーション側からはUPM参照して使う場合、以下のことに注意しておく必要がある。
- PackageにScriptableObjectがあったとしても、アプリケーション側で画面を開くと新たに設定ファイルが生成される
SentryUnity.Initの動きが怪しい
Web画面
Issues
同様の特性を持つイベントごとにグループ化されて表示される。
「Custom Search」はSaveすることができる。一番良く使うものをデフォルトに設定しておくと良い。

リアルタイムに更新したい場合、右上にボタンがあるので押しておく。

Discover
Discoverにエラーのレポートが表示される。

Consoleに出力されたErrorがすべて送信されているわけではなさそう。
UnityEngine.Debug:LogErrorの出力を転送しているっぽい。UnityEngine自体が自身で出力しているエラーは送信されていなかった。
エラーの詳細画面を開くと、スタックトレースやパンくず、タグやコンテキスト情報などが参照できる。 パンくずについては、以下引用から。
Sentry は、パンくずリストを使用して、問題が発生する前に発生したイベントの軌跡を作成します。
Tag

Attach Screenshot を有効にするとタグの右側にスクショが表示される。
独自のタグを設定するには、SentryEvent.SetTagやScope.SetTagなどで設定する。
また、Scope.Environmentに設定したenvironmentは、Tagsの中に表示される。

Environmentは、デフォルトでも設定される。その場合、Unity Editorで実行している場合はeditor、それ以外の場合はproductionが設定される。
User
Contextsには様々な情報が表示される。次の画像はUserのブロック。 IP AddressやGeographyが出力されることがあるが、出力条件がはっきりしない。

Scope.UserからUserの情報を設定できる。new SentryUser()するとすべて上書きされるため、Userのプロパティを直接編集するとIP Addressとかは残りそう。
と思ったが、出たり出なかったりしたのでよく分からなくなった。

scope.User = new SentryUser() { Id = "12345", Username = "yotiky", Email = "example@example.com", IpAddress = "127.0.0.1", Other = new Dictionary<string, string>() { { "hoge", "1" }, { "fuga", "2" }, } };
一覧のUSER.DISPLAYには、Email、Username、Idの順に設定されてるものから表示される。何も設定されていなければ自動生成された値がIdに設定されてそれが表示される。

IP Addressは、Web側の設定で収集しないようにもできるらしい。
LitMotion Animation を拡張する
注意点として、Awake で実行すると予期せぬ動きになるのでStart以降で実行するのが良さそう。
完了
モーションを完了させる。未再生の場合は再生してから完了させる。 モーションは再生中のままになる。
public static void Complete(this LitMotionAnimation animation) { if (!animation.IsPlaying) animation.Play(); foreach (var component in animation.Components) { var handle = component.TrackedHandle; handle.TryComplete(); component.TrackedHandle = handle; } }
キャンセル
再生中のモーションをキャンセルする。 備え付けのStopだとモーションの状態が破棄されてオブジェクトの元の状態に戻る。 Cancelはキャンセルした時の状態を残す。
public static void Cancel(this LitMotionAnimation animation) { foreach (var component in animation.Components) { var handle = component.TrackedHandle; handle.TryCancel(); component.TrackedHandle = handle; } }
再生速度を指定して再生
再生速度を指定して再生する。引数は倍率。1fで等倍、0で一時停止。
public static void Play(this LitMotionAnimation animation, float playbackSpeed) { if (!animation.IsPlaying) animation.Play(); foreach (var component in animation.Components) { var handle = component.TrackedHandle; handle.PlaybackSpeed = playbackSpeed; component.TrackedHandle = handle; } }
モーションの最初の状態にする
モーションの最初の状態に遷移させる。再生はキャンセルするので未再生状態になる。
public static void Rewind(this LitMotionAnimation animation) { if (!animation.IsPlaying) animation.Play(); foreach (var component in animation.Components) { var handle = component.TrackedHandle; handle.Time = 0; component.TrackedHandle = handle; } animation.Cancel(); }
こっちは上手く動かないバージョン。連続で呼ぶと機能しないっぽい。
public static void RewindBug(this LitMotionAnimation animation) { animation.Restart(); animation.Cancel(); }
モーションの最初の位置を取得する(不可)
settings.StartValueはアクセスできないので位置を取得することは不可能だった。
public static void StartPosition(this LitMotionAnimation animation) { var startPos = (TransformPositionAnimation)animation.Components.FirstOrDefault(x => x is TransformPositionAnimation); if (startPos == null) return; // アクセス不可 //Debug.Log(startPos.settings.StartValue); }