时间轴
时间轴系统驱动多轨道动画,在时间线上协调属性变化、Spine 播放、精灵动画、音频事件和实体激活。时间轴在编辑器的 Timeline 面板中创建,运行时通过 TimelinePlayer 组件和 TimelineControl API 播放。
教程:动画化一个精灵
我们来创建一个时间轴,让精灵从左移到右并淡出。
第 1 步:选择实体
在 Hierarchy 中点击你要动画化的实体。
第 2 步:打开 Timeline 面板
打开 View → Timeline(或在面板菜单中找到它)。如果实体还没有 TimelinePlayer 组件,面板会显示 Create 按钮——点击它自动添加。
第 3 步:添加属性轨道
- 点击轨道列表中的 + 按钮
- 选择 Property → Transform → position.x
- 新轨道出现,目标指向所选实体的
Transform.position.x
第 4 步:添加关键帧
- 点击顶部标尺将播放头移到时间 0.0
- 点击菱形图标(或按
K)添加关键帧——捕获当前值 - 将播放头移到 1.0 秒
- 在 Inspector 中把 position.x 改为 200
- 再按
K——添加第二个关键帧,记录新值
第 5 步:添加第二条轨道
- 点击 + → Property → Sprite → color.a
- 在时间 0.0 添加关键帧(值:1.0)
- 在时间 1.0 添加关键帧(值:0.0)
第 6 步:预览
按 Space 在编辑器中播放。精灵在 1 秒内向右移动同时淡出。
第 7 步:配置播放
在 Inspector 中设置 TimelinePlayer 属性:
- wrapMode:
loop循环播放 - speed:
0.5半速播放
轨道目标定位
每条轨道都有一个目标实体。默认情况下,目标是拥有 TimelinePlayer 组件的实体。
动画化子实体
要动画化子实体,设置轨道的 Child Path —— 以 / 分隔的名称层级路径:
Arm/Hand → 找到子实体 "Arm",再找它的子实体 "Hand"Body → 找到直接子实体 "Body"(空) → 目标为根实体自身在编辑器中,当你在选中子实体时添加轨道,child path 会自动设置。
轨道类型
Property(属性)
通过关键帧插值动画化任意数值组件属性。
内置属性(优化路径,在 C++ 中计算):
| 组件 | 属性 |
|---|---|
| Transform | position.x/y/z, scale.x/y/z, rotation |
| Sprite | color.r/g/b/a, opacity, size.x/y |
| UIRect | offsetMin.x/y, offsetMax.x/y, anchorMin.x/y, anchorMax.x/y, pivot.x/y |
| Camera | orthoSize |
自定义属性:其他任何数值组件字段都可以通过嵌套路径动画化(如 myComponent.health)。
关键帧插值
右键关键帧可选择插值模式:
| 模式 | 行为 |
|---|---|
| Hermite | 使用切线手柄的平滑三次曲线(默认) |
| Linear | 关键帧之间的直线 |
| Step | 保持值直到下一个关键帧(无插值) |
| EaseIn | 缓慢开始,加速到下一个关键帧 |
| EaseOut | 快速开始,减速到下一个关键帧 |
| EaseInOut | 平滑的加速和减速 |
Hermite 模式下,拖拽曲线编辑器中的切线手柄可以调整插值曲线形状。
Spine
在 SpineAnimation 组件上触发 Spine 动画片段。每个关键帧指定一个在该时间播放的动画名称。
Sprite Animation(精灵动画)
播放精灵动画片段。每个关键帧指定一个在该时间开始的片段名称。
AnimFrames(帧动画)
用于精灵序列帧动画的可视化帧轨道。与精灵动画片段不同,每帧显示为时间轴上带颜色、可调整大小的色块:
- 每个色块引用一张纹理,拥有独立的时长(默认:1/12 秒)
- 拖拽色块边缘调整帧时序
- 目标实体必须有
Sprite组件——每帧自动切换纹理
当你需要逐帧控制时序而不想创建单独的动画片段时,使用 AnimFrames。
Audio(音频)
在特定时间触发音频播放。每个关键帧指定一个要播放的音频资源。
Activation(激活)
在时间范围内切换实体可见性。实体在活动范围内启用,范围外禁用。
Marker(标记)
在特定时间放置命名标记。标记不影响播放——仅作为编辑器中的参考点和定位用途。
Custom Event(自定义事件)
在特定时间触发命名事件。在你的系统中监听这些事件,可以在动画的精确时刻执行自定义逻辑。
编辑器控制
快捷键
| 快捷键 | 操作 |
|---|---|
Space | 播放 / 暂停 |
, / . | 后退 / 前进一帧 |
K | 在播放头位置添加关键帧 |
Delete | 删除选中的关键帧 |
Ctrl+Click | 切换关键帧选择 |
Shift+Click | 范围选择关键帧 |
| 框选拖拽 | 橡皮筋选择多个关键帧 |
工具栏
- 录制:切换自动关键帧模式——在 Inspector 中修改属性时自动在当前播放头位置记录关键帧
- 速度:循环切换播放速度(0.25x、0.5x、1x、2x、4x)
- 循环模式:循环切换 Once、Loop 和 PingPong
- 吸附:切换拖拽关键帧时的网格吸附
- 时长:双击时间显示可编辑总时长
多选与批量操作
选择多个关键帧(Ctrl+Click、Shift+Click 或框选),然后:
- 拖拽一起移动
- Delete 作为单次撤销步骤删除
轨道管理
- 拖拽轨道重新排序
- 右键轨道可重命名/删除
- 每种轨道类型在侧边栏有不同颜色标识
实时播放头同步
进入 Play 模式时,时间轴自动切换到 LIVE 模式。播放头以 60fps 实时跟踪运行时动画位置,工具栏显示红色闪烁的”LIVE”指示器。LIVE 模式下编辑器播放控件被禁用,退出 Play 模式后恢复。
TimelinePlayer 组件
将 TimelinePlayer 添加到实体上即可在运行时播放时间轴资源。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeline | string | '' | .estl 时间轴资源路径 |
playing | boolean | false | 设为 true 开始播放 |
speed | number | 1.0 | 播放速度倍率 |
wrapMode | string | 'once' | 'once'、'loop' 或 'pingPong' |
当 playing 设为 true 时开始播放。播放完成后(once 模式下),playing 自动恢复为 false。
WrapMode
| 值 | 说明 |
|---|---|
once | 播放一次后停止 |
loop | 播放完毕后从头开始 |
pingPong | 正向和反向交替播放 |
TimelineControl API
在系统中使用 TimelineControl 进行运行时播放控制:
import { defineSystem, addSystem, Query, TimelinePlayer, TimelineControl } from 'esengine';import { Res, Input } from 'esengine';
addSystem(defineSystem( [Res(Input), Query(TimelinePlayer)], (input, query) => { for (const [entity] of query) { if (input.isKeyPressed('Space')) { TimelineControl.play(entity); } if (input.isKeyPressed('KeyP')) { TimelineControl.pause(entity); } } }));方法
| 方法 | 返回值 | 说明 |
|---|---|---|
TimelineControl.play(entity) | void | 开始或恢复播放 |
TimelineControl.pause(entity) | void | 暂停播放 |
TimelineControl.stop(entity) | void | 停止并重置到开头 |
TimelineControl.setTime(entity, time) | void | 跳转到指定时间(秒) |
TimelineControl.isPlaying(entity) | boolean | 检查是否正在播放 |
TimelineControl.getCurrentTime(entity) | number | 获取当前播放时间(秒) |
注册时间轴资源
在编辑器外加载时间轴(如自定义资源管线中)时,使用注册 API:
import { registerTimelineAsset, getTimelineHandle, parseTimelineAsset } from 'esengine';
const asset = parseTimelineAsset(jsonData);registerTimelineAsset('timelines/intro.estl', asset);
const tl = getTimelineHandle('timelines/intro.estl');示例:开场序列
import { defineSystem, addStartupSystem, addSystem, Query, Mut, TimelinePlayer, TimelineControl, Res, Input} from 'esengine';
// 启动时播放开场时间轴addStartupSystem(defineSystem( [Query(Mut(TimelinePlayer))], (query) => { for (const [entity, player] of query) { if (player.timeline === 'timelines/intro.estl') { TimelineControl.play(entity); } } }));
// 按 Space 跳过addSystem(defineSystem( [Res(Input), Query(TimelinePlayer)], (input, query) => { if (input.isKeyPressed('Space')) { for (const [entity] of query) { if (TimelineControl.isPlaying(entity)) { TimelineControl.stop(entity); } } } }));资源清理
实体被销毁时,附加在实体上的时间轴和补间资源会自动清理。引擎通过 onDespawn 调用 _tl_destroy 释放 C++ 时间轴对象,并调用 Tween.cancelAll() 取消活跃的补间动画。销毁实体时无需手动停止时间轴或取消补间。