添加spine的支持

This commit is contained in:
2026-04-09 17:31:46 +08:00
parent c3ab6e8a0d
commit a221d681ab
69 changed files with 40197 additions and 134 deletions

View File

@@ -0,0 +1,416 @@
// ============================================================
// SpineMgr —— Spine 动画管理器(独立文件)
// 集成到 gameabc Canvas 2D 绘制循环,零侵入 gamemain.js
// 在 index.html 中置于 gameabc.min.js 之后、gamemain.js 之前加载
// ============================================================
(function(){
var SpineMgr = {
// --- 内部状态 ---
_entries: {}, // {id: EntryObject}
_assetManager: null,
_renderer: null, // spine.SkeletonRenderer (Canvas 2D)
_lastTime: 0, // 上一帧时间戳(ms)
_inited: false,
_loadedPaths: {}, // 已请求加载的资源路径,避免重复请求
_pendingCmds: {}, // {id: [{method, args}]} 等待实例就绪后执行的命令队列
// 默认资源根路径(可通过 init 覆盖)
_basePath: "assets/spine/",
// ----------------------------------------------------------
// init(basePath) 手动初始化(可选)
// 如不调用load 时会自动以默认路径 "assets/spine/" 初始化
// ----------------------------------------------------------
init: function(basePath) {
if (!window.spine) {
logmessage("[SpineMgr] spine-canvas.js 未加载,请检查引用");
return;
}
this._basePath = basePath || this._basePath;
this._assetManager = new spine.AssetManager(this._basePath);
this._lastTime = Date.now();
this._inited = true;
},
// (内部) 确保已初始化
_ensureInit: function() {
if (!this._inited) {
this.init(this._basePath);
}
},
// (内部) 自动加载:根据 id 约定文件名 id.json / id.atlas
_autoLoad: function(id) {
if (this._entries[id]) return;
this.load(id, id + ".json", id + ".atlas", {});
},
// (内部) 将命令加入等待队列
_queueCmd: function(id, method, args) {
if (!this._pendingCmds[id]) this._pendingCmds[id] = [];
this._pendingCmds[id].push({method: method, args: args});
},
// (内部) 实例就绪后执行队列中的命令
_flushCmds: function(id) {
var cmds = this._pendingCmds[id];
if (!cmds || cmds.length === 0) return;
delete this._pendingCmds[id];
for (var i = 0; i < cmds.length; i++) {
var cmd = cmds[i];
this[cmd.method].apply(this, [id].concat(cmd.args));
}
},
// ----------------------------------------------------------
// load(id, jsonFile, atlasFile, option)
// 加载一组 Spine 资源, id 为自定义标识
// option: {x, y, scale, skin, animation, loop, mixDuration}
// ----------------------------------------------------------
load: function(id, jsonFile, atlasFile, option) {
this._ensureInit();
if (!this._inited) return;
var opt = option || {};
// 只加载尚未请求过的资源(预加载过的会跳过)
if (!this._loadedPaths[jsonFile]) {
this._assetManager.loadText(jsonFile);
this._loadedPaths[jsonFile] = true;
}
if (!this._loadedPaths[atlasFile]) {
this._assetManager.loadTextureAtlas(atlasFile);
this._loadedPaths[atlasFile] = true;
}
this._entries[id] = {
jsonFile: jsonFile,
atlasFile: atlasFile,
skeleton: null,
state: null,
x: opt.x || 0,
y: opt.y || 0,
scale: opt.scale || 1,
skin: opt.skin || "default",
defAnim: opt.animation || null,
defLoop: opt.loop !== undefined ? opt.loop : true,
mixDur: opt.mixDuration || 0.2,
visible: true,
ready: false,
_hideOnComplete: false, // 播放完成后是否自动隐藏
_hideAfterCompletes: 0 // 剩余多少次 complete 后触发隐藏
};
},
// ----------------------------------------------------------
// (内部) 资源加载完成后实例化骨骼
// ----------------------------------------------------------
_buildEntry: function(entry) {
var atlas = this._assetManager.require(entry.atlasFile);
var loader = new spine.AtlasAttachmentLoader(atlas);
var skelJson = new spine.SkeletonJson(loader);
skelJson.scale = entry.scale;
var skelData = skelJson.readSkeletonData(
this._assetManager.require(entry.jsonFile)
);
entry.skeleton = new spine.Skeleton(skelData);
// 设置皮肤
if (entry.skin && entry.skin !== "default") {
entry.skeleton.setSkinByName(entry.skin);
}
entry.skeleton.setToSetupPose();
// AnimationState
var stateData = new spine.AnimationStateData(skelData);
stateData.defaultMix = entry.mixDur;
entry.state = new spine.AnimationState(stateData);
// 绑定事件回调 —— 转发到 Spine_Event.js
entry.state.addListener({
complete: function(trackEntry) {
// 自动隐藏playOnce / playQueue 设置的计数器
if (entry._hideOnComplete && !trackEntry.loop) {
entry._hideAfterCompletes--;
if (entry._hideAfterCompletes <= 0) {
entry.visible = false;
entry._hideOnComplete = false;
}
}
if (gameabc_face.spine_onComplete) {
gameabc_face.spine_onComplete(entry._id, trackEntry.animation.name, trackEntry.trackIndex);
}
},
event: function(trackEntry, event) {
if (gameabc_face.spine_onEvent) {
gameabc_face.spine_onEvent(entry._id, event.data.name, event.intValue, event.floatValue, event.stringValue);
}
}
});
// 默认动画
if (entry.defAnim) {
entry.state.setAnimation(0, entry.defAnim, entry.defLoop);
}
entry.ready = true;
},
// ----------------------------------------------------------
// (内部) 每帧调用:确保所有资源就绪
// ----------------------------------------------------------
_tryBuild: function() {
if (!this._assetManager.isLoadingComplete()) return false;
for (var id in this._entries) {
var e = this._entries[id];
if (!e.ready) {
e._id = id;
try {
this._buildEntry(e);
logmessage("[SpineMgr] " + id + " 构建完成");
this._flushCmds(id);
} catch(err) {
logmessage("[SpineMgr] " + id + " 构建失败: " + err.message);
e.ready = false;
}
}
}
return true;
},
// ----------------------------------------------------------
// updateAndDraw(ctx) 每帧自动调用
// ctx: gameabc_face.dc (Canvas 2D Context)
// ----------------------------------------------------------
updateAndDraw: function(ctx) {
if (!this._inited) return;
this._tryBuild();
var now = Date.now();
var dt = (now - this._lastTime) / 1000;
this._lastTime = now;
if (dt <= 0 || dt > 0.5) dt = 1/30;
if (!this._renderer) {
this._renderer = new spine.SkeletonRenderer(ctx);
this._renderer.triangleRendering = true; // ★ 启用三角形渲染以支持 Mesh 网格附件
}
// 确保 renderer 用的是当前 ctx (gameabc可能重建)
this._renderer.ctx = ctx;
for (var id in this._entries) {
var e = this._entries[id];
if (!e.ready || !e.visible) continue;
e.state.update(dt);
e.state.apply(e.skeleton);
// 骨骼位置归零,由 ctx.translate 控制实际位置
e.skeleton.x = 0;
e.skeleton.y = 0;
e.skeleton.updateWorldTransform(spine.Physics.update);
ctx.save();
ctx.translate(e.x, e.y);
ctx.scale(1, -1); // ★ Spine Y-up → Canvas Y-down
this._renderer.draw(e.skeleton);
ctx.restore();
}
},
// ==========================================================
// 公开控制 API
// ==========================================================
setAnimation: function(id, animName, loop, track) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "setAnimation", [animName, loop, track]);
return null;
}
if (!e.ready) {
this._queueCmd(id, "setAnimation", [animName, loop, track]);
return null;
}
return e.state.setAnimation(track || 0, animName, loop !== false);
},
addAnimation: function(id, animName, loop, delay, track) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "addAnimation", [animName, loop, delay, track]);
return null;
}
if (!e.ready) {
this._queueCmd(id, "addAnimation", [animName, loop, delay, track]);
return null;
}
return e.state.addAnimation(track || 0, animName, loop !== false, delay || 0);
},
setPosition: function(id, x, y) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
e = this._entries[id];
}
e.x = x; e.y = y;
},
setScale: function(id, sx, sy) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "setScale", [sx, sy]);
return;
}
if (!e.skeleton) {
this._queueCmd(id, "setScale", [sx, sy]);
return;
}
e.skeleton.scaleX = sx;
e.skeleton.scaleY = sy !== undefined ? sy : sx;
},
setFlip: function(id, flipX, flipY) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "setFlip", [flipX, flipY]);
return;
}
if (!e.skeleton) {
this._queueCmd(id, "setFlip", [flipX, flipY]);
return;
}
e.skeleton.scaleX = Math.abs(e.skeleton.scaleX) * (flipX ? -1 : 1);
e.skeleton.scaleY = Math.abs(e.skeleton.scaleY) * (flipY ? -1 : 1);
},
setVisible: function(id, visible) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
e = this._entries[id];
}
e.visible = !!visible;
},
setSkin: function(id, skinName) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "setSkin", [skinName]);
return;
}
if (!e.ready) {
this._queueCmd(id, "setSkin", [skinName]);
return;
}
e.skeleton.setSkinByName(skinName);
e.skeleton.setSlotsToSetupPose();
},
getAnimations: function(id) {
var e = this._entries[id];
if (!e) { this._autoLoad(id); return []; }
if (!e.ready) return [];
var anims = e.skeleton.data.animations;
var names = [];
for (var i = 0; i < anims.length; i++) names.push(anims[i].name);
return names;
},
getSkins: function(id) {
var e = this._entries[id];
if (!e) { this._autoLoad(id); return []; }
if (!e.ready) return [];
var skins = e.skeleton.data.skins;
var names = [];
for (var i = 0; i < skins.length; i++) names.push(skins[i].name);
return names;
},
playOnce: function(id, animName, track) {
this.setVisible(id, true);
this.setAnimation(id, animName, false, track);
var e = this._entries[id];
if (e) {
e._hideOnComplete = true;
e._hideAfterCompletes = 1;
}
},
playQueue: function(id, animList, hideOnComplete) {
if (!animList || animList.length === 0) return;
this.setVisible(id, true);
this.setAnimation(id, animList[0], false, 0);
for (var i = 1; i < animList.length; i++) {
this.addAnimation(id, animList[i], false, 0, 0);
}
var e = this._entries[id];
if (e) {
e._hideOnComplete = hideOnComplete !== false;
e._hideAfterCompletes = animList.length;
}
},
remove: function(id) {
delete this._entries[id];
},
removeAll: function() {
this._entries = {};
}
};
// 挂载到 gameabc_face 上
gameabc_face.spineMgr = SpineMgr;
// ★ 自动预加载:读取 gameabc_face.spineAssets 清单,在引擎启动前预加载所有 Spine 资源
// 如果存在 spineTextData嵌入文本数据则通过 setRawDataURI 注入,
// 彻底避免 file:// 协议下 XHR CORS 拦截问题
if (gameabc_face.spineAssets && gameabc_face.spineAssets.length > 0) {
SpineMgr._ensureInit();
var list = gameabc_face.spineAssets;
var textData = gameabc_face.spineTextData || {};
for (var i = 0; i < list.length; i++) {
var name = list[i];
var jsonKey = name + ".json";
var atlasKey = name + ".atlas";
// 将嵌入文本数据注册为 rawDataURISpine Downloader 会优先从内存读取
if (textData[jsonKey]) {
SpineMgr._assetManager.setRawDataURI(jsonKey, "data:," + textData[jsonKey]);
}
if (textData[atlasKey]) {
SpineMgr._assetManager.setRawDataURI(atlasKey, "data:," + textData[atlasKey]);
}
SpineMgr._assetManager.loadText(jsonKey);
SpineMgr._loadedPaths[jsonKey] = true;
SpineMgr._assetManager.loadTextureAtlas(atlasKey);
SpineMgr._loadedPaths[atlasKey] = true;
}
logmessage("[SpineMgr] 预加载 " + list.length + " 组 Spine 资源" +
(Object.keys(textData).length > 0 ? "(使用嵌入数据)" : "(使用网络请求)"));
}
// ★ 用 defineProperty 拦截 gameenddraw 赋值
// 无论 gamemain.js 或其他代码如何定义 gameenddraw
// Spine 渲染都会自动追加在用户逻辑之后
var _userEndDraw = gameabc_face.gameenddraw || null;
var _wrappedEndDraw = function(gameid, spid, times, timelong) {
if (typeof _userEndDraw === "function") {
_userEndDraw(gameid, spid, times, timelong);
}
var ctx = gameabc_face.dc;
if (ctx && SpineMgr._inited) {
SpineMgr.updateAndDraw(ctx);
}
};
Object.defineProperty(gameabc_face, "gameenddraw", {
configurable: true,
get: function() { return _wrappedEndDraw; },
set: function(fn) { _userEndDraw = fn; }
});
})();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
var gameabc_face = gameabc_face||{};
{
gameabc_face.tag=12; //定义你的游戏全局内存
gameabc_face.tag1=123;//定义你的游戏全局内存
gameabc_face.tag2=123;//定义你的游戏全局内存
gameabc_face.tag3=123;//定义你的游戏全局内存
}
gameabc_face.gamestart=function(gameid)
{
//游戏初始化代码
gameabc_face.spineMgr.setPosition("gamestart", 640, 500);
gameabc_face.spineMgr.setAnimation("gamestart", "start", true);
};
gameabc_face.ani_doend=function(id,sx,count,allend)
{
logmessage(id+"/"+sx+"/"+count+"/"+allend);
//play_ani(0,2,18,50,200,0,1000,0,0,0,0,6000,1);//主动关闭
};
gameabc_face.box_doend=function(id,sx,timelen)
{
//play_box 结束事件
//showmessage("box_doend:"+id+"/"+sx+"/"+timelen);
logmessage("box_doend:"+id+"/"+sx+"/"+timelen);
};
gameabc_face.onloadurl1=function(recid,rectype,url,error,count,len)
{
//修改为gameabc_face.onloadurl 则自己处理图片加载进度
//资源加载完成函数
//recid:资源id
//rectype:1 图片 2声音
//url :网络地址
//error:是否加载错误
//len:资源大小
//count加载的个数百分比
logmessage("onload:"+recid+"/"+rectype+"/"+count+"/"+error);
/*
if (rectype==0)
{
open_load("","1.mp3","");
gameabc_face.randombase=0;//使用系统浏览器缓存
}
if (count==100)
{
game_close_zsmsg("");
} else
{
game_open_zsmsg(count+"%"+" 加载中...");
};
*/
};
gameabc_face.chongzhi=function(userid,zt,data)
{
//游戏接口代码
};
gameabc_face.onresize=function(pmw/*屏幕宽*/,pmh/*屏幕宽*/,sjweww/*设计宽*/,sjnewh/*设计宽*/,nweww/*显示宽*/,newh/*显示高*/)
{
//屏幕变化
// 在此调整 列表控件的宽高和区域 不是整体缩放
logmessage("onresize:"+pmw+"/"+pmh+"/"+sjweww+"/"+sjnewh+"/"+nweww+"/"+newh);
};
gameabc_face.gamebegindraw=function(gameid, spid, times, timelong)
{
//更新开始代码
};
gameabc_face.gameenddraw=function(gameid, spid, times, timelong)
{
//更新完成代码
};
gameabc_face.mousedown=function(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6)
{
//点击代码
};
gameabc_face.mousedown_nomove=function(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5)
{
//点击代没移动代码
};
gameabc_face.mouseup=function(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2)
{
//点击弹起代码
//可以通过spid_down和spid_up 的比较 来判断是 点击还是 移动
};
gameabc_face.mousemove=function(gameid, spid, downx, downy, movex,movey ,timelong,offmovex, offmovey, no1)
{
//点击后移动代码
//set_self(spid,18,offmovex,1,0);
//set_self(spid,19,offmovey,1,0);
};
gameabc_face.gamemydraw=function(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7)
{
//每个精灵更新绘画代码
};
gameabc_face.gamemydrawbegin=function(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7)
{
//每个精灵更新前绘画代码
};
gameabc_face.ontimer= function(gameid, spid, /* 本次间隔多少次了 */ times, /* 本次间隔多久 */ timelong,/* 开启后运行多少次了 */ alltimes){
/*请在下面输入您的代码
*/
//set_self(1,18,5,1,0);
} ;
gameabc_face.tcpconnected=function(tcpid)
{
/*
ifast_tcp_open(1,"127.0.0.1:5414");//连接ws tcp
*/
logmessage("tcpopen:"+tcpid);
};
gameabc_face.tcpmessage=function(tcpid,data)
{
logmessage("tcpread:"+data);
};
gameabc_face.tcpdisconnected=function(tcpid)
{
logmessage("tcpclose:"+tcpid);
};
gameabc_face.tcperror=function(tcpid,data)
{
logmessage("tcperror:"+tcpid);
};
gameabc_face.httpmessage=function(myid,url,data)
{
/*
ifast_http(1,"web/test.txt",1);//获取文件 同域
*/
logmessage("httpread:"+myid+"/"+url+":"+data);
};

File diff suppressed because it is too large Load Diff