トゥイーンシーケンス

トゥイーンを 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 = 641 フレームで発火する最大ステップ数。暴走ループへのガードです。1–4096 にクランプ。
int MaxConcurrentSequences = 0サービス全体での、生きているシーケンスの最大数。0 は無制限。0–65536 にクランプ。
Clock DefaultClock = Clock.Gameplay再生ヘッドを駆動するタイムクロック。デフォルトはゲームのポーズと一緒にポーズします。
bool LetStepExceptionsPropagate = falsefalse(デフォルト)のとき、ステップのコールバックが投げた例外は捕捉されてログされます。true のときは伝播します — 開発中に便利です。
bool LogSequenceLifecycle = falsetrue のとき、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() の後に AppendJoinAppendIntervalAppendCallback を呼ぶと InvalidOperationException を投げます。タイムラインを延長したければ、新しいシーケンスを作ってください。

関連ページ

  • トゥイーン — これらのシーケンスが合成する単一値アニメーションサービス
  • Time — クロックのバインドとポーズ挙動
  • スケジューラー — 絶対時刻でのコールバック。シーケンスコールバックの補完