27 KiB
Spine 动画集成手册
本手册针对 gameabc 引擎 的 Spine 项目,说明如何在 Canvas 2D 游戏中加载、 控制和管理 Spine 骨骼动画。
运行时版本:spine-canvas 4.2 | 引擎:gameabc
目录
- 项目结构总览
- 快速开始:5 分钟跑通
- 从 Spine 编辑器导出资源
- SpineMgr 完整 API 参考
- 事件系统
- 常见用法示例
- 与 gameabc 精灵系统配合
- 性能优化建议
- 常见问题排查
- 附录:文件加载顺序
1. 项目结构总览
Projects/Spine/
├── index.html ← 入口 HTML
├── js/
│ ├── spine-canvas.js ← Spine Canvas 2D 运行时 (第三方库)
│ ├── gameabc.min.js ← gameabc 游戏引擎
│ ├── SpineMgr.js ← ★ Spine 动画管理器(独立文件,自动挂钩渲染)
│ ├── gamemain.js ← 游戏主逻辑(无需修改)
│ ├── Spine_Event.js ← Spine 事件回调 (complete / event)
│ └── Project1_Event.js ← 精灵事件单元
├── assets/
│ ├── bmp/ ← gameabc 图片资源
│ └── spine/ ← ★ Spine 资源目录
│ ├── xxx.json ← 骨骼数据 (Spine 编辑器导出)
│ ├── xxx.atlas ← 图集描述文件
│ └── xxx.png ← 图集纹理图片
├── output/ ← gameabc 编译输出的配置数据
├── save/ ← gameabc 编辑器保存的 XML
└── docs/ ← 本文档所在目录
关键文件说明
| 文件 | 作用 | 需要修改 |
|---|---|---|
js/spine-canvas.js |
Spine 4.2 Canvas 渲染运行时 | ✗ 不要修改 |
js/SpineMgr.js |
Spine 动画管理器,自动初始化+自动渲染 | ✗ 不需要修改 |
js/gamemain.js |
游戏主逻辑(保持原样) | ✗ 不需要修改 |
js/Spine_Event.js |
Spine 动画完成/自定义事件回调 | ✓ 处理动画事件 |
assets/spine/* |
Spine 导出的资源文件 | ✓ 放入你的资源 |
2. 快速开始:5 分钟跑通
第一步:准备 Spine 资源
将 Spine 编辑器导出的 3 个文件复制到 assets/spine/ 目录:
assets/spine/
├── hero.json ← 骨骼 JSON
├── hero.atlas ← 图集描述
└── hero.png ← 图集纹理
第二步:在任意位置调用 API 播放动画
无需手动 load,直接调用 setAnimation 即可。如果该 Spine 实例尚未加载,会自动根据 id 查找资源(约定文件名 id.json / id.atlas)并创建实例。
渲染也已自动挂钩到 gameenddraw,开发者无需编写任何渲染代码。
在任何 gameabc 事件回调中直接调用即可:
// 设置位置 + 播放动画,无需先 load
gameabc_face.spineMgr.setPosition("hero", 640, 500);
gameabc_face.spineMgr.setAnimation("hero", "idle", true);
// 之后切换动画
gameabc_face.spineMgr.setAnimation("hero", "attack", false);
约定:id 必须与资源文件名一致(不含扩展名)。例如 id 为
"hero", 则需要assets/spine/hero.json、hero.atlas、hero.png。可选:如果资源不在默认路径
assets/spine/,可手动调用一次init():gameabc_face.spineMgr.init("other/path/");
第三步:打开 index.html 即可运行
用浏览器打开 index.html(需要 HTTP 服务器环境,不能直接 file://):
# 简易方法:在项目目录下启动 HTTP 服务
cd codes/games/client/Projects/Spine
npx http-server -p 8080
# 浏览器访问 http://localhost:8080
或者使用 gameabc 自带的预览环境。
3. 从 Spine 编辑器导出资源
导出设置(Spine 编辑器 → Export)
- 打开 Spine 编辑器,加载你的
.spine项目 - 点击菜单 Spine → Export...
- 左侧选择 JSON 格式
- 配置项:
| 设置项 | 推荐值 | 说明 |
|---|---|---|
| Output folder | assets/spine/ |
直接导出到项目目录 |
| Extension | .json |
骨骼数据格式 |
| Create atlas | ✓ 勾选 | 同时生成图集 |
| Atlas extension | .atlas |
图集描述格式 |
| Images folder | (默认) | 纹理来源 |
| Max width/height | 2048 / 2048 | 图集纹理最大尺寸 |
| Pack settings → Power of two | ✓ 勾选 | 纹理尺寸为 2 的幂 |
| Pack settings → Premultiply alpha | ✗ 不勾选 | Canvas 2D 不需要预乘 |
- 点击 Export 按钮
导出后得到的文件
hero.json ← 骨骼数据,包含骨骼、插槽、动画等
hero.atlas ← 图集描述,记录每个区域在纹理中的位置
hero.png ← 图集纹理图片
注意:如果图集很大被拆分成多张(
hero.png、hero2.png),所有.png都需要放到assets/spine/目录中。.atlas文件会自动引用它们。
4. SpineMgr 完整 API 参考
SpineMgr 挂载在 gameabc_face.spineMgr 上,所有调用形如:
gameabc_face.spineMgr.方法名(参数);
4.1 init(basePath) (可选)
手动设置资源根路径。通常无需调用——load() 会自动以默认路径 "assets/spine/" 初始化。
仅在资源放在其他目录时才需要调用:
gameabc_face.spineMgr.init("other/spine/path/");
| 参数 | 类型 | 说明 |
|---|---|---|
basePath |
string | Spine 资源文件的根目录路径,需要以 / 结尾。省略则使用默认值 "assets/spine/" |
4.2 load(id, jsonFile, atlasFile, option) (可选)
手动加载一组 Spine 资源并注册为一个实例。通常无需调用——所有 API 在实例不存在时会自动加载。
仅在以下情况需要手动调用 load():
- id 与文件名不一致(如 id 为
"mj"但文件为mj_gangshangkaihua.json) - 需要在加载时指定 skin、scale 等初始参数
gameabc_face.spineMgr.load("hero", "hero.json", "hero.atlas", {
x: 640,
y: 500,
scale: 0.5,
skin: "default",
animation: "idle",
loop: true,
mixDuration: 0.2
});
| 参数 | 类型 | 说明 |
|---|---|---|
id |
string | 唯一标识,后续所有操作通过此 id 引用 |
jsonFile |
string | 骨骼 JSON 文件名(相对于 basePath) |
atlasFile |
string | 图集 atlas 文件名(相对于 basePath) |
option |
object | 可选配置对象 |
option 字段:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x |
number | 0 | Canvas X 坐标 |
y |
number | 0 | Canvas Y 坐标 |
scale |
number | 1 | 初始缩放比例 |
skin |
string | "default" |
初始皮肤名称 |
animation |
string | null | 加载完成后自动播放的动画名 |
loop |
boolean | true | 默认动画是否循环 |
mixDuration |
number | 0.2 | 动画切换时的过渡混合时长(秒) |
4.3 setAnimation(id, animName, loop, track)
切换动画。立即替换指定轨道上的当前动画。如果实例尚未加载,会自动根据 id 加载资源并在就绪后播放。
// 无需先 load,直接调用即可
gameabc_face.spineMgr.setAnimation("hero", "attack", false);
gameabc_face.spineMgr.setAnimation("hero", "run", true, 0);
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id |
string | - | 实例标识 |
animName |
string | - | 动画名称(必须在 Spine 中存在) |
loop |
boolean | true | 是否循环播放 |
track |
number | 0 | 轨道号(多轨道可叠加动画) |
返回值: TrackEntry 对象,可用于进一步控制;加载未完成时返回 null。
4.4 addAnimation(id, animName, loop, delay, track)
将动画添加到播放队列,在当前动画结束后自动播放。
// 先播放 attack,attack 完成后自动切换到 idle
gameabc_face.spineMgr.setAnimation("hero", "attack", false);
gameabc_face.spineMgr.addAnimation("hero", "idle", true, 0);
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id |
string | - | 实例标识 |
animName |
string | - | 队列中的下一个动画 |
loop |
boolean | true | 是否循环 |
delay |
number | 0 | 延迟秒数(0 = 上一动画结束时立即开始) |
track |
number | 0 | 轨道号 |
4.5 setPosition(id, x, y)
设置 Spine 实例在 Canvas 上的位置。
gameabc_face.spineMgr.setPosition("hero", 640, 500);
Spine 的坐标原点在骨骼的根骨骼处。Y 轴向上为正(与 Canvas 的 Y 轴方向相反), spine-canvas 运行时已做了内部转换。
4.6 setScale(id, sx, sy)
设置缩放。
gameabc_face.spineMgr.setScale("hero", 0.5); // 等比缩放
gameabc_face.spineMgr.setScale("hero", 0.5, 0.8); // 分别设置 X/Y
| 参数 | 类型 | 说明 |
|---|---|---|
sx |
number | X 方向缩放 |
sy |
number | Y 方向缩放(省略则等于 sx) |
4.7 setFlip(id, flipX, flipY)
水平/垂直翻转。
gameabc_face.spineMgr.setFlip("hero", true, false); // 水平翻转
4.8 setVisible(id, visible)
显示或隐藏 Spine 实例。
gameabc_face.spineMgr.setVisible("hero", false); // 隐藏
gameabc_face.spineMgr.setVisible("hero", true); // 显示
4.9 setSkin(id, skinName)
切换皮肤。
gameabc_face.spineMgr.setSkin("hero", "warrior");
切换皮肤后会自动重置插槽到 Setup Pose。皮肤名必须在 Spine 编辑器中预定义。
4.10 getAnimations(id)
获取该实例所有可用动画名称列表。
var anims = gameabc_face.spineMgr.getAnimations("hero");
// 返回: ["idle", "walk", "run", "attack", "die"]
logmessage("动画列表: " + anims.join(", "));
4.11 getSkins(id)
获取该实例所有可用皮肤名称列表。
var skins = gameabc_face.spineMgr.getSkins("hero");
// 返回: ["default", "warrior", "mage"]
4.12 playOnce(id, animName, track)
播放一次动画后自动隐藏。 自动显示实例、播放指定动画(不循环),动画完成后自动设置 visible = false。
// 播放一次攻击动画,播完自动隐藏
gameabc_face.spineMgr.playOnce("hero", "attack");
// 指定轨道
gameabc_face.spineMgr.playOnce("hero", "attack", 0);
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id |
string | - | 实例标识 |
animName |
string | - | 动画名称 |
track |
number | 0 | 轨道号 |
4.13 playQueue(id, animList, hideOnComplete)
按顺序播放一组动画(队列),全部播完后可选择隐藏或保持显示。 自动显示实例,队列中每个动画均播放一次(不循环),依次播放。
// 播放 attack → die,全部播完后自动隐藏(默认)
gameabc_face.spineMgr.playQueue("hero", ["attack", "die"]);
// 播放 intro → idle,全部播完后保持显示
gameabc_face.spineMgr.playQueue("hero", ["intro", "idle"], false);
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id |
string | - | 实例标识 |
animList |
string[] | - | 动画名称数组,按顺序依次播放 |
hideOnComplete |
boolean | true | true = 队列全部播完后自动隐藏;false = 保持显示 |
4.14 stop(id)
停止指定 Spine 实例的动画并隐藏。 实例保留在内存中,可随时重新播放。
与 setVisible(false) 不同,stop 会彻底清空动画轨道、重置骨骼姿态,并清除待执行命令队列。
// 停止单个 Spine 动画(保留实例,可重播)
gameabc_face.spineMgr.stop("hero");
// 之后可以重新播放
gameabc_face.spineMgr.playOnce("hero", "attack");
| 行为 | 说明 |
|---|---|
| 隐藏 | visible = false |
| 清空动画轨道 | state.clearTracks() |
| 重置骨骼姿态 | skeleton.setToSetupPose() |
| 清除待执行命令 | 删除 _pendingCmds[id] |
| 清除自动隐藏标记 | _hideOnComplete = false |
4.15 stopAll()
停止所有 Spine 实例的动画并隐藏。 所有实例保留在内存中,可随时重新播放。
// 停止全部 Spine 动画(如:切换游戏场景时)
gameabc_face.spineMgr.stopAll();
4.16 remove(id)
彻底销毁指定 Spine 实例,释放内存。 销毁后无法重新播放,需要重新 load 或自动加载。
gameabc_face.spineMgr.remove("hero");
4.17 removeAll()
彻底销毁所有 Spine 实例,释放内存。
gameabc_face.spineMgr.removeAll();
4.18 spine_onComplete(spineId, animName, trackIndex) (事件回调)
动画完成回调。每次动画播放一轮结束时触发。 在 js/Spine_Event.js 中定义。
// 在 Spine_Event.js 中定义
gameabc_face.spine_onComplete = function(spineId, animName, trackIndex) {
// 示例:攻击播完后恢复 idle
if (animName === "attack") {
gameabc_face.spineMgr.setAnimation(spineId, "idle", true);
}
};
| 参数 | 类型 | 说明 |
|---|---|---|
spineId |
string | 实例标识(即 load / autoLoad 时的 id) |
animName |
string | 刚完成的动画名称 |
trackIndex |
number | 轨道号(通常为 0) |
注意:循环动画每播完一轮也会触发。
playOnce/playQueue的自动隐藏在此回调之前执行, 因此回调中可以检查visible状态或重新显示实例。
4.19 spine_onEvent(spineId, eventName, intValue, floatValue, stringValue) (事件回调)
自定义事件回调。当动画播放到 Spine 编辑器中定义的 Event 关键帧时触发。 在 js/Spine_Event.js 中定义。
// 在 Spine_Event.js 中定义
gameabc_face.spine_onEvent = function(spineId, eventName, intValue, floatValue, stringValue) {
if (eventName === "footstep") {
// 播放脚步声
}
if (eventName === "hit") {
// 产生伤害判定
}
};
| 参数 | 类型 | 说明 |
|---|---|---|
spineId |
string | 实例标识 |
eventName |
string | Spine 编辑器中定义的事件名 |
intValue |
number | 事件的整数参数 |
floatValue |
number | 事件的浮点参数 |
stringValue |
string | 事件的字符串参数 |
事件需要在 Spine 编辑器的时间线中预先添加 Event Key,导出 JSON 后运行时自动解析。
5. 事件系统(详细说明)
Spine 动画在运行时会触发两类事件,回调定义在 js/Spine_Event.js 中。
API 签名见 4.18 和 4.19。
5.1 动画完成事件 spine_onComplete
每次动画循环播放一轮结束时触发。
// 在 Spine_Event.js 中
gameabc_face.spine_onComplete = function(spineId, animName, trackIndex) {
// spineId : load 时的唯一标识, 如 "hero"
// animName : 完成的动画名, 如 "attack"
// trackIndex : 轨道号 (通常为 0)
// 示例:非循环攻击动画播完后恢复 idle
if (animName === "attack") {
gameabc_face.spineMgr.setAnimation(spineId, "idle", true);
}
};
5.2 自定义事件 spine_onEvent
当动画播放到 Spine 编辑器中定义的 Event 关键帧时触发。
// 在 Spine_Event.js 中
gameabc_face.spine_onEvent = function(spineId, eventName, intValue, floatValue, stringValue) {
// spineId : 唯一标识
// eventName : Spine 编辑器中定义的事件名
// intValue : 整数参数
// floatValue : 浮点参数
// stringValue : 字符串参数
if (eventName === "footstep") {
// 播放脚步声
}
if (eventName === "hit") {
// 产生伤害判定
}
};
如何在 Spine 编辑器中添加事件
- 打开 Spine 编辑器,选中动画
- 在时间线底部点击右键 → Add Event Key
- 在 Tree 面板中创建并命名事件(如
hit、footstep) - 可为事件设置 int / float / string 参数
- 导出 JSON 后,运行时会自动解析并触发回调
6. 常见用法示例
6.1 加载多个角色
// 在 gamestart 或任意时机,直接设置位置并播放(自动加载)
gameabc_face.spineMgr.setPosition("hero", 640, 500);
gameabc_face.spineMgr.setAnimation("hero", "idle", true);
gameabc_face.spineMgr.setPosition("npc", 300, 500);
gameabc_face.spineMgr.setAnimation("npc", "idle", true);
gameabc_face.spineMgr.setPosition("monster", 900, 500);
gameabc_face.spineMgr.setAnimation("monster", "walk", true);
6.2 播放一次后隐藏 / 队列播放
// 播放一次攻击动画,完成后自动隐藏
gameabc_face.spineMgr.setPosition("effect", 640, 400);
gameabc_face.spineMgr.playOnce("effect", "explode");
// 队列播放:攻击 → 死亡,全部播完后自动隐藏
gameabc_face.spineMgr.playQueue("monster", ["hit", "die"]);
// 队列播放:入场 → 待机,全部播完后保持显示
gameabc_face.spineMgr.playQueue("hero", ["intro", "idle"], false);
6.3 点击切换动画(攻击→恢复)
gameabc_face.mousedown = function(gameid, spid, downx, downy) {
// 点击播放攻击(非循环)
gameabc_face.spineMgr.setAnimation("hero", "attack", false);
// 攻击完自动切回 idle
gameabc_face.spineMgr.addAnimation("hero", "idle", true, 0);
};
6.4 角色移动 + 动画联动
var heroState = "idle";
gameabc_face.mousedown = function(gameid, spid, downx, downy) {
heroState = "run";
gameabc_face.spineMgr.setAnimation("hero", "run", true);
};
gameabc_face.mouseup = function(gameid, spid_down, downx, downy, spid_up, upx, upy) {
heroState = "idle";
gameabc_face.spineMgr.setAnimation("hero", "idle", true);
};
gameabc_face.mousemove = function(gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey) {
// 通过拖拽移动角色
var mgr = gameabc_face.spineMgr;
var entry = mgr._entries["hero"];
if (entry) {
mgr.setPosition("hero", entry.x + offmovex, entry.y + offmovey);
// 根据移动方向翻转
mgr.setFlip("hero", offmovex < 0, false);
}
};
6.5 多轨道叠加(走路 + 射击)
Spine 支持多轨道同时播放动画。低轨道(track 0)为基础动画,高轨道覆盖部分骨骼:
// track 0: 下半身走路
gameabc_face.spineMgr.setAnimation("hero", "walk", true, 0);
// track 1: 上半身射击(在 Spine 编辑器中只设置上半身骨骼的关键帧)
gameabc_face.spineMgr.setAnimation("hero", "shoot", false, 1);
6.6 动态切换皮肤(换装系统)
// 查看有哪些皮肤
var skins = gameabc_face.spineMgr.getSkins("hero");
logmessage("可用皮肤: " + skins.join(", "));
// 切换到战士皮肤
gameabc_face.spineMgr.setSkin("hero", "warrior");
// 切换到法师皮肤
gameabc_face.spineMgr.setSkin("hero", "mage");
6.7 运行时查询动画列表
gameabc_face.spine_onComplete = function(spineId, animName, trackIndex) {
var anims = gameabc_face.spineMgr.getAnimations(spineId);
logmessage(spineId + " 拥有的动画: " + anims.join(", "));
};
7. 与 gameabc 精灵系统配合
渲染时序
gameabc 引擎循环 (每帧)
│
├── gamebegindraw() ← 帧开始
├── 遍历 Layer → 每个精灵:
│ ├── gamemydrawbegin()
│ ├── 精灵自绘 (图片/文字)
│ └── gamemydraw()
├── gameenddraw() ← 用户自定义逻辑
│ └── (自动) spineMgr.updateAndDraw(ctx) ← ★ 通过 defineProperty 自动追加
└── 帧结束
Spine 动画在 gameenddraw 末尾自动渲染(通过 Object.defineProperty 拦截实现,
无论开发者如何重新定义 gameenddraw,Spine 渲染都不会丢失),
因此会覆盖在所有 gameabc 精灵之上。
开发者在 gameenddraw 中编写的自定义逻辑会先执行,Spine 渲染在其后自动执行。
如果需要 Spine 在精灵之下渲染
可以通过手动控制渲染时机来实现。在 gamebegindraw 中手动调用渲染,并禁用自动渲染:
// 方式:在 gamebegindraw 中手动渲染
gameabc_face.gamebegindraw = function(gameid, spid, times, timelong) {
var ctx = gameabc_face.dc;
if (ctx) {
gameabc_face.spineMgr.updateAndDraw(ctx);
}
// 标记自动渲染跳过(因为已在此手动渲染)
gameabc_face.spineMgr._rendered = true;
};
注意:当前自动挂钩在 gameenddraw 末尾,如需精细控制层级, 可将 SpineMgr._inited 临时置 false 跳过自动渲染,手动选择渲染时机。
让 Spine 跟随某个精灵移动
gameabc_face.gamemydraw = function(gameid, spid, times, timelong) {
// 让 Spine 角色跟随精灵 1 的位置
if (spid === 1) {
var sx = get_self(1, 18, 0, 0, 0); // 获取精灵 1 的 X
var sy = get_self(1, 19, 0, 0, 0); // 获取精灵 1 的 Y
gameabc_face.spineMgr.setPosition("hero", sx, sy);
}
};
8. 性能优化建议
8.1 图集纹理
- 纹理尺寸建议不超过 2048×2048
- 导出时勾选 Power of two 确保尺寸为 2 的幂
- 不要勾选 Premultiply alpha(Canvas 2D 不需要预乘 Alpha)
8.2 控制实例数量
- Canvas 2D 渲染性能有限,建议同屏 Spine 实例不超过 5-8 个
- 不可见的实例调用
setVisible(id, false),跳过渲染和更新 - 场景切换时调用
stopAll()一键关闭所有 Spine 动画 - 不再需要的实例调用
remove(id)释放内存
8.3 减少骨骼复杂度
- 骨骼数量建议控制在 50 个以内
- 减少网格变形(Mesh Deform),对 Canvas 2D 影响较大
- 使用裁剪(Clipping)时性能开销大,谨慎使用
8.4 动画混合时长
mixDuration 越长,过渡越平滑,但在切换瞬间需要同时计算两个动画。
建议设为 0.1 ~ 0.3 秒。
9. 常见问题排查
Q1: 画面上看不到 Spine 动画
检查清单:
-
文件路径是否正确?
- 确认
assets/spine/目录下有.json、.atlas、.png - 文件名大小写必须一致(Linux/Mac 服务器区分大小写)
- 确认
-
是否通过 HTTP 访问?
file://协议无法加载跨域资源,必须使用 HTTP 服务器
-
打开浏览器控制台(F12)看报错
- 404 错误:文件路径有误
- JSON 解析错误:
.json文件格式异常 spine is not defined:spine-canvas.js未正确加载
-
坐标是否在可见范围内?
- 项目设计尺寸为 1280×720,检查
x和y是否在此范围
- 项目设计尺寸为 1280×720,检查
-
logmessage 输出是否有 "[SpineMgr] xxx 构建完成"?
- 有 → 加载成功,检查坐标和缩放
- 有 "构建失败" → 查看具体错误信息
- 没有 → 资源还在加载中或路径错误
Q2: 动画显示位置不对
- Spine 编辑器中设置骨骼原点的位置会影响运行时的锚点
- 调整
setPosition的坐标,或在 Spine 编辑器中修改根骨骼位置 - 注意 Spine 的 Y 轴与 Canvas Y 轴方向可能不同
Q3: 动画速度太快或太慢
- 检查
gameabc_Project中的fps设置(默认 30) - SpineMgr 内部是用
Date.now()计算真实时间差的,不依赖帧率 - 如果需要倍速播放,修改
state.timeScale:var entry = gameabc_face.spineMgr._entries["hero"]; entry.state.timeScale = 2.0; // 2 倍速
Q4: 切换动画时有跳帧
- 增大
mixDuration(加载时的 option 或修改stateData.defaultMix) - 使用
addAnimation排队而不是直接setAnimation打断
Q5: 多个 Spine 实例重叠时闪烁
- 确认没有同一个 id 加载两次
- 检查
ctx.save()/ctx.restore()是否配对(SpineMgr 内部已处理)
Q6: spine-canvas.js 版本与 Spine 编辑器版本不匹配
- spine-canvas.js 4.2 需搭配 Spine 编辑器 4.2.x 导出的数据
- 如果使用 Spine 4.1 编辑器,请下载对应版本的运行时:
https://unpkg.com/@esotericsoftware/spine-canvas@4.1/dist/iife/spine-canvas.js
10. 附录:文件加载顺序
index.html 中的 script 标签加载顺序至关重要:
1. spine-canvas.js ← 先加载 Spine 运行时 (定义 window.spine)
2. gameabc.min.js ← 再加载游戏引擎
3. SpineMgr.js ← Spine 管理器 + defineProperty 自动挂钩渲染
4. gamemain.js ← 游戏主逻辑(不需修改,直接调用 API 即可)
5. Spine_Event.js ← Spine 事件回调 (依赖 gameabc_face)
6. Project1_Event.js ← 精灵事件
7. gameabc_data.min.js ← 项目配置数据 (引擎初始化)
不能调换顺序,
SpineMgr.js必须在gamemain.js之前加载, 否则会出现spine is not defined或gameabc_face.spineMgr is undefined的错误。
附录:完整最小示例 gamemain.js
gamemain.js 不需要任何修改,保持原样即可。在任意事件回调中直接调用 gameabc_face.spineMgr 的 API:
// gamemain.js —— 无需修改框架,只需在回调中调用 API
gameabc_face.gamestart = function(gameid) {
// 直接设置位置并播放动画(自动加载,无需 load)
gameabc_face.spineMgr.setPosition("mj_gangshangkaihua", 640, 500);
gameabc_face.spineMgr.setAnimation("mj_gangshangkaihua", "animation", true);
};
// gameenddraw 保持原样,Spine 渲染由 SpineMgr.js 自动处理
gameabc_face.gameenddraw = function(gameid, spid, times, timelong) {
// 这里写其他自定义绘制逻辑,或留空
};
gameabc_face.mousedown = function(gameid, spid, downx, downy) {
// 点击播放动画
gameabc_face.spineMgr.setAnimation("mj_gangshangkaihua", "animation", false);
};
重要:
SpineMgr.js是独立文件,通过Object.defineProperty自动拦截gameenddraw, 无论gamemain.js如何定义gameenddraw,Spine 渲染都会自动追加在其后执行。gamemain.js完全不需要修改。
参考链接
- Spine 官方运行时文档: https://zh.esotericsoftware.com/spine-api-reference
- Spine Player 在线演示: https://jp.esotericsoftware.com/spine-player
- spine-canvas npm 包: https://www.npmjs.com/package/@esotericsoftware/spine-canvas