feat: 支持静态组合立绘与相对动画 - #1027
feat: 支持静态组合立绘与相对动画#1027starrybamboo wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds engine support for static composite “Character” figures (managed by a new character script command) and introduces structured animation resources whose frames can be interpreted relative to a target’s resolved/base transform, enabling deferred (async) figure delivery while preserving animation behavior.
Changes:
- Added a new
characterscript command and a Character composition pipeline (template parsing/validation, async image composition, and Pixi delivery sync). - Added “relative frame” animation support via structured animation resources and runtime frame composition on top of a resolved base transform.
- Extended figure/animation state to preserve a
baseTransformfor deferred presentation replay.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/webgal/src/Core/parser/sceneParser.ts | Registers the new character script tag in the runtime script map. |
| packages/webgal/src/Core/Modules/stage/stageInterface.ts | Adds baseTransform to stage animation settings to preserve a resolved baseline transform. |
| packages/webgal/src/Core/Modules/animations.ts | Adds structured animation resource parsing and relative-frame marking via createUserAnimation. |
| packages/webgal/src/Core/Modules/animationFunctions.ts | Composes relative animation frames against a computed/resolved source transform (incl. optional override). |
| packages/webgal/src/Core/initializeScript.ts | Uses createUserAnimation when loading user animation JSON resources. |
| packages/webgal/src/Core/gameScripts/character.ts | Implements the character script command and delegates presentation to changeFigure. |
| packages/webgal/src/Core/gameScripts/changeFigure.ts | Persists baseTransform into animation settings and refactors transform baseline handling. |
| packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts | Integrates Character figure sync and deferred presentation into Pixi stage synchronization. |
| packages/webgal/src/Core/controller/scene/sceneInterface.ts | Adds commandType.character to the runtime command enum. |
| packages/webgal/src/Core/character/characterTemplate.ts | Defines/validates character templates and resolves component/preset selections into ordered layers. |
| packages/webgal/src/Core/character/characterImageComposer.ts | Loads component images and composes them into a single bitmap for delivery. |
| packages/webgal/src/Core/character/characterFigureTarget.ts | Resolves and enumerates figure targets (positional slots + free figures) for Character management. |
| packages/webgal/src/Core/character/characterFigureSourceSync.ts | Handles async composition requests and applies only the latest result per target (epoch gating). |
| packages/webgal/src/Core/character/characterFigureSource.ts | Serializes/parses stable Character figure sources and collects Character targets from stage state. |
| packages/webgal/src/Core/character/characterFigureService.ts | Loads/validates templates and caches in-flight + completed compositions in memory. |
| packages/webgal/src/Core/character/characterDeferredPresentationRuntime.ts | Replays deferred enter animations using stored baseTransform when the composed image arrives. |
| packages/parser/src/interface/sceneInterface.ts | Adds commandType.character to the parser-side command enum. |
| packages/parser/src/config/scriptConfig.ts | Registers character as a supported script token in the parser config. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (sourceUrl && parseCharacterFigureSource(sourceUrl)) { | ||
| return; | ||
| } |
| for (const target of listFigureTargets(state)) { | ||
| const source = parseCharacterFigureSource(target.source); | ||
| if (source) { | ||
| targets.push({ key: target.key, position: target.position, source }); | ||
| } | ||
| } |
| for (const { layer, image } of loadedLayers) { | ||
| const { width, height } = resolveDrawSize(layer, image); | ||
| context.drawImage(image.source, layer.x, layer.y, width, height); | ||
| } | ||
| return canvas.toDataURL('image/png'); |
| export function createUserAnimation(name: string, resource: UserAnimationResource): IUserAnimation { | ||
| if (Array.isArray(resource)) { | ||
| return { name, effects: resource }; | ||
| } | ||
| return { | ||
| name, | ||
| effects: resource.effects, | ||
| ...(resource.frameMode !== 'absolute' ? { frameMode: 'relative' as const } : {}), | ||
| }; | ||
| } |
|
使用序列化方式来在原有的立绘系统上接入角色系统会显著增加系统复杂性。我认为需要在状态表里添加角色字段,并让 pixiStage 像 figure 一样把角色状态同步到舞台。相比普通 figure,角色只是多了一步异步合成立绘,这里和普通静态立绘的图像加载采用相同方法处理; 对于同 id 的角色,切换预设本质上类似于 Live2D 立绘的切换表情/动作,应在合成新立绘后继承原有舞台效果,尤其是 transform。这一点应该参考 Live2D 支持中切换表情的逻辑。 |
关联 #1010,
概要
本 PR 提交以下引擎侧能力:
character脚本管理静态组合立绘;character脚本每条角色声明包含角色名,以及本次需要使用的完整、有序部件列表:
可以按角色名清除,也可以清除所有由
character管理的立绘:同名角色同时只能占用一个 Figure Target。将角色移动到其他位置或 ID 时,会先清除旧目标,再更新新目标。
这个可能需要商榷,我个人是希望能这样的,这样就不用手动管立绘的退场了。(方便AI写脚本)
位置、ID、transform、transition、duration、z-index 与 blend mode 等行为继续委托给现有
changeFigure链路。角色模板
模板固定从以下路径加载:
最小的 Version 1 模板示例:
{ "Version": 1, "canvas": { "width": 1600, "height": 3000 }, "components": { "body": { "src": "body.webp", "x": 0, "y": 0 }, "uniform_summer": { "src": "uniform-summer.webp", "x": 0, "y": 0, "scale": 0.8 }, "face_smile": { "src": "faces/smile.webp", "x": 0, "y": 0, "width": 512, "height": 512 } }, "presets": { "summer_uniform": ["body", "uniform_summer"] } }部件与预设会按声明顺序递归展开,重复引用会被保留;后出现的图层绘制在先出现的图层之上。
部件可以使用图片原始尺寸、正数等比
scale,或成对指定width与height。目前支持静态 PNG、WebP、JPG/JPEG,部件路径必须保持在角色目录内。模板、已完成的组合结果和正在执行的合成任务均使用内存缓存。加载与合成不会阻塞剧本流程。
舞台状态与存档中保存的是稳定的角色逻辑来源。生成结果作为普通图片交付,因此
figure.json不会进入 Live2D 资源解析链路。相对动画资源
原有的纯帧数组动画格式保持不变,但加载后直接使用相对语义:
[ { "position": { "x": 0, "y": -30 }, "duration": 100, "ease": "linear" } ]动画帧会与目标已解析的 transform 组合:
position与rotation使用加法;scale与alpha使用乘法;引擎内部生成且未标记的 transform、进场与退场时间线继续使用绝对值,避免内部关键帧被重复叠加。
为什么放在同一个 PR
组合立绘复用了 Figure 的变换与动画,但最终 Pixi 图片是在脚本完成舞台状态演算后才异步生成的。
图片准备完成时,延迟演出必须以命令执行时解析出的原始 transform 为基准重新构造动画。因此,相对动画帧映射与组合立绘交付会在同一个演出边界相交:异步替换 Pixi 图片后,仍需保留 Figure 的位置基准并重放其入场动画。
将两者放在同一个 PR,可以继续复用统一的 Figure 动画链路,不需要增加一套 Character 专用动画实现。
兼容性与范围
localforage、预取/预热线路或任何新依赖;验证
yarn parser:test --run:2 个测试文件、29 个测试通过;yarn build:Parser Rollup、WebGAL TypeScript 与 Vite 生产构建通过;git diff --check:通过;BA-jump-twice相对动画结束后回到原 transform 基准位置。