Communitygithub.com

精灵动画工作流

在 Godot 4、Unity 6 或 Phaser 3 中导入现有的精灵表或有序帧,并修正切片、图集引用、锚点或时间设置;不用于生成美术资源或骨骼绑定。

精灵动画工作流 是什么?

此技能提供了一套完整的工作流,用于将现有精灵动画(精灵表或有序 PNG 帧)导入 Godot 4、Unity 6 和 Phaser 3 引擎,并修复常见的动画问题。它能处理切片修复、帧对齐(基于脚部基线)、图集打包、锚点设置以及动画时间调整,但明确不生成新动画或进行骨骼绑定。核心操作依赖于 `pixo` 命令行工具,通过 `pixo asset info`、`pixo asset slice`、`pixo asset pack`、`pixo asset frames check/align` 和 `pixo game verify` 等命令完成,所有操作都需要 `PIXO_PROJECT_ID` 环境变量或 `--project` 参数。技能文档详细说明了每个引擎的具体集成方式:Godot 4 使用 `.tres` SpriteFrames 格式;Phaser 3 使用纹理打包器 JSON 或精灵表;Unity 6 则需要通过 Sprite Editor 手动切片,或使用自定义 Editor 脚本创建动画片段。它还指出了常见陷阱,例如帧排序、持续时间单位不一致以及锚点跳动,并提供了具体的最佳实践。最后通过一份清单来验证动画是否正确导入,包括帧数、循环/单次播放行为、脚部稳定性以及碰撞体是否受到影响。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OpenPixo/OpenPixo/tree/HEAD/skills/sprite-animation-pipeline

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

精灵动画工作流 是做什么的?

将有序帧或精灵表导入 Godot 4、Unity 6 或 Phaser 3。修复动画时间、图集引用和锚点。 项目约定、已安装版本、架构、美术风格和用户的明确选择具有更高优先级。 当任务仅更改时间时,保留可用的精灵表和格式;切片和重新打包是可选的。 此工作流使用现有美术资源而不是生成新动画;新帧来自 pixo media generate --sheet <列>x<行> --frames(位于提示词的 media 部分),它会将新帧写入同一画布。

快速路径

  1. 使用 pixo asset info <路径> --json 检查选定的精灵表或帧;读取当前的剪辑定义。
  2. 确认单元格大小和顺序、剪辑范围、基线、锚点以及循环或单次播放的时间。pixo asset frames check <帧...> --json 会为您测量基线和漂移。
  3. 选择下面的直接精灵表或打包分支;读取操作的实际输出路径。
  4. 通过引擎现有的动画所有者进行集成,不移动游戏碰撞体。
  5. 运行 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 的有序 PNGpack --format godot-tres输入顺序;保持 PNG 和 TRES 文件在一起
用于 Phaser 的有序 PNGpack --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,包括资源错误和跳过的步骤。

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

相关技能