oh-my-pet
v0.1.2
Published
Low-intrusion, extensible pixel pets for any web page
Maintainers
Readme
oh-my-pet
English | 简体中文
一个低侵入、可扩展的像素网页宠物库。宠物生活在网页元素的边缘上:在卡片顶上走、跳、爬墙、飞,挖个洞钻进去再从别处冒出来;可以戳、拖起来扔,也会搬东西、说话、彼此串门。
- 只读取几何位置和你主动声明的
data-pet-*属性,不读页面文字,不修改页面元素 - Shadow DOM 隔离样式,兼容严格 CSP;零网络请求
- 命中区之外,页面原有的滚动、点击、键盘行为完全不受影响;手指在宠物身上滑动照常滚动页面,长按才拎起来
- 一切能力都是插件:移动方式、离散移动的变体、动作、场景、插件都能由第三方扩展
- 体积小、开销低:核心压缩并 gzip 后约 26KB,加上全部内置插件约 31KB;
<script>直接引入的整包(含 Mochi 和全部插件)gzip 后约 34KB。空闲时主线程每秒不到 1ms
安装
npm i oh-my-pet oh-my-pet-pack-mochioh-my-pet-pack-mochi 是默认角色 Mochi;自己做角色包用命令行工具 oh-my-pet-cli。
最快的用法
<script src="https://unpkg.com/oh-my-pet/dist/oh-my-pet.iife.js" async></script>
<oh-my-pet characters="mochi" activity="calm"></oh-my-pet>
<article data-pet-surface="top left right">宠物可以站在这张卡片上,也能爬两侧</article>这个整包已经带了默认角色 Mochi 和全部内置插件。地址栏加上 ?pets-debug 可以看到调试面板:所有的边、规划的路线、每只宠物的状态和需求。
在构建工具里用
import { createPets } from "oh-my-pet";
import { cursor, drag, items, persist, quietControl, scroll, speech } from "oh-my-pet/plugins";
import mochi from "oh-my-pet-pack-mochi";
const stage = createPets({
characters: [mochi],
plugins: [drag(), cursor(), scroll(), speech(), items(), persist(), quietControl()],
locale: "zh",
scale: 3
});
const pet = stage.pet("mochi");
await pet?.goTo({ tag: "featured" });
await pet?.say("你好");createPets 根据传入的插件推导类型:没装 speech 时,pet.say 在 TypeScript 里就不存在。在 React 里,放进一个客户端组件的 useEffect,清理时调用 stage.destroy()。
告诉宠物页面上有什么
| 属性 | 含义 |
| --- | --- |
| data-pet-surface="top left right" | 元素的哪些边可以站或爬,默认 top |
| data-pet-tags="featured ai" | 给行为和场景用的标签 |
| data-pet-item="vue" | 可以被拿起、搬运的物品 |
| data-pet-avoid | 宠物不进入、气泡不遮挡的区域 |
不方便改 HTML 时,用 createPets({ surfaces, items, avoid }) 传选择器。
宿主 API
stage.add(pack, { id: "mochi-2", palette: "sakura" });
stage.pet("mochi")?.do("sleep");
stage.pet("mochi")?.appear({ tag: "featured" }, "dig");
stage.itemVisual("vue", "<svg …>…</svg>");
stage.setActivity("reactive"); // 只回应访客,不主动活动
stage.pause();
stage.resume();
await stage.quiet(true); // 全部退场
stage.destroy();
stage.on("deliver", ({ item, target }) => {
// 宠物把物品搬到了 target 上;后果由你决定
});所有让宠物做事的请求都按优先级调度:拖拽 > 宿主指令 > 场景 > 反应 > 自主行为。被抢占的任务以 OhMyPetError(code: "aborted")失败,任务名下的移动、动画、台词、物品占用都会统一清理。
场景
stage.scene("greet", {
cast: ["mochi", "sakura"],
async run({ cast, world, parallel }) {
const featured = world.edges.find({ tag: "featured" });
if (!featured) return;
await parallel(
(b) => b.cast.mochi.goTo(featured),
(b) => b.cast.sakura.goTo({ edge: featured.id, s: featured.length - 30 })
);
await cast.mochi.say("看这个!");
}
});
await stage.play("greet", { queue: true });开始前一次性占用全部参与者;宿主指令或拖拽抢占了任意一只,整个场景取消。parallel 任一分支失败会取消其余分支。
角色包
角色包是纯数据。源格式是一个 TypeScript 模块:
import type { PackSource } from "oh-my-pet/pack";
const pack: PackSource = {
meta: { id: "blob", name: "Blob", formatVersion: 1 },
palettes: { default: { ".": null, k: "#1c2536", w: "#ffffff" } },
sprites: { idle_0: { anchor: [3, 4], rows: ["..kk..", ".kwwk.", "kwwwwk", ".kkkk."] } },
hitbox: [-3, -4, 6, 4],
clips: {
idle: { frames: [{ sprite: "idle_0", ms: 500 }], loop: true }
// walk、fall、land、drag、sleep、talk 也是必备的
},
traversals: { walk: { speed: 30 }, drop: true }
};
export default pack;oh-my-pet-cli pack check src/blob.ts
oh-my-pet-cli pack build src/blob.ts -o dist/blob.js
oh-my-pet-cli pack preview src/blob.ts
oh-my-pet-cli pack import-aseprite blob.json -o src/blob.ts在 Vite 里也可以直接 import pack from "./blob.ts?pack",配合 oh-my-pet/vite 里的 ohMyPetPacks() 插件,改了源文件会热更新。用 TypeScript 时,在 tsconfig 的 compilerOptions.types 里加上 "oh-my-pet/client",?pack 导入就有类型。
协议
MIT
