---
name: mc-texture
description: 像素纹理、纹理动画与特效。与 pixel-art-upstream（junyudev/pixel-art，MIT，已收录）配套使用——32px 以上或画人物/立绘时同时读它。当用户要画、改或修方块纹理、物品图标、GUI 贴图、粒子贴图、烟花/爆炸/烟雾特效，或要做 .png.mcmeta 纹理动画时激活：先问清用户的风格要求，用户没说再按 MC 惯例（16×16、可平铺、材质符合游戏内逻辑，如铜的四级锈蚀）；照「平涂剪影→打光→内部细节→描边→五官」分层画，渲染后按 1× 原尺寸核对并至少迭代 3 轮，动画用参数和公式生成每帧。模型 JSON 走 mc-model，目录与 pack.mcmeta 走 mc-resourcepack。不适用于游戏内渲染效果调试与 GeckoLib 几何动画。
---

# mc-texture — 像素纹理与纹理动画

## 铁律

1. **1× 认得出**是唯一硬判据。放大图好看不算数，缩小到真实尺寸认不出就是重画。
2. **用户要求优先于 MC 惯例**。用户说了风格/色数/尺寸就照办；用户没说，才按本文的 MC 默认。
3. **质量来自迭代次数**，不是第一版的想象。至少 3 轮，每轮写清改了什么、为什么。
4. **只输出文字也要闭环**：写文本网格 → 渲染 PNG → 看图 → 改数字 → 再渲染。

## 配套技能：什么时候去读 pixel-art-upstream

本技能与 `pixel-art-upstream`（[junyudev/pixel-art](https://github.com/junyudev/pixel-art)，MIT，已原样收录）**是一套，不是两套**。默认两个一起用：

- 本文 = **MC 特有规则**（尺寸、可平铺、材质逻辑、mcmeta 动画、接线）
- 上游 = **长篇方法论**（32px 以上的构造法、脸和手的画法、风格卡、概念图转像素）

**先读上游再动手的场合**（用 `read_knowledge_resource uri=mcskill://skill/pixel-art-upstream`，或直接读 `common_skill/pixel-art-upstream/`）：

| 场合 | 读哪篇 |
|---|---|
| 画布 > 32px（实体贴图、立绘、大 GUI） | `common_skill/pixel-art-upstream/reference/large.md` — 构造法全流程，23KB |
| 要把参考图/AI 出的"像素风"图转成干净像素画 | `common_skill/pixel-art-upstream/reference/imagegen.md` |
| 需要按某款游戏的风格对齐（尺寸、共享调色板、脸型语法） | `common_skill/pixel-art-upstream/reference/small.md`；风格卡目录未随仓库分发，见本节末 |
| 想知道「1× 判据」「半尺寸放大失败」这些结论的出处 | `common_skill/pixel-art-upstream/reference/large.md` 的 Lessons 一节 |

**用它的渲染器做交叉验证**：两个渲染器各有一套校验，能互相抓错。

```bash
# 上游（需要 python + pillow + numpy），会报包围盒、贴边、孤立像素
python -I common_skill/pixel-art-upstream/tools/grid.py render sprite.txt out/ --size 16x16

# 本仓（零依赖），会报孤立像素、半透明、尺寸不符
node common_skill/mc-texture/tools/render-pixels.mjs sprite.txt --out out/sprite.png --size 16x16
```

⚠️ 上游 `grid.py` 用 `Path.read_text()` 读输入，**默认编码不是 UTF-8 时会报 UnicodeDecodeError** ⇒ 给它的 `.txt` 里不要写中文注释，注释单独放或改英文。

⚠️ 上游的 `styles/eastward*` 是 Eastward（Pixpil）风格卡，上游 README 自述「授权使用」但**未给 CC 许可** ⇒ **不要搬进工程、不要当素材分发**，只在本机当参考。

**冲突时以本文为准**：上游面向通用像素画（RPG 立绘、角色 48×64），本文面向 MC（16×16 方块必须可平铺、材质要符合游戏内逻辑）。上游讲「人物身高 52px」「头身比」的那些数值，与 MC 方块贴图无关，别硬套。

## 第 0 步 · 先问，再画

动手前必须确认（用户已明确的跳过，**不要重复问**）：

| 问什么 | 用户没说时的 MC 默认 |
|---|---|
| 尺寸 | 方块/物品 16×16；实体 64×64 起 |
| 风格 | 原版像素质感，硬边、无渐变 |
| 色数 | 方块物品 4–8 色；实体 ≤16 色 |
| 描边 | 深色同系描边（不用纯黑） |
| 光源 | 左上 |
| 材质的游戏内状态 | 按该材质的 MC 逻辑，见第 6 步 |

**用户的要求和 MC 惯例冲突时，永远听用户的**，并在交付时一句话说明哪里偏离了原版（用户可能是故意的）。

## 第 1 步 · 定尺寸与落点

| 用途 | 尺寸 | 落点 |
|---|---|---|
| 方块纹理 | 16×16，**必须可平铺** | 资源包 textures/block/ 下的 `<name>.png` |
| 物品图标 | 16×16 | 资源包 textures/item/ 下的 `<name>.png` |
| 粒子 | 8×8 / 16×16 | 图集行优先 |
| GUI | 256×256 图集内切 | 按控件格子对齐 |
| 实体 | 64×64 起 | 按 UV 展开 |
| 纹理动画 | 帧宽 × (帧数 × 帧高) | 竖排图集 + `.png.mcmeta` |

**可平铺**是方块纹理的隐藏要求：把图往右、往下各复制一份拼起来，接缝不能看得出。做法是让纹理元素跨过边缘时从对侧接回来。

## 第 2 步 · 规划（先写版式再画像素）

按**单位格**想，不要直接想像素坐标。16×16 的方块可以当 8×8 个 2px 格来布局。

写下来再动手：

```
画布: 16×16
主体占位: 主体 x2-13, y2-13
对称轴: x=7 (或「不对称」)
大形体坐标: ...
```

**对称的东西只画一半**，另一半用镜像生成：像素 x 的镜像是 `W-1-x`，宽 `w` 位于 `x` 的部件镜像到 `W-x-w`。奇数宽最容易错一格。

## 第 3 步 · 调色板

**色数就是质量上限。** 每种材质只要 2 档（主体 + 暗部），最多给 1–3 种材质配亮部。所有材质都配三档 = 花。

**色阶生成法**（比单纯调明暗有质感）：暗部往**冷**偏（加蓝紫），亮部往**暖**偏（加黄）。手动做两三个纯色即可，逐像素渐变交给抖动或换色阶。

**明度要分散**：别让所有暗色落在同一行，否则远看是一条糊带。

## 第 4 步 · 分层画（顺序不能乱）

这是保证「不是一堆气球拼起来」的关键。**严格按这个顺序**：

```
1. 平涂剪影    只用底色，确认形状对不对
2. 打光        上部/左侧提亮，下部/右侧压暗（一个光源，全局统一）
3. 内部细节    花纹、铆钉、锈斑 —— 必须在打光之后，否则会被当成洞而围一圈阴影
4. 描边        1px 深色同系描边
5. 特殊标记    眼睛、高光点、文字等，放最后保持干净
```

**线只画在形体交叠处**（甲片压住底板、帽檐压住头）。同一块材质内部不描线，否则就是「每块零件各自描边」的气球拼装。

**小图形用字符画**：4–6px 的圆用字符画比几何原语好看（原语在这个尺寸会画成方块）。

## 第 5 步 · 画法守则

- **剪影不对称**：1× 下涂黑成一块还能看出是什么。左右完全对称 = 认不出
- **特征比你以为的更大更黑**：16×16 里眼睛/铆钉至少 1–2px；单个 1px 点 = 噪点
- **一个光源**：暗部只在背光一侧，全图统一打光
- **禁渐变、禁抗锯齿**：透明度只 0 或 255。要有明暗就用纯色块分区
- **别做「半尺寸再放大」**：缩放器会把边缘磨圆，细节永远回不来

## 第 6 步 · 符合 MC 逻辑（用户没指定时）

纹理要让人一眼认出「这是 MC 里的这东西」。**先看该材质在游戏里的实际状态**，再决定画成什么样。

### 6.1 材质逻辑表

| 材质 | 画法要点 |
|---|---|
| 石头 / 矿石 | 以噪点为主，无方向性；矿石 = 石底 + 矿斑，矿斑要亮于石底 |
| 木头 | **有纹理方向**，年轮/木纹沿一个轴；六个面各不相同（顶底是年轮） |
| 金属（铁/金） | 平整，靠**大面积高光块**表现，不是噪点 |
| 铜 | **有四级氧化过程**，见下 |
| 玻璃 | 几乎全透明，只有边框和几条高光 |
| 植物 | 明度变化大，边缘参差，不用描边 |
| 羊毛 | 细密均匀的噪点，无高光 |

### 6.2 铜的锈蚀是**过程**，不是「铜块加一块绿」

这是最容易画错的一条。MC 的铜有**四个阶段**，是绿色逐渐覆盖铜面的过程：

| 阶段 | 外观 | 英文 |
|---|---|---|
| 0 | 纯铜色，无绿 | Unoxidized |
| 1 | 铜色为主，**绿色斑点开始出现** | Exposed |
| 2 | **绿色为主，棕色斑点** | Weathered |
| 3 | **蓝绿色为主，还有几处绿斑** | Oxidized |

要点：

- **绿色从边角开始**向外扩散（游戏里氧化从棱角开始蔓延）
- 阶段 1 的绿是**零星几点**，不是一大块
- 阶段 2 已经**反过来**了：底色变绿，剩铜色斑点
- 阶段 3 是**青绿（teal）**，不是草绿
- 涂蜡（waxed）不氧化；斧头可以刮回上一个阶段

画铜之前先问用户要**哪一阶段**。画「半铜半绿、绿占三分之一」这种中间态，游戏里没有对应物。

**成套交付**：如果这个铜件会跟着方块一起氧化，就该把四张都做出来（原版铜块/楼梯/门都是四套贴图）。只画一张然后说「这是铜」，等于没做。

其它材质同理，先问「它在游戏里是什么状态」，再决定画哪一档：

| 材质 | 游戏内状态 | 画法差别 |
|---|---|---|
| 铜 | 四级锈蚀 | 四套全做，绿色占比逐级上升 |
| 木头 | 原木 / 去皮 / 木板 | 原木带树皮纹、去皮是裸木、木板是平行板缝 |
| 石头 | 石头 / 圆石 / 石砖 | 圆石是不规则块面，石砖是规整分隔 |
| 泥土 | 普通 / 耕地 / 湿润 | 湿润明显更深 |
| 作物 | 生长阶段 | 每阶段高度和颜色不同 |

### 6.3 其它要遵守的 MC 惯例

- 方块纹理 16×16 且可平铺
- 同一套资源内不要混用 16 和 32 分辨率
- 发光类（荧石、海晶灯）用亮块 + 少量暗点，不要画真实光照
- 物品图标留 1px 透明边距，别贴满整格

## 第 7 步 · 渲染与核对（每轮必做）

```bash
node tools/render-pixels.mjs NAME.txt --out out/NAME.png --size 16x16 --scale 16
```

脚本会拦：行宽不一致、未定义色号、尺寸不符；并警告孤立像素与非 0/255 半透明。

**渲染后四问**，任何一问不过就回第 4 步：

1. **1× 认得出吗？** 把 1× 当真图标看，能不能立刻说出这是什么
2. **剪影清楚吗？** 全涂黑还有形状吗
3. **色数对吗？** 超预算就合并相邻档
4. **光照一致吗？** 暗部是不是都在同一侧

### 反向指认（最有效的一招）

遮住标题，问自己——或**让另一个 agent 只看图、不给任何描述**，问「这是什么？每个部件是什么？」。它可能读成完全别的东西。规则检查不出来的误读，只有这一招能抓。

### 读不到图时

把图打印成字符回读，一样能看出轮廓、明暗分区和五官位置。

## 第 8 步 · 纹理动画

MC 的纹理动画 = **竖排图集 + 同名 `.png.mcmeta`**，不是 GIF。

### 8.1 用参数生成每帧，不要手画

身体/主体只画一次，变化量做成参数：

```
frame(n) = base + 参数(n)      // 参数可以是呼吸幅度、帧序、缓动值
```

这样所有帧天然同尺寸、同锚点、风格一致。**手画每一帧一定会跳。**

### 8.2 排图集

每帧尺寸完全相同，总高必须能被帧高整除，否则游戏显示紫黑缺失纹理。竖排最不容易错：`16×48` = 3 帧 16×16。

### 8.3 写 mcmeta

`assets/<modid>/textures/block/<name>.png.mcmeta`（文件名**带 `.png`**）：

```json
{
  "animation": {
    "frametime": 2,
    "interpolate": false,
    "frames": [0, 1, 2, 1]
  }
}
```

| 字段 | 含义 | 默认 |
|---|---|---|
| `frametime` | 每帧几 game tick | 1 |
| `interpolate` | 自动补中间帧 | false |
| `frames` | 帧序；可只列部分帧，或用 `{"index":N,"time":T}` 定时 | 全部按序 |
| `width` / `height` | 单帧宽高；不写按最短边推断 | 自动 |

**`frames: [0,1,2,1]` 做来回循环**，比画 4 帧省事——最常用的省帧技巧。

### 8.4 动画守则

- 帧间**只改该动的部分**，别整张重画（会闪）
- 相邻帧位移 ≤ 1–2px，否则跳
- 首尾要接得上：末帧的下一帧就是首帧
- 做呼吸/起伏时，附属部件（天线、挂饰）**滞后一拍**
- 帧数够再用 `interpolate`——2 帧补间只会变糊

### 8.5 特效类动画：用规则算，不要手画

爆炸、火焰、烟花、烟雾、魔法这种**粒子特效**，每一帧都是「同一套规则在不同时刻的结果」。手画每一帧必然跳，正确做法是写规则：

```
1. 拆元素   每个元素记 t0（出生时间）、life（寿命）、初速度、方向
2. 生成     随机必须带种子（mulberry32），否则每次跑都不一样、没法逐版对比
3. 积分     固定步长推进：速度受阻力衰减，烟受浮力上升，火花受重力下坠
4. 着色     按「已飞出的距离」或「剩余寿命」取调色板一档，不是按帧号
5. 清理     只对烟团层做开运算/去碎块；1px 的火花和余烬不能一起清理
```

**四个必踩的坑**（都是上游实测出来的）：

| 症状 | 病因 | 修法 |
|---|---|---|
| 火球歪成一条斜线 / 粒子叠成一个点 | 方向完全随机会扎堆；同方向粒子速度相同会重叠 | 方向用分层抽样（黄金角或等分），同方向必须**分壳层给不同距离** |
| 中心是空的，像个环 | 按年龄着色，但粒子都从外壳出发 | 改按**已飞出距离**着色，并给球心一点持续辉光 |
| 冷却时中间一整块红盘 | 用了整体温度场，所有粒子一起变色 | 每个粒子**单独**按自己的热度冷却，时间常数 tau 各不相同 |
| 火球表面有白划痕 | 火花从中心出发却画在火球上面 | 火花画在烟团**后面**（分层绘制） |

**浅色特效必须检查亮背景**：白色、浅黄、青色在浅色背景上会消失。解法是加 1px 深色描边，或让最暗一档足够暗。

**判定要三张图一起看**：
1. 联系表（逐帧并排）——看形状和节奏
2. 三种背景（暗/中/亮）——看可读性
3. 1× 原尺寸——看游戏里的真实大小

### 8.6 MC 烟花：五种形状是确定的

做烟花前先查 MC 的机制，别自己发明。官方语料（`resources_server_loottables_lootfunctions.md`）写明：

```
shape 合法值 = "small_ball" | "large_ball" | "star" | "creeper" | "burst"
             缺省 small_ball
另有 trail（拖尾）与 flicker（闪烁）两个附加效果
```

| 形状 | 外观 | 画法 |
|---|---|---|
| `small_ball` | 小球，半径小 | 均匀球面 + 少量粒子 |
| `large_ball` | 大球，半径大 | 同球面，粒子更多、速度更大 |
| `star` | 星芒状，有放射光条 | 角度连续铺满 + 光芒方向拉长（关键：光条是**速度差**做出来的，不是画出来的） |
| `creeper` | 苦力怕脸 | 平面网格分布，正面朝观察者 |
| `burst` | 球状但密度不均 | 球面 + 随机偏置 |

**颜色是染料色**，主色 + 渐隐色（fade）。同一颗炸开的星里颜色要同步渐变，不能各变各的。

本仓库自带生成器：`common_skill/mc-texture/tools/firework.mjs`

```bash
node tools/firework.mjs out/ --shape star --fps 12 --size 32 --trail --flicker
# → firework.png（竖排图集）+ firework.png.mcmeta
```

### 8.7 预览交付：GIF 与逐帧图

图集（`.png`）在编辑器里不会动，验收时必须能看到动画。两种交付物：

```bash
# 1) GIF：给用户直接播放（零依赖，自己编码 GIF89a）
node tools/gif.mjs out/firework.png out/firework.gif --delayCs 8 --scale 6

# 2) 逐帧分解图：所有帧并排，用于核对节奏和跳帧
#    （联系表比 GIF 更能看出"哪一帧不对"）
```

**GIF 延迟换算**：`delayCs` 单位是 1/100 秒。12 fps ⇒ 每帧 8.33cs，取 8；6 fps ⇒ 16cs。

**预览页要自包含**：交付给用户的 HTML 不要把 GIF 写成相对路径 `<img src="star/firework.gif">`。
本地 `file://` 打开时，浏览器常因安全策略拒绝加载同目录资源，用户看到的是空白框 —— **而且页面不会报错，只是静默空白**。

正确做法是把 GIF 直接 base64 内嵌进 HTML：

```js
const b64 = fs.readFileSync(gifPath).toString("base64");
const img = `<img src="data:image/gif;base64,${b64}">`;
```

代价是文件变大（五个 GIF 约 108 KB），换来「双击就能看、换机器也不坏」。

**注意**：GIF 在部分聊天/文档宿主里只会显示首帧（被转成静态图）。要让人真看到动画，
**同时交付 GIF + 逐帧分解图**，或做一个 HTML 播放器页。

### 8.8 拼图集

```bash
node tools/render-pixels.mjs f0.txt f1.txt f2.txt --atlas out/x.png --frametime 3
```

自动校验每帧尺寸、竖排拼接、写出 `.png.mcmeta`。

## 第 9 步 · 接线

文件放 `assets/<modid>/textures/block/` 或 `assets/<modid>/textures/item/`，命名与模型里 `textures` 引用**逐字对齐**（大小写敏感，不要空格）。

- 引用完整性 → `audit_resources`
- 模型 JSON / blockstate → `mc-model`
- 目录与 `pack.mcmeta` → `mc-resourcepack`

## 常见翻车

| 症状 | 病因 | 修法 |
|---|---|---|
| 糊 | 生成图缩小的，不是真像素画 | 回第 4 步网格法重画 |
| 透明处有黑边 | 混了半透明像素 | 全透明或全不透明 |
| 游戏里偏色 | 非 sRGB / 带色彩配置 | 存标准 8 位 sRGB PNG |
| 紫黑格 | 图集总高不能被帧高整除 | 核 `.mcmeta` 与图集尺寸 |
| 动画不播 | mcmeta 文件名少了 `.png` | 应为 `<name>.png.mcmeta` |
| 像别的游戏 | 色数过多、加了渐变 | 压到 8 色、去渐变 |
| 认不出 | 剪影对称、特征太小 | 改剪影、放大特征 1–2px |
| 方块边缘有缝 | 纹理未做平铺 | 让元素跨边接回对侧 |
| 铜不像铜 | 画成「铜块 + 一块绿」 | 按四级锈蚀选一阶段重画 |

## 边界

- 只管**贴图**。模型形状、UV、几何动画走 `mc-model`。
- 生成式出图只当草稿，商用许可去站点条款确认，确认不了就别用。
- **风格归属由用户拍板**，本文的 MC 惯例只是用户没说时的默认值。