补间序列
用一条流式调用链把补间组合成顺序与并行、可随游戏暂停的时间线。
把补间、延迟和回调串成一条会随游戏暂停的时间线。
| 接口 | 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 = false | 为 false(默认)时,步回调抛出的异常被捕获并记录。为 true 时异常向外传播——开发期间很有用。 |
bool LogSequenceLifecycle = false | 为 true 时,记录构建/播放/完成/取消的踪迹。这些日志总是被编译进来,即使在发布构建中。 |
示例
一个滑入的同时淡入、然后宣布自己就位的 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/Join的durationSeconds。它必须与你的工厂创建的补间的实际时长一致。声明 3 秒而补间实际运行 2.8 秒,时间安排就会漂移。补间服务无法报告某个补间的剩余时间,所以序列没法替你检查——把同一个时长字面量抄进两处调用。 AppendCallback是相对的,不是绝对的。 它在序列时间线的当前位置触发,而不是在墙钟时间触发。要实现"游戏开始 5 秒后",请改用调度器服务。- 时钟源在启动时捕获。 序列默认绑定到
Clock.Gameplay。如果你在启动后替换时间服务(ServiceLocator.Replace<ITimeService>(…)),序列会继续使用构建时的原始时间源。 - 取消不等于完成。 对播放句柄调用
Dispose()会取消序列,OnSequenceComplete不会触发。该事件只用于成功路径的清理;取消是静默的。 Play()之后构建器冻结。 在Play()之后调用Append、Join、AppendInterval或AppendCallback会抛出InvalidOperationException。需要延长时间线时请新建一个序列。