精灵动画工作流 是做什么的?
将有序帧或精灵表导入 Godot 4、Unity 6 或 Phaser 3。修复动画时间、图集引用和锚点。
项目约定、已安装版本、架构、美术风格和用户的明确选择具有更高优先级。
当任务仅更改时间时,保留可用的精灵表和格式;切片和重新打包是可选的。
此工作流使用现有美术资源而不是生成新动画;新帧来自 pixo media generate --sheet <列>x<行> --frames(位于提示词的 media 部分),它会将新帧写入同一画布。
快速路径
- 使用
pixo asset info <路径> --json检查选定的精灵表或帧;读取当前的剪辑定义。 - 确认单元格大小和顺序、剪辑范围、基线、锚点以及循环或单次播放的时间。
pixo asset frames check <帧...> --json会为您测量基线和漂移。 - 选择下面的直接精灵表或打包分支;读取操作的实际输出路径。
- 通过引擎现有的动画所有者进行集成,不移动游戏碰撞体。
- 运行
pixo game verify --steps check,build,run --screenshot --json,检查日志和帧,然后交互式观看完整的循环和单次播放。
命令假定 PIXO_PROJECT_ID 已设置;否则请添加 --project <id>。
下面的路径和帧数只是示例;无元数据的精灵表无法识别自己的剪辑。
Unity 没有 run 步骤或截图:请使用 --steps check,build 并在编辑器中观看剪辑。
选择最小的格式变更
| 输入或请求 | 选择 | 保留 |
|---|---|---|
| 可用的常规精灵表,仅时间变更 | 现有的 SpriteFrames、Unity 剪辑或 Phaser 精灵表 | 帧范围、纹理键、共享基线 |
| 常规网格需要单独的帧 | pixo asset slice --cell 宽x高 或 --grid 列x行 附带输入路径 | 边距、间距和预期覆盖范围 |
| 帧抖动、滑动或大小不同 | pixo asset frames check,然后 frames align | 输入顺序,所有帧使用同一缩放比例 |
| 用于 Godot 的有序 PNG | pack --format godot-tres | 输入顺序;保持 PNG 和 TRES 文件在一起 |
| 用于 Phaser 的有序 PNG | pack --format texturepacker-json | 输入顺序,实际的描述符键 |
| 用于 Unity 的精灵表或帧 | 在 Unity 中作为 Sprite 导入并切片;pack 没有 Unity 输出 | 锚点、每单位像素数、现有的 .meta 文件 |
| 带自定义锚点的已修剪帧 | 现有元数据感知工作流 | 修剪偏移和锚点;仅图像的打包无法恢复它们 |
仅记录相关的合同字段:画布和单元格大小、行优先顺序、剪辑范围、FPS、帧保持、循环以及脚部的基线。 具有共享锚点的相同大小画布可避免帧间抖动;不透明边界不是锚点。
切片和打包锚点
pixo asset slice public/assets/player.png --cell 32x32 --out derived/player-frames --dry-run --json
pixo asset slice public/assets/player.png --cell 32x32 --out derived/player-frames --json
pixo asset pack sprites/run/frame-0000.png sprites/run/frame-0001.png --format godot-tres --padding 1 --out derived/run-godot --json
pixo asset pack public/sprites/run/frame-0000.png public/sprites/run/frame-0001.png --format texturepacker-json --padding 1 --out public/atlases/run --json
使用切片报告返回的 PNG 路径;两个帧的打包命令仅用于说明顺序。
--grid 要求边距和间距后精确分割;--cell 可能留下未使用的边缘单元格。
检查完整覆盖范围和帧数。有意排除来自其他剪辑的空单元格和帧。
读取 results、extraOutputs、reportPath、失败信息以及 originalsPreserved;现有目标位置将被跳过。
打包器事实:
- 名称派生自第一个输入,并保留其在
--out下的相对目录;请读取路径而不是猜测。 - 输出为
<stem>.atlas.png加上.atlas.tres或.atlas.json。 - 打包可能会重新排列空间位置,但描述符保留输入顺序。
- 不会生成旋转、修剪、边缘拉伸、剪辑命名或自定义锚点。
- 填充是透明分隔,而不是重复的边缘像素。
检查和对齐帧
pixo asset frames check public/assets/hero/hero-run-1.png public/assets/hero/hero-run-2.png public/assets/hero/hero-run-3.png --json
pixo asset frames align public/assets/hero/hero-run-*.png --canvas 64x64 --padding 1 --out derived/hero-run --json
按播放顺序传递帧(shell 通配符会将 -10 排在 -2 之前;如果有十个或更多帧,请列出它们)。
frames check 不写入图像。对于每一帧,results[].detail 包含绘制的 bounds、bottomGap(脚下的行数)、feetX(最低行的中心,挥动的武器不会移动它)、empty 和 touchesEdge。
summary 对集合进行判断:baselineSpreadPx(脚部弹跳)、feetXSpreadPx(滑动)、heightDriftPercent、emptyFrames、edgeFrames 以及在 issues 中每个问题的一条简单语句;如果没有问题,则 ok 为 true。
蹲下、压缩或跳跃会故意改变高度;相同姿势在不同高度是模型漂移。
frames align 将每个帧写入 <out>/<输入路径>.png 的一个画布上:脚在底线以上 padding 的同一行上,feetX 在中线上,所有帧使用同一缩放比例,因此蹲下姿势会保持比站立姿势矮。
--canvas 宽x高 将所有帧按相同因子缩放以适应;没有此选项时,画布是容纳所有帧的最小尺寸。
--match-height 首先将每个绘图缩放到中位数高度,仅用于漂移。--filter nearest 保持像素艺术清晰。
其 summary 重新检查对齐后的帧。与所有资产操作一样,现有输出将被跳过。
Godot 4:SpriteFrames 时间
godot-tres 发出格式为 3 的 SpriteFrames,其中包含一个以 5 FPS 循环的 default 剪辑。
保持 PNG 和 TRES 文件在一起;纹理引用是相对的。通过 pixo game verify --steps check --json 导入新图像。
替换报告路径后,以下代码适配了一个实际的四帧剪辑:
func configure_run(sprite: AnimatedSprite2D) -> void:
var packed := load("res://sprites/run/run.atlas.tres") as SpriteFrames
if packed == null or packed.get_frame_count(&"default") != 4:
push_error("Expected four run frames")
return
var frames := packed.duplicate() as SpriteFrames
frames.rename_animation(&"default", &"run")
frames.set_animation_speed(&"run", 10.0)
frames.set_animation_loop(&"run", true)
frames.set_frame(&"run", 0, frames.get_frame_texture(&"run", 0), 2.0)
sprite.sprite_frames = frames
sprite.centered = true
sprite.offset = Vector2(0, -16) # 示例:在 32px 画布上的底部中心锚点。
sprite.play(&"run")
帧持续时间 2.0 表示正常帧持续时间的两倍,而不是两秒。
使用商定的基线作为偏移量;保持 CharacterBody2D 和 CollisionShape2D 稳定。
单次播放剪辑需要 loop=false;连接 animation_finished 一次,并通过活动动画来门控转换。
参考:Godot 4.3 SpriteFrames。
Phaser 3:精灵表或图集时间
// preload(),用于实际的 32px 精灵表:
this.load.spritesheet('player', 'assets/player.png', { frameWidth: 32, frameHeight: 32 });
// create(),使用项目的实际运行范围:
if (!this.anims.exists('player-run')) {
this.anims.create({ key: 'player-run',
frames: this.anims.generateFrameNumbers('player', { start: 2, end: 5 }),
frameRate: 10, repeat: -1 });
}
player.setOrigin(0.5, 1).play('player-run', true);
Vite 公共资产加载时 URL 中不带 public/。
对于图集,this.load.atlas(key, pngUrl, jsonUrl) 加载返回的一对文件。
TexturePacker 哈希键是输入的项目相对路径;读取它们或使用 getFrameNames()。
当名称不编码顺序时,请使用显式有序列表。
// orderedNames 来自已审查的描述符/剪辑列表。
this.anims.create({ key: 'hero-run', frameRate: 10, repeat: -1,
frames: orderedNames.map((frame, index) => ({
key: 'hero-atlas', frame, duration: index === 0 ? 100 : 0,
})),
}); // 在 10 FPS 下,100 额外毫秒使第一帧总共持续 200 毫秒。
像上面那样使用 this.anims.exists 保护全局动画创建;键在场景重启后仍然存在。
repeat: 0 播放一次;使用一个拥有的单次播放完成监听器。
更改原点会影响身体位置;检查大小和偏移量,而不是移动碰撞体来隐藏不良的锚点。
参考:Phaser 3.90 动画类型。
Unity 6:导入器、剪辑和 Animator
Unity 在没有额外包的情况下无法读取 godot-tres 或 TexturePacker JSON。将精灵表放在 Assets/ 下,然后设置 Texture Type 为 Sprite,Sprite Mode 为 Multiple 以及实际的 Pixels Per Unit。对于像素艺术,添加 Point 过滤和无压缩。在 Sprite Editor 中按单元格大小切片,并使用共享锚点,例如 Bottom。脚本化切片通过 SpriteDataProviderFactories 的 ISpriteEditorDataProvider 进行;TextureImporter.spritesheet 是旧路径。com.unity.2d.aseprite 直接导入 .aseprite 文件,当美术资源从那里开始时,包括剪辑。
剪辑可以通过在 Animation 窗口中选择精灵并拖放来创建,或者当有很多精灵时通过脚本创建。此仅限编辑器的示例构建了一个四帧循环剪辑并保持第一帧:
using System;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
public static class HeroClips
{
[MenuItem("Tools/Create Hero Run Clip")]
public static void CreateRun()
{
const float frame = 0.1f; // 10 FPS。
// LoadAllAssetsAtPath 不保证顺序,并且 hero_10 作为文本排在 hero_2 之前。
var sprites = AssetDatabase.LoadAllAssetsAtPath("Assets/Art/hero.png").OfType<Sprite>()
.OrderBy(sprite => int.Parse(Regex.Match(sprite.name, @"\d+$").Value))
.Skip(2).Take(4).ToArray();
if (sprites.Length != 4) throw new InvalidOperationException("Expected four run frames");
var keys = sprites.Select((sprite, index) => new ObjectReferenceKeyframe {
time = index == 0 ? 0f : (index + 1) * frame, value = sprite,
}).ToArray();
var clip = new AnimationClip { frameRate = 1f / frame };
AnimationUtility.SetObjectReferenceCurve(clip,
EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite"), keys);
var settings = AnimationUtility.GetAnimationClipSettings(clip);
settings.loopTime = true;
settings.stopTime = (sprites.Length + 1) * frame;
AnimationUtility.SetAnimationClipSettings(clip, settings);
AssetDatabase.CreateAsset(clip, "Assets/Animations/hero_run.anim");
}
}
关键帧时间是秒,因此保持是键之间更宽的间隙。设置 stopTime 以便最后一帧获得其完整长度。
- 在 Animator 中,精灵转换通常希望关闭 Has Exit Time 并将 Transition Duration 设置为 0,否则下一个姿势会延迟到达。
animator.Play(hash)会保持一个已经单独播放的状态,因此每次更新调用它是安全的。重放单次播放需要Play(hash, 0, 0f)。使用Animator.StringToHash对名称进行一次哈希。- 通过 Animation Event 检测单次播放的结束,或者在
IsInTransition(0)为 false 且状态哈希匹配时检查normalizedTime >= 1。否则,转换可能会报告旧状态。 - 将 Animator 和 SpriteRenderer 放在一个子对象上,将 Rigidbody2D 和碰撞体放在根对象上,并关闭 Apply Root Motion。
flipX不会翻转碰撞体或子对象,并且它会绕锚点镜像,因此不居中的锚点会跳动。 - 紧密的精灵网格可能会剪裁绘制在帧上的效果;当着色器在轮廓之外采样时,请使用 Full Rect。
- Sprite Atlas 会批量处理单独导入的帧。给它填充,并关闭 Tight Packing 和 Allow Rotation 以保证像素艺术。
陷阱:错误 → 正确
| 错误 | 正确,以及原因 |
|---|---|
| 按字典序对无填充的文件名排序 | 显式顺序;否则 frame-10 会排在 frame-2 之前 |
| 独立修剪每个帧 | 保持画布或有效偏移量,使脚部保持不动 |
| 猜测打包后的基名称 | 读取描述符键和返回的输出路径 |
| 在每个引擎中使用相同的持续时间单位 | Godot 使用相对乘数,Phaser 帧持续时间增加毫秒,Unity 键是秒 |
每次更新都调用 play(key) | play(key, true) 避免重新启动已经活动的剪辑 |
| 让视觉剪辑速度隐式改变伤害或物理 | 保持游戏时间权威,除非明确耦合 |
完成时
- 实际的帧数、帧顺序、剪辑时间、循环和单次播放行为与请求匹配。
- 在空闲和奔跑之间转换、镜像和重新启动时,脚部和锚点保持稳定;
pixo asset frames check为每个状态报告ok,或者其剩余问题是有意为之的。 - 图集引用和加载路径是真实的;视觉变化保留碰撞行为。
- 使用
pixo game run <projectId> --json,或在 Unity 中使用编辑器,观看完整的循环和单次播放完成;静态图像无法证明时间。报告测试过的时间和未解决的问题。 - 检查
pixo game verify --steps check,build,run --screenshot --json,包括资源错误和跳过的步骤。