SpriteMark
用与矢量角色相同的状态驱动的手绘角色。
两套精灵图和一个程序化角色,都响应同一个协议编号。已经基于 CharacterMark 写好状态逻辑的宿主,可以换成吉祥物而不用碰那些逻辑。
什么时候用
矢量角色是 Aora Bot 的核心:程序化、可主题化、零重量。SpriteMark 是给它们做不到的那一件事留的逃生口——一张特定的插画。品牌吉祥物、手绘的宠物、从照片衍生的生物:那些无法生成的外观。
代价是实在的。一套图是几十个 PNG 文件而不是几 KB 的数学;它不会跟随主题换色;它只有别人画过的那些状态。把它当作资源,而不是可配置的组件。
清单文件
sprite-mate 清单是一个 PNG 帧文件夹加一个按协议编号索引的 manifest.json:
frames把编号映射到一个或多个相对于清单的文件名。一个文件是静帧;多个按fps播放。fallback让一套图通过指向已画的编号来回答没画的编号。链最多跟随四跳,所以一个笔误不会挂起页面。anchor是角色在帧内站立的位置,以比例表示。图片按object-fit: contain并据此定位,所以高的图集脚仍落在盒子的脚上。
一个编号的解析顺序:编号本身;清单的 fallback 链;分段回退——未知的代理工作编号显示这套图的「处理中」帧,未知的情绪显示待机;最后是待机(02),每套合法的图都必须有它。
spriteCoverage(manifest, EMOTION_IDS) 审计一套图:直接服务多少编号、回退服务多少、完全没有多少,以及所有制作问题(缺待机、悬空回退、循环)。在测试里对你自己的图集跑一遍。
加载
给 src 时清单在客户端获取,并按 URL 缓存到页面结束。在它到达之前——以及失败时——标记显示一个同状态的程序化角色,所以布局永远不会有洞、状态也不会丢。传 fallback="none" 则显示空盒子。
服务端渲染,或清单已打包时,直接传入清单和帧所在目录。第一帧作为纯标记输出。
导入的 JSON 文件会把 kind 推断为 string,所以先用 isSpriteManifest 收窄类型:
动效
帧用单个 <img> 显示,其 src 按定时器切换。没有 canvas,除了这一次赋值之外没有逐帧 JavaScript,而且片段的其余帧会预加载,所以切换从不闪出空盒子。在 prefers-reduced-motion 下只显示第一帧。paused 停在第一帧;fps 覆盖清单的帧率。
内置图集
文档在 /sprites/ 下带了两套用于演示,都是项目原创,遵循 Aora Bot 许可协议:bao,32 个编号,每个 2 帧,由程序化的 bao 角色(bun 形状)渲染而来;tapkin,12 个编号,每个 1 帧,一只提示词设计的仓鼠。它们的 provenance 块记录了各自的制作过程。
API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
alt | string | — | Accessible name. Defaults to the manifest's English name. |
baseUrl | string | — | Directory the manifest's frame files live in. Required with `manifest`. |
className | string | — | — |
color | "black" | "brown" | "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "magenta" | "pink" | "gray" | — | — |
emotionId | string | — | Protocol id. Ignored when `state` is given. |
face | "cute" | "eyes" | "line" | — | — |
fallback | SpriteFallback | 'character' | Shown until a `src` manifest arrives, and if it fails: a procedural character in the same state, or an empty box of the final size. |
fps | number | — | Override the manifest's frame rate. |
manifest | SpriteManifest | — | An already-loaded manifest. Takes precedence over `src`. |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | — |
paused | boolean | false | Hold the first frame. |
preset | "mochi" | "sprout" | "boba" | "dot" | "pip" | "byte" | "momo" | "bao" | "teal" | "tato" | "hexa" | "tri" | "gemma" | "nimbo" | "dew" | "leafy" | "bloom" | "lucky" | "shroom" | "oak" | "pokey" | "nashi" | "pomme" | "berry" | "cheri" | "twinkle" | "luv" | "ember" | "chime" | "jelly" | "beep" | "luna" | "finn" | "inky" | "miso" | "hop" | "bruno" | "chirp" | "waddle" | "oink" | "rusty" | "kiki" | "maru" | "zip" | "fuzz" | "lulu" | — | A named character from the cast, supplying the four above together. |
shape | "mochi" | "circle" | "blob" | "pebble" | "egg" | "bun" | "tofu" | "pill" | "hex" | "onigiri" | "gem" | "cloud" | "drop" | "leaf" | "flower" | "clover" | "sprout" | "mushroom" | "acorn" | "cactus" | "pear" | "apple" | "strawberry" | "cherry" | "star" | "heart" | "flame" | "bell" | "ghost" | "bot" | "crescent" | "boba" | "fish" | "octopus" | "cat" | "bunny" | "bear" | "chick" | "penguin" | "pig" | "fox" | "bob" | "topknot" | "spiky" | "curly" | "twinbuns" | — | — |
sizePx | number | 28 | — |
src | string | — | URL of a `manifest.json`. Frames resolve relative to it. Fetched on the client; use `manifest` + `baseUrl` instead to render on the server. |
state | "idle" | "sleeping" | "drowsy" | "bored" | "waking" | "powering-down" | "listening" | "thinking" | "searching" | "reading" | "writing" | "working" | "loading" | "progress" | "radar" | "orbit" | "dictating" | "uploading" | "sending" | "receiving" | "messaging" | "waiting" | "spawning" | "composing" | "dragging" | "happy" | "excited" | "laughing" | "playful" | "proud" | "celebrate" | "bouncing" | "shy" | "sad" | "angry" | "confused" | "suspicious" | "surprised" | "scared" | "curious" | "humming" | "alerting" | "notifying" | "refusing" | — | — |
style | CSSProperties | — | — |