---
name: anidoodle
description: "要用代码画画、做动画时用：32 种画风的静帧、自己画出来的延时、任意长度的影片、讲解片、发布片和响应页面操作的网页动画，配乐也用代码作曲；能仿用户给的图的画风、跨镜头保持角色一致、教人画画。同一份源码到哪都出同样的像素和声音，不用任何生成素材"
metadata:
  created_by: Alex Greenshpun (10x Company)
  license: Apache-2.0
copy_source: true
---

# anidoodle：用代码手绘插画、动画和配乐

图画、影片和可交互作品，全部用代码画出来：一个纯函数画出每一帧，另一个函数写出配乐并演奏。同一份源码在任何机器上、任何尺寸、任何长度下，都重建出同样的像素和同样的声音。八种原声乐器可以改用真乐器的录音演奏，录音来自一个可选下载的音源包。做任何配乐之前先**问**用户；用户没开口，绝不下载。

引擎是容易的那一半。作品被扔掉，原因无非几种：没有要点；主体画成了它自己的图标；换个画风只是换了调色板；音乐没有曲式；或者用户的耐心全耗在审批零碎片段上。这个 skill 带着防住每一种的手艺。如果要调用 2 次以上本 skill 中的脚本，直接将[本 skill](anidoodle.tar.gz)下载到本地。引擎要先用 `scaffold.mjs` 拷成一个作品项目才能跑，出一张静帧就要用到整个 `engine/`。

<figure markdown>
![32 种画风各一张样张：铅笔水彩、马克笔漫画、丝网印刷、像素画、积木、木版画、水墨、刮画、刺绣、油画、民间故事绘本等](assets/styles.jpg)
<figcaption>风格总览图：32 种画风各一张，整张图也是本 skill 的代码画的</figcaption>
</figure>

## 先问，再动手 { #start }

读 [references/workflows/start-here.md](references/workflows/start-here.md)，照它跑需求问询：一次只问一个问题，只问请求里没说清的，先给出你的推荐。问清做的是**什么**；用**哪种画风**（给用户看[风格总览图](assets/styles.jpg)，清单在 [references/styles/index.md](references/styles/index.md)）；什么**画幅**（1x1、9x16、16x9、4x5 或任意 W x H）；多**长**（任意长度，绝不设上限）；什么**声音**（静音，或一种音乐风格加情绪）；有哪些**角色**。用户说「你来定」，就定下来，用一句话说理由。动笔之前，把答案写成一份简短的 brief。

## 你要做的是什么？ { #route }

| 要做 | 读 |
|---|---|
| 一张静帧：头图、点缀图、封面、卡片、海报、一个系列 | 下文「静帧」工作流，[references/formats.md](references/formats.md) |
| 一张自己画出来的图（延时绘制、制作过程） | [references/workflows/drawing-process.md](references/workflows/drawing-process.md) |
| 循环动画、贴纸、动态 logo、页头氛围动画 | 下文「循环」工作流，[references/formats.md](references/formats.md) |
| 一部故事片，任意长度 | 下文「影片」工作流，[references/workflows/long-film.md](references/workflows/long-film.md) |
| 讲解片或信息图；把一个想法讲清楚的机器，会拆开再合上，任意画风 | [references/workflows/explainer.md](references/workflows/explainer.md)、[references/workflows/machines.md](references/workflows/machines.md) |
| 发布片或产品视频（欢迎用户的截图） | [references/workflows/launch-video.md](references/workflows/launch-video.md)；在 [references/workflows/launch-video-kit.md](references/workflows/launch-video-kit.md) 上搭 |
| 网站用图（标题旁的头图、卡片、点缀图、分享卡），贴合页面底色和品牌；或会响应界面操作的网页动画 | [references/workflows/site-art.md](references/workflows/site-art.md)；[references/workflows/interactive.md](references/workflows/interactive.md) |
| 一堂绘画课，或者「这个怎么画？」 | [references/workflows/teach-drawing.md](references/workflows/teach-drawing.md) |
| 一个在任何画风里都长得一样的角色（从木版画到积木） | [references/workflows/character-consistency.md](references/workflows/character-consistency.md)、[references/anatomy.md](references/anatomy.md) |
| 照用户的图仿一种画风 | [references/workflows/adapt-a-style.md](references/workflows/adapt-a-style.md) |
| 配乐或原声 | [references/music/index.md](references/music/index.md) |

- **配乐**：选一种风格词汇和情绪，勾出张力曲线和动机，自己写和声、动机和曲式（[references/music/compose.md](references/music/compose.md)，手艺细节在 [references/music/theory/](references/music/theory/index.md)），反复改到 `node tools/music.mjs craft` 只剩你自己选择保留的发现，然后 `check` 必须 PASS，才交给人听。风格只给音色，从不给音符。
- **音效**：[references/music/sound-design.md](references/music/sound-design.md)。14 种分层、带种子的音效，按影片帧号打点（`filmSfx`）；配乐在音效下闪避，每个 cue 都要在配乐的 6 dB 以内。音效只用在真正的方向变化上，一部影片一个上升音加一个冲击音。
- **发布片**：`launchTemplate.ts` 用数据渲染一部产品发布片（prompt、样张或产品自己的界面、为它作的配乐、文字、安装命令），一条时间线出所有画幅；`launch.mjs new|ship`。省 token 的习惯见 [references/working-method.md](references/working-method.md)。

## 七条铁律 { #laws }

开一个作品项目时，把这七条写进该项目的 `CLAUDE.md` 或 `AGENTS.md`：一条一行、只写加粗那句，末尾附上本页网址。只读不写，换个会话就丢了。

1. **每件作品都有一个要点。** 静帧：一个想法、一个视觉焦点、一处光源。影片：一次转变、一个结局回报、一个会重现的信物。→ [references/storytelling.md](references/storytelling.md)
2. **把写实说出来。** 动物、物件、人一视同仁：说出解剖结构、视角，以及你打开看过的参考。→ [references/realism-and-craft.md](references/realism-and-craft.md)、[references/anatomy.md](references/anatomy.md)
3. **画风是一种落笔的方式。** 要变的是媒介、边缘和笔触的先后顺序，不止调色板。→ [references/style-guide.md](references/style-guide.md)
4. **先在一张静帧上证明画面效果，再从头做到尾。** 过了这一步，闸门花的成本比省下的多。→ [references/working-method.md](references/working-method.md)
5. **契约是绝对的。** 对 `(frame, env)` 是纯函数（可交互作品再加上输入日志），随机只用 `rng(seed)`，不读时钟，不用 `ctx.filter`。不用任何生成素材；用户自带的图可以按哈希嵌入，音源包是单独下载、钉了哈希的。→ [references/determinism-and-contract.md](references/determinism-and-contract.md)
6. **音乐要有计划，绝不只靠一个形容词。** 一种风格、每段一个情绪、音符写成数据，然后测量。你听不见它，所以发布前要有一个人听一遍。→ [references/music/](references/music/index.md)
7. **一个负责导演，一个负责搭建，两边都把一切写下来。** 会话变差就重开，文件就是记忆。→ [references/working-method.md](references/working-method.md)

## 工作流 { #workflows }

**静帧。** (1) 用一句话说出想法；点出视觉焦点和光源。(2) 凡是真实存在的东西都打开真实参考，并记下看的是哪些。(3) 选定画风，先读它样张的文件头。(4) 按那种媒介的顺序画。(5) 用 `still.mjs` 按全尺寸渲染，在局部裁切（脸、手、脚、连接处；[references/craft-bar.md](references/craft-bar.md)）上做**细节检查**，书面写下批评。✋ **一次审批。** (6) 从同一份源码交付要的每种尺寸和画幅，并说明打印出了 `reproducible`。

**循环。** 同静帧，另加：每个运动在整个循环内都是周期性的，主体从不停下，每秒都有东西在动。检查接缝（最后一帧对照第 0 帧）。✋ **一次审批，看的是动起来的文件。**

**影片。** (1) **用三句话说出故事**（铺垫、转变、结局回报）和信物；更长的影片写一条主线，每章加一行。写不出来就停下。(2) **网格**：一拍是 60 x fps / bpm 帧，bpm 取自 brief 里的配乐；一张 cue 表记下所有帧号，加载时有检查器跑一遍。(3) 凡是真实存在的东西都找**真实参考**。(4) **一张定调静帧**，选最难的那一帧，书面写下批评。✋ **审批。** (5) **一段 8 秒的音乐小样。** ✋ **审批。** (6) **把整部影片做出来**；机械检查一直在跑。(7) 对着渲染出的文件**只评审一次**，分四遍读并打分（[references/workflows/review.md](references/workflows/review.md)）。一份排好序的修改清单，一次重做。(8) ✋ **最终审批。** 说清你验证了什么、哪些验证不了。

审批闸门最多三道。审批是预算，花在做错代价高的地方。

## 手艺及格线 { #craft-bar }

检验标准：一位有自尊心的人类插画师会交出这件作品吗，还是它只是机械的形状拼装？完整的露馅清单在 [references/craft-bar.md](references/craft-bar.md)。最常毁掉作品的几种：主体用椭圆拼成；一种画风换个颜色就当另一种；东西是淡入的而不是画出来的；人的关节像橡皮；同一个角色每个镜头画得都不一样。

**不许冷场**：凡是会动的东西，每秒都要有看得见的变化。一部自己画自己的影片要声明 `kind: "drawing"`，按更细的下限评判；画面冻住就算不合格。

## 引擎 { #engine }

`engine/` 是可移植的绘画核心加一套工具。`engine/src/canvas-core` 对 DOM 和任何后端一无所知。`W`、`H`、`fps` 和 `durationFrames` 来自影片的 `meta`。镜头运动（摇移、推拉、视差、抖动）：`engine/src/canvas-core/camera.ts`，[references/camera.md](references/camera.md)。场景之间的接缝：[references/motion-grammar.md](references/motion-grammar.md)（带着运动过去的剪辑用 `seams.ts`；`tools/deck.mjs deal` 给每部影片发不同的动作）。`render.mjs --blur auto` 渲染时加运动模糊（每帧按测得的屏幕速度决定子帧数，在线性光下平均，`--shutter 180`）；默认路径不受影响。

```bash
node <skill>/engine/tools/scaffold.mjs ~/art --film intro --format 9x16 --duration 12   # 时长单位是秒
cd ~/art && npm install && npx playwright-core install chromium
node tools/still.mjs intro --frame 0 --out out/look.png --scale 2   # 一张静帧，附带它的哈希
node tools/render.mjs intro                                        # MP4；--out x.gif|x.webm|x.apng
node tools/gate.mjs intro                                          # 确定性、契约、冷场
node tools/emit.mjs intro --out out/intro.html                     # 一个自包含、可离线的播放器
node <skill>/engine/tools/soundfetch.mjs list    # 不联网。什么都没装、也没被拒绝过：现在就问用户
node <skill>/engine/tools/soundfetch.mjs get piano rooms  # 用户同意后再下（piano 233 MB，全部 373 MB）。用户拒绝：soundfetch.mjs decline
```

| 工具 | 做什么 |
|---|---|
| `scaffold.mjs` | 建一个带静帧和/或影片的项目；`--duration`、`--format` 或 `--size`、`--fps`、`--bpm` |
| `still.mjs` / `render.mjs` | 每帧画两遍并给出哈希（`--frames a,b,c --sheet` 在一个浏览器里出多帧）/ 把作品导出成任意格式（`--from/--to`、`--poster-frame`、`--hashes`） |
| `gate.mjs` | 确定性、契约、冷场、产物；`--self-test` 证明它真的会判失败 |
| `registry.mjs` / `gallery.mjs` | 从样张自身读出画风清单 / 出总览图 |
| `verify-export.mjs` | 对渲染出的文件做质检：帧数、时长、配乐、音画同步、编码后的真峰值；可选检查首帧、循环接缝、`--delivery` |
| `launch.mjs` | 发布片：`new <name>` 生成 spec 骨架；`ship <name> --shapes 16x9,9x16` 对每种画幅渲染、出封面帧、出字幕、验证并做 framecheck（`framecheck.mjs`：没有文字被画面边缘切掉） |
| `test.mjs`（`npm test`） | `engine/test/` 里的单元测试，不开浏览器 |
| `music.mjs` | `vocab` 打印一种风格的词汇；`check` 给你写的配乐把关（调性、母带、守卫、分轨、新颖度、哪些声部用了录音）并打印它的形状（ARC）；`render`、`meter` |
| `soundfetch.mjs` | 音源包：`list`、`get <id...\|all>`、`where`、`verify`、`remove <id>` |
| `sfx.mjs` | 音效包：`list`、`one`、`kit <dir>`、`test` |

四个后端，一个绘画核心：`playwright`、`html-player`、`remotion`、`hyperframes`。→ [references/backends-and-adapters.md](references/backends-and-adapters.md)；适配器的接口约定在 [engine/tools/adapters/index.md](engine/tools/adapters/index.md)。

## 范例 { #examples }

- **画风样张**在 `engine/src/canvas-core/` 里：每种手笔都有一张静帧和一部画它的过程片，配方写在文件头和 [references/styles/](references/styles/index.md) 下各画风的那一篇里。
- **MECHANICAL LEPIDOPTERA（机械鳞翅目）**在 [example/](example/index.md)：一部 47 秒、带配乐的影片。`scaffold.mjs ~/study --example`。

**研究它们，不要照抄。** 手艺及格线不会因为继承而达标。

## 老实交代的局限 { #limits }

你听不见配乐，看不到影片动起来，也用不了可交互作品。关于它们的每一个说法，要么来自测量，要么来自人；说清是哪一种。绿色对勾在你重跑之前只是一个说法。信任一张静帧之前，先用 `--scale 2` 渲染一帧：一个图层按错的变换贴上去，1x 下看着没问题，2x 就露馅了。

<div class="zx-no-copy" markdown>

---

维护记录：[从哪来、改了什么](../../maintenance/skills/anidoodle.md)。正常使用不用看，出问题再看。

</div>
