线条动画
动画插图组件:一切均以单支细笔在纸上绘制,通过TypeScript实时构建SVG,因此在任何尺寸下都保持清晰,支持指针交互,并可逐帧检查或导出视频。
本技能附带一个小型库(lib/)、两个示例(examples/walker,单个人物约150行;examples/village,完整场景)以及工具(scripts/)。在编写任何代码前,请先阅读最接近需求的示例。
人们如何评判这类作品
他们像动画师一样审视:逐帧、放大、对照现实或参考。以下均是实践中被拒绝的情况,每条都附有对应规则:
- 像正弦波一样上下浮动的行走、滑动的脚、为了匹配延时摄影而加速的步态
- 画成封闭管状的肢体;尺子一样直的线条;忽略腿部的僵硬衣服
- 一次性构建整套看起来都雷同的组件(“AI垃圾”)
- 感觉像UI界面而非世界反应的交互
- 页面背景随场景变化(“看起来像个视频,而不是组件”)
- 在需要新角色时重复使用参考片段中的角色
- 残留的没人要求的道具(他们要求移除晾晒的衣物)
工作流程
- 在任何东西动起来之前先定下风格。 阅读
references/style.md。确定笔触粗细、纸张颜色、人物比例以及一到两种扁平强调色。 - 搭建项目。 将
lib/复制到项目中(共四个无依赖的.ts文件),使用 Vite + TypeScript(此仓库中的package.json、tsconfig.json和vite.config.ts展示了设置方式)。组件是纯 DOM/SVG——无需框架。 - 先构建一个人物或一个角落 并展示(截图或短片段,如有参考则放在旁边)。获得肯定后再构建其余部分。一次性构建完整集合会显得千篇一律;一个好的局部组件能展示水准。
- 让它动起来,使用
references/motion.md:gait()用于行走和奔跑,spring()/pendulum()用于任何附着或悬挂的物体,使用curve()或sequence()设置关键姿态。永远不要用自由正弦波来动画人物。 - 组合场景,使用
references/scenes.md:一个自包含的卡片,固定在页面背景上,分层,使用lib/daynight.ts的时段调色板,灯光,风,按计划到达的人物,互动即反应。 - 在展示之前先验证,使用
references/verify.md:类型检查,?debug逐步调试,每个运动物体的放大帧条,触发并逐步执行交互,在真实标签页中保持60 fps。 - 如果有要匹配的参考片段,请遵循
references/matching.md——从片段中测量时间和姿态,而不是猜测,并使用叠加层检查。 - 如果他们想要视频,请遵循
references/video.md(使用scripts/capture.ts进行确定性无头捕获,然后使用 ffmpeg)。
使其有效的规则
- 肢体是两条开放的笔画。
limb()绘制一条填充纸张的带子,两端的两条墨线在远端提前收住。这是该风格的标志。 - 线条弯曲。 关节是圆润的,边缘略弯,没有直线般笔直的线条。
- 在每个人物自己的空间内绘制 — 约95单位高,脚在y=120处,面向+x方向,站在x=40处 — 并使用
place(g, x, y, dir, scale)放置。比例、笔触粗细和步态预设均基于此空间。 - 衣服围绕骨骼绘制。 先解决腿(即使隐藏),然后根据前膝和脚踝绘制外套或和服下摆,使布料随步伐张开和闭合。袖子、围巾和包袋在弹簧上摆动。
- 脚是固定的。 以
步态速度 × 缩放每秒精确移动行走者,支撑脚保持静止在地面上。不要为了匹配压缩的时间而加速步态;应压缩到达事件。 - 墨线是
currentColor,纸张是.pp。 使用applyPalette()重新着色整个绘图(白天:深色墨线在暖色纸张上;夜晚:浅色墨线在靛蓝纸张上,暖色窗户)。纯色肢体填充必须使用solid(),否则纸张的 CSS 会胜出。 - 组件是一个卡片。 页面周围的背景从不改变颜色。
- 交互是世界在反应:人物鞠躬或脱帽致意,猫醒来并伸懒腰,灯笼摇摆,鸟群散开,鱼跃出水面。指针移动即为风。场景内没有按钮;一个微弱的提示线就足够了。
- 新就是新。 如果需求是“像这个但要新的”,请保留媒介、笔触、比例和节奏感,创造新的人物、活动和道具。
库一览
| 模块 | 提供的功能 |
|---|---|
lib/ink.ts | pen shape blot paint limb solid joint hang curve shoe place toScene inkDefs run |
lib/gait.ts | `gait({ kind: 'walk' |
lib/motion.ts | spring pendulum pointerWind sequence seeded wobble ease minJerk … |
lib/daynight.ts | palette(τ) → 天空、纸张、墨线、lamp、night、sunset;applyPalette nightTint |
run(draw) 使用 draw(t, dt) 驱动一切。有两个 URL 标志用于检查:?t=1.25 冻结时间;?debug 禁用 rAF 并暴露 window.__step(seconds) 用于精确步进(后台和无头标签页会限制 rAF,因此务必在调试模式下验证)。
脚本
scripts/capture.ts <url?debug> <outDir> <frames> <fps> [w] [h] [setupJs] [frameJs]— 从无头 Chrome 获取确定性的 PNG 帧scripts/clip.ts period|blobs …— 循环一个区域的长度;每帧进行暗斑跟踪scripts/frames.sh <clip> x y w h outDir— 用于从片段读取姿态的网格叠加帧
所有脚本在 Node ≥ 22.18(TypeScript 被原生剥离)和 ffmpeg 下运行;capture.ts 会查找本地 Chrome/Chromium 或使用 $CHROME。