添加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,2 @@
G:\Works\YouleGames\codes\games\client\Projects\Project1\js\gamemain.js
G:\Works\YouleGames\codes\games\client\Projects\Project1\js\Project1_Event.js

View File

@@ -0,0 +1,6 @@
LayerStartID=1
SpiritStartID=1
ImgResStartID=1
TxtResStartID=1
VoiResStartID=1
OpenLayerList=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -0,0 +1,255 @@
var gameabc_Project =
{
"Property": {
"ProjectName": "Spine",
"ScreenWidth": 1280,
"ScreenHeight": 720,
"GameSceneWidth": 2337,
"GameSceneHeight": 1800,
"ScreenFitMode": 0,
"TcpIP": "",
"TcpPort": "",
"Http": "",
"title": ""
},
"Event": {
"gamestart": 0,
"gamebegindraw": 0,
"gameenddraw": 0,
"mousedown": 0,
"mousedown_nomove": 0,
"mouseup": 0,
"mousemove": 0,
"gamemydraw": 0,
"gamemydrawbegin": 0,
"chongzhi": 0,
"tcpconnected": 0,
"tcpdisconnected": 0,
"tcpmessage": 0,
"tcperror": 0,
"httpmessage": 0,
"ani_doend": 0,
"box_doend": 0,
"onresize": 0,
"ontimer": 0,
"onloadurl": 0
},
"Option": {
"fps": 30,
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"showmodel": 0
}
}
;
var gameabc_Layer =
{
"LayerList": [
{},{
"Property": {
"LayerID": 1,
"LayerName": "Layer1"
},
"ObjectList": [
1,2,3]
}]
}
;
var gameabc_GroupList =
{
"GroupList": []
}
;
var gameabc_Object =
{
"ObjectList": [
{},{
"Property": {
"ObjectID": 1,
"ObjectType": 4,
"ObjectName": "Spirit1",
"Left": -668,
"Top": 19,
"Width": 79,
"Height": 35,
"BelongLayerID": 1,
"IndexOfLayer": 1,
"Text": "Spirit1",
"FontSize": 20,
"FontBold": 0,
"FontColorR": 0,
"FontColorG": 0,
"FontColorB": 0,
"FontColor": "#000000",
"BackColorR": 255,
"BackColorG": 255,
"BackColorB": 255,
"BackColorA": 0,
"BackColor": "#FFFFFF",
"GameTxtStyle": 0,
"LineSpace": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": -668,
"offY": 19
},
"Event": {
"mousedown": 1,
"mouseup": 1,
"mousemove": 1,
"ontimer": 1
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
},{
"Property": {
"ObjectID": 2,
"ObjectType": 2,
"ObjectName": "Spirit2",
"Left": 0,
"Top": 0,
"Width": 1280,
"Height": 720,
"BelongLayerID": 1,
"IndexOfLayer": 2,
"ImageFileID": 1,
"FrameStyle": 0,
"FrameIndex": 0,
"TextFrames": "",
"L9": 0,
"T9": 0,
"R9": 0,
"B9": 0,
"VoiceFileID": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": 0,
"offY": 0
},
"Event": {
"mousedown": 2,
"mouseup": 2,
"mousemove": 2,
"ontimer": 2
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
},{
"Property": {
"ObjectID": 3,
"ObjectType": 2,
"ObjectName": "Spirit3",
"Left": 393,
"Top": 412,
"Width": 560,
"Height": 148,
"BelongLayerID": 1,
"IndexOfLayer": 3,
"ImageFileID": 2,
"FrameStyle": 0,
"FrameIndex": 0,
"TextFrames": "",
"L9": 0,
"T9": 0,
"R9": 0,
"B9": 0,
"VoiceFileID": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": 393,
"offY": 412
},
"Event": {
"mousedown": 3,
"mouseup": 3,
"mousemove": 3,
"ontimer": 3
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
}]
}
;
var gameabc_Image =
{
"ImageFileList": [
{},{
"id": 1,
"w_all": 1280,
"h_all": 720,
"w": 1,
"h": 1,
"frame_all": 1,
"bmp": "00001.png",
"w1": 1280,
"h1": 720
},{
"id": 2,
"w_all": 1920,
"h_all": 740,
"w": 1,
"h": 1,
"frame_all": 1,
"bmp": "00002.png",
"w1": 1920,
"h1": 740
}]
}
;
var gameabc_GameTxt =
{
"GameTxtList": []
}
;
var gameabc_Voice =
{
"VoiceFileList": []
}
;

View File

@@ -0,0 +1 @@
var gameabc_Project = { "Property": { "ProjectName": "Spine", "ScreenWidth": 1280, "ScreenHeight": 720, "GameSceneWidth": 2337, "GameSceneHeight": 1800, "ScreenFitMode": 0, "TcpIP": "", "TcpPort": "", "Http": "", "title": "" }, "Event": { "gamestart": 0, "gamebegindraw": 0, "gameenddraw": 0, "mousedown": 0, "mousedown_nomove": 0, "mouseup": 0, "mousemove": 0, "gamemydraw": 0, "gamemydrawbegin": 0, "chongzhi": 0, "tcpconnected": 0, "tcpdisconnected": 0, "tcpmessage": 0, "tcperror": 0, "httpmessage": 0, "ani_doend": 0, "box_doend": 0, "onresize": 0, "ontimer": 0, "onloadurl": 0 }, "Option": { "fps": 30, "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "showmodel": 0 }};var gameabc_Layer = { "LayerList": [ {},{ "Property": { "LayerID": 1, "LayerName": "Layer1" }, "ObjectList": [ 1,2,3] }]};var gameabc_GroupList = { "GroupList": []};var gameabc_Object = { "ObjectList": [ {},{ "Property": { "ObjectID": 1, "ObjectType": 4, "ObjectName": "Spirit1", "Left": -668, "Top": 19, "Width": 79, "Height": 35, "BelongLayerID": 1, "IndexOfLayer": 1, "Text": "Spirit1", "FontSize": 20, "FontBold": 0, "FontColorR": 0, "FontColorG": 0, "FontColorB": 0, "FontColor": "#000000", "BackColorR": 255, "BackColorG": 255, "BackColorB": 255, "BackColorA": 0, "BackColor": "#FFFFFF", "GameTxtStyle": 0, "LineSpace": 0, "GroupID": 0, "TimerInterval": 0, "Data": "", "Parent": 0, "OriginID": 0, "OriginPos": 1, "SelfPos": 1, "offX": -668, "offY": 19 }, "Event": { "mousedown": 1, "mouseup": 1, "mousemove": 1, "ontimer": 1 }, "Option": { "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "vx": 0, "vy": 0, "vw": 0, "vh": 0, "canclick": 1, "visbale": 1 } },{ "Property": { "ObjectID": 2, "ObjectType": 2, "ObjectName": "Spirit2", "Left": 0, "Top": 0, "Width": 1280, "Height": 720, "BelongLayerID": 1, "IndexOfLayer": 2, "ImageFileID": 1, "FrameStyle": 0, "FrameIndex": 0, "TextFrames": "", "L9": 0, "T9": 0, "R9": 0, "B9": 0, "VoiceFileID": 0, "GroupID": 0, "TimerInterval": 0, "Data": "", "Parent": 0, "OriginID": 0, "OriginPos": 1, "SelfPos": 1, "offX": 0, "offY": 0 }, "Event": { "mousedown": 2, "mouseup": 2, "mousemove": 2, "ontimer": 2 }, "Option": { "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "vx": 0, "vy": 0, "vw": 0, "vh": 0, "canclick": 1, "visbale": 1 } },{ "Property": { "ObjectID": 3, "ObjectType": 2, "ObjectName": "Spirit3", "Left": 393, "Top": 412, "Width": 560, "Height": 148, "BelongLayerID": 1, "IndexOfLayer": 3, "ImageFileID": 2, "FrameStyle": 0, "FrameIndex": 0, "TextFrames": "", "L9": 0, "T9": 0, "R9": 0, "B9": 0, "VoiceFileID": 0, "GroupID": 0, "TimerInterval": 0, "Data": "", "Parent": 0, "OriginID": 0, "OriginPos": 1, "SelfPos": 1, "offX": 393, "offY": 412 }, "Event": { "mousedown": 3, "mouseup": 3, "mousemove": 3, "ontimer": 3 }, "Option": { "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "vx": 0, "vy": 0, "vw": 0, "vh": 0, "canclick": 1, "visbale": 1 } }]};var gameabc_Image = { "ImageFileList": [ {},{ "id": 1, "w_all": 1280, "h_all": 720, "w": 1, "h": 1, "frame_all": 1, "bmp": "00001.png", "w1": 1280, "h1": 720 },{ "id": 2, "w_all": 1920, "h_all": 740, "w": 1, "h": 1, "frame_all": 1, "bmp": "00002.png", "w1": 1920, "h1": 740 }]};var gameabc_GameTxt = { "GameTxtList": []};var gameabc_Voice = { "VoiceFileList": []};

View File

@@ -0,0 +1,35 @@
Zaijiezaili.png
size:2048,512
filter:Linear,Linear
fennu
bounds:1006,307,176,171
lei
bounds:576,164,53,73
lei1
bounds:519,108,55,129
lianhong
bounds:2,285,526,193
majiang
bounds:530,239,222,239
meimao
bounds:754,251,141,67
meimaozuo
bounds:361,5,142,109
shaizi
bounds:1682,368,108,110
shitou
bounds:754,320,250,158
toufa
bounds:1184,353,207,125
yanjing
bounds:1792,408,111,70
youjiao
bounds:1393,346,187,132
youshou
bounds:1582,300,98,178
zuiba
bounds:1682,319,98,47
zuojiao
bounds:361,116,156,167
zuoshou
bounds:2,2,357,281

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

View File

@@ -0,0 +1,79 @@
chipengganghu.png
size:2048,512
filter:Linear,Linear
Dda_Jt03
bounds:1947,288,92,92
Dda_Jt03-1
bounds:311,343,138,138
Dda_Jt05
bounds:1134,353,128,128
banyuanguangquan (4)
bounds:1947,382,99,99
chi1
bounds:609,244,108,100
chi2
bounds:572,22,24,18
chi4
bounds:726,347,134,134
gang1
bounds:609,144,108,98
gang2
bounds:862,347,134,134
guo1
bounds:1356,284,85,77
guo2
bounds:1385,369,112,112
hu1
bounds:2,5,23,26
hu7
bounds:475,8,95,101
hu8
bounds:590,346,134,135
peng1
bounds:150,149,113,93
quan-002
bounds:572,42,100,100
saoguang_00000
bounds:1499,370,110,111
saoguang_00001
bounds:1611,370,110,111
saoguang_00002
bounds:1723,370,110,111
saoguang_00003
bounds:1835,370,110,111
saoguang_00004
bounds:275,230,110,111
saoguang_00005
bounds:141,36,110,111
saoguang_00006
bounds:265,117,110,111
saoguang_00007
bounds:253,4,110,111
saoguang_00008
bounds:387,224,110,111
saoguang_00009
bounds:377,111,110,111
bounds:365,2,108,107
bounds:674,45,94,97
图层 10
bounds:1264,363,119,118
图层 3
bounds:157,345,152,136
图层 4
bounds:2,327,153,154
图层 7 拷贝
bounds:2,179,146,146
图层 9
bounds:998,349,134,132
bounds:499,227,108,108
bounds:157,244,116,99
组 12
bounds:2,33,137,144
组 13
bounds:451,337,137,144
bounds:1264,279,90,82

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

View File

@@ -0,0 +1,37 @@
gamestart.png
size:2048,512
filter:Linear,Linear
dai
bounds:1521,395,96,90
deng
bounds:1418,395,101,90
dian
bounds:1167,183,24,22
duijukaishi
bounds:929,393,380,92
fangkuai
bounds:446,47,32,32
fangkuai_kuang
bounds:2,2,32,32
glow_bian
bounds:2,36,330,9
heitao_01
bounds:929,173,236,218
heitao_02
bounds:2,47,442,219
lamp_glow
bounds:446,81,198,185
lizi_1
bounds:646,247,19,19
loading_aixin
bounds:1273,363,28,28
new_tiao_orange
bounds:2,268,667,217
pei
bounds:1311,393,105,92
pi
bounds:1167,302,104,89
xingguang
bounds:671,229,256,256
zhong
bounds:1167,207,98,93

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View File

@@ -0,0 +1,806 @@
# Spine 动画集成手册
> 本手册针对 **gameabc 引擎** 的 Spine 项目,说明如何在 Canvas 2D 游戏中加载、
> 控制和管理 Spine 骨骼动画。
> 运行时版本:**spine-canvas 4.2** | 引擎:**gameabc**
---
## 目录
1. [项目结构总览](#1-项目结构总览)
2. [快速开始5 分钟跑通](#2-快速开始5-分钟跑通)
3. [从 Spine 编辑器导出资源](#3-从-spine-编辑器导出资源)
4. [SpineMgr 完整 API 参考](#4-spinemgr-完整-api-参考)
5. [事件系统](#5-事件系统)
6. [常见用法示例](#6-常见用法示例)
7. [与 gameabc 精灵系统配合](#7-与-gameabc-精灵系统配合)
8. [性能优化建议](#8-性能优化建议)
9. [常见问题排查](#9-常见问题排查)
10. [附录:文件加载顺序](#10-附录文件加载顺序)
---
## 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 事件回调中直接调用即可:
```javascript
// 设置位置 + 播放动画,无需先 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()`
> ```javascript
> gameabc_face.spineMgr.init("other/path/");
> ```
### 第三步:打开 index.html 即可运行
用浏览器打开 `index.html`(需要 HTTP 服务器环境,不能直接 `file://`
```bash
# 简易方法:在项目目录下启动 HTTP 服务
cd codes/games/client/Projects/Spine
npx http-server -p 8080
# 浏览器访问 http://localhost:8080
```
或者使用 gameabc 自带的预览环境。
---
## 3. 从 Spine 编辑器导出资源
### 导出设置Spine 编辑器 → Export
1. 打开 Spine 编辑器,加载你的 `.spine` 项目
2. 点击菜单 **Spine → Export...**
3. 左侧选择 **JSON** 格式
4. 配置项:
| 设置项 | 推荐值 | 说明 |
|--------|--------|------|
| 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 不需要预乘 |
5. 点击 **Export** 按钮
### 导出后得到的文件
```
hero.json ← 骨骼数据,包含骨骼、插槽、动画等
hero.atlas ← 图集描述,记录每个区域在纹理中的位置
hero.png ← 图集纹理图片
```
> **注意**:如果图集很大被拆分成多张(`hero.png`、`hero2.png`),所有 `.png`
> 都需要放到 `assets/spine/` 目录中。`.atlas` 文件会自动引用它们。
---
## 4. SpineMgr 完整 API 参考
`SpineMgr` 挂载在 `gameabc_face.spineMgr` 上,所有调用形如:
```javascript
gameabc_face.spineMgr.方法名(参数);
```
---
### 4.1 init(basePath) *(可选)*
**手动设置资源根路径。通常无需调用——`load()` 会自动以默认路径 `"assets/spine/"` 初始化。**
仅在资源放在其他目录时才需要调用:
```javascript
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 等初始参数
```javascript
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 加载资源并在就绪后播放。**
```javascript
// 无需先 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)
**将动画添加到播放队列,在当前动画结束后自动播放。**
```javascript
// 先播放 attackattack 完成后自动切换到 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 上的位置。**
```javascript
gameabc_face.spineMgr.setPosition("hero", 640, 500);
```
> Spine 的坐标原点在骨骼的根骨骼处。Y 轴向上为正(与 Canvas 的 Y 轴方向相反),
> spine-canvas 运行时已做了内部转换。
---
### 4.6 setScale(id, sx, sy)
**设置缩放。**
```javascript
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)
**水平/垂直翻转。**
```javascript
gameabc_face.spineMgr.setFlip("hero", true, false); // 水平翻转
```
---
### 4.8 setVisible(id, visible)
**显示或隐藏 Spine 实例。**
```javascript
gameabc_face.spineMgr.setVisible("hero", false); // 隐藏
gameabc_face.spineMgr.setVisible("hero", true); // 显示
```
---
### 4.9 setSkin(id, skinName)
**切换皮肤。**
```javascript
gameabc_face.spineMgr.setSkin("hero", "warrior");
```
> 切换皮肤后会自动重置插槽到 Setup Pose。皮肤名必须在 Spine 编辑器中预定义。
---
### 4.10 getAnimations(id)
**获取该实例所有可用动画名称列表。**
```javascript
var anims = gameabc_face.spineMgr.getAnimations("hero");
// 返回: ["idle", "walk", "run", "attack", "die"]
logmessage("动画列表: " + anims.join(", "));
```
---
### 4.11 getSkins(id)
**获取该实例所有可用皮肤名称列表。**
```javascript
var skins = gameabc_face.spineMgr.getSkins("hero");
// 返回: ["default", "warrior", "mage"]
```
---
### 4.12 playOnce(id, animName, track)
**播放一次动画后自动隐藏。** 自动显示实例、播放指定动画(不循环),动画完成后自动设置 `visible = false`
```javascript
// 播放一次攻击动画,播完自动隐藏
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)
**按顺序播放一组动画(队列),全部播完后可选择隐藏或保持显示。** 自动显示实例,队列中每个动画均播放一次(不循环),依次播放。
```javascript
// 播放 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 remove(id)
**销毁指定 Spine 实例,释放内存。**
```javascript
gameabc_face.spineMgr.remove("hero");
```
---
### 4.15 removeAll()
**销毁所有 Spine 实例。**
```javascript
gameabc_face.spineMgr.removeAll();
```
### 4.16 spine_onComplete(spineId, animName, trackIndex) *(事件回调)*
**动画完成回调。每次动画播放一轮结束时触发。**`js/Spine_Event.js` 中定义。
```javascript
// 在 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.17 spine_onEvent(spineId, eventName, intValue, floatValue, stringValue) *(事件回调)*
**自定义事件回调。当动画播放到 Spine 编辑器中定义的 Event 关键帧时触发。**`js/Spine_Event.js` 中定义。
```javascript
// 在 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.16](#416-spine_oncompletespineid-animname-trackindex--事件回调) 和 [4.17](#417-spine_oneventspineid-eventname-intvalue-floatvalue-stringvalue--事件回调)。
### 5.1 动画完成事件 spine_onComplete
**每次动画循环播放一轮结束时触发。**
```javascript
// 在 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 关键帧时触发。**
```javascript
// 在 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 编辑器中添加事件
1. 打开 Spine 编辑器,选中动画
2. 在时间线底部点击右键 → **Add Event Key**
3.**Tree** 面板中创建并命名事件(如 `hit``footstep`
4. 可为事件设置 int / float / string 参数
5. 导出 JSON 后,运行时会自动解析并触发回调
---
## 6. 常见用法示例
### 6.1 加载多个角色
```javascript
// 在 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 播放一次后隐藏 / 队列播放
```javascript
// 播放一次攻击动画,完成后自动隐藏
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 点击切换动画(攻击→恢复)
```javascript
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 角色移动 + 动画联动
```javascript
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为基础动画高轨道覆盖部分骨骼
```javascript
// track 0: 下半身走路
gameabc_face.spineMgr.setAnimation("hero", "walk", true, 0);
// track 1: 上半身射击(在 Spine 编辑器中只设置上半身骨骼的关键帧)
gameabc_face.spineMgr.setAnimation("hero", "shoot", false, 1);
```
### 6.6 动态切换皮肤(换装系统)
```javascript
// 查看有哪些皮肤
var skins = gameabc_face.spineMgr.getSkins("hero");
logmessage("可用皮肤: " + skins.join(", "));
// 切换到战士皮肤
gameabc_face.spineMgr.setSkin("hero", "warrior");
// 切换到法师皮肤
gameabc_face.spineMgr.setSkin("hero", "mage");
```
### 6.7 运行时查询动画列表
```javascript
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` 中手动调用渲染,并禁用自动渲染:
```javascript
// 方式:在 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 跟随某个精灵移动
```javascript
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)`,跳过渲染和更新
- 不再需要的实例调用 `remove(id)` 释放内存
### 8.3 减少骨骼复杂度
- 骨骼数量建议控制在 **50 个**以内
- 减少网格变形Mesh Deform对 Canvas 2D 影响较大
- 使用裁剪Clipping时性能开销大谨慎使用
### 8.4 动画混合时长
`mixDuration` 越长,过渡越平滑,但在切换瞬间需要同时计算两个动画。
建议设为 **0.1 ~ 0.3 秒**
---
## 9. 常见问题排查
### Q1: 画面上看不到 Spine 动画
**检查清单:**
1. **文件路径是否正确?**
- 确认 `assets/spine/` 目录下有 `.json``.atlas``.png`
- 文件名大小写必须一致Linux/Mac 服务器区分大小写)
2. **是否通过 HTTP 访问?**
- `file://` 协议无法加载跨域资源,必须使用 HTTP 服务器
3. **打开浏览器控制台F12看报错**
- 404 错误:文件路径有误
- JSON 解析错误:`.json` 文件格式异常
- `spine is not defined``spine-canvas.js` 未正确加载
4. **坐标是否在可见范围内?**
- 项目设计尺寸为 1280×720检查 `x``y` 是否在此范围
5. **logmessage 输出是否有 "[SpineMgr] xxx 构建完成"**
- 有 → 加载成功,检查坐标和缩放
- 有 "构建失败" → 查看具体错误信息
- 没有 → 资源还在加载中或路径错误
### Q2: 动画显示位置不对
- Spine 编辑器中设置骨骼原点的位置会影响运行时的锚点
- 调整 `setPosition` 的坐标,或在 Spine 编辑器中修改根骨骼位置
- 注意 Spine 的 Y 轴与 Canvas Y 轴方向可能不同
### Q3: 动画速度太快或太慢
- 检查 `gameabc_Project` 中的 `fps` 设置(默认 30
- SpineMgr 内部是用 `Date.now()` 计算真实时间差的,不依赖帧率
- 如果需要倍速播放,修改 `state.timeScale`
```javascript
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
```javascript
// 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

View File

@@ -0,0 +1,6 @@
// Spine resource list (auto-generated, do not edit manually)
gameabc_face.spineAssets = [
"chipengganghu",
"gamestart",
"Zaijiezaili"
];

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,66 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta name="viewport" content="width=device-width,initial-scale=1 user-scalable=0"/>
<title>gameabc</title>
</head>
<body style='position:fixed;bottom:0;'>
<div id="ifastgame2" style="position:absolute;left:0px;top:0px;z-index:10">
<canvas id="bg1" width="1" height="1">
<p>Your browser does not support the canvas element.</p>
</canvas>
</div>
<div id="ifastgame_4" style="position:absolute;left:0px;top:0px;z-index:1">
<canvas id="ifastgame_bg" width="1" height="1">
<p>Your browser does not support the canvas element.</p>
</canvas>
</div>
<div id="ifastgame3" style="position:absolute;left:0px;top:0px;z-index:11">
<canvas id="bg2" width="1" height="1">
<p>Your browser does not support the canvas element.</p>
</canvas>
</div>
<div id="ifastgame" style="position:absolute;left:0px;top:0px;z-index:2">
<canvas id="canvas" width="6000" height="3000">
<p>Your browser does not support the canvas element.</p>
</canvas>
</div>
<style type="text/css">
* { margin: 0; padding: 0; }
html, body { height: 100%; width: 100%;
overflow-x:hidden;overflow-y:hidden;
backgroundColor:"rab(255,0,0)";
}
canvas { display: block; }
</style>
<script language='javascript'>var gameabc_face = gameabc_face||{};</script>
<script language='javascript'>gameabc_face.path="assets/bmp";</script>
<!-- Spine Canvas Runtime -->
<script type="text/javascript" src="js/spine-canvas.js"></script>
<script type="text/javascript" src="js/gameabc.min.js"></script>
<!-- Spine 资源清单:列出需要预加载的 Spine 资源基础名 -->
<script type="text/javascript" src="generated/spine_assets.js"></script>
<!-- Spine 文本数据嵌入(解决 file:// 协议 CORS 问题) -->
<script type="text/javascript" src="generated/spine_data.js"></script>
<!-- SpineMgr: 自动初始化+自动渲染,必须在 gamemain.js 之前加载 -->
<script type="text/javascript" src="js/SpineMgr.js"></script>
<script type="text/javascript" src="js/gamemain.js"></script>
<script type="text/javascript" src="js/Project1_Event.js"></script>
<script type="text/javascript" src="output/gameabc_data.min.js"></script>
</body>
</html>

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

View File

@@ -0,0 +1,3 @@
{
"GameTxtList": []
}

View File

@@ -0,0 +1,3 @@
{
"GroupList": []
}

View File

@@ -0,0 +1,24 @@
{
"ImageFileList": [
{},{
"id": 1,
"w_all": 1280,
"h_all": 720,
"w": 1,
"h": 1,
"frame_all": 1,
"bmp": "00001.png",
"w1": 1280,
"h1": 720
},{
"id": 2,
"w_all": 1920,
"h_all": 740,
"w": 1,
"h": 1,
"frame_all": 1,
"bmp": "00002.png",
"w1": 1920,
"h1": 740
}]
}

View File

@@ -0,0 +1,11 @@
{
"LayerList": [
{},{
"Property": {
"LayerID": 1,
"LayerName": "Layer1"
},
"ObjectList": [
1,2,3]
}]
}

View File

@@ -0,0 +1,153 @@
{
"ObjectList": [
{},{
"Property": {
"ObjectID": 1,
"ObjectType": 4,
"ObjectName": "Spirit1",
"Left": -668,
"Top": 19,
"Width": 79,
"Height": 35,
"BelongLayerID": 1,
"IndexOfLayer": 1,
"Text": "Spirit1",
"FontSize": 20,
"FontBold": 0,
"FontColorR": 0,
"FontColorG": 0,
"FontColorB": 0,
"FontColor": "#000000",
"BackColorR": 255,
"BackColorG": 255,
"BackColorB": 255,
"BackColorA": 0,
"BackColor": "#FFFFFF",
"GameTxtStyle": 0,
"LineSpace": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": -668,
"offY": 19
},
"Event": {
"mousedown": 1,
"mouseup": 1,
"mousemove": 1,
"ontimer": 1
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
},{
"Property": {
"ObjectID": 2,
"ObjectType": 2,
"ObjectName": "Spirit2",
"Left": 0,
"Top": 0,
"Width": 1280,
"Height": 720,
"BelongLayerID": 1,
"IndexOfLayer": 2,
"ImageFileID": 1,
"FrameStyle": 0,
"FrameIndex": 0,
"TextFrames": "",
"L9": 0,
"T9": 0,
"R9": 0,
"B9": 0,
"VoiceFileID": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": 0,
"offY": 0
},
"Event": {
"mousedown": 2,
"mouseup": 2,
"mousemove": 2,
"ontimer": 2
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
},{
"Property": {
"ObjectID": 3,
"ObjectType": 2,
"ObjectName": "Spirit3",
"Left": 393,
"Top": 412,
"Width": 560,
"Height": 148,
"BelongLayerID": 1,
"IndexOfLayer": 3,
"ImageFileID": 2,
"FrameStyle": 0,
"FrameIndex": 0,
"TextFrames": "",
"L9": 0,
"T9": 0,
"R9": 0,
"B9": 0,
"VoiceFileID": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": 393,
"offY": 412
},
"Event": {
"mousedown": 3,
"mouseup": 3,
"mousemove": 3,
"ontimer": 3
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
}]
}

View File

@@ -0,0 +1,44 @@
{
"Property": {
"ProjectName": "Spine",
"ScreenWidth": 1280,
"ScreenHeight": 720,
"GameSceneWidth": 2337,
"GameSceneHeight": 1800,
"ScreenFitMode": 0,
"TcpIP": "",
"TcpPort": "",
"Http": "",
"title": ""
},
"Event": {
"gamestart": 0,
"gamebegindraw": 0,
"gameenddraw": 0,
"mousedown": 0,
"mousedown_nomove": 0,
"mouseup": 0,
"mousemove": 0,
"gamemydraw": 0,
"gamemydrawbegin": 0,
"chongzhi": 0,
"tcpconnected": 0,
"tcpdisconnected": 0,
"tcpmessage": 0,
"tcperror": 0,
"httpmessage": 0,
"ani_doend": 0,
"box_doend": 0,
"onresize": 0,
"ontimer": 0,
"onloadurl": 0
},
"Option": {
"fps": 30,
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"showmodel": 0
}
}

View File

@@ -0,0 +1,3 @@
{
"VoiceFileList": []
}

View File

@@ -0,0 +1,255 @@
var gameabc_Project =
{
"Property": {
"ProjectName": "Spine",
"ScreenWidth": 1280,
"ScreenHeight": 720,
"GameSceneWidth": 2337,
"GameSceneHeight": 1800,
"ScreenFitMode": 0,
"TcpIP": "",
"TcpPort": "",
"Http": "",
"title": ""
},
"Event": {
"gamestart": 0,
"gamebegindraw": 0,
"gameenddraw": 0,
"mousedown": 0,
"mousedown_nomove": 0,
"mouseup": 0,
"mousemove": 0,
"gamemydraw": 0,
"gamemydrawbegin": 0,
"chongzhi": 0,
"tcpconnected": 0,
"tcpdisconnected": 0,
"tcpmessage": 0,
"tcperror": 0,
"httpmessage": 0,
"ani_doend": 0,
"box_doend": 0,
"onresize": 0,
"ontimer": 0,
"onloadurl": 0
},
"Option": {
"fps": 30,
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"showmodel": 0
}
}
;
var gameabc_Layer =
{
"LayerList": [
{},{
"Property": {
"LayerID": 1,
"LayerName": "Layer1"
},
"ObjectList": [
1,2,3]
}]
}
;
var gameabc_GroupList =
{
"GroupList": []
}
;
var gameabc_Object =
{
"ObjectList": [
{},{
"Property": {
"ObjectID": 1,
"ObjectType": 4,
"ObjectName": "Spirit1",
"Left": -668,
"Top": 19,
"Width": 79,
"Height": 35,
"BelongLayerID": 1,
"IndexOfLayer": 1,
"Text": "Spirit1",
"FontSize": 20,
"FontBold": 0,
"FontColorR": 0,
"FontColorG": 0,
"FontColorB": 0,
"FontColor": "#000000",
"BackColorR": 255,
"BackColorG": 255,
"BackColorB": 255,
"BackColorA": 0,
"BackColor": "#FFFFFF",
"GameTxtStyle": 0,
"LineSpace": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": -668,
"offY": 19
},
"Event": {
"mousedown": 1,
"mouseup": 1,
"mousemove": 1,
"ontimer": 1
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
},{
"Property": {
"ObjectID": 2,
"ObjectType": 2,
"ObjectName": "Spirit2",
"Left": 0,
"Top": 0,
"Width": 1280,
"Height": 720,
"BelongLayerID": 1,
"IndexOfLayer": 2,
"ImageFileID": 1,
"FrameStyle": 0,
"FrameIndex": 0,
"TextFrames": "",
"L9": 0,
"T9": 0,
"R9": 0,
"B9": 0,
"VoiceFileID": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": 0,
"offY": 0
},
"Event": {
"mousedown": 2,
"mouseup": 2,
"mousemove": 2,
"ontimer": 2
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
},{
"Property": {
"ObjectID": 3,
"ObjectType": 2,
"ObjectName": "Spirit3",
"Left": 393,
"Top": 412,
"Width": 560,
"Height": 148,
"BelongLayerID": 1,
"IndexOfLayer": 3,
"ImageFileID": 2,
"FrameStyle": 0,
"FrameIndex": 0,
"TextFrames": "",
"L9": 0,
"T9": 0,
"R9": 0,
"B9": 0,
"VoiceFileID": 0,
"GroupID": 0,
"TimerInterval": 0,
"Data": "",
"Parent": 0,
"OriginID": 0,
"OriginPos": 1,
"SelfPos": 1,
"offX": 393,
"offY": 412
},
"Event": {
"mousedown": 3,
"mouseup": 3,
"mousemove": 3,
"ontimer": 3
},
"Option": {
"tag": 0,
"tag1": 0,
"tag2": 0,
"tag3": 0,
"vx": 0,
"vy": 0,
"vw": 0,
"vh": 0,
"canclick": 1,
"visbale": 1
}
}]
}
;
var gameabc_Image =
{
"ImageFileList": [
{},{
"id": 1,
"w_all": 1280,
"h_all": 720,
"w": 1,
"h": 1,
"frame_all": 1,
"bmp": "00001.png",
"w1": 1280,
"h1": 720
},{
"id": 2,
"w_all": 1920,
"h_all": 740,
"w": 1,
"h": 1,
"frame_all": 1,
"bmp": "00002.png",
"w1": 1920,
"h1": 740
}]
}
;
var gameabc_GameTxt =
{
"GameTxtList": []
}
;
var gameabc_Voice =
{
"VoiceFileList": []
}
;

View File

@@ -0,0 +1 @@
var gameabc_Project = { "Property": { "ProjectName": "Spine", "ScreenWidth": 1280, "ScreenHeight": 720, "GameSceneWidth": 2337, "GameSceneHeight": 1800, "ScreenFitMode": 0, "TcpIP": "", "TcpPort": "", "Http": "", "title": "" }, "Event": { "gamestart": 0, "gamebegindraw": 0, "gameenddraw": 0, "mousedown": 0, "mousedown_nomove": 0, "mouseup": 0, "mousemove": 0, "gamemydraw": 0, "gamemydrawbegin": 0, "chongzhi": 0, "tcpconnected": 0, "tcpdisconnected": 0, "tcpmessage": 0, "tcperror": 0, "httpmessage": 0, "ani_doend": 0, "box_doend": 0, "onresize": 0, "ontimer": 0, "onloadurl": 0 }, "Option": { "fps": 30, "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "showmodel": 0 }};var gameabc_Layer = { "LayerList": [ {},{ "Property": { "LayerID": 1, "LayerName": "Layer1" }, "ObjectList": [ 1,2,3] }]};var gameabc_GroupList = { "GroupList": []};var gameabc_Object = { "ObjectList": [ {},{ "Property": { "ObjectID": 1, "ObjectType": 4, "ObjectName": "Spirit1", "Left": -668, "Top": 19, "Width": 79, "Height": 35, "BelongLayerID": 1, "IndexOfLayer": 1, "Text": "Spirit1", "FontSize": 20, "FontBold": 0, "FontColorR": 0, "FontColorG": 0, "FontColorB": 0, "FontColor": "#000000", "BackColorR": 255, "BackColorG": 255, "BackColorB": 255, "BackColorA": 0, "BackColor": "#FFFFFF", "GameTxtStyle": 0, "LineSpace": 0, "GroupID": 0, "TimerInterval": 0, "Data": "", "Parent": 0, "OriginID": 0, "OriginPos": 1, "SelfPos": 1, "offX": -668, "offY": 19 }, "Event": { "mousedown": 1, "mouseup": 1, "mousemove": 1, "ontimer": 1 }, "Option": { "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "vx": 0, "vy": 0, "vw": 0, "vh": 0, "canclick": 1, "visbale": 1 } },{ "Property": { "ObjectID": 2, "ObjectType": 2, "ObjectName": "Spirit2", "Left": 0, "Top": 0, "Width": 1280, "Height": 720, "BelongLayerID": 1, "IndexOfLayer": 2, "ImageFileID": 1, "FrameStyle": 0, "FrameIndex": 0, "TextFrames": "", "L9": 0, "T9": 0, "R9": 0, "B9": 0, "VoiceFileID": 0, "GroupID": 0, "TimerInterval": 0, "Data": "", "Parent": 0, "OriginID": 0, "OriginPos": 1, "SelfPos": 1, "offX": 0, "offY": 0 }, "Event": { "mousedown": 2, "mouseup": 2, "mousemove": 2, "ontimer": 2 }, "Option": { "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "vx": 0, "vy": 0, "vw": 0, "vh": 0, "canclick": 1, "visbale": 1 } },{ "Property": { "ObjectID": 3, "ObjectType": 2, "ObjectName": "Spirit3", "Left": 393, "Top": 412, "Width": 560, "Height": 148, "BelongLayerID": 1, "IndexOfLayer": 3, "ImageFileID": 2, "FrameStyle": 0, "FrameIndex": 0, "TextFrames": "", "L9": 0, "T9": 0, "R9": 0, "B9": 0, "VoiceFileID": 0, "GroupID": 0, "TimerInterval": 0, "Data": "", "Parent": 0, "OriginID": 0, "OriginPos": 1, "SelfPos": 1, "offX": 393, "offY": 412 }, "Event": { "mousedown": 3, "mouseup": 3, "mousemove": 3, "ontimer": 3 }, "Option": { "tag": 0, "tag1": 0, "tag2": 0, "tag3": 0, "vx": 0, "vy": 0, "vw": 0, "vh": 0, "canclick": 1, "visbale": 1 } }]};var gameabc_Image = { "ImageFileList": [ {},{ "id": 1, "w_all": 1280, "h_all": 720, "w": 1, "h": 1, "frame_all": 1, "bmp": "00001.png", "w1": 1280, "h1": 720 },{ "id": 2, "w_all": 1920, "h_all": 740, "w": 1, "h": 1, "frame_all": 1, "bmp": "00002.png", "w1": 1920, "h1": 740 }]};var gameabc_GameTxt = { "GameTxtList": []};var gameabc_Voice = { "VoiceFileList": []};

View File

@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<Layer Version="2.7" SaveTime="2026-04-09 01:13:11">
<PropertyList>
<Property Name="ID" Value="1"/>
<Property Name="Name" Value="Layer1"/>
<Property Name="Description" Value=""/>
</PropertyList>
<OptionList/>
<EventList/>
<SpiritList>
<Spirit>
<PropertyList>
<Property Name="SpiritType" Value="1"/>
<Property Name="ID" Value="1"/>
<Property Name="Name" Value="Spirit1"/>
<Property Name="FontSize" Value="20"/>
<Property Name="FontBold" Value="0"/>
<Property Name="FontColor" Value="0"/>
<Property Name="FontColorA" Value="255"/>
<Property Name="BackColor" Value="16777215"/>
<Property Name="BackColorA" Value="0"/>
<Property Name="AutoSize" Value="1"/>
<Property Name="TextStyle" Value="0"/>
<Property Name="WordWrap" Value="0"/>
<Property Name="LineSpace" Value="0"/>
<Property Name="AlignHorz" Value="0"/>
<Property Name="AlignVert" Value="0"/>
<Property Name="Text" Value="Spirit1"/>
<Property Name="X" Value="-668"/>
<Property Name="Y" Value="19"/>
<Property Name="Width" Value="79"/>
<Property Name="Height" Value="35"/>
<Property Name="BelongLayerID" Value="1"/>
<Property Name="IndexOfLayer" Value="1"/>
<Property Name="BelongGroupID" Value="0"/>
<Property Name="IsVisible" Value="1"/>
<Property Name="CanClick" Value="1"/>
<Property Name="CanDragSize" Value="0"/>
<Property Name="TimerInterval" Value="0"/>
<Property Name="Data" Value=""/>
<Property Name="Description" Value=""/>
<Property Name="CoordParentID" Value="0"/>
<Property Name="CoordParentPos" Value="0"/>
<Property Name="CoordSelfPos" Value="0"/>
<Property Name="CoordRelaX" Value="-668"/>
<Property Name="CoordRelaY" Value="19"/>
<Property Name="SizeCalcMode" Value="0"/>
<Property Name="SizeParentID" Value="0"/>
<Property Name="WidthCalcMode" Value="0"/>
<Property Name="SizeRelaWidth" Value="79"/>
<Property Name="HeightCalcMode" Value="0"/>
<Property Name="SizeRelaHeight" Value="35"/>
</PropertyList>
<OptionList>
<Option Name="tag" Value="0"/>
<Option Name="tag1" Value="0"/>
<Option Name="tag2" Value="0"/>
<Option Name="tag3" Value="0"/>
<Option Name="vx" Value="0"/>
<Option Name="vy" Value="0"/>
<Option Name="vw" Value="0"/>
<Option Name="vh" Value="0"/>
</OptionList>
<EventList>
<Event Name="MouseDown" Value=""/>
<Event Name="MouseUp" Value=""/>
<Event Name="MouseMove" Value=""/>
<Event Name="OnTimer" Value=""/>
</EventList>
</Spirit>
<Spirit>
<PropertyList>
<Property Name="SpiritType" Value="0"/>
<Property Name="ID" Value="2"/>
<Property Name="Name" Value="Spirit2"/>
<Property Name="ImgResID" Value="1"/>
<Property Name="X" Value="0"/>
<Property Name="Y" Value="0"/>
<Property Name="Width" Value="1280"/>
<Property Name="Height" Value="720"/>
<Property Name="BelongLayerID" Value="1"/>
<Property Name="IndexOfLayer" Value="2"/>
<Property Name="BelongGroupID" Value="0"/>
<Property Name="IsVisible" Value="1"/>
<Property Name="CanClick" Value="1"/>
<Property Name="CanDragSize" Value="0"/>
<Property Name="TimerInterval" Value="0"/>
<Property Name="Data" Value=""/>
<Property Name="Description" Value=""/>
<Property Name="CoordParentID" Value="0"/>
<Property Name="CoordParentPos" Value="0"/>
<Property Name="CoordSelfPos" Value="0"/>
<Property Name="CoordRelaX" Value="0"/>
<Property Name="CoordRelaY" Value="0"/>
<Property Name="SizeCalcMode" Value="2"/>
<Property Name="SizeParentID" Value="1"/>
<Property Name="WidthCalcMode" Value="1"/>
<Property Name="SizeRelaWidth" Value="10000"/>
<Property Name="HeightCalcMode" Value="1"/>
<Property Name="SizeRelaHeight" Value="10000"/>
</PropertyList>
<OptionList>
<Option Name="tag" Value="0"/>
<Option Name="tag1" Value="0"/>
<Option Name="tag2" Value="0"/>
<Option Name="tag3" Value="0"/>
<Option Name="vx" Value="0"/>
<Option Name="vy" Value="0"/>
<Option Name="vw" Value="0"/>
<Option Name="vh" Value="0"/>
</OptionList>
<EventList>
<Event Name="MouseDown" Value=""/>
<Event Name="MouseUp" Value=""/>
<Event Name="MouseMove" Value=""/>
<Event Name="OnTimer" Value=""/>
</EventList>
</Spirit>
<Spirit>
<PropertyList>
<Property Name="SpiritType" Value="0"/>
<Property Name="ID" Value="3"/>
<Property Name="Name" Value="Spirit3"/>
<Property Name="ImgResID" Value="2"/>
<Property Name="X" Value="393"/>
<Property Name="Y" Value="412"/>
<Property Name="Width" Value="560"/>
<Property Name="Height" Value="148"/>
<Property Name="BelongLayerID" Value="1"/>
<Property Name="IndexOfLayer" Value="3"/>
<Property Name="BelongGroupID" Value="0"/>
<Property Name="IsVisible" Value="1"/>
<Property Name="CanClick" Value="1"/>
<Property Name="CanDragSize" Value="1"/>
<Property Name="TimerInterval" Value="0"/>
<Property Name="Data" Value=""/>
<Property Name="Description" Value=""/>
<Property Name="CoordParentID" Value="0"/>
<Property Name="CoordParentPos" Value="0"/>
<Property Name="CoordSelfPos" Value="0"/>
<Property Name="CoordRelaX" Value="393"/>
<Property Name="CoordRelaY" Value="412"/>
<Property Name="SizeCalcMode" Value="0"/>
<Property Name="SizeParentID" Value="0"/>
<Property Name="WidthCalcMode" Value="0"/>
<Property Name="SizeRelaWidth" Value="560"/>
<Property Name="HeightCalcMode" Value="0"/>
<Property Name="SizeRelaHeight" Value="148"/>
</PropertyList>
<OptionList>
<Option Name="tag" Value="0"/>
<Option Name="tag1" Value="0"/>
<Option Name="tag2" Value="0"/>
<Option Name="tag3" Value="0"/>
<Option Name="vx" Value="0"/>
<Option Name="vy" Value="0"/>
<Option Name="vw" Value="0"/>
<Option Name="vh" Value="0"/>
</OptionList>
<EventList>
<Event Name="MouseDown" Value=""/>
<Event Name="MouseUp" Value=""/>
<Event Name="MouseMove" Value=""/>
<Event Name="OnTimer" Value=""/>
</EventList>
</Spirit>
</SpiritList>
</Layer>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<Project SoftVersion="2.7" SaveTime="2026-04-09 01:13:11">
<PropertyList>
<Property Name="Name" Value="Spine"/>
<Property Name="SceneWidth" Value="2337"/>
<Property Name="SceneHeight" Value="1800"/>
<Property Name="ScreenWidth" Value="1280"/>
<Property Name="ScreenHeight" Value="720"/>
<Property Name="ScreenFitMode" Value="0"/>
<Property Name="WebTitle" Value=""/>
<Property Name="Description" Value=""/>
<Property Name="TcpIP" Value=""/>
<Property Name="TcpPort" Value=""/>
<Property Name="Http" Value=""/>
<Property Name="RealLayerCount" Value="1"/>
<Property Name="RealGroupCount" Value="0"/>
<Property Name="RealSpiritCount" Value="3"/>
</PropertyList>
<OptionList>
<Option Name="fps" Value="30"/>
<Option Name="tag" Value="0"/>
<Option Name="tag1" Value="0"/>
<Option Name="tag2" Value="0"/>
<Option Name="tag3" Value="0"/>
<Option Name="showmodel" Value="0"/>
</OptionList>
<EventList>
<Event Name="GameStart" Value="0"/>
<Event Name="GameBeginDraw" Value="0"/>
<Event Name="GameEndDraw" Value="0"/>
<Event Name="MouseDown" Value="0"/>
<Event Name="MouseDown_NoMove" Value="0"/>
<Event Name="MouseUp" Value="0"/>
<Event Name="MouseMove" Value="0"/>
<Event Name="GameMyDraw" Value="0"/>
<Event Name="GameMyDrawBegin" Value="0"/>
<Event Name="ChongZhi" Value="0"/>
<Event Name="TcpConnected" Value="0"/>
<Event Name="TcpDisconnected" Value="0"/>
<Event Name="TcpMessage" Value="0"/>
<Event Name="TcpError" Value="0"/>
<Event Name="HttpMessage" Value="0"/>
<Event Name="Ani_DoEnd" Value="0"/>
<Event Name="Box_DoEnd" Value="0"/>
<Event Name="OnResize" Value="0"/>
<Event Name="OnTimer" Value="0"/>
<Event Name="OnLoadUrl" Value="0"/>
</EventList>
<GroupList/>
</Project>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ImageResList SoftVersion="2.7" SaveTime="2026-04-09 01:13:11">
<ImageRes ID="1" ImageFile="00001.png" Name="启动图" ImageWidth="1280" ImageHeight="720" HorzFrameCount="1" VertFrameCount="1" TotalFrameCount="1" ImageContent="" Description=""/>
<ImageRes ID="2" ImageFile="00002.png" Name="生成图片" ImageWidth="1920" ImageHeight="740" HorzFrameCount="1" VertFrameCount="1" TotalFrameCount="1" ImageContent="" Description=""/>
</ImageResList>

View File

@@ -0,0 +1,17 @@
@echo off
chcp 65001 >nul
REM ============================================================
REM Spine 资源数据生成脚本
REM 扫描 assets/spine/ 目录,自动生成:
REM generated/spine_assets.js — 资源名清单
REM generated/spine_data.js — .json/.atlas 文本内容嵌入
REM
REM 用法:双击运行,或在命令行执行:
REM cd codes\games\client\Projects\Spine\scripts
REM build_spine_data.cmd
REM ============================================================
pushd "%~dp0"
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0build_spine_data.ps1"
popd
pause

View File

@@ -0,0 +1,99 @@
# Spine resource data generation script
# Scans assets/spine/ directory and generates:
# generated/spine_assets.js - resource name list
# generated/spine_data.js - .json/.atlas text content embedded (file:// CORS fix)
$ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$projectDir = Split-Path -Parent $scriptDir
$spineDir = Join-Path $projectDir "assets\spine"
$outDir = Join-Path $projectDir "generated"
$assetsOut = Join-Path $outDir "spine_assets.js"
$dataOut = Join-Path $outDir "spine_data.js"
if (-not (Test-Path $outDir)) {
New-Item -ItemType Directory -Path $outDir | Out-Null
}
if (-not (Test-Path $spineDir)) {
Write-Host "[WARN] Spine assets directory not found: $spineDir — generating empty files" -ForegroundColor Yellow
$jsonFiles = @()
} else {
$jsonFiles = Get-ChildItem $spineDir -Filter "*.json" | Sort-Object Name
}
if ($jsonFiles.Count -eq 0) {
Write-Host "[INFO] No .json files found — generating empty placeholder files" -ForegroundColor Yellow
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($assetsOut, "// Spine resource list (auto-generated, do not edit manually)`ngameabc_face.spineAssets = [];`n", $utf8NoBom)
[System.IO.File]::WriteAllText($dataOut, "// Spine text data (auto-generated, do not edit manually)`ngameabc_face.spineTextData = {};`n", $utf8NoBom)
Write-Host "[OK] $assetsOut (empty)" -ForegroundColor Green
Write-Host "[OK] $dataOut (empty)" -ForegroundColor Green
exit 0
}
$names = @()
foreach ($f in $jsonFiles) {
$names += $f.BaseName
}
Write-Host "Found $($names.Count) Spine resource(s): $($names -join ', ')" -ForegroundColor Cyan
# ==================== spine_assets.js ====================
$assetsSB = [System.Text.StringBuilder]::new()
[void]$assetsSB.AppendLine('// Spine resource list (auto-generated, do not edit manually)')
[void]$assetsSB.Append('gameabc_face.spineAssets = [')
for ($i = 0; $i -lt $names.Count; $i++) {
if ($i -gt 0) { [void]$assetsSB.Append(',') }
[void]$assetsSB.AppendLine('')
[void]$assetsSB.Append("`t`"$($names[$i])`"")
}
[void]$assetsSB.AppendLine('')
[void]$assetsSB.AppendLine('];')
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($assetsOut, $assetsSB.ToString(), $utf8NoBom)
Write-Host "[OK] $assetsOut" -ForegroundColor Green
# ==================== spine_data.js ====================
$dataSB = [System.Text.StringBuilder]::new()
[void]$dataSB.AppendLine('// Spine text data (auto-generated, do not edit manually)')
[void]$dataSB.AppendLine('// Embeds .json/.atlas content into JS to bypass file:// XHR CORS')
[void]$dataSB.AppendLine('gameabc_face.spineTextData = {};')
$totalOriginal = 0
foreach ($name in $names) {
foreach ($ext in '.json', '.atlas') {
$filePath = Join-Path $spineDir "$name$ext"
if (-not (Test-Path $filePath)) {
Write-Host "[WARN] Missing file: $name$ext" -ForegroundColor Yellow
continue
}
$raw = [System.IO.File]::ReadAllText($filePath, [System.Text.Encoding]::UTF8)
$totalOriginal += (Get-Item $filePath).Length
$escaped = $raw.Replace('\', '\\').Replace("'", "\'")
$escaped = $escaped.Replace("`r`n", '\n').Replace("`n", '\n').Replace("`r", '')
[void]$dataSB.AppendLine("gameabc_face.spineTextData['$name$ext'] = '$escaped';")
}
}
[System.IO.File]::WriteAllText($dataOut, $dataSB.ToString(), $utf8NoBom)
$dataSize = (Get-Item $dataOut).Length
$overhead = $dataSize - $totalOriginal
Write-Host "[OK] $dataOut" -ForegroundColor Green
Write-Host ''
Write-Host '=== Summary ===' -ForegroundColor Cyan
Write-Host " Resources: $($names.Count)"
Write-Host " Original size: $([math]::Round($totalOriginal/1024, 1)) KB"
Write-Host " Output size: $([math]::Round($dataSize/1024, 1)) KB (overhead: $overhead bytes)"

View File

@@ -0,0 +1,141 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta name="viewport" content="width=device-width,initial-scale=1 user-scalable=0"/>
<title>gameabc</title>
</head>
<body>
<div id="divChat">
<textarea name="txtChatMain" id="txtChatMain" readonly="readonly" class="txtarea"></textarea>
<!-- Inputs are disabled by default -->
<input type="text" name="txtChatMsg" id="txtChatMsg" readonly="readonly" class="txt" />
<input type="button" name="btnChatSend" id="btnChatSend" value="Send" onClick="btnChatSendClick();" disabled="disabled" class="btn" />
</div>
<style type='text/css'>
div#divChat { width:30em; }
textarea.txtarea { float:left; width: 30em; height: 15em;}
input.txt { float:left; display: inline; width: 26em; }
input.btn { float:left; display: inline; width: 4em; }
</style>
<script language='javascript'>var gameabc_face = gameabc_face||{}; gameabc_face.isserver=1</script>
<script language='javascript'>gameabc_face.path="assets/bmp";</script>
<script type="text/javascript" src="../../web/gameabc.min.js"></script>
<script type="text/javascript" src="js/gamemain.js"></script>
<script type="text/javascript" src="output/gameabc_data.min.js"></script>
<script type="text/javascript">
// Websocket status values
const rsConnecting = 0;
const rsOpen = 1;
const rsClosed = 2;
// Websocket object
var ws = null;
// Helper functions
function $() {
return document.getElementById(arguments[0]);
}
function logMsg(msg) {
$('txtChatMain').value += msg + '\n';
}
function logmsg(msg) {
$('txtChatMain').value += msg + '\n';
}
function enableInputs(val) {
$('txtChatMsg').readOnly = ! val;
$('btnChatSend').disabled = ! val;
}
function sendMsg(msg) {
ws.send(msg);
}
function sendmsg(msg) {
ws.send(msg);
}
function btnChatSendClick() {
var msg = $('txtChatMsg').value;
if (msg) {
sendMsg(msg);
$('txtChatMsg').value = '';
}
}
// Websocket functions
function connectWebSocket() {
gameabc_face.gamestart();
if ("WebSocket" in window) {
// Create new websocket connection
ws = new WebSocket("ws://127.0.0.1:5414");
// Called after connection is established
ws.onopen = function() {
enableInputs(true);
logMsg('Connected to: ' + ws.URL);
sendMsg('dfw19770109');
//sendMsg('Hello!');
};
// Called when a new message is received
ws.onmessage = function (msg) {
if (msg.data) {
logMsg(msg.data);
gameabc_face.httpmessage(-1,'server',msg.data);
};
return;
if (msg.data) {
logMsg(msg.data);
var arr=msg.data;
var arrs=arr.split(":");
var tou='@tooneone';
logMsg(arrs[1]);
if (arrs.length>2)
{
arrs[0]=tou;
arrs[2]=arrs[2]+'_return';
var re=arrs.join(":");
sendMsg(re);
};
}
};
// Called when connection is closed
ws.onclose = function() {
enableInputs(false);
logMsg("Connection closed!");
};
} else {
logMsg('Browser doesn\'t support websockets!');
}
}
window.onload=connectWebSocket();
</script>
</body>
</html>