补间序列

用一条流式调用链把补间组合成顺序与并行、可随游戏暂停的时间线。

把补间、延迟和回调串成一条会随游戏暂停的时间线。

接口ITweenSequenceService
关闭开关NullTweenSequenceService
程序集CommonGameSystem.Core
启动启动时自动注册(23 个服务之一)——无需任何设置

它做什么

补间序列服务用一条流式调用链构建多步动画时间线。你可以把补间一个接一个地追加、让它们并行重叠、插入延迟,并在途中触发回调。每一步的时长都预先声明,与你传给补间服务的时长一致。序列在启动时就算出精确的时间安排——它从不轮询各个补间。

播放遵循时间时钟:暂停游戏,绑定到 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正常完成时恰好触发一次。取消时绝不触发。

ITweenSequenceService — 工厂

成员说明
ITweenSequence Create(SequenceOptions options = default)创建一个新的空序列构建器。
int ActiveSequenceCount { get; }存活序列的数量(已启动、尚未完成或取消)。

SequenceOptions — 设置

成员说明
int MaxStepsPerSequence = 256每个序列的最大步数(Append/Join/AppendInterval/AppendCallback 合计)。钳制到 1–4096。
int MaxStepsPerTick = 64单帧内触发的最大步数;防止失控循环。钳制到 1–4096。
int MaxConcurrentSequences = 0整个服务范围内的最大存活序列数;0 表示不限。钳制到 0–65536。
Clock DefaultClock = Clock.Gameplay驱动播放头的时间时钟。默认值会在游戏暂停时一起暂停。
bool LetStepExceptionsPropagate = falsefalse(默认)时,步回调抛出的异常被捕获并记录。为 true 时异常向外传播——开发期间很有用。
bool LogSequenceLifecycle = falsetrue 时,记录构建/播放/完成/取消的踪迹。这些日志总是被编译进来,即使在发布构建中。

示例

一个滑入的同时淡入、然后宣布自己就位的 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() 返回空句柄,回调永不触发。适合不需要动画的测试,或在低端机器上关闭动效。替换实现构造时会记录一条警告,因此替换绝不会悄无声息。编程错误——null 的补间工厂、无效的时钟——仍会抛异常,真正的错误依然可见。

常见陷阱

  • 时长不匹配是你的 bug。 序列信任你传给 Append/JoindurationSeconds。它必须与你的工厂创建的补间的实际时长一致。声明 3 秒而补间实际运行 2.8 秒,时间安排就会漂移。补间服务无法报告某个补间的剩余时间,所以序列没法替你检查——把同一个时长字面量抄进两处调用。
  • AppendCallback 是相对的,不是绝对的。 它在序列时间线的当前位置触发,而不是在墙钟时间触发。要实现"游戏开始 5 秒后",请改用调度器服务。
  • 时钟源在启动时捕获。 序列默认绑定到 Clock.Gameplay。如果你在启动后替换时间服务(ServiceLocator.Replace<ITimeService>(…)),序列会继续使用构建时的原始时间源。
  • 取消不等于完成。 对播放句柄调用 Dispose() 会取消序列,OnSequenceComplete 不会触发。该事件只用于成功路径的清理;取消是静默的。
  • Play() 之后构建器冻结。Play() 之后调用 AppendJoinAppendIntervalAppendCallback 会抛出 InvalidOperationException。需要延长时间线时请新建一个序列。

相关页面

  • 补间 — 这些序列所组合的单值动画服务
  • 时间 — 时钟绑定与暂停行为
  • 调度器 — 在绝对时间触发回调;与序列回调互补