トゥイーンシーケンス
トゥイーンを 1 本のフルーエントなチェーンで、順次・並列のポーズ対応タイムラインへ合成する。
トゥイーン、ディレイ、コールバックを、1 本のポーズ対応タイムラインにチェーンします。
| インターフェース | ITweenSequenceService |
| オフスイッチ | NullTweenSequenceService |
| アセンブリ | CommonGameSystem.Core |
| 起動 | 起動時に自動登録(23 サービスの 1 つ)— セットアップ不要 |
概要
トゥイーンシーケンスサービスは、複数ステップのアニメーションタイムラインを 1 本のフルーエントなチェーンで構築します。トゥイーンを順番に追加し、並列に重ね、ディレイを挿入し、途中でコールバックを発火できます。各ステップの長さは前もって宣言し、トゥイーンサービスに渡す duration と一致させます。シーケンスは開始時に正確なタイミングを計算します — 個々のトゥイーンをポーリングすることはありません。
再生はタイムクロックに従います:ゲームをポーズすると、Clock.Gameplay(デフォルト)にバインドされたシーケンスも一緒にポーズします。パッケージに同梱される可視デモシーン Demo/MotionLab.unity で、シーケンスが動く様子を実際に見られます。
クイックスタート
using System;
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)] // Run after the framework has started.
public class MyFirstSequence : MonoBehaviour
{
private void Start()
{
ITweenSequenceService sequences = ServiceLocator.Resolve<ITweenSequenceService>();
ITweenService tween = ServiceLocator.Resolve<ITweenService>();
float value = 0f;
IDisposable handle = sequences.Create()
.Append(() => tween.To(0f, 1f, 3f, v => value = v), 3f) // step 1: 3 seconds
.AppendInterval(0.5f) // then wait half a second
.AppendCallback(() => Debug.Log("Done!")) // then fire a callback
.Play(); // start playback; keep the handle to cancel
}
}
実際のコードでは、サービスは Awake() で解決してキャッシュし、キャンセルできるよう再生ハンドルをフィールドに保持してください — 下のフル例がそのパターンを示しています。
API リファレンス
ITweenSequence — ビルダー兼ハンドル
| メンバー | 説明 |
|---|---|
ITweenSequence Append(Func<IDisposable> tweenFactory, float durationSeconds) | 前のステップの終了後に開始するトゥイーンを追加します。チェーン用に this を返します。 |
ITweenSequence Join(Func<IDisposable> tweenFactory, float durationSeconds) | 前のステップと並行して走るトゥイーンを追加します。this を返します。 |
ITweenSequence AppendInterval(float seconds) | 純粋なディレイ(トゥイーンなし)を追加します。this を返します。 |
ITweenSequence AppendCallback(Action callback) | タイムラインの現在位置に長さゼロのコールバックを追加します。絶対時刻ではなく、シーケンスを基準に発火します。this を返します。 |
IDisposable Play() | ビルダーを凍結して再生を開始します。ハンドルを返します。キャンセルするにはそのハンドルの Dispose() を呼びます。 |
bool IsPlaying { get; } | Play() から完了またはキャンセルまでの間 true。 |
float Elapsed { get; } | これまでの再生ヘッド時間(クロックベース、完了時は Duration にクランプ)。 |
float Duration { get; } | シーケンス全体の長さ。シーケンスの構築時に計算されます。 |
event Action OnSequenceComplete | 正常完了時にちょうど 1 回発火します。キャンセル時には決して発火しません。 |
ITweenSequenceService — ファクトリ
| メンバー | 説明 |
|---|---|
ITweenSequence Create(SequenceOptions options = default) | 新しい空のシーケンスビルダーを作ります。 |
int ActiveSequenceCount { get; } | 生きているシーケンス(開始済みで、まだ完了もキャンセルもしていない)の数。 |
SequenceOptions — 設定
| メンバー | 説明 |
|---|---|
int MaxStepsPerSequence = 256 | シーケンスあたりの最大ステップ数(Append/Join/AppendInterval/AppendCallback の合計)。1–4096 にクランプ。 |
int MaxStepsPerTick = 64 | 1 フレームで発火する最大ステップ数。暴走ループへのガードです。1–4096 にクランプ。 |
int MaxConcurrentSequences = 0 | サービス全体での、生きているシーケンスの最大数。0 は無制限。0–65536 にクランプ。 |
Clock DefaultClock = Clock.Gameplay | 再生ヘッドを駆動するタイムクロック。デフォルトはゲームのポーズと一緒にポーズします。 |
bool LetStepExceptionsPropagate = false | false(デフォルト)のとき、ステップのコールバックが投げた例外は捕捉されてログされます。true のときは伝播します — 開発中に便利です。 |
bool LogSequenceLifecycle = false | true のとき、build/play/complete/cancel のトレースをログします。これらのログはリリースビルドでも常にコンパイルに含まれます。 |
使用例
スライドインしながらフェードインし、完了を告げる UI パネル:
using System;
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class PanelTransition : MonoBehaviour
{
private ITweenSequenceService _seqService;
private ITweenService _tween;
private CanvasGroup _canvasGroup;
private RectTransform _rect;
private IDisposable _playingHandle;
private void Awake()
{
_seqService = ServiceLocator.Resolve<ITweenSequenceService>();
_tween = ServiceLocator.Resolve<ITweenService>();
_canvasGroup = GetComponent<CanvasGroup>();
_rect = GetComponent<RectTransform>();
}
public void PlayEntrance()
{
_playingHandle?.Dispose(); // cancel the previous run, if any
Vector2 startPos = _rect.anchoredPosition;
_playingHandle = _seqService.Create()
// The Tween service only computes the value; you apply it in onUpdate.
.Append(() => _tween.To(startPos, Vector2.zero, 0.3f, v => _rect.anchoredPosition = v), 0.3f)
.Join(() => _tween.To(_canvasGroup.alpha, 1f, 0.3f, a => _canvasGroup.alpha = a), 0.3f)
.AppendCallback(() => OnEntranceComplete())
.Play();
}
private void OnEntranceComplete() => Debug.Log("Panel in!");
private void OnDestroy() => _playingHandle?.Dispose(); // cleanup
}
無効化する
ServiceLocator.Replace<ITweenSequenceService>(new NullTweenSequenceService());
これですべてのシーケンスがミュートされます。Create() はステップを捨てるビルダーを返し、Play() は空のハンドルを返し、コールバックは決して発火しません。アニメーション不要のテストや、ローエンドマシンでモーションを無効化するのに便利です。置き換えの構築時に 1 回警告がログされるので、差し替えが無音で起きることはありません。プログラミングエラー — null のトゥイーンファクトリ、不正なクロック — は引き続き例外を投げるので、本物のミスは見えるままです。
よくある落とし穴
- duration の不一致はあなたのバグです。 シーケンスは
Append/Joinに渡されたdurationSecondsを信頼します。それはファクトリが作るトゥイーンの実際の長さと一致していなければなりません。3 秒と宣言してトゥイーンが 2.8 秒で走れば、タイミングはずれます。トゥイーンサービスはトゥイーンの残り時間を報告できないため、シーケンス側では検査できません — 同じ duration リテラルを両方の呼び出しにコピーしてください。 AppendCallbackは相対であって絶対ではありません。 シーケンスのタイムライン上の現在位置で発火し、壁時計時刻では発火しません。「ゲーム開始から 5 秒後」には、代わりにスケジューラーサービスを使ってください。- クロックソースは起動時にキャプチャされます。 シーケンスはデフォルトで
Clock.Gameplayにバインドされます。起動後に Time サービスを置き換えても(ServiceLocator.Replace<ITimeService>(…))、シーケンスは構築時の元のタイムソースを使い続けます。 - キャンセルは完了ではありません。 再生ハンドルの
Dispose()はシーケンスをキャンセルし、OnSequenceCompleteは発火しません。このイベントは成功パスのクリーンアップにだけ使ってください。キャンセルは無音です。 Play()の後、ビルダーは凍結されます。Play()の後にAppend、Join、AppendInterval、AppendCallbackを呼ぶとInvalidOperationExceptionを投げます。タイムラインを延長したければ、新しいシーケンスを作ってください。