CraftEngine 动画图片系统
一、简介
CraftEngineAnim —— 一款 Minecraft 着色器帧动画系统
CraftEngineAnim 通过精灵图(Sprite Sheet)与自定义着色器,实现在 Minecraft 聊天、GUI、物品名称等任何支持 bitmap font 的位置播放帧动画。基于 GameTime 驱动自动切帧,无需服务端参与,完全客户端渲染。
CraftEngineAnim 支持 Minecraft 1.21.4+ 版本,配合 CraftEngine 资源包系统使用,并且会第一时间支持未来版本。
CraftEngineAnim
- 着色器驱动 > 顶点着色器(vsh)拦截标记颜色,自动计算帧号并重映射UV
- 精灵图 > 多帧合并为单张精灵图,支持任意网格尺寸
- Bitmap Font > 通过 CraftEngine 注册为自定义字符使用
- 两种模式 > Legacy模式(4帧固定) + Metadata模式(无限帧)
- GIF转换 > 附带 Python 工具自动将 GIF 转换为精灵图
- 表情符号 > 可用作聊天表情、物品描述装饰
- 零服务端开销 > 纯客户端渲染,不影响服务器性能
- 可视化编辑器 > 内置精灵图编辑器,实时预览
二、插件前置说明
都是非必须(除 CraftEngine)
- CraftEngine — 必需,资源包管理框架
💡 核心思路
多帧动画 → 精灵图(Sprite Sheet) → bitmap font 字符 → 着色器按 GameTime 自动切帧
渲染管线
玩家输入 :allay:
↓
CraftEngine → <!shadow><color:#fd0900><image:minecraft:allay_gif>
↓
Minecraft 渲染文字 → 顶点颜色 = #fd0900 (R=253, G=9, B=0)
↓
顶点着色器 (vsh) 拦截标记颜色
→ 读取精灵图(0,0)元数据 → 计算帧号 → 重映射 UV
↓
片段着色器 (fsh): 100% 原版 → 采样重映射 UV → 显示单帧
标记颜色
R
253 固定
+
G
1~50 动画ID
+
B
0 校验
=
🎬
#fdXX00UV 重映射
4×4 网格精灵图 (256×256, 每帧 64×64):
┌──────┬──────┬──────┬──────┐
│ F0 │ F1 │ F2 │ F3 │
├──────┼──────┼──────┼──────┤
│ F4 │ F5 │ F6 │ F7 │
├──────┼──────┼──────┼──────┤
│ F8 │ F9 │ F10 │ F11 │
├──────┼──────┼──────┼──────┤
│ F12 │ F13 │ F14 │ F15 │
└──────┴──────┴──────┴──────┘
当前帧=F5: col=5%4=1, row=5/4=1 → UV 映射到第1列第1行
目录结构
animated_text/
├── pack.yml CraftEngine 包配置
├── wiki.html 本文档
├── configuration/
│ └── animated_images.yml 图片+emoji+模板配置
├── resourcepack/assets/minecraft/
│ ├── shaders/core/
│ │ ├── rendertype_text.vsh 顶点着色器(核心)
│ │ ├── rendertype_text.fsh 片段着色器(原版)
│ │ └── rendertype_text.jsonuniform定义
│ └── textures/font/anim/
│ ├── *_sprite.png Legacy精灵图
│ └── *_gif.png Metadata精灵图
└── tools/
├── gen_test_sprites.py 测试生成器
└── gif_to_sprite.py GIF转换工具
下一篇动画模式 →
动画模式
Legacy 模式 G=1~5
| 属性 | 值 |
|---|---|
| 精灵图 | 256×256, 2×2 网格, 4 帧固定 |
| 元数据 | 不需要 |
| 速度 | G 值编码 fps |
| G | 颜色 | fps | 动画 |
|---|---|---|---|
| 1 | #fd0100 | 1.5 | fire_anim |
| 2 | #fd0200 | 1.0 | water_anim |
| 3 | #fd0300 | 2.0 | lightning_anim |
| 4 | #fd0400 | 0.8 | heartbeat_anim |
| 5 | #fd0500 | 1.2 | rainbow_anim |
Pixel-metadata 模式 G=6~50 推荐
精灵图左上角像素(0,0)编码动画参数,支持任意网格。
R
速度倍率
1~255
1~255
G
帧宽(px)
如64
如64
B
帧高(px)
如64
如64
A
速度档
1/2/3
1/2/3
| 通道 | 含义 | 说明 |
|---|---|---|
| R | 速度倍率 | 值越大越快,推荐6~12 |
| G | 帧宽 | 如64=每帧宽64px |
| B | 帧高 | 如64=每帧高64px |
| A | 速度档 | 3=慢(×1000) 2=中(×500) 1=快(×300) |
⚠️ Alpha 必须是 1/2/3
着色器用此验证元数据有效性。其他值=无效。
案例: allay_gif.png
像素(0,0) = RGBA(8, 64, 64, 3)
→ speed=8, frameW=64, frameH=64, tier=3(慢)
→ 网格: 256/64=4列, 256/64=4行 = 16帧
→ 实际速度: GameTime × 1000 × 8
上一篇← 系统概述
下一篇精灵图展示 →
精灵图素材展示
Legacy 精灵图 (2×2)
fire_sprite.png
water_sprite.png
lightning_sprite.png
heartbeat_sprite.png
rainbow_sprite.png
Pixel-metadata 测试 (4×4)

fire_gif.png

water_gif.png

rainbow_gif.png

heart_gif.png
NPC 动画素材

allay_gif.png

antique_gif.png

blacksmith_1_gif.png

golden_allay_gif.png

cemetery_gif.png

copper_golem_gif.png

decompose_gif.png

prop_gif.png

reclaim_gif.png

toporigin_gif.png
上一篇← 动画模式
下一篇制作教程 →
制作教程
教程 A: 从 GIF 制作精灵图
方法 1: 转换工具 (推荐)
python tools/gif_to_sprite.py npc_walk.gif npc_walk_gif.png
python tools/gif_to_sprite.py npc_walk.gif npc_walk_gif.png --speed 12 --tier 2
python tools/gif_to_sprite.py --verify allay_gif.png
方法 2: 手动 Python
from PIL import Image
gif = Image.open("my_animation.gif")
frames = []
try:
while True:
frames.append(gif.copy().convert("RGBA"))
gif.seek(gif.tell() + 1)
except EOFError: pass
FRAME_SIZE = 64
resized = [f.resize((FRAME_SIZE, FRAME_SIZE), Image.LANCZOS) for f in frames]
sprite = Image.new("RGBA", (256, 256), (0, 0, 0, 0))
for i in range(16):
frame = resized[i % len(resized)]
col, row = i % 4, i // 4
sprite.paste(frame, (col * FRAME_SIZE, row * FRAME_SIZE))
# 嵌入元数据: R=速度, G=帧宽, B=帧高, A=档位
sprite.putpixel((0, 0), (8, 64, 64, 3))
sprite.save("my_animation_gif.png")
⚠️ 重要
精灵图必须 256×256 正方形! 非正方形会导致渲染变形。
教程 B: 添加新动画 (wizard, G=20)
# Step 1: G=20 → hex(20)=14 → 颜色 #fd1400
# Step 2: 制作精灵图
python tools/gif_to_sprite.py wizard.gif wizard_gif.png --speed 8 --tier 3
# Step 3: 放到 textures/font/anim/wizard_gif.png
# Step 4: animated_images.yml
images:
minecraft:wizard_gif:
height: 48
ascent: 42
font: minecraft:default
file: minecraft:font/anim/wizard_gif.png
templates:
minecraft:anim_emoji/wizard:
content: <!shadow><color:#fd1400><image:minecraft:wizard_gif></color></!shadow>
emoji:
minecraft:gif_wizard:
content: <!shadow><color:#fd1400><image:minecraft:wizard_gif></color></!shadow>
image: minecraft:wizard_gif
permission: emoji.gif.wizard
keywords:
- ':wizard:'
💡 无需改着色器
Pixel-metadata 模式只需分配 G 值 + 放精灵图 + 写配置。
上一篇← 精灵图展示
下一篇着色器详解 →
着色器详解
顶点着色器 rendertype_text.vsh
标记检测
元数据模式
Legacy模式
颜色处理
ivec3 iColor = ivec3(Color.rgb * 255.0 + 0.5);
if (iColor.r == 253 && iColor.b == 0 && iColor.g >= 1 && iColor.g <= 50) {
ivec2 atlasSize = textureSize(Sampler0, 0);
float aw = float(atlasSize.x);
float ah = float(atlasSize.y);
int vid = gl_VertexID % 4;
bool isRight = (vid >= 2);
bool isBottom = (vid == 1 || vid == 2);
}
// Pixel-metadata (G >= 6)
float tryW = 256.0;
float cw = tryW / aw, ch = 256.0 / ah;
float uMin = UV0.x - (isRight ? cw : 0.0);
float vMin = UV0.y - (isBottom ? ch : 0.0);
ivec2 tlPx = ivec2(uMin * aw + 0.5, vMin * ah + 0.5);
vec4 meta = texelFetch(Sampler0, tlPx, 0) * 255.0;
float frameW = floor(meta.g + 0.5);
float frameH = floor(meta.b + 0.5);
float speed = floor(meta.r + 0.5);
float speedMul = 1000.0; // A=3→1000, A=2→500, A=1→300
int gridCols = int(charPixW / frameW);
int totalFrames = gridCols * int(256.0 / frameH);
int frame = int(mod(GameTime * speedMul * speed, float(totalFrames)));
float fcol = float(frame % gridCols);
float frow = float(frame / gridCols);
texCoord0 = vec2(
uMin + (fcol + (isRight ? 1.0 : 0.0)) * frameW/aw,
vMin + (frow + (isBottom ? 1.0 : 0.0)) * frameH/ah
);
// Legacy (G=1..5) 固定 2×2, 4帧
float fps = 1.0;
if (iColor.g == 1) fps = 1.5;
if (iColor.g == 2) fps = 1.0;
if (iColor.g == 3) fps = 2.0;
if (iColor.g == 4) fps = 0.8;
if (iColor.g == 5) fps = 1.2;
int frame = int(mod(GameTime * 1200.0 * fps, 4.0));
float col = mod(float(frame), 2.0);
float row = floor(float(frame) / 2.0);
// 动画字符: 不乘 Color (标记色会变红)
vertexColor = texelFetch(Sampler2, UV2 / 16, 0);
// 非动画字符: 原版
vertexColor = Color * texelFetch(Sampler2, UV2 / 16, 0);
texCoord0 = UV0;
片段着色器 rendertype_text.fsh
🚫 绝对不修改!
添加自定义 varying 会导致 1.21.4 所有文字乱码!
// 100% 原版
vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator;
if (color.a < 0.1) discard;
fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor);
rendertype_text.json
// uniforms 必须包含 GameTime
{ "name": "GameTime", "type": "float", "count": 1, "values": [ 0.0 ] }
上一篇← 制作教程
下一篇配置文件 →
animated_images.yml
# 1. images: 注册图片
images:
minecraft:allay_gif:
height: 48
ascent: 42
font: minecraft:default
file: minecraft:font/anim/allay_gif.png
# 2. templates: MiniMessage 模板
templates:
minecraft:anim_emoji/allay:
content: <!shadow><color:#fd0900><image:minecraft:allay_gif></color></!shadow>
# 3. emoji: 聊天触发
emoji:
minecraft:gif_allay:
content: <!shadow><color:#fd0900><image:minecraft:allay_gif></color></!shadow>
image: minecraft:allay_gif
permission: emoji.gif.allay
keywords:
- ':allay:'
⚠️ <!shadow> 必须加!
阴影使 Color×0.25, R从253变63, 着色器无法识别→显示整张精灵图。
上一篇← 着色器详解
下一篇精灵图编辑器 →
精灵图编辑器
两种方式创建动画精灵图,自动生成配置文件并打包成 ZIP 下载。
📁 方式一: 多张帧图片
🖼 方式二: 从整图选取帧
上传帧图片
📂
拖拽 GIF 或多张 PNG/JPG 帧图到此处
GIF 自动拆帧 · 点击选择文件
帧排序 (拖拽排序 · 点击选中 · ×删除)
上传整图
🖼
拖拽一张包含多帧的精灵图/合图到此处
PNG / JPG · 点击选择文件
配置参数
生成与下载
上一篇← 配置文件
下一篇速查与 FAQ →
速查与 FAQ
颜色编码速查表
| G | 颜色 | 动画 | 模式 | 关键词 |
|---|---|---|---|---|
| 1 | #fd0100 | fire_anim | Legacy | :fire: |
| 2 | #fd0200 | water_anim | Legacy | :water: |
| 3 | #fd0300 | lightning_anim | Legacy | :lightning: |
| 4 | #fd0400 | heartbeat_anim | Legacy | :heartbeat: |
| 5 | #fd0500 | rainbow_anim | Legacy | :rainbow: |
| 6~19 | #fd0600~#fd1300 | 各 Meta 动画 | Meta | — |
| 20~50 | — | ✅ 31 个空位可用 | ||
# G → 颜色代码: #fd + hex(G, 2位) + 00
G=20 → #fd1400 G=30 → #fd1e00 G=50 → #fd3200
速度参考
| Speed(R) | Tier=3(慢) | Tier=2(中) | Tier=1(快) |
|---|---|---|---|
| 4 | 很慢 | 慢 | 中 |
| 6 | 慢 | 中 | 较快 |
| 8 | 推荐 | 较快 | 快 |
| 12 | 较快 | 快 | 很快 |
常见问题
Q: 显示整张精灵图(不动画)
检查 <!shadow> 是否添加, 颜色代码是否正确, G值是否在1~50
Q: 所有文字乱码
fsh被修改了! 确保 rendertype_text.fsh 是100%原版
Q: 动画显示窄长
精灵图不是正方形. 重排帧到256×256画布
Q: Metadata模式不播放
像素(0,0) Alpha 不是 1/2/3. 验证: img.getpixel((0,0))[3]
上一篇← 精灵图编辑器