跳转到内容

时间轴

时间轴是一段 Sequencer 动画——随时间排布的关键帧轨道,可以移动 Transform、切换精灵、 触发音频、开关实体或驱动 Spine。你在编辑器的 Sequencer 里编辑它,用 TimelinePlayer 组件 附加,并通过 Timeline 资源控制播放。因为一条时间轴能同时驱动很多东西,它是过场与脚本化 序列的工具。

TimelinePlayer 组件把实体指向一个时间轴资源。通常在编辑器里设;用代码则以 Commands 插入:

import { defineSystem, Commands, TimelinePlayer } from 'esengine';
const attach = defineSystem([Commands()], (cmds) => {
cmds.spawn().insert(TimelinePlayer, {
timeline: 'timelines/intro.estimeline',
playing: true,
speed: 1.0,
wrapMode: 'once',
});
});
属性 类型 默认 说明
timeline asset '' 时间轴资源(.estimeline)。
playing boolean false 播放旗标——拉起播放,放下暂停。对已播完的片段拉起 = 从头重播。
speed number 1 播放速率倍率。
wrapMode string 'once' 'once''loop''pingPong'(驼峰写法——其它值会静默地只播一次)。
finished boolean false 'once' 片段播完时闩为 true;下次播放时清除。运行时只读。

组件旗标就是播放控制本身:引擎每帧把它们调和进内部时钟,所以任何翻动 playing 的一方——你的代码、检视器、AI 动作—— 都经同一条通道驱动片段。

Timeline 资源来驱动某个实体的时间轴:

import { defineSystem, Query, Res, TimelinePlayer, Timeline } from 'esengine';
const control = defineSystem([Query(TimelinePlayer), Res(Timeline)], (q, timeline) => {
for (const [entity] of q) {
timeline.play(entity);
timeline.pause(entity);
timeline.stop(entity);
timeline.setTime(entity, 1.5); // seek to 1.5s
const t = timeline.getCurrentTime(entity);
const playing = timeline.isPlaying(entity);
}
});
方法 说明
play(entity) 开始或恢复播放(已播完的片段从头重播)。
pause(entity) 暂停,保留当前时间。
stop(entity) 停止、重置到开头并清除 finished 闩。
setTime(entity, seconds) 定位到某时间(scrub 所有轨道)。
getCurrentTime(entity) 当前播放头时间(秒)。
isPlaying(entity) 是否正在播放。

这些方法写穿到 TimelinePlayer 的旗标——timeline.play(e)player.playing = true 是同一条通道上的同一个操作。

时间轴是一组每帧一起求值的轨道:

轨道 TrackType 驱动
属性 Property 组件字段关键帧——位置 / 旋转 / 缩放及任意数值点路径。
精灵帧 AnimFrames 随时间切换精灵表帧。
精灵剪辑 SpriteAnim 在某时刻启动一个命名 anim-clip
音频 Audio 在关键帧触发一次性声音。
激活 Activation 按区间开关实体。
Spine Spine 把 Spine 动画作为片段播放。
标记 Marker 命名时间标记。
自定义事件 CustomEvent 带 payload 的命名事件,供你自己的系统消费。

双击一个 .estimeline(或打开选中实体的时间轴),就在 Sequencer(关键帧动画编辑器)里 创作它。**摊开表(dope sheet)**把每条轨道及其关键帧沿一条共享、可拖动的播放头排开;加轨道、 拖关键帧,然后 Save

Sequencer 摊开表——一段过场时间轴的轨道与关键帧

摊开表:一轨一行(这里是某 Transform 的 position.x / position.y,加两条黑边激活轨道),关键帧是菱形,播放头可拖动,顶部是走带控制。

切到 Curves 标签可塑形关键帧之间的插值——拖动值曲线及其切线手柄,做出摊开表表达不了的缓动:

Sequencer 曲线编辑器——带切线手柄的值曲线

曲线编辑器:每个关键帧通道变成一条可编辑曲线(这里 position.x 缓入、position.y 起伏),每个关键帧带切线手柄。

.estimeline 文档就是普通数据——用 TimelineAsset 类型加上 TrackType / InterpType / WrapMode 常量,可以完全在代码里构建。用 registerTimelineAsset(path, asset) 注册后,任何 timeline 指向同一路径的 TimelinePlayer 都会像播放编辑器资产一样播放它:

import {
defineSystem, Commands, registerTimelineAsset, TimelinePlayer,
TrackType, InterpType, WrapMode, type TimelineAsset,
} from 'esengine';
const slideIn: TimelineAsset = {
version: '1.1',
type: 'timeline',
duration: 2,
wrapMode: WrapMode.Once,
tracks: [{
type: TrackType.Property,
name: 'Slide in',
childPath: '', // '' = the player entity itself
component: 'Transform',
channels: [{
property: 'position.x',
keyframes: [
{ time: 0, value: -200, inTangent: 0, outTangent: 0, interpolation: InterpType.EaseOut },
{ time: 2, value: 0, inTangent: 0, outTangent: 0 },
],
}],
}],
};
const attach = defineSystem([Commands()], (cmds) => {
registerTimelineAsset('code/slide-in', slideIn);
cmds.spawn().insert(TimelinePlayer, {
timeline: 'code/slide-in',
playing: true,
wrapMode: 'once',
});
});

Keyframe{ time, value, inTangent, outTangent, interpolation? }。以某 关键帧起始的分段用该关键帧的 interpolation:Hermite(默认——切线驱动曲线)、 LinearStepEaseInEaseOutEaseInOut。属性轨道按 childPath 解析目标实体(子实体名以 / 分隔;空 = 播放器实体本身),把通道的点路径字段写到 命名组件上。

播放采用播放器组件wrapMode 字符串;数值型 WrapMode 常量用在资产和纯求值 API 里。映射与行为:

WrapMode 播放器字符串 越过终点后的行为
WrapMode.Once(0) 'once' 停在 duration,停止,闩上 finished
WrapMode.Loop(1) 'loop' 回绕(time % duration)。
WrapMode.PingPong(2) 'pingPong' 每周期先正放再倒放。

applyWrapMode(time, duration, mode) 就是这个纯映射——对绝对时钟时间返回 { time, stopped }

求值器是纯 TypeScript,播放与编辑器 scrub 共用——你也可以自己调用:

函数 说明
sampleTimelineInWorld(asset, time, world, rootEntity, opts?) time 求值所有属性轨道并把结果写进 world(即 scrub 操作)。
sampleTimeline(asset, time, rootEntity, deps, opts?) 同上,但注入 SampleDeps(world / 组件查找 / 子实体解析)——用于测试和工具。
evaluateChannel(channel, time) 单个属性通道在 time 的值;端点截断。
serializeTimelineAsset(asset) 资产转为可 JSON 化对象(.estimeline 文档形状)。
serializeTimelineToJson(asset, indent?) 美化打印的 .estimeline JSON 字符串(indent 默认 2)。

采样只覆盖属性轨道;事件型轨道(音频、标记、自定义事件)在播放中按边沿检测触发, 采样不会触发它们。

  • 在 Sequencer 里编辑——时间轴资源持有轨道,代码只负责播放与定位。
  • 过场用 wrapMode: 'once',环境/待机序列用 'loop'
  • setTime scrub 来预览,或把过场与玩法状态同步。
  • 当很多属性/实体需要同步动画时用时间轴(而非补间);一次性的单属性手感用 补间
  • 动画 —— 补间与动画状态机。
  • Spine 动画 —— 时间轴可以驱动 Spine 片段。