添加spine的支持
@@ -123,16 +123,16 @@ DocumentTypeKeyWordsFile1=
|
||||
DocumentTypeFunctionRegExp1=
|
||||
|
||||
[MRUFiles]
|
||||
MRUItem1=G:\Works\YouleGames\games\games\Projects\clinet\gamehall\js\gamemain.js
|
||||
MRUItem2=G:\Works\YouleGames\games\games\Projects\clinet\zpy\js\gamemain.js
|
||||
MRUItem3=G:\Works\YouleGames\games\games\Projects\Project1\js\gamemain.js
|
||||
MRUItem4=G:\Works\YouleGames\games\games\Projects\clinet\sangelaok\js\gamemain.js
|
||||
MRUItem5=G:\Works\YouleGames\games\games\Projects\clinet\niuniu\js\gamemain.js
|
||||
MRUItem6=G:\Works\YouleGames\games\games\Projects\clinet\guanpai-jx\js\gamemain.js
|
||||
MRUItem7=G:\Works\YouleGames\games\games\Projects\clinet\doudizhu\js\gamemain.js
|
||||
MRUItem8=G:\Works\JinXianProjects\JinXianMahjong\Projects\client\js\gamemain.js
|
||||
MRUItem9=G:\Works\JinXianProjects\JinXianMahjong\Projects\Project1\js\gamemain.js
|
||||
MRUItem10=G:\Works\JinXianProjects\JinXianMahjong\Projects\client\js\client_Event.js
|
||||
MRUItem1=G:\Works\YouleGames\codes\games\client\Projects\Project1\js\gamemain.js
|
||||
MRUItem2=G:\Works\YouleGames\codes\games\client\Projects\Spine\js\gamemain.js
|
||||
MRUItem3=G:\Works\YouleGames\codes\games\client\Projects\Spine\index.html
|
||||
MRUItem4=G:\Works\YouleGames\games\games\Projects\clinet\gamehall\js\gamemain.js
|
||||
MRUItem5=G:\Works\YouleGames\games\games\Projects\clinet\zpy\js\gamemain.js
|
||||
MRUItem6=G:\Works\YouleGames\games\games\Projects\Project1\js\gamemain.js
|
||||
MRUItem7=G:\Works\YouleGames\games\games\Projects\clinet\sangelaok\js\gamemain.js
|
||||
MRUItem8=G:\Works\YouleGames\games\games\Projects\clinet\niuniu\js\gamemain.js
|
||||
MRUItem9=G:\Works\YouleGames\games\games\Projects\clinet\guanpai-jx\js\gamemain.js
|
||||
MRUItem10=G:\Works\YouleGames\games\games\Projects\clinet\doudizhu\js\gamemain.js
|
||||
|
||||
[MRUFindText]
|
||||
MRUItem1=ÅäÖÃ
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
G:\Works\YouleGames\codes\games\client\Projects\Spine\
|
||||
G:\Works\YouleGames\codes\games\client\Projects\Project1\
|
||||
G:\Works\YouleGames\games\games\Projects\clinet\gamehall\
|
||||
G:\Works\YouleGames\games\games\Projects\clinet\zpy\
|
||||
G:\Works\YouleGames\games\games\Projects\clinet\sangelaok\
|
||||
|
||||
806
codes/games/client/Projects/Game_Surface_3/docs/Spine动画集成手册.md
Normal 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
|
||||
// 先播放 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 上的位置。**
|
||||
|
||||
```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
|
||||
@@ -0,0 +1,4 @@
|
||||
// Spine resource list (auto-generated, do not edit manually)
|
||||
gameabc_face.spineAssets = [
|
||||
"gamestart"
|
||||
];
|
||||
@@ -1,144 +1,125 @@
|
||||
<!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,viewport-fit=cover"/>
|
||||
<title>gameabc</title>
|
||||
<style>
|
||||
body {
|
||||
padding-top: constant(safe-area-inset-top);
|
||||
padding-left: constant(safe-area-inset-left);
|
||||
<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,viewport-fit=cover" />
|
||||
<title>gameabc</title>
|
||||
<style>
|
||||
body {
|
||||
padding-top: constant(safe-area-inset-top);
|
||||
padding-left: constant(safe-area-inset-left);
|
||||
padding-right: constant(safe-area-inset-right);
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
}
|
||||
</style>
|
||||
}
|
||||
</style>
|
||||
|
||||
</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 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 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 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>
|
||||
<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>
|
||||
|
||||
function setupWebViewJavascriptBridge(callback) {
|
||||
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
|
||||
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
|
||||
window.WVJBCallbacks = [callback];
|
||||
var WVJBIframe = document.createElement('iframe');
|
||||
WVJBIframe.style.display = 'none';
|
||||
WVJBIframe.src = 'https://__bridge_loaded__';
|
||||
document.documentElement.appendChild(WVJBIframe);
|
||||
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
|
||||
}
|
||||
setupWebViewJavascriptBridge(function(bridge){});
|
||||
|
||||
<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>
|
||||
|
||||
function setupWebViewJavascriptBridge(callback) {
|
||||
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
|
||||
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
|
||||
window.WVJBCallbacks = [callback];
|
||||
var WVJBIframe = document.createElement('iframe');
|
||||
WVJBIframe.style.display = 'none';
|
||||
WVJBIframe.src = 'https://__bridge_loaded__';
|
||||
document.documentElement.appendChild(WVJBIframe);
|
||||
setTimeout(function () { document.documentElement.removeChild(WVJBIframe) }, 0)
|
||||
}
|
||||
setupWebViewJavascriptBridge(function (bridge) { });
|
||||
</script>
|
||||
<script src="http://pv.sohu.com/cityjson?ie=utf-8" ></script>
|
||||
<script src="http://pv.sohu.com/cityjson?ie=utf-8"></script>
|
||||
<script type="text/javascript" src="js/jquery-2.1.1.min.js"></script>
|
||||
<script language='javascript'>var gameabc_face = gameabc_face||{};</script>
|
||||
<script language='javascript'>gameabc_face.path="assets/bmp";</script>
|
||||
|
||||
<script type="text/javascript" src="app_data.js"></script>
|
||||
<script type="text/javascript" src="app_battery.js"></script>
|
||||
<script type="text/javascript" src="app_network.js"></script>
|
||||
<script type="text/javascript" src="app_gamesname.js"></script>
|
||||
<script type="text/javascript" src="js/gameabc.min.js"></script>
|
||||
<script type="text/javascript" src="js/gamemain.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/02_Const.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/04_Data.js"></script>
|
||||
<script type="text/javascript" src="version.js"></script>
|
||||
<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/00_Surface/08_Utl_Output.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/07_Desk.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/05_Func.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/10_Game.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/11_GameUI.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/12_Logic.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/00_minhttp.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/09_Net.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/06_Player.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/03_Banwords.js"></script>
|
||||
<script type="text/javascript" src="js/01_SubGame/00_SubGame_Config.js"></script>
|
||||
<script type="text/javascript" src="js/01_SubGame/01_SubGame_modify.js"></script>
|
||||
<script type="text/javascript" src="js/01_SubGame/02_SubGame_Input.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="app_data.js"></script>
|
||||
<script type="text/javascript" src="app_battery.js"></script>
|
||||
<script type="text/javascript" src="app_network.js"></script>
|
||||
<script type="text/javascript" src="app_gamesname.js"></script>
|
||||
<script type="text/javascript" src="js/gameabc.min.js"></script>
|
||||
<script type="text/javascript" src="js/gamemain.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/02_Const.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/04_Data.js"></script>
|
||||
<script type="text/javascript" src="version.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/00_Surface/08_Utl_Output.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/07_Desk.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/05_Func.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/10_Game.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/11_GameUI.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/12_Logic.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/00_minhttp.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/09_Net.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/06_Player.js"></script>
|
||||
<script type="text/javascript" src="js/00_Surface/03_Banwords.js"></script>
|
||||
<script type="text/javascript" src="js/01_SubGame/00_SubGame_Config.js"></script>
|
||||
<script type="text/javascript" src="js/01_SubGame/01_SubGame_modify.js"></script>
|
||||
<script type="text/javascript" src="js/01_SubGame/02_SubGame_Input.js"></script>
|
||||
<!----------------------------------------------------------------------------->
|
||||
|
||||
|
||||
<script type="text/javascript" src="js/Game_Surface_3_Event.js"></script>
|
||||
<script type="text/javascript" src="output/gameabc_data.min.js"></script>
|
||||
<script type="text/javascript" src="js/Game_Surface_3_Event.js"></script>
|
||||
<script type="text/javascript" src="output/gameabc_data.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</html>
|
||||
416
codes/games/client/Projects/Game_Surface_3/js/SpineMgr.js
Normal 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";
|
||||
// 将嵌入文本数据注册为 rawDataURI,Spine 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; }
|
||||
});
|
||||
|
||||
})();
|
||||
11710
codes/games/client/Projects/Game_Surface_3/js/spine-canvas.js
Normal 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
|
||||
@@ -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)"
|
||||
2
codes/games/client/Projects/Spine/FilesOrd.txt
Normal 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
|
||||
6
codes/games/client/Projects/Spine/Project.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
LayerStartID=1
|
||||
SpiritStartID=1
|
||||
ImgResStartID=1
|
||||
TxtResStartID=1
|
||||
VoiResStartID=1
|
||||
OpenLayerList=1
|
||||
BIN
codes/games/client/Projects/Spine/assets/bmp/00001.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
codes/games/client/Projects/Spine/assets/bmp/00002.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
255
codes/games/client/Projects/Spine/assets/bmp/gameabc_data.js
Normal 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": []
|
||||
}
|
||||
;
|
||||
1
codes/games/client/Projects/Spine/assets/bmp/gameabc_data.min.js
vendored
Normal 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": []};
|
||||
@@ -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
|
||||
BIN
codes/games/client/Projects/Spine/assets/spine/Zaijiezaili.png
Normal file
|
After Width: | Height: | Size: 378 KiB |
@@ -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
|
||||
BIN
codes/games/client/Projects/Spine/assets/spine/chipengganghu.png
Normal file
|
After Width: | Height: | Size: 474 KiB |
@@ -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
|
||||
BIN
codes/games/client/Projects/Spine/assets/spine/gamestart.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
806
codes/games/client/Projects/Spine/docs/Spine动画集成手册.md
Normal 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
|
||||
// 先播放 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 上的位置。**
|
||||
|
||||
```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
|
||||
@@ -0,0 +1,6 @@
|
||||
// Spine resource list (auto-generated, do not edit manually)
|
||||
gameabc_face.spineAssets = [
|
||||
"chipengganghu",
|
||||
"gamestart",
|
||||
"Zaijiezaili"
|
||||
];
|
||||
66
codes/games/client/Projects/Spine/index.html
Normal 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>
|
||||
|
||||
416
codes/games/client/Projects/Spine/js/SpineMgr.js
Normal 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";
|
||||
// 将嵌入文本数据注册为 rawDataURI,Spine 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; }
|
||||
});
|
||||
|
||||
})();
|
||||
5544
codes/games/client/Projects/Spine/js/gameabc.min.js
vendored
Normal file
165
codes/games/client/Projects/Spine/js/gamemain.js
Normal 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);
|
||||
|
||||
};
|
||||
|
||||
11710
codes/games/client/Projects/Spine/js/spine-canvas.js
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"GameTxtList": []
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"GroupList": []
|
||||
}
|
||||
24
codes/games/client/Projects/Spine/output/gameabc_Image.json
Normal 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
|
||||
}]
|
||||
}
|
||||
11
codes/games/client/Projects/Spine/output/gameabc_Layer.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"LayerList": [
|
||||
{},{
|
||||
"Property": {
|
||||
"LayerID": 1,
|
||||
"LayerName": "Layer1"
|
||||
},
|
||||
"ObjectList": [
|
||||
1,2,3]
|
||||
}]
|
||||
}
|
||||
153
codes/games/client/Projects/Spine/output/gameabc_Object.json
Normal 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
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"VoiceFileList": []
|
||||
}
|
||||
255
codes/games/client/Projects/Spine/output/gameabc_data.js
Normal 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": []
|
||||
}
|
||||
;
|
||||
1
codes/games/client/Projects/Spine/output/gameabc_data.min.js
vendored
Normal 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": []};
|
||||
168
codes/games/client/Projects/Spine/save/Layer00001.xml
Normal 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>
|
||||
50
codes/games/client/Projects/Spine/save/Project.xml
Normal 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>
|
||||
5
codes/games/client/Projects/Spine/save/ResImage.xml
Normal 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>
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
141
codes/games/client/Projects/Spine/server.html
Normal 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>
|
||||
1089
codes/games/server/games/erqiwang/class.arith.js
Normal file
185
codes/games/server/games/erqiwang/class.desk.js
Normal file
@@ -0,0 +1,185 @@
|
||||
///////////////////////////////////////////////////
|
||||
////////// cls_youle_erqiwang_desk: 牌桌 //////////
|
||||
///////////////////////////////////////////////////
|
||||
var cls_youle_erqiwang_desk = cls_youle_erqiwang_desk || {
|
||||
|
||||
countdown_jiaofen: 15, //叫分倒计时
|
||||
countdown_xuanzhu: 20, //选主倒计时
|
||||
countdown_maipai: 25, //埋牌倒计时
|
||||
countdown_chupai: 30, //出牌倒计时
|
||||
|
||||
//创建牌桌对象
|
||||
new: function(o_room){
|
||||
|
||||
var desk = {};
|
||||
|
||||
desk.o_room = o_room; //桌所属的房间对象
|
||||
desk.seatlist = []; //玩家得分
|
||||
desk.seatlist.push([0, []]); //第一位为累积得分,第二位为每局得分
|
||||
desk.seatlist.push([0, []]);
|
||||
desk.seatlist.push([0, []]);
|
||||
desk.prepare = [0,0,0]; //玩家准备状态
|
||||
desk.paiju_list = []; //牌局列表
|
||||
|
||||
//方法
|
||||
desk.method = {};
|
||||
|
||||
//当前牌局
|
||||
desk.method.curr_paiju = function(){
|
||||
return desk.paiju_list[desk.paiju_list.length - 1];
|
||||
}
|
||||
|
||||
//新牌局
|
||||
desk.method.do_new_paiju = function(firstseat){
|
||||
cls_youle_erqiwang_desk.do_new_paiju(desk, firstseat);
|
||||
}
|
||||
|
||||
//叫分倒计时
|
||||
desk.method.get_countdown_jiaofen = function(){
|
||||
return cls_youle_erqiwang_desk.countdown_jiaofen;
|
||||
}
|
||||
|
||||
//叫主倒计时
|
||||
desk.method.get_countdown_xuanzhu = function(){
|
||||
return cls_youle_erqiwang_desk.countdown_xuanzhu;
|
||||
}
|
||||
|
||||
//埋牌倒计时
|
||||
desk.method.get_countdown_maipai = function(){
|
||||
return cls_youle_erqiwang_desk.countdown_maipai;
|
||||
}
|
||||
|
||||
//出牌倒计时
|
||||
desk.method.get_countdown_chupai = function(){
|
||||
return cls_youle_erqiwang_desk.countdown_chupai;
|
||||
}
|
||||
|
||||
//准备
|
||||
desk.method.do_prepare = function(seat){
|
||||
return cls_youle_erqiwang_desk.do_prepare(desk, seat);
|
||||
}
|
||||
|
||||
//获取大局结算包
|
||||
desk.method.get_desk_account = function(msg){
|
||||
return cls_youle_erqiwang_desk.get_desk_account(desk, msg);
|
||||
}
|
||||
|
||||
return desk;
|
||||
},
|
||||
|
||||
//新开一局
|
||||
do_new_paiju: function(o_desk, firstseat){
|
||||
//恢复玩家准备状态
|
||||
o_desk.prepare = [0,0,0];
|
||||
|
||||
if (o_desk.paiju_list.length > 0){
|
||||
delete o_desk.paiju_list[o_desk.paiju_list.length - 1].tmp_jiesuan_aset;
|
||||
}
|
||||
|
||||
//新开一局
|
||||
cls_youle_erqiwang_paiju.new(o_desk, firstseat);
|
||||
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.rpc = "fapai";
|
||||
msg.data = {};
|
||||
msg.data.asetidx = o_desk.paiju_list.length;
|
||||
msg.data.asetcount = o_desk.o_room.asetcount;
|
||||
msg.data.seat = o_desk.method.curr_paiju().method.get_callgrade_seat();
|
||||
msg.data.countdown = o_desk.method.get_countdown_jiaofen();
|
||||
for (var i = 0; i < o_desk.o_room.seatlist.length; i++) {
|
||||
msg.conmode = o_desk.o_room.seatlist[i].conmode;
|
||||
msg.fromid = o_desk.o_room.seatlist[i].fromid;
|
||||
msg.data.cards = o_desk.method.curr_paiju().method.get_seat_cards(i);
|
||||
youle_erqiwang.app.SendPack(msg);
|
||||
}
|
||||
},
|
||||
|
||||
//准备
|
||||
do_prepare: function(o_desk, seat){
|
||||
o_desk.prepare[seat] = 1;
|
||||
if (o_desk.prepare[0] && o_desk.prepare[1] && o_desk.prepare[2]){
|
||||
//新开一局
|
||||
var paiju = o_desk.method.curr_paiju();
|
||||
var firstseat = paiju.banker;
|
||||
if (paiju.result == 1 || paiju.result == 2){
|
||||
firstseat = (firstseat + 1) % 3;
|
||||
}
|
||||
cls_youle_erqiwang_desk.do_new_paiju(o_desk, firstseat);
|
||||
} else {
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.rpc = "zhunbei";
|
||||
msg.data = {};
|
||||
msg.data.seat = seat;
|
||||
o_desk.o_room.method.sendpack_toother(msg, -1);
|
||||
}
|
||||
},
|
||||
|
||||
//大局结算包
|
||||
get_desk_account: function(o_desk, msg){
|
||||
//保存战绩
|
||||
var do_save_grade = function(){
|
||||
var o_gameinfo1 = {};
|
||||
o_gameinfo1.roomcode = o_desk.o_room.roomcode;
|
||||
o_gameinfo1.asetcount = o_desk.paiju_list.length;
|
||||
o_gameinfo1.createtime = o_desk.o_room.createtime;
|
||||
o_gameinfo1.makewartime = o_desk.o_room.makewartime;
|
||||
o_gameinfo1.players = [];
|
||||
for (var i = 0; i < o_desk.o_room.seatlist.length; i++){
|
||||
var _player = {};
|
||||
_player.seat = i;
|
||||
_player.playerid = o_desk.o_room.seatlist[i].playerid;
|
||||
_player.name = o_desk.o_room.seatlist[i].nickname;
|
||||
_player.avatar = o_desk.o_room.seatlist[i].avatar;
|
||||
_player.score = o_desk.seatlist[i][0];
|
||||
o_gameinfo1.players.push(_player);
|
||||
}
|
||||
|
||||
var o_gameinfo2 = [];
|
||||
for (var i = 0; i < o_desk.paiju_list.length; i++) {
|
||||
var _paiju = {};
|
||||
_paiju.starttime = o_desk.paiju_list[i].starttime;
|
||||
_paiju.endtime = o_desk.paiju_list[i].endtime;
|
||||
_paiju.seatlist = [0, 0, 0];
|
||||
_paiju.seatlist[0] = o_desk.seatlist[0][1][i];
|
||||
_paiju.seatlist[1] = o_desk.seatlist[1][1][i];
|
||||
_paiju.seatlist[2] = o_desk.seatlist[2][1][i];
|
||||
_paiju.callproc = o_desk.paiju_list[i].callproc;
|
||||
_paiju.banker = o_desk.paiju_list[i].banker;
|
||||
_paiju.call = o_desk.paiju_list[i].call;
|
||||
_paiju.flower = o_desk.paiju_list[i].flower;
|
||||
_paiju.result = o_desk.paiju_list[i].result;
|
||||
//数据量太多,json转数组后进行存储
|
||||
_paiju.cards = [];
|
||||
for (var j = 0; j < o_desk.paiju_list[i].cards.length; j++) {
|
||||
var _pai = o_desk.paiju_list[i].cards[j];
|
||||
var _card = [];
|
||||
_card.push(_pai.id);
|
||||
_card.push(_pai.flower);
|
||||
_card.push(_pai.number);
|
||||
_card.push(_pai.score);
|
||||
_card.push(_pai.dealowner);
|
||||
_card.push(_pai.playround);
|
||||
_card.push(_pai.playindex);
|
||||
_card.push(_pai.playowner);
|
||||
_paiju.cards.push(_card);
|
||||
}
|
||||
o_gameinfo2.push(_paiju);
|
||||
}
|
||||
|
||||
//累积游戏得分
|
||||
for (var i = 0; i < o_desk.o_room.seatlist.length; i++) {
|
||||
o_desk.o_room.seatlist[i].gameinfo.grade = o_desk.seatlist[i][0];
|
||||
}
|
||||
//保存战绩
|
||||
youle_erqiwang.import.save_grade(o_desk.o_room, o_gameinfo1, o_gameinfo2, 1);
|
||||
}
|
||||
min_ontimeout(do_save_grade, 1000);
|
||||
|
||||
msg.data.account = o_desk.seatlist;
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
273
codes/games/server/games/erqiwang/class.export.js
Normal file
@@ -0,0 +1,273 @@
|
||||
///////////////////////////////////////////////////
|
||||
/////// cls_youle_erqiwang_export: 输出接口 ///////
|
||||
///////////////////////////////////////////////////
|
||||
var cls_youle_erqiwang_export = cls_youle_erqiwang_export || {
|
||||
|
||||
new: function() {
|
||||
|
||||
var exp = {};
|
||||
|
||||
//解析roomtype获取开房所需房卡
|
||||
exp.get_needroomcard = function(roomtype){
|
||||
switch (roomtype[2])
|
||||
{
|
||||
case 1: //房主扣卡
|
||||
switch (roomtype[0])
|
||||
{
|
||||
case 1:
|
||||
return 1;
|
||||
break;
|
||||
case 2:
|
||||
return 2;
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2: //每人扣卡
|
||||
switch (roomtype[0])
|
||||
{
|
||||
case 1:
|
||||
return 1;
|
||||
break;
|
||||
case 2:
|
||||
return 2;
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//解析roomtype获取总局数
|
||||
exp.get_asetcount = function(roomtype){
|
||||
switch (roomtype[0])
|
||||
{
|
||||
case 1:
|
||||
return 2;
|
||||
break;
|
||||
case 2:
|
||||
return 4;
|
||||
break;
|
||||
default:
|
||||
return 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//加入房间时根据roomtype获取需要的房卡数量
|
||||
exp.get_needroomcard_joinroom = function(roomtype){
|
||||
switch (roomtype[2])
|
||||
{
|
||||
case 1: //房主扣卡
|
||||
return 0;
|
||||
break;
|
||||
case 2: //每人扣卡
|
||||
switch (roomtype[0])
|
||||
{
|
||||
case 1:
|
||||
return 1;
|
||||
break;
|
||||
case 2:
|
||||
return 2;
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//开战
|
||||
exp.makewar = function(o_room){
|
||||
//牌桌对象
|
||||
o_room.o_desk = cls_youle_erqiwang_desk.new(o_room);
|
||||
for (var i = 0; i < o_room.seatlist.length; i++) {
|
||||
o_room.seatlist[i].gameinfo.isbet = 1;
|
||||
}
|
||||
|
||||
//开始第一局
|
||||
var do_newpaiju = function(){
|
||||
o_room.o_desk.method.do_new_paiju(0);
|
||||
}
|
||||
min_ontimeout(do_newpaiju, 1000);
|
||||
}
|
||||
|
||||
//获取某个玩家的牌桌整桌当前信息
|
||||
exp.get_deskinfo = function(o_room, seat){
|
||||
if (!o_room.o_desk){
|
||||
return null;
|
||||
}
|
||||
var paiju = o_room.o_desk.method.curr_paiju();
|
||||
var deskinfo = {};
|
||||
deskinfo.count = o_room.asetcount; //总局数
|
||||
deskinfo.idx = paiju.idx; //当前局数
|
||||
deskinfo.PlayerInfo = []; //玩家当前累积得分
|
||||
for (var i = 0; i < o_room.o_desk.seatlist.length; i++) {
|
||||
deskinfo.PlayerInfo.push(o_room.o_desk.seatlist[i][0]);
|
||||
};
|
||||
deskinfo.step = paiju.step; //牌局状态
|
||||
deskinfo.MyCards = paiju.method.get_seat_cards(seat); //我手上的牌
|
||||
switch (paiju.step){
|
||||
case 1: //发完牌叫分
|
||||
deskinfo.CallRun = {};
|
||||
//当前叫分位置
|
||||
deskinfo.CallRun.seat = paiju.method.get_callgrade_seat();
|
||||
//叫分倒计时
|
||||
deskinfo.CallRun.countdown = o_room.o_desk.method.get_countdown_jiaofen();
|
||||
//当前叫分
|
||||
deskinfo.CallRun.nowcall = paiju.method.get_callgrade_value();
|
||||
//当前叫分倍数
|
||||
deskinfo.CallRun.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(deskinfo.CallRun.nowcall);
|
||||
//所有玩家的叫分情况
|
||||
deskinfo.CallRun.call = [null, null, null];
|
||||
deskinfo.CallRun.call[0] = paiju.method.get_seat_callgrade(0);
|
||||
deskinfo.CallRun.call[1] = paiju.method.get_seat_callgrade(1);
|
||||
deskinfo.CallRun.call[2] = paiju.method.get_seat_callgrade(2);
|
||||
break;
|
||||
case 2: //选主
|
||||
deskinfo.ChooseMain = {};
|
||||
//庄家
|
||||
deskinfo.ChooseMain.banker = paiju.banker;
|
||||
//庄家叫分
|
||||
deskinfo.ChooseMain.call = paiju.call;
|
||||
//庄家叫分倍数
|
||||
deskinfo.ChooseMain.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(paiju.call);
|
||||
//选主倒计时
|
||||
deskinfo.ChooseMain.countdown = o_room.o_desk.method.get_countdown_xuanzhu();
|
||||
//底牌
|
||||
deskinfo.ChooseMain.bottomcards = paiju.method.get_bottomcards();
|
||||
//是否可投降
|
||||
if (paiju.call < 65){
|
||||
deskinfo.ChooseMain.touxiang = 0;
|
||||
} else {
|
||||
deskinfo.ChooseMain.touxiang = 1;
|
||||
}
|
||||
break;
|
||||
case 3: //埋牌
|
||||
deskinfo.BuryCards = {};
|
||||
//庄家
|
||||
deskinfo.BuryCards.banker = paiju.banker;
|
||||
//庄家叫分
|
||||
deskinfo.BuryCards.call = paiju.call;
|
||||
//庄家叫分倍数
|
||||
deskinfo.BuryCards.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(paiju.call);
|
||||
//埋牌倒计时
|
||||
deskinfo.BuryCards.countdown = o_room.o_desk.method.get_countdown_maipai();
|
||||
//底牌
|
||||
deskinfo.BuryCards.bottomcards = paiju.method.get_bottomcards();
|
||||
//主牌花色
|
||||
deskinfo.BuryCards.flower = paiju.flower;
|
||||
//是否可投降
|
||||
if (paiju.call < 65){
|
||||
deskinfo.BuryCards.touxiang = 0;
|
||||
} else {
|
||||
deskinfo.BuryCards.touxiang = 1;
|
||||
}
|
||||
break;
|
||||
case 4: //投降
|
||||
deskinfo.Surrender = {};
|
||||
//庄家
|
||||
deskinfo.Surrender.banker = paiju.banker;
|
||||
//庄家叫分
|
||||
deskinfo.Surrender.call = paiju.call;
|
||||
//庄家叫分倍数
|
||||
deskinfo.Surrender.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(paiju.call);
|
||||
//出牌倒计时
|
||||
deskinfo.Surrender.countdown = o_room.o_desk.method.get_countdown_chupai();
|
||||
//主牌花色
|
||||
deskinfo.Surrender.flower = paiju.flower;
|
||||
if (seat == paiju.banker){
|
||||
//埋的牌
|
||||
deskinfo.Surrender.bottomcards = paiju.method.get_burycard();
|
||||
}
|
||||
//是否可投降
|
||||
if (paiju.call < 65){
|
||||
deskinfo.Surrender.touxiang = 0;
|
||||
} else {
|
||||
deskinfo.Surrender.touxiang = 1;
|
||||
}
|
||||
break;
|
||||
case 5: //出牌
|
||||
deskinfo.PushCards = {};
|
||||
//庄家
|
||||
deskinfo.PushCards.banker = paiju.banker;
|
||||
//庄家叫分
|
||||
deskinfo.PushCards.call = paiju.call;
|
||||
//庄家叫分倍数
|
||||
deskinfo.PushCards.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(paiju.call);
|
||||
//出牌倒计时
|
||||
deskinfo.PushCards.countdown = o_room.o_desk.method.get_countdown_chupai();
|
||||
//主牌花色
|
||||
deskinfo.PushCards.flower = paiju.flower;
|
||||
//捡分
|
||||
var jian_grade = paiju.method.get_jian_grade();
|
||||
if (seat == paiju.banker){
|
||||
//埋的牌
|
||||
deskinfo.PushCards.bottomcards = paiju.method.get_burycard();
|
||||
} else {
|
||||
//闲家当前的捡分分牌
|
||||
deskinfo.PushCards.gradecards = jian_grade.cards;
|
||||
}
|
||||
//闲家当前的捡分分数
|
||||
deskinfo.PushCards.grade = jian_grade.grade;
|
||||
//当前的出牌情况
|
||||
deskinfo.PushCards.playproc = paiju.playproc;
|
||||
//座位列表
|
||||
deskinfo.PushCards.seatlist = paiju.seatlist;
|
||||
//出牌历史
|
||||
deskinfo.PushCards.pushlist = [];
|
||||
for (var i = 0; i < paiju.cards.length; i++) {
|
||||
var pai = paiju.cards[i];
|
||||
if (pai.playround > 0){
|
||||
if (deskinfo.PushCards.pushlist.length < pai.playround){
|
||||
deskinfo.PushCards.pushlist.length = pai.playround;
|
||||
}
|
||||
if (!deskinfo.PushCards.pushlist[pai.playround - 1]){
|
||||
deskinfo.PushCards.pushlist[pai.playround - 1] = [[], [], []];
|
||||
}
|
||||
|
||||
var owner = pai.dealowner;
|
||||
if (owner == 0){
|
||||
owner = paiju.banker + 1;
|
||||
}
|
||||
deskinfo.PushCards.pushlist[pai.playround - 1][owner - 1].push(i);
|
||||
}
|
||||
}
|
||||
//按大小排序
|
||||
for (var i = 0; i < deskinfo.PushCards.pushlist.length; i++) {
|
||||
for (var j = 0; j < deskinfo.PushCards.pushlist.length; j++) {
|
||||
deskinfo.PushCards.pushlist[i][j] = cls_youle_erqiwang_arith.order_cards(pai.flower, deskinfo.PushCards.pushlist[i][j]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 6: //结算
|
||||
deskinfo.Balance = {};
|
||||
//玩家的准备状态
|
||||
deskinfo.Balance.readystate = o_room.o_desk.prepare;
|
||||
if (o_room.o_desk.prepare[seat] == 0){
|
||||
//结算情况
|
||||
deskinfo.Balance.aset = paiju.tmp_jiesuan_aset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return deskinfo;
|
||||
}
|
||||
|
||||
//解散房间
|
||||
exp.get_disbandRoom = function(o_room){
|
||||
var msg = {};
|
||||
return o_room.o_desk.method.curr_paiju().method.get_paiju_account(2, msg);
|
||||
}
|
||||
|
||||
return exp;
|
||||
}
|
||||
}
|
||||
|
||||
//对内输出接口
|
||||
youle_erqiwang.export = cls_youle_erqiwang_export.new();
|
||||
30
codes/games/server/games/erqiwang/class.import.js
Normal file
@@ -0,0 +1,30 @@
|
||||
///////////////////////////////////////////////////
|
||||
/////// cls_youle_platform_import: 输入接口 ///////
|
||||
///////////////////////////////////////////////////
|
||||
var cls_youle_erqiwang_import = cls_youle_erqiwang_import || {
|
||||
|
||||
new: function() {
|
||||
|
||||
var imp = {};
|
||||
|
||||
//检查玩家
|
||||
imp.check_player = function(agentid, gameid, roomcode, seat, playerid, conmode, fromid){
|
||||
return youle_erqiwang.app.youle_room.export.check_player(agentid, gameid, roomcode, seat, playerid, conmode, fromid);
|
||||
}
|
||||
|
||||
//第一局结算扣除房卡
|
||||
imp.deduct_roomcard = function(o_room){
|
||||
return youle_erqiwang.app.youle_room.export.deduct_roomcard(o_room);
|
||||
}
|
||||
|
||||
//保存战绩
|
||||
imp.save_grade = function(o_room, o_gameinfo1, o_gameinfo2, freeroomflag){
|
||||
youle_erqiwang.app.youle_room.export.save_grade(o_room, o_gameinfo1, o_gameinfo2, freeroomflag);
|
||||
}
|
||||
|
||||
return imp;
|
||||
}
|
||||
}
|
||||
|
||||
// 输入接口
|
||||
youle_erqiwang.import = cls_youle_erqiwang_import.new();
|
||||
61
codes/games/server/games/erqiwang/class.pai.js
Normal file
@@ -0,0 +1,61 @@
|
||||
///////////////////////////////////////////////////
|
||||
////////// cls_youle_erqiwang_pai: 牌 //////////
|
||||
///////////////////////////////////////////////////
|
||||
var cls_youle_erqiwang_pai = cls_youle_erqiwang_pai || {
|
||||
|
||||
//创建单张牌对象
|
||||
new: function(id, flower, number, score, dealowner){
|
||||
/*
|
||||
参数说明:
|
||||
id 绝对id
|
||||
flower 牌面花色
|
||||
number 牌面数值
|
||||
code 牌编码
|
||||
|
||||
扑克牌的统一编码规则:
|
||||
1,牌面花色的定义 5:王 4:黑桃 3:红心 2:梅花 1:方块
|
||||
2,牌面数值的定义 01:A 02:2 03:3 ... 09:9 10:10 11:J 12:Q 13:K 53:小王 54:大王
|
||||
3,牌编码的定义
|
||||
3.1 常主的牌编码定义:
|
||||
未叫主之前:
|
||||
2的牌编码定义 黑桃2:2402 红心2:2302 梅花2:2202 方块2:2102
|
||||
7的牌编码定义 黑桃7:7407 红心2:7307 梅花7:7207 方块7:7107
|
||||
王的牌编码定义 小王:9553 大王:9554
|
||||
叫主之后:
|
||||
主2牌编码定义 原有牌编码的基础上+1000,例如红心为主牌,则红心2的牌编码变为3302
|
||||
主7牌编码定义 原有牌编码的基础上+1000,例如红心为主牌,则红心7的牌编码变为8307
|
||||
3.2 普通牌的牌编码定义
|
||||
A的定义:14
|
||||
未叫主之前:
|
||||
3位数字 第一位为牌面花色 第二三位为牌面数值
|
||||
叫主之后:
|
||||
主牌花色 4位数字,即在原有牌编码的基础上+1000,表示是主牌
|
||||
非主牌花色 3位数字,即原有牌编码不变
|
||||
3.3 综上所述
|
||||
牌编码是3位的,即小于1000的都是非主牌
|
||||
牌编码是4位的,即大于1000的都是主牌
|
||||
可根据牌编码进行牌大小的比较和排序
|
||||
4 牌型定义
|
||||
>100: 单张, 101一张单张,102两张单张,103三张单张 ...
|
||||
>200: 对子, 201一对,202两对,203三对 ...
|
||||
>300: 拖拉机,302两连对拖拉机,303三连对拖拉机 ...
|
||||
|
||||
*/
|
||||
var pai = {};
|
||||
pai.id = id; //绝对id,即数组下标,从0开始计数
|
||||
pai.flower = flower; //牌面花色
|
||||
pai.number = number; //牌面数值
|
||||
pai.score = score; //牌在游戏中的分值
|
||||
pai.dealowner = dealowner; //发牌状态
|
||||
//-1:规则去除的牌
|
||||
// 0:未发的牌,即底牌
|
||||
//>0:发牌发到谁手上,从1开始计数,即位置编号+1,位置编号是从0开始计数的
|
||||
pai.playround = -1; //出牌状态
|
||||
//-1:未出的牌
|
||||
// 0:埋牌
|
||||
//>0:牌是第几轮出出去的,从1开始计数
|
||||
pai.playindex = -1; //本轮中的出牌顺序,从1开始计数
|
||||
pai.playowner = -1; //出牌后被谁得到,与位置编号对应
|
||||
return pai;
|
||||
}
|
||||
}
|
||||
863
codes/games/server/games/erqiwang/class.paiju.js
Normal file
@@ -0,0 +1,863 @@
|
||||
///////////////////////////////////////////////////
|
||||
////////// cls_youle_erqiwang_paiju: 牌局 /////////
|
||||
///////////////////////////////////////////////////
|
||||
var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
|
||||
new: function(o_desk, firstseat) {
|
||||
var paiju = {};
|
||||
o_desk.paiju_list.push(paiju);
|
||||
|
||||
// ================= 实例的私有属性 ================= //
|
||||
//发牌过程中待发的位置列表
|
||||
var tmpdeal = [];
|
||||
//三家需要各发28张牌
|
||||
for (var i = 0; i < 28; i++){
|
||||
tmpdeal.push(0); //0表示第一个玩家的位置
|
||||
tmpdeal.push(1); //1表示第二个玩家的位置
|
||||
tmpdeal.push(2); //2表示第三个玩家的位置
|
||||
}
|
||||
//8张底牌
|
||||
for (var i = 0; i < 8; i++){
|
||||
tmpdeal.push(-1); //-1 表示是底牌
|
||||
}
|
||||
|
||||
// ================= 实例的私有方法 ================= //
|
||||
|
||||
//初始化座位列表
|
||||
var init_seatlist = function(){
|
||||
//第一层是玩家列表,与位置序号对应
|
||||
//第二层前4个数组是花色列表,与花色-1对应
|
||||
//第三层的第一位是无该花色的牌标志
|
||||
//第三层的第二位是该花色无对标志
|
||||
//第二层第5个数组是报副情况
|
||||
//第三层的第一位是剩余主牌数量
|
||||
//第三层的第二位是剩余主牌对子
|
||||
//0号位
|
||||
paiju.seatlist.push([[0,0],[0,0],[0,0],[0,0],[-1,-1]]);
|
||||
//1号位
|
||||
paiju.seatlist.push([[0,0],[0,0],[0,0],[0,0],[-1,-1]]);
|
||||
//2号位
|
||||
paiju.seatlist.push([[0,0],[0,0],[0,0],[0,0],[-1,-1]]);
|
||||
}
|
||||
|
||||
//初始化牌列表并发牌
|
||||
var init_cards = function(){
|
||||
//两副牌
|
||||
for (var i = 1; i <= 2; i++){
|
||||
//四种花色
|
||||
for (var j = 1; j <= 4; j++){
|
||||
for (var k = 1; k <= 13; k++){
|
||||
//牌id
|
||||
var id = (i - 1) * 54 + (j - 1) * 13 + k - 1;
|
||||
//分值
|
||||
var score = 0;
|
||||
//发牌状态
|
||||
var dealowner = 0;
|
||||
//特殊处理
|
||||
switch (k)
|
||||
{
|
||||
case 5:
|
||||
score = 5;
|
||||
break;
|
||||
case 10:
|
||||
score = 10;
|
||||
break;
|
||||
case 13:
|
||||
score = 10;
|
||||
break;
|
||||
case 3:
|
||||
dealowner = -1;
|
||||
break;
|
||||
case 4:
|
||||
dealowner = -1;
|
||||
break;
|
||||
}
|
||||
var pai = cls_youle_erqiwang_pai.new(id, j, k, score, dealowner);
|
||||
paiju.cards.push(pai);
|
||||
//发牌
|
||||
if (k != 3 && k != 4){
|
||||
do_dealpai(pai);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//小王
|
||||
var pai = cls_youle_erqiwang_pai.new((i - 1) * 54 + 53 - 1, 5, 53, 0, 0);
|
||||
paiju.cards.push(pai);
|
||||
do_dealpai(pai);
|
||||
|
||||
//大王
|
||||
var pai = cls_youle_erqiwang_pai.new((i - 1) * 54 + 54 - 1, 5, 54, 0, 0);
|
||||
paiju.cards.push(pai);
|
||||
do_dealpai(pai);
|
||||
}
|
||||
}
|
||||
|
||||
//将单张牌随机发到玩家手中或成为底牌
|
||||
var do_dealpai = function(o_pai){
|
||||
var idx = min_random(0, tmpdeal.length - 1);
|
||||
if (tmpdeal[idx] == -1) { //底牌
|
||||
o_pai.dealowner = 0;
|
||||
} else {
|
||||
o_pai.dealowner = tmpdeal[idx] + 1;
|
||||
}
|
||||
//将数组的最后一位至该位置
|
||||
if (idx < tmpdeal.length - 1) {
|
||||
tmpdeal[idx] = tmpdeal[tmpdeal.length - 1];
|
||||
};
|
||||
//数组长度减一
|
||||
tmpdeal.length = tmpdeal.length - 1;
|
||||
}
|
||||
|
||||
//进入叫分阶段
|
||||
var start_call = function(){
|
||||
paiju.step = 1;
|
||||
var o_call = {};
|
||||
o_call.seat = paiju.firstseat;
|
||||
o_call.call = null; //null:未叫分 0:不叫分 >0:叫分分数
|
||||
paiju.callproc.push(o_call);
|
||||
}
|
||||
|
||||
// ================= 实例的公有属性 ================= //
|
||||
paiju.o_desk = o_desk;
|
||||
//牌局号
|
||||
paiju.idx = o_desk.paiju_list.length;
|
||||
//第一个叫分者的位置
|
||||
paiju.firstseat = firstseat;
|
||||
//开始时间
|
||||
paiju.starttime = min_now();
|
||||
//结束时间
|
||||
paiju.endtime = null;
|
||||
//座位列表
|
||||
paiju.seatlist = [];
|
||||
//牌列表
|
||||
paiju.cards = [];
|
||||
//当前阶段
|
||||
paiju.step = -1; //1发完牌叫分 2选主 3埋牌 4投降 5出牌 6结算
|
||||
//叫分过程
|
||||
paiju.callproc = [];
|
||||
//庄家位置
|
||||
paiju.banker = -1;
|
||||
//庄家叫分
|
||||
paiju.call = -1;
|
||||
//主牌花色
|
||||
paiju.flower = -1;
|
||||
//当前出牌情况
|
||||
paiju.playproc = {};
|
||||
//输赢结果 0:庄赢 1:闲赢 2:庄投降 3:中途解散
|
||||
paiju.result = null;
|
||||
//结算包,供断线重连时使用,新开一局时清除
|
||||
paiju.tmp_jiesuan_aset = null;
|
||||
|
||||
//初始化座位列表
|
||||
init_seatlist();
|
||||
//初始化牌列表并发牌
|
||||
init_cards();
|
||||
//进入叫分阶段
|
||||
start_call();
|
||||
|
||||
// ================= 实例的公有方法 ================= //
|
||||
paiju.method = {};
|
||||
|
||||
//获取某个位置玩家手上的牌
|
||||
paiju.method.get_seat_cards = function(seat){
|
||||
return cls_youle_erqiwang_paiju.get_seat_cards(paiju, seat);
|
||||
}
|
||||
|
||||
//获取当前的叫分位置
|
||||
paiju.method.get_callgrade_seat = function(){
|
||||
return paiju.callproc[paiju.callproc.length - 1].seat;
|
||||
}
|
||||
|
||||
//获取当前的叫分分数
|
||||
paiju.method.get_callgrade_value = function(){
|
||||
return cls_youle_erqiwang_paiju.get_callgrade_value(paiju);
|
||||
}
|
||||
|
||||
//获取某个玩家的当前叫分情况
|
||||
paiju.method.get_seat_callgrade = function(seat){
|
||||
return cls_youle_erqiwang_paiju.get_seat_callgrade(paiju, seat);
|
||||
}
|
||||
|
||||
//叫分或不叫
|
||||
paiju.method.do_callgrade = function(call){
|
||||
cls_youle_erqiwang_paiju.do_callgrade(paiju, call);
|
||||
}
|
||||
|
||||
//获取底牌
|
||||
paiju.method.get_bottomcards = function(){
|
||||
return cls_youle_erqiwang_paiju.get_bottomcards(paiju);
|
||||
}
|
||||
|
||||
//选主
|
||||
paiju.method.do_choiceflower = function(flower){
|
||||
cls_youle_erqiwang_paiju.do_choiceflower(paiju, flower);
|
||||
}
|
||||
|
||||
//判断牌是否在玩家手上
|
||||
paiju.method.check_cards_inhand = function(cards, seat){
|
||||
return cls_youle_erqiwang_paiju.check_cards_inhand(paiju, cards, seat);
|
||||
}
|
||||
|
||||
//埋牌
|
||||
paiju.method.do_burycard = function(cards){
|
||||
cls_youle_erqiwang_paiju.do_burycard(paiju, cards);
|
||||
}
|
||||
|
||||
//检查选中的牌是否可出,并出牌
|
||||
paiju.method.do_playcard = function(cards){
|
||||
return cls_youle_erqiwang_paiju.do_playcard(paiju, cards);
|
||||
}
|
||||
|
||||
//是否有人报副
|
||||
paiju.method.have_baofu = function(){
|
||||
return (paiju.seatlist[0][4][0] == 0 || paiju.seatlist[1][4][0] == 0 || paiju.seatlist[2][4][0] == 0);
|
||||
}
|
||||
|
||||
//在牌列表中获取分牌和分值
|
||||
paiju.method.get_grade_incard = function(cards){
|
||||
return cls_youle_erqiwang_paiju.get_grade_incard(paiju, cards);
|
||||
}
|
||||
|
||||
//获取埋牌
|
||||
paiju.method.get_burycard= function(){
|
||||
return cls_youle_erqiwang_paiju.get_burycard(paiju);
|
||||
}
|
||||
|
||||
//获取抠底包
|
||||
paiju.method.get_bottom_account = function(msg){
|
||||
return cls_youle_erqiwang_paiju.get_bottom_account(paiju, msg);
|
||||
}
|
||||
|
||||
//小局结算,type 0正常结算 1投降结算 2解散结算
|
||||
paiju.method.get_paiju_account = function(type, msg){
|
||||
return cls_youle_erqiwang_paiju.get_paiju_account(paiju, type, msg);
|
||||
}
|
||||
|
||||
//获取闲家当前的捡分分数和分牌
|
||||
paiju.method.get_jian_grade = function(){
|
||||
return cls_youle_erqiwang_paiju.get_jian_grade(paiju);
|
||||
}
|
||||
|
||||
return paiju;
|
||||
},
|
||||
|
||||
//下一个位置
|
||||
get_nextseat: function(seat){
|
||||
return (seat + 1) % 3;
|
||||
},
|
||||
|
||||
//获取某个位置玩家手上的牌
|
||||
get_seat_cards: function(o_paiju, seat){
|
||||
var aryCardIDs = [];
|
||||
for (var i = 0; i < o_paiju.cards.length; i++){
|
||||
var o_card = o_paiju.cards[i];
|
||||
if (o_card.playround == -1){ //未出的牌
|
||||
if ((o_card.dealowner == seat + 1) || //发到手上
|
||||
(o_card.dealowner == 0 && seat == o_paiju.banker)){ //庄家包括底牌
|
||||
aryCardIDs.push(o_paiju.cards[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs);
|
||||
},
|
||||
|
||||
//获取某个位置玩家发到手上的牌
|
||||
get_seat_cards_deal: function(o_paiju, seat){
|
||||
var aryCardIDs = [];
|
||||
for (var i = 0; i < o_paiju.cards.length; i++){
|
||||
var o_card = o_paiju.cards[i];
|
||||
if (o_card.dealowner == seat + 1){
|
||||
aryCardIDs.push(o_paiju.cards[i].id);
|
||||
}
|
||||
}
|
||||
return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs);
|
||||
},
|
||||
|
||||
//获取某个位置玩家发到手上的牌(庄家包含底牌)
|
||||
get_seat_cards_owner: function(o_paiju, seat){
|
||||
var aryCardIDs = [];
|
||||
for (var i = 0; i < o_paiju.cards.length; i++){
|
||||
var o_card = o_paiju.cards[i];
|
||||
if ((o_card.dealowner == seat + 1) || //发到手上
|
||||
(o_card.dealowner == 0 && seat == o_paiju.banker)){ //庄家包括底牌
|
||||
aryCardIDs.push(o_paiju.cards[i].id);
|
||||
}
|
||||
}
|
||||
return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs);
|
||||
},
|
||||
|
||||
//获取某个位置玩家手上的主牌
|
||||
get_seat_zhucards: function(o_paiju, seat){
|
||||
var aryCardIDs = [];
|
||||
for (var i = 0; i < o_paiju.cards.length; i++){
|
||||
var o_card = o_paiju.cards[i];
|
||||
if (o_card.playround == -1){ //未出的牌
|
||||
if ((o_card.dealowner == seat + 1) || //发到手上
|
||||
(o_card.dealowner == 0 && seat == o_paiju.banker)){ //庄家包括底牌
|
||||
if (o_card.flower == o_paiju.flower || o_card.flower == 5 || o_card.number == 2 || o_card.number == 7){
|
||||
aryCardIDs.push(o_paiju.cards[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs);
|
||||
},
|
||||
|
||||
//获取当前的叫分分数
|
||||
get_callgrade_value: function(o_paiju){
|
||||
var value = 0;
|
||||
for (var i = o_paiju.callproc.length - 2; i >= 0; i--) {
|
||||
if (o_paiju.callproc[i].call) {
|
||||
value = o_paiju.callproc[i].call;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
//获取某个位置的叫分 返回值:null未叫 0不叫 >0当前叫分
|
||||
get_seat_callgrade: function(o_paiju, seat){
|
||||
for (var i = o_paiju.callproc.length - 2; i >= 0; i--){
|
||||
if (o_paiju.callproc[i].seat == seat){
|
||||
return o_paiju.callproc[i].call;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
//叫分
|
||||
do_callgrade: function(o_paiju, call){
|
||||
var o_call = o_paiju.callproc[o_paiju.callproc.length - 1];
|
||||
o_call.call = call;
|
||||
|
||||
//等待下一个叫分者叫分
|
||||
var do_wait_nextseat_call = function(seat){
|
||||
var o_call = {};
|
||||
o_call.seat = seat;
|
||||
o_call.call = null;
|
||||
o_paiju.callproc.push(o_call);
|
||||
}
|
||||
|
||||
//上庄
|
||||
var do_up_banker = function(seat, call){
|
||||
//庄家位置
|
||||
o_paiju.banker = seat;
|
||||
//庄家叫分
|
||||
o_paiju.call = call;
|
||||
//进入选主阶段
|
||||
o_paiju.step = 2;
|
||||
}
|
||||
|
||||
//叫了5分直接上庄
|
||||
if (call == 5){
|
||||
do_up_banker(o_call.seat, call);
|
||||
return;
|
||||
}
|
||||
//叫了分
|
||||
if (call > 0){
|
||||
//下一个位置
|
||||
var nextseat = cls_youle_erqiwang_paiju.get_nextseat(o_call.seat);
|
||||
//下一个位置的叫分
|
||||
var nextcall = cls_youle_erqiwang_paiju.get_seat_callgrade(o_paiju, nextseat);
|
||||
//下一个位置不叫分
|
||||
if (nextcall == 0){
|
||||
//再下一个位置
|
||||
nextseat = cls_youle_erqiwang_paiju.get_nextseat(nextseat);
|
||||
}
|
||||
//等待下一个叫分者叫分
|
||||
do_wait_nextseat_call(nextseat);
|
||||
return;
|
||||
}
|
||||
|
||||
//不叫
|
||||
if (call == 0){
|
||||
//下一个位置
|
||||
var nextseat_1 = cls_youle_erqiwang_paiju.get_nextseat(o_call.seat);
|
||||
//下一个位置的叫分
|
||||
var nextcall_1 = cls_youle_erqiwang_paiju.get_seat_callgrade(o_paiju, nextseat_1);
|
||||
//再下一个位置
|
||||
var nextseat_2 = cls_youle_erqiwang_paiju.get_nextseat(nextseat_1);
|
||||
//再下一个位置的叫分
|
||||
var nextcall_2 = cls_youle_erqiwang_paiju.get_seat_callgrade(o_paiju, nextseat_2);
|
||||
|
||||
if (nextcall_1 != 0 && nextcall_2 != 0){
|
||||
//另外两家都没有不叫分
|
||||
do_wait_nextseat_call(nextseat_1);
|
||||
return;
|
||||
}
|
||||
if (nextcall_1 != 0 && nextcall_2 == 0){
|
||||
//nextseat_1上庄
|
||||
do_up_banker(nextseat_1, nextcall_1);
|
||||
return;
|
||||
}
|
||||
if (nextcall_1 == 0 && nextcall_2 != 0){
|
||||
//nextseat_2上庄
|
||||
do_up_banker(nextseat_2, nextcall_2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//获取底牌
|
||||
get_bottomcards: function(o_paiju){
|
||||
var aryCardIDs = [];
|
||||
for (var i = 0; i < o_paiju.cards.length; i++){
|
||||
if (o_paiju.cards[i].dealowner == 0){
|
||||
aryCardIDs.push(o_paiju.cards[i].id);
|
||||
}
|
||||
}
|
||||
return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs);
|
||||
},
|
||||
|
||||
//选主
|
||||
do_choiceflower: function(o_paiju, flower){
|
||||
o_paiju.flower = flower;
|
||||
//修改主牌的牌编码
|
||||
for (var i = 1; i <= 2; i++){ //两副牌
|
||||
for (var j = 0; j < 13; j++){
|
||||
var o_card = o_paiju.cards[(i - 1) * 54 + (flower - 1) * 13 + j];
|
||||
}
|
||||
}
|
||||
//进入埋牌阶段
|
||||
o_paiju.step = 3;
|
||||
},
|
||||
|
||||
//判断牌是否在玩家手上
|
||||
check_cards_inhand: function(o_paiju, cards, seat){
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
var o_card = o_paiju.cards[cards[i]];
|
||||
if (o_card.playround != -1){
|
||||
return;
|
||||
}
|
||||
if (seat == o_paiju.banker){
|
||||
//是庄家
|
||||
if (o_card.dealowner != seat + 1 && o_card.dealowner != 0){
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
//是闲家
|
||||
if (o_card.dealowner != seat + 1){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
//埋牌
|
||||
do_burycard: function(o_paiju, cards){
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
o_paiju.cards[cards[i]].playround = 0;
|
||||
};
|
||||
//进入投降阶段
|
||||
o_paiju.step = 4;
|
||||
cls_youle_erqiwang_paiju.new_playround(o_paiju, 1, o_paiju.banker);
|
||||
},
|
||||
|
||||
//新一轮出牌
|
||||
new_playround: function(o_paiju, round, startseat){
|
||||
o_paiju.playproc.round = round; //第几轮
|
||||
o_paiju.playproc.start = startseat; //本轮的起始出牌位置
|
||||
o_paiju.playproc.currseat = startseat; //本轮的当前的出牌位置
|
||||
o_paiju.playproc.startcount = -1; //本轮第一个出牌的张数
|
||||
o_paiju.playproc.startflower = -1; //本轮第一个出牌的花色
|
||||
o_paiju.playproc.starttype = -1; //本轮第一个出牌的牌型
|
||||
o_paiju.playproc.maxseat = -1; //谁的牌最大,位置
|
||||
o_paiju.playproc.maxcard = -1; //满足第一个玩家出牌的牌型最大牌
|
||||
o_paiju.playproc.cards = []; //本轮各位置出的牌,数组与位置序号对应
|
||||
o_paiju.playproc.cards.length= 3;
|
||||
},
|
||||
|
||||
//在牌列表中获取分牌和分值
|
||||
get_grade_incard: function(o_paiju, cards){
|
||||
var re = {};
|
||||
re.grade = 0;
|
||||
re.cards = [];
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
if (o_paiju.cards[cards[i]].score > 0){
|
||||
re.grade = re.grade + o_paiju.cards[cards[i]].score;
|
||||
re.cards.push(cards[i]);
|
||||
}
|
||||
}
|
||||
return re;
|
||||
},
|
||||
|
||||
//检查选中的牌是否可出,并出牌
|
||||
do_playcard: function(o_paiju, cards){
|
||||
//出牌
|
||||
var doplay = function(playindex){
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
o_paiju.cards[cards[i]].playround = o_paiju.playproc.round;
|
||||
o_paiju.cards[cards[i]].playindex = playindex;
|
||||
};
|
||||
o_paiju.playproc.cards[o_paiju.playproc.currseat] = cards;
|
||||
|
||||
//进入出牌阶段
|
||||
o_paiju.step = 5;
|
||||
|
||||
//是否报副处理
|
||||
if (o_paiju.seatlist[o_paiju.playproc.currseat][4][0] != 0){
|
||||
var _zhu = cls_youle_erqiwang_paiju.get_seat_zhucards(o_paiju, o_paiju.playproc.currseat);
|
||||
if (_zhu.length == 0){
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][4][0] = 0;
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][4][1] = 0;
|
||||
}
|
||||
if (o_paiju.method.have_baofu()){
|
||||
var _parilist = cls_youle_erqiwang_arith.get_pairlist(o_paiju.flower, _zhu);
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][4][0] = _zhu.length;
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][4][1] = _parilist.length;
|
||||
|
||||
if (o_paiju.seatlist[o_paiju.playproc.currseat][4][0] == 0){
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.flower - 1][0] = 1;
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.flower - 1][1] = 1;
|
||||
} else if (o_paiju.seatlist[o_paiju.playproc.currseat][4][1] == 0){
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.flower - 1][1] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//本轮中的第二个出牌位置
|
||||
var nextseat_1 = cls_youle_erqiwang_paiju.get_nextseat(o_paiju.playproc.start);
|
||||
//本轮中的第三个出牌位置
|
||||
var nextseat_2 = cls_youle_erqiwang_paiju.get_nextseat(nextseat_1);
|
||||
|
||||
if (o_paiju.playproc.currseat == o_paiju.playproc.start){ //是本轮的第一个出牌
|
||||
var can = cls_youle_erqiwang_arith.can_playcard(o_paiju.flower, cards, o_paiju.playproc.currseat, o_paiju.seatlist);
|
||||
if (!can.result){
|
||||
var re = {};
|
||||
re.result = false;
|
||||
return re;
|
||||
}
|
||||
//出牌
|
||||
doplay(1);
|
||||
//记录下牌型、花色、牌型最大牌
|
||||
o_paiju.playproc.startcount = cards.length;
|
||||
o_paiju.playproc.startflower = can.flower;
|
||||
o_paiju.playproc.starttype = can.cardtype;
|
||||
o_paiju.playproc.maxseat = o_paiju.playproc.currseat;
|
||||
o_paiju.playproc.maxcard = can.cardvalue;
|
||||
o_paiju.playproc.currseat = nextseat_1;
|
||||
//返回结果
|
||||
var re1 = {};
|
||||
re1.result = true;
|
||||
re1.idx = 1;
|
||||
re1.count = cards.length;
|
||||
re1.flower = can.flower;
|
||||
re1.cardtype = can.cardtype;
|
||||
return re1;
|
||||
}
|
||||
|
||||
//不是本轮中的第一个出牌,是跟牌
|
||||
var cardsinhand = cls_youle_erqiwang_paiju.get_seat_cards(o_paiju, o_paiju.playproc.currseat);
|
||||
var can = cls_youle_erqiwang_arith.can_followcard(o_paiju.flower, cardsinhand, cards, o_paiju.playproc.startcount, o_paiju.playproc.startflower, o_paiju.playproc.starttype);
|
||||
if (!can.result){
|
||||
var re = {};
|
||||
re.result = false;
|
||||
return re;
|
||||
}
|
||||
|
||||
//与之前的出牌比较大小
|
||||
if (can.cardvalue > o_paiju.playproc.maxcard){
|
||||
o_paiju.playproc.maxseat = o_paiju.playproc.currseat;
|
||||
o_paiju.playproc.maxcard = can.cardvalue;
|
||||
}
|
||||
|
||||
//无花色、无对子处理
|
||||
if (can.nopair){
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.playproc.startflower - 1][1] = 1;
|
||||
}
|
||||
if (can.noflower){
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.playproc.startflower - 1][0] = 1;
|
||||
o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.playproc.startflower - 1][1] = 1;
|
||||
}
|
||||
|
||||
//是本轮中的第二个出牌
|
||||
if (o_paiju.playproc.currseat == nextseat_1){
|
||||
//出牌
|
||||
doplay(2);
|
||||
//下一个出牌者
|
||||
o_paiju.playproc.currseat = nextseat_2;
|
||||
//返回结果
|
||||
var re2 = {};
|
||||
re2.idx = 2;
|
||||
re2.result = true;
|
||||
return re2;
|
||||
}
|
||||
|
||||
//是本轮中的第三个出牌
|
||||
//出牌
|
||||
doplay(3);
|
||||
//本轮出牌结束
|
||||
for (var i = 0; i < o_paiju.playproc.cards.length; i++) {
|
||||
for (var j = 0; j < o_paiju.playproc.cards[i].length; j++) {
|
||||
o_paiju.cards[o_paiju.playproc.cards[i][j]].playowner = o_paiju.playproc.maxseat;
|
||||
}
|
||||
}
|
||||
|
||||
//返回结果
|
||||
var re3 = {};
|
||||
re3.result = true;
|
||||
re3.idx = 3;
|
||||
re3.maxseat = o_paiju.playproc.maxseat;
|
||||
if (o_paiju.playproc.maxseat != o_paiju.banker){
|
||||
re3.grade = 0;
|
||||
// re3.gradecards = [];
|
||||
for (var i = 0; i < o_paiju.playproc.cards.length; i++) {
|
||||
var grade_incards = cls_youle_erqiwang_paiju.get_grade_incard(o_paiju, o_paiju.playproc.cards[i]);
|
||||
re3.grade = re3.grade + grade_incards.grade;
|
||||
// re3.gradecards = re3.gradecards.concat(grade_incards.cards);
|
||||
}
|
||||
}
|
||||
|
||||
//判断是否还有牌在玩家手上未出
|
||||
var havecard_inhand = function(){
|
||||
for (var i = 0; i < o_paiju.cards.length; i++) {
|
||||
if (o_paiju.cards[i].playround == -1 && o_paiju.cards[i].number != 3 && o_paiju.cards[i].number != 4){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// if (!havecard_inhand() || o_paiju.playproc.round == 2){ //测试用
|
||||
if (!havecard_inhand()){
|
||||
//进入结算阶段
|
||||
o_paiju.step = 6;
|
||||
} else {
|
||||
//新一轮出牌
|
||||
cls_youle_erqiwang_paiju.new_playround(o_paiju, o_paiju.playproc.round + 1, o_paiju.playproc.maxseat);
|
||||
}
|
||||
return re3;
|
||||
},
|
||||
|
||||
//获取埋牌
|
||||
get_burycard: function(o_paiju){
|
||||
var aryCardIDs = [];
|
||||
for (var i = 0; i < o_paiju.cards.length; i++){
|
||||
if (o_paiju.cards[i].playround == 0){
|
||||
aryCardIDs.push(o_paiju.cards[i].id);
|
||||
}
|
||||
}
|
||||
return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs);
|
||||
},
|
||||
|
||||
//获取抠底包
|
||||
get_bottom_account: function(o_paiju, msg){
|
||||
msg.data.bottom = {};
|
||||
msg.data.bottom.cards = o_paiju.method.get_burycard();
|
||||
if (o_paiju.playproc.maxseat != o_paiju.banker){ //闲家抠底
|
||||
var multiple = cls_youle_erqiwang_arith.get_bottom_multiple(o_paiju.flower, o_paiju.playproc.cards[o_paiju.playproc.maxseat]);
|
||||
if (multiple > 0){
|
||||
msg.data.bottom.multiple = multiple;
|
||||
var get = o_paiju.method.get_grade_incard(msg.data.bottom.cards);
|
||||
msg.data.bottom.grade1 = get.grade;
|
||||
msg.data.bottom.grade2 = multiple * msg.data.bottom.grade1;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
},
|
||||
|
||||
//获取闲家捡分(不包含抠底)
|
||||
get_jian_grade: function(o_paiju){
|
||||
var re = {};
|
||||
re.grade = 0;
|
||||
re.cards = [];
|
||||
for (var i = 0; i < o_paiju.cards.length; i++){
|
||||
if (o_paiju.cards[i].playowner > -1 && o_paiju.cards[i].playowner != o_paiju.banker){
|
||||
if (o_paiju.cards[i].score > 0){
|
||||
re.grade = re.grade + o_paiju.cards[i].score;
|
||||
re.cards.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return re;
|
||||
},
|
||||
|
||||
//获取小局结算包,type 0正常结算 1投降结算 2解散结算
|
||||
get_paiju_account: function(o_paiju, type, msg){
|
||||
o_paiju.endtime = min_now();
|
||||
//第一局结算扣除房卡
|
||||
if (o_paiju.idx == 1 && type != 2){
|
||||
var do_deduct_roomcard = function(){
|
||||
youle_erqiwang.import.deduct_roomcard(o_paiju.o_desk.o_room);
|
||||
}
|
||||
min_ontimeout(do_deduct_roomcard, 1000);
|
||||
}
|
||||
|
||||
if (msg.rpc != "jiesuan"){
|
||||
msg.rpc = "jiesuan";
|
||||
}
|
||||
if (!msg.data){
|
||||
msg.data = {};
|
||||
}
|
||||
msg.data.aset = {};
|
||||
msg.data.aset.banker = o_paiju.banker;
|
||||
msg.data.aset.call = o_paiju.call;
|
||||
msg.data.aset.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(o_paiju.call);
|
||||
msg.data.aset.flower = o_paiju.flower;
|
||||
var jian_grade = cls_youle_erqiwang_paiju.get_jian_grade(o_paiju);
|
||||
msg.data.aset.grade = jian_grade.grade;
|
||||
if (msg.data.bottom && msg.data.bottom.grade2){
|
||||
msg.data.aset.grade = msg.data.aset.grade + msg.data.bottom.grade2;
|
||||
}
|
||||
switch (type){
|
||||
case 0:
|
||||
msg.data.aset.upgrade = cls_youle_erqiwang_arith.get_upgrade(o_paiju.call, msg.data.aset.grade);
|
||||
if (msg.data.aset.upgrade > 0){
|
||||
o_paiju.result = 0;
|
||||
} else {
|
||||
o_paiju.result = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
msg.data.aset.upgrade = -99;
|
||||
o_paiju.result = 2;
|
||||
break;
|
||||
case 2:
|
||||
msg.data.aset.upgrade = 0;
|
||||
o_paiju.result = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
msg.data.aset.seatlist = [];
|
||||
var _player0 = {};
|
||||
var _player1 = {};
|
||||
var _player2 = {};
|
||||
//获取玩家的牌
|
||||
var _card0 = cls_youle_erqiwang_paiju.get_seat_cards_owner(o_paiju, 0);
|
||||
var _card1 = cls_youle_erqiwang_paiju.get_seat_cards_owner(o_paiju, 1);
|
||||
var _card2 = cls_youle_erqiwang_paiju.get_seat_cards_owner(o_paiju, 2);
|
||||
//计算冲关或王牌
|
||||
var _flower = o_paiju.flower;
|
||||
if (type != 2){
|
||||
_flower = -1;
|
||||
}
|
||||
var _chongguan0 = cls_youle_erqiwang_arith.get_chongguan(_flower, _card0);
|
||||
var _chongguan1 = cls_youle_erqiwang_arith.get_chongguan(_flower, _card1);
|
||||
var _chongguan2 = cls_youle_erqiwang_arith.get_chongguan(_flower, _card2);
|
||||
//冲关的牌或王牌
|
||||
_player0.cards = _chongguan0.cards;
|
||||
_player1.cards = _chongguan1.cards;
|
||||
_player2.cards = _chongguan2.cards;
|
||||
//关数
|
||||
_player0.chongguan = _chongguan0.count;
|
||||
_player1.chongguan = _chongguan1.count;
|
||||
_player2.chongguan = _chongguan2.count;
|
||||
//是否作废
|
||||
_player0.obsolete = 0;
|
||||
_player1.obsolete = 0;
|
||||
_player2.obsolete = 0;
|
||||
//根据叫分判断冲关是否作废
|
||||
var do_obsolete_with_call = function(player, seat){
|
||||
var _dealcards = cls_youle_erqiwang_paiju.get_seat_cards_deal(o_paiju, seat);
|
||||
var _dealchongguan = cls_youle_erqiwang_arith.get_chongguan(-1, _dealcards);
|
||||
if (_dealchongguan.count == 0){
|
||||
return;
|
||||
}
|
||||
var _call = null;
|
||||
for (var i = 0; i < o_paiju.callproc.length; i++){
|
||||
if (o_paiju.callproc[i].seat == seat){
|
||||
_call = o_paiju.callproc[i].call;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_call){ //冲关必叫
|
||||
player.obsolete = 1;
|
||||
} else if (_dealchongguan.wang == 4 && _call > 60){ //四王必踢
|
||||
player.obsolete = 1;
|
||||
}
|
||||
}
|
||||
|
||||
do_obsolete_with_call(_player0, 0);
|
||||
do_obsolete_with_call(_player1, 1);
|
||||
do_obsolete_with_call(_player2, 2);
|
||||
//计算冲关得分
|
||||
var _cg0 = _player0.chongguan;
|
||||
var _cg1 = _player1.chongguan;
|
||||
var _cg2 = _player2.chongguan;
|
||||
if (_player0.obsolete){
|
||||
_cg0 = 0;
|
||||
}
|
||||
if (_player1.obsolete){
|
||||
_cg1 = 0;
|
||||
}
|
||||
if (_player2.obsolete){
|
||||
_cg2 = 0;
|
||||
}
|
||||
_player0.grade_cg = _cg0 * 2 - _cg1 - _cg2;
|
||||
_player1.grade_cg = _cg1 * 2 - _cg2 - _cg0;
|
||||
_player2.grade_cg = _cg2 * 2 - _cg0 - _cg1;
|
||||
//计算傍王得分
|
||||
var _w0 = _chongguan0.wang;
|
||||
var _w1 = _chongguan1.wang;
|
||||
var _w2 = _chongguan2.wang;
|
||||
if (_player0.obsolete){
|
||||
_w0 = 0;
|
||||
}
|
||||
if (_player1.obsolete){
|
||||
_w1 = 0;
|
||||
}
|
||||
if (_player2.obsolete){
|
||||
_w2 = 0;
|
||||
}
|
||||
_player0.grade_w = _w0 * 2 - _w1 - _w2;
|
||||
_player1.grade_w = _w1 * 2 - _w2 - _w0;
|
||||
_player2.grade_w = _w2 * 2 - _w0 - _w1;
|
||||
//计算捡分得分
|
||||
var _upgrade = msg.data.aset.upgrade;
|
||||
if (_upgrade == -99){
|
||||
_upgrade = -2;
|
||||
}
|
||||
switch (o_paiju.banker){
|
||||
case -1:
|
||||
_player0.grade_jf = 0;
|
||||
_player1.grade_jf = 0;
|
||||
_player2.grade_jf = 0;
|
||||
break;
|
||||
case 0:
|
||||
_player0.grade_jf = _upgrade * msg.data.aset.multiple * 2;
|
||||
_player1.grade_jf = _upgrade * msg.data.aset.multiple * -1;
|
||||
_player2.grade_jf = _player1.grade_jf;
|
||||
break;
|
||||
case 1:
|
||||
_player0.grade_jf = _upgrade * msg.data.aset.multiple * -1;
|
||||
_player1.grade_jf = _upgrade * msg.data.aset.multiple * 2;
|
||||
_player2.grade_jf = _player0.grade_jf;
|
||||
break;
|
||||
case 2:
|
||||
_player0.grade_jf = _upgrade * msg.data.aset.multiple * -1;
|
||||
_player1.grade_jf = _player0.grade_jf;
|
||||
_player2.grade_jf = _upgrade * msg.data.aset.multiple * 2;
|
||||
break;
|
||||
}
|
||||
//计算本局总得分
|
||||
_player0.grade = _player0.grade_cg + _player0.grade_w + _player0.grade_jf;
|
||||
_player1.grade = _player1.grade_cg + _player1.grade_w + _player1.grade_jf;
|
||||
_player2.grade = _player2.grade_cg + _player2.grade_w + _player2.grade_jf;
|
||||
//大局累积得分
|
||||
o_paiju.o_desk.seatlist[0][0] = o_paiju.o_desk.seatlist[0][0] + _player0.grade;
|
||||
o_paiju.o_desk.seatlist[1][0] = o_paiju.o_desk.seatlist[1][0] + _player1.grade;
|
||||
o_paiju.o_desk.seatlist[2][0] = o_paiju.o_desk.seatlist[2][0] + _player2.grade;
|
||||
_player0.score = o_paiju.o_desk.seatlist[0][0];
|
||||
_player1.score = o_paiju.o_desk.seatlist[1][0];
|
||||
_player2.score = o_paiju.o_desk.seatlist[2][0];
|
||||
//保存每局得分
|
||||
o_paiju.o_desk.seatlist[0][1].push(_player0.grade);
|
||||
o_paiju.o_desk.seatlist[1][1].push(_player1.grade);
|
||||
o_paiju.o_desk.seatlist[2][1].push(_player2.grade);
|
||||
|
||||
msg.data.aset.seatlist.push(_player0);
|
||||
msg.data.aset.seatlist.push(_player1);
|
||||
msg.data.aset.seatlist.push(_player2);
|
||||
|
||||
if (o_paiju.idx >= o_paiju.o_desk.o_room.asetcount || type == 2){
|
||||
msg = o_paiju.o_desk.method.get_desk_account(msg);
|
||||
}
|
||||
|
||||
//保存结算包,供断线重连时使用
|
||||
o_paiju.tmp_jiesuan_aset = msg.data.aset;
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
457
codes/games/server/games/erqiwang/mod.js
Normal file
@@ -0,0 +1,457 @@
|
||||
//////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////咻一咻/////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
var youle_erqiwang = youle_erqiwang || cls_mod.new("youle_erqiwang", "erqiwang", youle_app);
|
||||
|
||||
//牌类
|
||||
min_loadJsFile("games2/erqiwang/class.pai.js", function(){
|
||||
//牌局类
|
||||
min_loadJsFile("games2/erqiwang/class.paiju.js", function(){
|
||||
//牌桌类
|
||||
min_loadJsFile("games2/erqiwang/class.desk.js", function(){
|
||||
//算法类
|
||||
min_loadJsFile("games2/erqiwang/class.arith.js", function(){
|
||||
//对内的输入接口类
|
||||
min_loadJsFile("games2/erqiwang/class.import.js", function(){
|
||||
//对内的输出接口类
|
||||
min_loadJsFile("games2/erqiwang/class.export.js", function(){
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
//对内的输入接口类(需要的外部接口)
|
||||
youle_erqiwang.import = null;
|
||||
//对内的输出接口类(提供给外部使用的接口)
|
||||
youle_erqiwang.export = null;
|
||||
|
||||
//叫分或不叫
|
||||
youle_erqiwang.jiaofen = function(pack){
|
||||
var agentid = pack.data.agentid;
|
||||
var playerid = parseInt(pack.data.playerid);
|
||||
var gameid = pack.data.gameid;
|
||||
var roomcode = parseInt(pack.data.roomcode);
|
||||
var seat = parseInt(pack.data.seat);
|
||||
var call = parseInt(pack.data.call);
|
||||
//检查玩家的位置是否正确
|
||||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||||
if (!o_room){
|
||||
return;
|
||||
}
|
||||
//牌桌
|
||||
var o_desk = o_room.o_desk;
|
||||
if (!o_desk){
|
||||
return;
|
||||
}
|
||||
//牌局
|
||||
var o_paiju = o_desk.method.curr_paiju();
|
||||
if (!o_paiju){
|
||||
return;
|
||||
}
|
||||
//检查当前的游戏阶段是否是叫分阶段
|
||||
if (o_paiju.step != 1){
|
||||
return;
|
||||
}
|
||||
//检查当前的叫分位置
|
||||
if (o_paiju.method.get_callgrade_seat() != seat){
|
||||
return;
|
||||
}
|
||||
//检查当前的叫分分值必须大于0,且是5的整数倍
|
||||
if (call < 0 || call % 5 != 0){
|
||||
return;
|
||||
}
|
||||
var curr_call = o_paiju.method.get_callgrade_value();
|
||||
//第一家不能不叫分
|
||||
if (curr_call == 0 && call == 0){
|
||||
return;
|
||||
}
|
||||
//叫分必须小于之前的叫分
|
||||
if (curr_call != 0 && call >= curr_call){
|
||||
return;
|
||||
}
|
||||
|
||||
//叫分成功
|
||||
o_paiju.method.do_callgrade(call);
|
||||
|
||||
//返回前端
|
||||
if (o_paiju.step == 1){
|
||||
//等待下一家叫分
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.rpc = "jiaofen";
|
||||
msg.data = {};
|
||||
msg.data.seat = seat;
|
||||
msg.data.call = call;
|
||||
msg.data.currcall = o_paiju.method.get_callgrade_value();
|
||||
msg.data.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(msg.data.currcall);
|
||||
msg.data.nextseat = o_paiju.method.get_callgrade_seat();
|
||||
msg.data.countdown = o_desk.method.get_countdown_jiaofen();
|
||||
o_room.method.sendpack_toother(msg, -1);
|
||||
return;
|
||||
}
|
||||
if (o_paiju.step == 2){
|
||||
//等待庄家叫主
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.rpc = "shangzhuang";
|
||||
msg.data = {};
|
||||
msg.data.seat = seat;
|
||||
msg.data.call = call;
|
||||
msg.data.banker = o_paiju.banker;
|
||||
msg.data.grade = o_paiju.call;
|
||||
msg.data.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(msg.data.grade);
|
||||
msg.data.bottomcards = o_paiju.method.get_bottomcards();
|
||||
msg.data.countdown = o_desk.method.get_countdown_xuanzhu();
|
||||
if (o_paiju.call < 65){
|
||||
msg.data.touxiang = 0;
|
||||
} else {
|
||||
msg.data.touxiang = 1;
|
||||
}
|
||||
|
||||
for (var i = 0; i < o_room.seatlist.length; i++) {
|
||||
msg.conmode = o_room.seatlist[i].conmode;
|
||||
msg.fromid = o_room.seatlist[i].fromid;
|
||||
if (i == o_paiju.banker){
|
||||
msg.data.cards = o_paiju.method.get_seat_cards(i);
|
||||
} else {
|
||||
delete msg.data.cards;
|
||||
}
|
||||
youle_erqiwang.app.SendPack(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//选主
|
||||
youle_erqiwang.xuanzhu = function(pack){
|
||||
var agentid = pack.data.agentid;
|
||||
var playerid = parseInt(pack.data.playerid);
|
||||
var gameid = pack.data.gameid;
|
||||
var roomcode = parseInt(pack.data.roomcode);
|
||||
var seat = parseInt(pack.data.seat);
|
||||
var flower = parseInt(pack.data.flower);
|
||||
//检查玩家的位置是否正确
|
||||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||||
if (!o_room){
|
||||
return;
|
||||
}
|
||||
//牌桌
|
||||
var o_desk = o_room.o_desk;
|
||||
if (!o_desk){
|
||||
return;
|
||||
}
|
||||
//牌局
|
||||
var o_paiju = o_desk.method.curr_paiju();
|
||||
if (!o_paiju){
|
||||
return;
|
||||
}
|
||||
//检查当前的游戏阶段是否是选主阶段
|
||||
if (o_paiju.step != 2){
|
||||
return;
|
||||
}
|
||||
//检查庄家位置
|
||||
if (o_paiju.banker != seat){
|
||||
return;
|
||||
}
|
||||
//检查花色
|
||||
if (flower != 1 && flower != 2 && flower != 3 && flower != 4){
|
||||
return;
|
||||
}
|
||||
|
||||
//选主成功
|
||||
o_paiju.method.do_choiceflower(flower);
|
||||
|
||||
//返回前端
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.rpc = "xuanzhu";
|
||||
msg.data = {};
|
||||
msg.data.banker = o_paiju.banker;
|
||||
msg.data.flower = flower;
|
||||
msg.data.countdown = o_desk.method.get_countdown_maipai();
|
||||
for (var i = 0; i < o_room.seatlist.length; i++) {
|
||||
msg.conmode = o_room.seatlist[i].conmode;
|
||||
msg.fromid = o_room.seatlist[i].fromid;
|
||||
msg.data.cards = o_paiju.method.get_seat_cards(i);
|
||||
youle_erqiwang.app.SendPack(msg);
|
||||
}
|
||||
}
|
||||
|
||||
//埋牌
|
||||
youle_erqiwang.maipai = function(pack){
|
||||
var agentid = pack.data.agentid;
|
||||
var playerid = parseInt(pack.data.playerid);
|
||||
var gameid = pack.data.gameid;
|
||||
var roomcode = parseInt(pack.data.roomcode);
|
||||
var seat = parseInt(pack.data.seat);
|
||||
var cards = pack.data.cards;
|
||||
//检查玩家的位置是否正确
|
||||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||||
if (!o_room){
|
||||
return;
|
||||
}
|
||||
//牌桌
|
||||
var o_desk = o_room.o_desk;
|
||||
if (!o_desk){
|
||||
return;
|
||||
}
|
||||
//牌局
|
||||
var o_paiju = o_desk.method.curr_paiju();
|
||||
if (!o_paiju){
|
||||
return;
|
||||
}
|
||||
//检查当前的游戏阶段是否是埋牌阶段
|
||||
if (o_paiju.step != 3){
|
||||
return;
|
||||
}
|
||||
//检查庄家位置
|
||||
if (o_paiju.banker != seat){
|
||||
return;
|
||||
}
|
||||
//检查埋牌是否是8张
|
||||
if (cards.length != 8){
|
||||
return;
|
||||
}
|
||||
//检查埋牌是否都在庄家手上
|
||||
if (!o_paiju.method.check_cards_inhand(cards, seat)){
|
||||
return;
|
||||
}
|
||||
|
||||
//埋牌成功
|
||||
o_paiju.method.do_burycard(cards);
|
||||
|
||||
//返回前端
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.rpc = "maipai";
|
||||
msg.data = {};
|
||||
msg.data.seat = o_paiju.playproc.currseat;
|
||||
msg.data.countdown = o_desk.method.get_countdown_chupai();
|
||||
for (var i = 0; i < o_room.seatlist.length; i++) {
|
||||
msg.conmode = o_room.seatlist[i].conmode;
|
||||
msg.fromid = o_room.seatlist[i].fromid;
|
||||
if (i == o_paiju.banker){
|
||||
msg.data.cards = o_paiju.method.get_seat_cards(i);
|
||||
msg.data.bottomcards = cards;
|
||||
} else {
|
||||
delete msg.data.cards;
|
||||
delete msg.data.bottomcards;
|
||||
}
|
||||
youle_erqiwang.app.SendPack(msg);
|
||||
}
|
||||
}
|
||||
|
||||
//投降
|
||||
youle_erqiwang.touxiang = function(pack){
|
||||
var agentid = pack.data.agentid;
|
||||
var playerid = parseInt(pack.data.playerid);
|
||||
var gameid = pack.data.gameid;
|
||||
var roomcode = parseInt(pack.data.roomcode);
|
||||
var seat = parseInt(pack.data.seat);
|
||||
//检查玩家的位置是否正确
|
||||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||||
if (!o_room){
|
||||
return;
|
||||
}
|
||||
//牌桌
|
||||
var o_desk = o_room.o_desk;
|
||||
if (!o_desk){
|
||||
return;
|
||||
}
|
||||
//牌局
|
||||
var o_paiju = o_desk.method.curr_paiju();
|
||||
if (!o_paiju){
|
||||
return;
|
||||
}
|
||||
//检查当前的游戏阶段是否是叫分、埋牌、投降阶段
|
||||
if (o_paiju.step != 2 && o_paiju.step != 3 && o_paiju.step != 4){
|
||||
return;
|
||||
}
|
||||
//检查庄家位置
|
||||
if (o_paiju.banker != seat){
|
||||
return;
|
||||
}
|
||||
//检查是否允许投降
|
||||
if (o_paiju.call < 65){
|
||||
return;
|
||||
}
|
||||
|
||||
//投降结算
|
||||
o_paiju.step = 6;
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.rpc = "jiesuan";
|
||||
msg.data = {};
|
||||
msg = o_paiju.method.get_paiju_account(1, msg);
|
||||
o_room.method.sendpack_toother(msg, -1);
|
||||
}
|
||||
|
||||
//出牌
|
||||
youle_erqiwang.chupai = function(pack){
|
||||
var agentid = pack.data.agentid;
|
||||
var playerid = parseInt(pack.data.playerid);
|
||||
var gameid = pack.data.gameid;
|
||||
var roomcode = parseInt(pack.data.roomcode);
|
||||
var seat = parseInt(pack.data.seat);
|
||||
var cards = pack.data.cards;
|
||||
//检查玩家的位置是否正确
|
||||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||||
if (!o_room){
|
||||
return;
|
||||
}
|
||||
//牌桌
|
||||
var o_desk = o_room.o_desk;
|
||||
if (!o_desk){
|
||||
return;
|
||||
}
|
||||
//牌局
|
||||
var o_paiju = o_desk.method.curr_paiju();
|
||||
if (!o_paiju){
|
||||
return;
|
||||
}
|
||||
//检查当前的游戏阶段是否是出牌阶段
|
||||
if (o_paiju.step != 4 && o_paiju.step != 5){
|
||||
return;
|
||||
}
|
||||
//检查出牌位置
|
||||
if (seat != o_paiju.playproc.currseat){
|
||||
return;
|
||||
}
|
||||
//检查出牌
|
||||
if (cards.length == 0){
|
||||
return;
|
||||
}
|
||||
//检查出牌是否都在玩家手上
|
||||
if (!o_paiju.method.check_cards_inhand(cards, seat)){
|
||||
return;
|
||||
}
|
||||
|
||||
//检查选中的牌是否可出,并出牌
|
||||
var re = o_paiju.method.do_playcard(cards);
|
||||
if (!re.result){
|
||||
return;
|
||||
}
|
||||
|
||||
//返回前端
|
||||
var msg = {};
|
||||
msg.app = "youle";
|
||||
msg.route = "erqiwang";
|
||||
msg.data = {};
|
||||
msg.data.seat = seat;
|
||||
msg.data.cards = cards;
|
||||
msg.data.info = o_paiju.seatlist[seat];
|
||||
msg.data.nextseat = o_paiju.playproc.currseat;
|
||||
msg.data.countdown = o_desk.method.get_countdown_chupai();
|
||||
if (o_paiju.method.have_baofu()){
|
||||
msg.data.baozhu = 1;
|
||||
} else {
|
||||
msg.data.baozhu = 0;
|
||||
}
|
||||
|
||||
switch (re.idx)
|
||||
{
|
||||
case 1: //第一个出牌
|
||||
msg.rpc = "chupai1";
|
||||
msg.data.count = re.count;
|
||||
msg.data.flower = re.flower;
|
||||
msg.data.cardtype = re.cardtype;
|
||||
break;
|
||||
case 2: //第二个出牌
|
||||
msg.rpc = "chupai2";
|
||||
break;
|
||||
case 3: //第三个出牌
|
||||
msg.rpc = "chupai3";
|
||||
msg.data.maxseat = re.maxseat;
|
||||
if (re.grade > 0){
|
||||
msg.data.grade = re.grade;
|
||||
// msg.data.gradecards = re.gradecards;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (o_paiju.step == 5){
|
||||
for (var i = 0; i < o_room.seatlist.length; i++){
|
||||
msg.conmode = o_room.seatlist[i].conmode;
|
||||
msg.fromid = o_room.seatlist[i].fromid;
|
||||
if (i == seat){
|
||||
msg.data.cardsinhand = o_paiju.method.get_seat_cards(i);
|
||||
} else {
|
||||
delete msg.data.cardsinhand;
|
||||
}
|
||||
youle_erqiwang.app.SendPack(msg);
|
||||
}
|
||||
} else if (o_paiju.step == 6){
|
||||
//小局结算
|
||||
//出牌包的调整
|
||||
msg.data.chupai = {};
|
||||
msg.data.chupai.seat = msg.data.seat;
|
||||
msg.data.chupai.cards = msg.data.cards;
|
||||
msg.data.chupai.maxseat = msg.data.maxseat;
|
||||
if (msg.data.grade){
|
||||
msg.data.chupai.grade = msg.data.grade;
|
||||
}
|
||||
if (msg.data.gradecards){
|
||||
msg.data.chupai.gradecards = msg.data.gradecards;
|
||||
}
|
||||
delete msg.data.seat;
|
||||
delete msg.data.cards;
|
||||
delete msg.data.info;
|
||||
delete msg.data.nextseat;
|
||||
delete msg.data.countdown;
|
||||
delete msg.data.baozhu;
|
||||
delete msg.data.cardsinhand;
|
||||
delete msg.data.maxseat;
|
||||
delete msg.data.grade;
|
||||
delete msg.data.gradecards;
|
||||
|
||||
//获取抠底包
|
||||
msg = o_paiju.method.get_bottom_account(msg);
|
||||
//获取小局结算包
|
||||
msg = o_paiju.method.get_paiju_account(0, msg);
|
||||
o_room.method.sendpack_toother(msg, -1);
|
||||
}
|
||||
}
|
||||
|
||||
//准备
|
||||
youle_erqiwang.zhunbei = function(pack){
|
||||
var agentid = pack.data.agentid;
|
||||
var playerid = parseInt(pack.data.playerid);
|
||||
var gameid = pack.data.gameid;
|
||||
var roomcode = parseInt(pack.data.roomcode);
|
||||
var seat = parseInt(pack.data.seat);
|
||||
//检查玩家的位置是否正确
|
||||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||||
if (!o_room){
|
||||
return;
|
||||
}
|
||||
//牌桌
|
||||
var o_desk = o_room.o_desk;
|
||||
if (!o_desk){
|
||||
return;
|
||||
}
|
||||
//牌局
|
||||
var o_paiju = o_desk.method.curr_paiju();
|
||||
if (!o_paiju){
|
||||
return;
|
||||
}
|
||||
//检查当前的游戏阶段是否是结算阶段
|
||||
if (o_paiju.step != 6){
|
||||
return;
|
||||
}
|
||||
//检查当前局数小于总局数
|
||||
if (o_desk.paiju_list.length >= o_room.asetcount){
|
||||
return;
|
||||
}
|
||||
|
||||
//准备成功
|
||||
o_desk.method.do_prepare(seat);
|
||||
}
|
||||
BIN
codes/games/server/games/erqiwang/二七王接口.xlsx
Normal file
@@ -189,15 +189,15 @@
|
||||
$(".btn___37Kse").text(btnText);
|
||||
$(".down_load").attr("href", downloadLink);
|
||||
|
||||
// if (isAndroid && !isHarmony) {
|
||||
// $(".down_load").after('<a href="' + androidUrlBak + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px;"><div class="btn___37Kse">无法安装时备用包下载</div></a>');
|
||||
// }
|
||||
if (isAndroid && !isHarmony) {
|
||||
$(".down_load").after('<a href="' + androidUrlBak + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px;"><div class="btn___37Kse">无法安装时备用包下载</div></a>');
|
||||
}
|
||||
} else {
|
||||
// PC Mode
|
||||
$(".shuoming").html('请选择对应系统下载');
|
||||
var btns = '';
|
||||
btns += '<a href="' + androidUrl + '" class="ms-btn template-btn clearfix pc-pwd down_load"><div class="btn___37Kse">Android版下载</div></a>';
|
||||
// btns += '<a href="' + androidUrlBak + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px"><div class="btn___37Kse">无法安装时备用包下载</div></a>';
|
||||
btns += '<a href="' + androidUrlBak + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px"><div class="btn___37Kse">无法安装时备用包下载</div></a>';
|
||||
btns += '<a href="' + harmonyUrl + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px"><div class="btn___37Kse">HarmonyOS版下载</div></a>';
|
||||
btns += '<a href="' + iosUrl + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px"><div class="btn___37Kse">iOS版下载</div></a>';
|
||||
|
||||
|
||||
1508
tools/downloadWeb2/css/download.css
Normal file
713
tools/downloadWeb2/css/main.css
Normal file
@@ -0,0 +1,713 @@
|
||||
@charset "utf-8";
|
||||
@charset "utf-8";
|
||||
/*防止用户自定义背景颜色对网页的影响,添加让用户可以自定义字体 */
|
||||
html{-webkit-text-size-adjust:100%; /*禁用Webkit内核浏览器的文字大小调整功能,默认是auto;*/
|
||||
-ms-text-size-adjust:100%; /*禁用IE内核浏览器的文字大小调整功能,默认是auto;*/
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
height: 100%;
|
||||
touch-action: manipulation; /*取消移动端click300ms的延迟*/
|
||||
}
|
||||
body{font-family: "Helvetica", "Tahoma", "Arial", "PingFang SC", "Microsoft Yahei", "SimSun", "SimHei", "sans-serif"; color: #848484; background: #f1f1f194; font-size: 14px;}
|
||||
|
||||
/*针对英文单词,强制让单词换行,break-word不拆分单词, break-all拆分单词*/
|
||||
*{word-wrap: break-word; /*word-break: break-all;*/}
|
||||
.break-all{word-break: break-all;}
|
||||
|
||||
a, abbr, acronym, address, applet, article, aside, audio, b, big, body, button, canvas, caption, center, cite, code, dd, del, details, dfn, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, html, hr, i, iframe, input, img, ins, kbd, label, legend, li, mark, menu, nav, object, ol, output, p, pre, q, ruby, s, samp, section, select, small, span, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, time, tr, tt, textarea, u, ul, var, video,.h1, .h2, .h3{margin: 0; padding: 0;}
|
||||
|
||||
dt,label{font-weight:normal; font-size:100%;}/*此处增加dt,label针对bootstrap的加粗定义*/
|
||||
|
||||
input,button,textarea,select,optgroup,option{font-size: 100%; font-weight: normal; outline: none;}
|
||||
input,button,textarea{-webkit-appearance: none;}/*去除ios按钮内阴影、圆角,对单选框、复选框有影响*/
|
||||
input[type=checkbox]{-webkit-appearance: checkbox;}
|
||||
input[type=radio]{-webkit-appearance: radio;}
|
||||
input::-webkit-input-safebox-button{display: none;}/*解决搜狗浏览器密码框自带安全键盘*/
|
||||
|
||||
input:focus,textarea:focus{outline: none;}
|
||||
|
||||
th,td,button,input,select,textarea{-webkit-font-smoothing: antialiased; -moz-font-smoothing: antialiased;}/*页面的字体抗锯齿,字体会更清晰圆滑*/
|
||||
textarea{resize: none; border: 1px solid #e5e5e5; padding: 5px; width: 100%; border-radius: 0;}
|
||||
|
||||
address,caption,cite,code,dfn,th,var,/*em,*/i{font-style: normal; font-weight: normal;}
|
||||
sub,sup{vertical-align: baseline;}
|
||||
|
||||
button{overflow: visible; vertical-align: middle; outline: none; height: auto;}
|
||||
|
||||
/*去掉列表标签的默认样式*/
|
||||
ul,ol,li{list-style-type: none;}
|
||||
|
||||
/*重置table样式*/
|
||||
table{border-collapse:collapse; border-spacing:0; /*table-layout: fixed;*/}/*table-layout: fixed; 表格布局尺寸固定(默认等分表格),表格的宽度不再由单元格的内容多少而决定,可自定义宽度(给th,td不起作用)*/
|
||||
/*border-collapse: separate(独立边框); border-spacing:20px 20px(边框单元格间距离);*/
|
||||
/*btn基础样式*/
|
||||
.ms-btn{display: inline-block; padding: 6px 12px; border-radius: 0; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; background: none; border: 1px solid transparent;}
|
||||
/*a链接*/
|
||||
a{outline:none; text-decoration: none; color: #848484; cursor: pointer;}
|
||||
a:focus{outline: none; text-decoration: none; color: inherit;}
|
||||
a:hover{outline: none; color: #157df1; text-decoration: none;}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container{}
|
||||
.table-responsive{overflow-x: visible;}
|
||||
}
|
||||
/*----------分发落地页----------*/
|
||||
.template-common .form-control{height: 40px;}
|
||||
.passwords .template-common{padding: 0 320px;}
|
||||
.template-common{padding: 0 235px;}
|
||||
.template-common hr{border-top-color: #e5e5e5; margin-top: 25px; display: none;}
|
||||
.template-common .t-icon{background-color: #fff; width: 180px; height: 180px; border-radius: 25px; box-shadow: 0 0 10px rgba(0,0,0,.2); display: flex; align-items: center; margin: 0 auto;}
|
||||
.template-common .t-icon img{width: 120px; height: 120px; border-radius: 30px; border: 1px solid #e5e5e5; margin: 0 auto;}
|
||||
.template-common .template-btn-wrap{text-align: center;}
|
||||
.template-common .template-btn{color: #fff; background-color: #51a3fe; height: 50px; padding: 0 38px; line-height: 48px; font-size: 18px; margin: 35px auto 15px; display: inline-block;}
|
||||
.template-common .decoding{color: #fff; background-color: #157df1; height: 50px; padding: 0; width: 180px; text-align: center; line-height: 48px; font-size: 18px; margin: 30px auto 20px; display: block;}
|
||||
.template-common .template-btn span:last-of-type{line-height: 50px;}
|
||||
.template-common .template-btn .iconfont{font-size: 36px; margin-left: -5px;}
|
||||
.template-common .t-apply{color: #666; text-align: center;}
|
||||
.template-common .t-code{color: #666; width: 200px; overflow: hidden; margin: 35px auto 0; text-align: center;}
|
||||
.template-common .t-code img{border: 1px solid #e5e5e5; width: 150px; height: 150px; margin: 0 auto 5px;}
|
||||
|
||||
.template-common .t-name{color: #333; text-align: center;}
|
||||
.template-common .t-name .name-info span{margin-right: 10px; color: #666;}
|
||||
.template-common .t-name .tit{font-size: 28px; margin-bottom: 10px; margin-top: 15px;}
|
||||
.template-common .t-introduce{color: #333;}
|
||||
.template-common .t-introduce .tit{font-size: 20px; margin-top: 25px;}
|
||||
.template-common .t-introduce p{margin-top: 15px;}
|
||||
.template-common .t-contact{color: #333; margin-top: 20px;}
|
||||
.template-common .t-contact .tit{font-size: 20px; margin-bottom: 15px;}
|
||||
.template-common .t-contact dl{margin-bottom: 5px;}
|
||||
.template-common .t-contact dl dt{margin-right: 10px;}
|
||||
|
||||
.template-pwd{border: 1px solid #e5e5e5; border-radius: 3px; padding: 45px 35px; margin-top: 45px; /*margin-bottom: 50px;*/ display: none;}
|
||||
.template-pwd label{color: #3e4753; text-align: center; display: block;}
|
||||
.template-pwd .form-control{margin-top: 15px;}
|
||||
|
||||
.passwords+.template-footer{margin-top: 50px;}
|
||||
.template-footer{margin-top: 30px;}
|
||||
.template-footer .methods-tutorial-full{background-color: #ff6666;}
|
||||
.template-footer .methods-tutorial-full a{color: #fff;}
|
||||
.template-footer .methods-tutorial-full a.fl{height: 50px; line-height: 50px;}
|
||||
.template-footer .methods-tutorial-full a.fl:hover{text-decoration: underline;}
|
||||
.template-footer .methods-tutorial-full a.fr{width: 22px; height: 22px; line-height: 22px; border: 1px solid #fff; border-radius: 50%; text-align: center; margin-top: 14px;}
|
||||
.template-footer .methods-tutorial-full a.fr .iconfont{color: #fff;}
|
||||
|
||||
.template-footer .t-footer{text-align: center; margin-top: 30px; padding-bottom: 30px;}
|
||||
.template-footer .t-footer a{color: #157df1;}
|
||||
.template-footer .t-footer a:hover{text-decoration: underline;}
|
||||
|
||||
/*应用截图*/
|
||||
.template-common .app-screen{margin-top: 20px;}
|
||||
.template-common .app-screen .tit{font-size: 20px; margin-bottom: 15px; color: #333;}
|
||||
.template-common .app-screen .s-responsive{overflow: hidden; height: 405px; width: 100%; overflow-x: auto;}
|
||||
/*.template-common .app-screen ul{height: 405px;}*/
|
||||
.template-common .app-screen ul li{float: left; margin-right: 20px;}
|
||||
.template-common .app-screen ul li img{width: auto; height: auto; max-width: 800px; max-height: 405px;}
|
||||
.template-common .app-screen ul li:last-of-type{margin-right: 0;}
|
||||
|
||||
/*模板1*/
|
||||
.template-1 .t-bg-1{background: #157df1 url("../images/t-04.png") repeat-x center bottom; height: 175px;}
|
||||
.template-1 .content{margin-top: -125px; padding: 0 80px;}
|
||||
.template-1 .template-common{padding: 0;}
|
||||
.template-1 .template-common .t-icon{box-shadow: none; width: auto; height: auto; background-color: transparent;}
|
||||
.template-1.passwords .template-common .t-code{width: 125px; margin-top: 35px;}
|
||||
.template-1 .template-common .t-code{width: 200px; margin-top: 15px;}
|
||||
.template-1 .template-common .t-code img{width: 125px; height: 125px; margin: 0 auto;}
|
||||
.template-1 .template-common .t-name{text-align: left;}
|
||||
.template-1 .template-common .t-name .name-info span{color: #fff;}
|
||||
.template-1+.template-footer{margin-top: 140px;}
|
||||
.template-1 .template-common .template-btn{margin-top: 30px; margin-bottom: 15px; padding: 0 15px;}
|
||||
|
||||
.template-1 .content .c-left{background: url("../images/t-05.png") no-repeat center; width: 308px; height: 608px; padding-top: 110px;}
|
||||
.template-1.passwords .content .c-left{padding-top: 140px;}
|
||||
.template-1.passwords .content .c-right{margin-left: 130px; width: auto;}
|
||||
.template-1 .content .c-right{margin-left: 100px; width: 630px;}
|
||||
.template-1 .content .c-right .t-name{color: #fff; margin-bottom: 100px;}
|
||||
.template-1 .template-common .t-name .tit{margin-top: 5px;}
|
||||
.template-1 .content .c-right .t-introduce{/*margin-top: 100px;*/ margin-bottom: 45px;}
|
||||
.template-1 .content .c-right .t-contact{margin-top: 0; margin-bottom: 45px;}
|
||||
|
||||
.template-1 .template-pwd{width: 500px; height: 230px; background-color: #fafafa; margin-top: 90px; padding: 60px 30px 0; position: relative; border-radius: 5px; display: none; border: none;}
|
||||
.template-1 .template-pwd .form-group{text-align: center;}
|
||||
.template-1 .template-pwd .form-group label{color: #3e4753;}
|
||||
|
||||
.template-1 .template-pwd .form-group .form-control{border-color: #e5e5e5; margin-top: 5px;}
|
||||
.template-1 .template-pwd .form-group .form-control:focus{border-color: #66afe9;}
|
||||
.template-1 .template-pwd .top-pwd{position: absolute; left: 50%; top: -20px; margin-left: -30px; width: 60px; height: 60px; border: 1px solid #157df1; border-radius: 50%; display: flex; justify-content: center; align-items: center; background-color: #fafafa;}
|
||||
.template-1 .template-pwd .top-pwd .iconfont{color: #157df1; font-size: 30px;}
|
||||
|
||||
.template-1 .template-common .app-screen .s-responsive{width: 730px;}
|
||||
/*/模板1*/
|
||||
|
||||
/*模板2*/
|
||||
.template-2 .t-bg-2{/* background: url("../images/t-06.jpg") center; */background-color: #51a3fe; height: 130px;}
|
||||
.template-2 .content{margin-top: -90px;}
|
||||
/*/模板2*/
|
||||
|
||||
/*模板3*/
|
||||
.template-3 .t-bg-3{background: url("../images/t-07.jpg") center; height: 160px;}
|
||||
.template-3 .content{margin-top: 20px;}
|
||||
.template-3 .template-common .t-icon{width: 160px; height: 160px; border-radius: 50%;}
|
||||
.template-3 .template-common .t-icon img{width: 105px; height: 105px;}
|
||||
/*/模板3*/
|
||||
|
||||
/*模板4*/
|
||||
.template-4 .t-bg-4{background: url("../images/t-09.png") no-repeat center; height: 485px;}
|
||||
.template-4 .t-left{width: 255px; margin-left: 265px; margin-top: 150px;}
|
||||
.template-4 .t-right{margin-left: 225px; margin-top: 45px;}
|
||||
.template-4 .content4 .template-common{padding: 0;}
|
||||
.template-4 .content4 .template-common .t-icon{width: auto; height: auto; background-color: transparent; box-shadow: none;}
|
||||
.template-4 .content4 .template-common .t-icon img{border: none;}
|
||||
.template-4 .content4 .template-common .t-name .tit{color: #fff; margin-top: 25px; margin-bottom: 20px;}
|
||||
.template-4 .content4 .template-common .t-name .name-info span{display: block; text-align: center; margin-right: 0; color: #fff; line-height: 24px;}
|
||||
.template-4 .template-common .template-btn{margin-bottom: 20px;}
|
||||
.template-4 .content>.template-common{padding: 80px 100px 0;}
|
||||
.template-4 .template-common .t-code{width: 200px;}
|
||||
.template-4 .template-common .t-code img{width: 120px; height: 120px; margin-left: auto; margin-right: auto;}
|
||||
.template-4 .template-common .decoding{width: 100%; margin-top: 30px;}
|
||||
|
||||
.template-4 .template-pwd{border: none; padding: 0; width: 490px; margin: 0 auto;}
|
||||
|
||||
.template-4+.template-footer{margin-top: 100px;}
|
||||
|
||||
.template-4 .content>.template-common .app-screen{width: 730px;}
|
||||
/*/模板4*/
|
||||
|
||||
/*模板5*/
|
||||
.template-5 .left-bg{position: absolute; left: 0; top: 0; background: url("../images/t11.png") no-repeat left top; width: 283px; height: 1012px;}
|
||||
.template-5 .right-bg{position: absolute; right: 0; top: 0; background: url("../images/t12.png") no-repeat right top; width: 283px; height: 1012px;}
|
||||
|
||||
.template-5 .content{padding-top: 100px;}
|
||||
.template-5 .template-common{padding: 0 235px;}
|
||||
.template-5 .template-common .t-icon{border-radius: 50%; position: relative; margin-left: 180px;}
|
||||
.template-5 .template-common .t-icon .bg-shadow{position: absolute; left: -20px; top: 70px; border-radius: 90px; width: 360px; height: 180px; background-color: #eff2f2; z-index: -1; transform: rotate(45deg);}
|
||||
|
||||
.template-5 .template-common .t-icon .code{position: absolute; top: 160px; right: -160px; transition: all 500ms;}
|
||||
.template-5 .template-common .t-icon .code img{border-radius: 0; border: none; width: 110px; height: 110px; transition: all 500ms;}
|
||||
.template-5 .template-common .t-icon .code:hover{width: 240px; height: 240px; right: -190px; top: 130px;}
|
||||
.template-5 .template-common .t-icon .code:hover img{width: 160px; height: 160px;}
|
||||
.template-5 .template-common .t-name-wrap{width: 350px; margin-left: auto; margin-right: auto;}
|
||||
.template-5 .template-common .t-name-wrap .t-name{text-align: left;}
|
||||
.template-5 .template-common .t-name-wrap .template-btn{margin: 45px 0 20px}
|
||||
.template-5 .template-common .t-name{margin-top: 200px;}
|
||||
.template-5 .template-common .t-name .tit{margin-bottom: 20px;}
|
||||
.template-5 .template-common .t-name .name-info{text-align: left; line-height: 24px;}
|
||||
.template-5 .template-common .t-apply{text-align: left; padding-left: 15px;}
|
||||
.template-5 .template-common .template-btn{padding: 0 53px;}
|
||||
|
||||
.template-5 .template-common hr{margin-top: 50px;}
|
||||
.template-5 .template-common .t-introduce .tit{margin-top: 50px;}
|
||||
.template-5 .template-common .t-contact{margin-top: 25px;}
|
||||
|
||||
.template-5.passwords+.template-footer{margin-top: 99px;}
|
||||
.template-5+.template-footer{margin-top: 40px;}
|
||||
|
||||
.template-5 .template-pwd{width: 555px; height: 245px; margin: 80px auto 0; padding: 60px 30px 0; position: relative; border-radius: 5px;}
|
||||
|
||||
.template-5 .template-pwd .top-pwd{position: absolute; left: 50%; top: -30px; margin-left: -30px; width: 60px; height: 60px; border: 1px solid #157df1; border-radius: 50%; display: flex; justify-content: center; align-items: center; background-color: #fafafa;}
|
||||
.template-5 .template-pwd .top-pwd .iconfont{color: #157df1; font-size: 30px;}
|
||||
|
||||
.template-5 .template-pwd .decoding{width: 100%; margin-top: 30px; margin-bottom: 30px;}
|
||||
/*/模板5*/
|
||||
|
||||
/*模板6*/
|
||||
.template-6 .t-bg-6{background-color: #f4f4f4; height: 80px; line-height: 80px;}
|
||||
.template-6 .t-top .tit{font-size: 30px; color: #333;}
|
||||
.template-6 .t-top .name-info{font-size: 18px; color: #666;}
|
||||
.template-6 .t-top .name-info span{margin-left: 10px;}
|
||||
|
||||
.template-6 .content{margin-top: 55px;}
|
||||
.template-6 .template-common .template-btn{margin-bottom: 15px;}
|
||||
.template-6 .template-common .show-hr{display: block; margin-top: 40px;}
|
||||
.template-6 .template-common .t-code{margin-top: 40px;}
|
||||
/*/模板6*/
|
||||
|
||||
/*密码显示控制*/
|
||||
.passwords .template-pwd{display: block;}
|
||||
.passwords .pc-pwd{display: none !important;}
|
||||
.passwords .template-pwd{display: block;}
|
||||
/*----------/分发落地页----------*/
|
||||
|
||||
/*----------/分发落地页----------*/
|
||||
.passwords .template-common{padding: 0;}
|
||||
.template-common{padding: 0;}
|
||||
.template-common .form-control{height: 34px;}
|
||||
.template-common hr{display: block;}
|
||||
.template-common .t-icon{width: 2.5rem; height: 2.5rem; background-color: #fff; box-shadow: 0 0 10px rgba(0,0,0,.1); border-radius: 25px; margin: 0 auto;}
|
||||
/*
|
||||
.template-common .t-icon{width: 1.8rem; height: 1.8rem; padding: .3rem; background-color: #a45dec21e0787f8b; box-shadow: 0 0 10px rgba(0,0,0,.1); border-radius: 20px; margin: 0 auto;}
|
||||
*/
|
||||
.template-common .t-icon img{border-radius: 15px; width: 110px; height: 110px;}
|
||||
|
||||
.template-common .t-name .tit{text-align: center; font-size: .4rem; font-weight: 600; margin-top: .3rem; margin-bottom: 8px;}
|
||||
.template-common .t-name .name-info{font-size: .28rem;}
|
||||
.template-common .t-name .name-info span{color: #999;}
|
||||
.template-common .t-name .name-info span:last-of-type{display: block;}
|
||||
.template-common .t-apply{color: #999; font-size: .24rem;}
|
||||
.template-common .t-code{color: #999; font-size: .24rem; margin-top: 25px; width: 200px;}
|
||||
.template-common .t-code img{width: 130px; height: 130px;}
|
||||
.template-common .t-introduce .tit{font-size: .3rem; font-weight: 600; margin-bottom: 5px;}
|
||||
.template-common .t-introduce p{font-size: .24rem;}
|
||||
.template-common .t-contact{margin-top: .4rem !important;}
|
||||
.template-common .t-contact .tit{font-size: .3rem; font-weight: 600; margin-bottom: 5px;}
|
||||
.template-common .t-contact p{font-size: .24rem;}
|
||||
.template-common .t-contact dl{font-size: .24rem;}
|
||||
|
||||
.template-common .template-btn{padding: 0 50px; height: 40px; line-height: 38px; border-radius: 20px; text-align: center; font-size: .32rem; margin-top: .4rem; margin-bottom: .2rem;}
|
||||
.template-common .template-btn span:last-of-type{line-height: .8rem;}
|
||||
.template-common .decoding{padding: 0; height: 40px; line-height: 40px; border-radius: 20px; text-align: center; font-size: .32rem; margin-top: .4rem; margin-bottom: .2rem; width: 3.6rem;}
|
||||
.template-common .template-btn .iconfont{font-size: 32px;}
|
||||
|
||||
.passwords+.template-footer{margin-top: 30px;}
|
||||
.template-footer{margin-top: 15px;}
|
||||
.template-footer .methods-tutorial-full{position: fixed; width: 100%; left: 0; bottom: 0;}
|
||||
.template-footer .methods-tutorial-full a.fl{height: 30px; line-height: 30px; font-size: .24rem;}
|
||||
.template-footer .methods-tutorial-full a.fr{margin-top: 4px;}
|
||||
|
||||
.template-footer .t-footer{margin-top: 0; margin-bottom: 40px; padding-bottom: .2rem; font-size: .24rem;}
|
||||
|
||||
.template-pwd{width: auto !important; margin: .4rem .7rem 0; background-color: #fff !important; box-shadow: 0 0 10px rgba(0,0,0,.2); padding: .4rem .3rem !important; height: auto !important;}
|
||||
.template-pwd .form-group label{color: #666; font-size: .24rem;}
|
||||
.template-pwd .form-control{margin-top: 10px;}
|
||||
|
||||
/*应用截图*/
|
||||
.template-common .app-screen .tit{font-size: .3rem; font-weight: 600; margin-bottom: 5px;}
|
||||
.template-common .app-screen .s-responsive{overflow: hidden; height: 350px; width: 100%; overflow-x: auto; /*-webkit-overflow-scrolling: touch;*/}
|
||||
.template-common .app-screen ul li{float: left; margin-right: 10px;}
|
||||
.template-common .app-screen ul li img{max-width: 600px; width: auto; height: auto; max-height: 350px;}
|
||||
|
||||
|
||||
/*模板1*/
|
||||
.template-1 .t-bg-1{background: #157df1 url("../images/t-04.png") repeat-x center bottom; height: 1.4rem;}
|
||||
.template-1 .content{margin-top: 0; padding: 0;}
|
||||
.template-1 .content .c-left{background-image: none; width: 100%; height: auto; float: none; padding-top: .4rem;}
|
||||
.template-1.passwords .content .c-left{padding-top: 25px;}
|
||||
.template-1 .template-common .t-name .tit{margin-bottom: 0; margin-top: 15px;}
|
||||
/*.template-1 .template-common .t-icon{width: 3.2rem; height: 3.2rem; background-color: #fff; box-shadow: 0 0 10px rgba(0,0,0,.1); border-radius: 25px; margin: 0 auto;}*/
|
||||
/*设计稿 单独定义APP图标大小*/
|
||||
.template-1 .template-common .t-icon{width: 1.8rem; height: 1.8rem; background-color: #fff; box-shadow: 0 0 10px rgba(0,0,0,.1); border-radius: 20px; margin: 0 auto;}
|
||||
.template-1 .template-common .t-icon img{width: 60px; height: 60px;}
|
||||
/*设计稿 单独定义APP图标大小*/
|
||||
|
||||
.template-1 .content .c-right{float: none; width: 100%; margin-left: 0 !important; padding-top: .5rem; border-top: 1px solid transparent; margin-top: 0; padding-top: 0;}
|
||||
.template-1 .content .c-right .template-common{padding: 0;}
|
||||
|
||||
.template-1 .template-common .t-name .name-info{text-align: center; margin-top: 5px;}
|
||||
.template-1 .template-common .t-name .name-info span{color: #999;}
|
||||
.template-1 .template-common .template-btn{margin-top: 20px; padding: 0 36px; margin-bottom: 10px;}
|
||||
.template-1 .content .c-right .t-introduce{margin-top: 0; margin-bottom: 20px;}
|
||||
.template-1 .content .c-right .t-introduce p{margin-top: 0;}
|
||||
.template-1 .content .c-right .t-contact{margin-bottom: 20px; margin-top: 0;}
|
||||
.template-1 .template-common .t-code{margin-top: 25px;}
|
||||
.template-1 .template-pwd{margin-top: .4rem;}
|
||||
.template-1.passwords+.template-footer{margin-top: 40px;}
|
||||
.template-1+.template-footer{margin-top: 40px;}
|
||||
|
||||
.template-1 .template-common .app-screen .s-responsive{width: 100%;}
|
||||
/*/模板1*/
|
||||
|
||||
/*模板2*/
|
||||
.template-2 .content{margin-top: -80px;}
|
||||
/*/模板2*/
|
||||
|
||||
/*模板3*/
|
||||
.template-3 .t-bg-3{background: url("../images/f57fbed0feda5a33.jpg") center; height: .7rem; background-size: cover;}
|
||||
.template-3 .content{margin-top: 20px;}
|
||||
.template-3 .template-common .t-icon{width: 100px; height: 100px; border-radius: 50%;}
|
||||
.template-3 .template-common .t-icon img{width: 65px; height: 65px;}
|
||||
.template-3 .template-common .t-name .tit{margin-top: 10px;}
|
||||
|
||||
.template-3.passwords+.template-footer{margin-top: 40px;}
|
||||
/*/模板3*/
|
||||
|
||||
/*模板4*/
|
||||
.template-4 .t-bg-4{background: none; height: auto;}
|
||||
.template-4 .t-bg-4>.container{padding: 0;}
|
||||
.template-4 .t-left{float: none; margin: 0; width: 100%; padding-top: 55px; background: url("../images/d9ee8741f22ce1f3.png") no-repeat center; height: 3.7rem; background-size: cover;}
|
||||
.template-4 .t-right{float: none; margin: 25px auto 0; width: 100%;}
|
||||
/*.template-4 .t-right{float: none; margin: 40px auto 0; width: 100%;}*/
|
||||
.template-4 .content4 .template-common .t-icon{margin-left: -3px;}
|
||||
.template-4 .content4 .template-common .t-icon img{width: 70px; height: 70px;}
|
||||
.template-4 .content4 .template-common .t-name .tit{font-weight: normal; margin-top: 10px; font-size: .32rem;}
|
||||
|
||||
.template-4 .t-right .template-common .t-name .name-info span{color: #999; display: inline-block; line-height: 20px;}
|
||||
.template-4 .t-right .template-common .t-name .name-info span:last-of-type{display: block;}
|
||||
|
||||
.template-4 .content>.template-common{padding: 0;}
|
||||
.template-4 .template-common .template-btn{margin-bottom: 5px;}
|
||||
.template-4 .template-common .decoding{width: 3.6rem; padding: 0; margin-top: .4rem;}
|
||||
.template-4 .template-common .template-pwd{margin: 0 .7rem;}
|
||||
|
||||
.template-4.passwords+.template-footer{margin-top: 55px;}
|
||||
.template-4+.template-footer{margin-top: 15px;}
|
||||
|
||||
.template-4 .content>.template-common .app-screen{width: 100%;}
|
||||
/*/模板4*/
|
||||
|
||||
/*模板5*/
|
||||
.template-5 .left-bg{position: absolute; left: 0; top: 0; background: url("../images/t11.png") no-repeat left top; width: 1.1rem; height: 3.93rem; background-size: cover;}
|
||||
.template-5 .right-bg{position: absolute; right: 0; top: 0; background: url("../images/t12.png") no-repeat right top; width: 1.1rem; height: 3.93rem; background-size: cover;}
|
||||
|
||||
.template-5 .content{padding-top: 25px;}
|
||||
.template-5.passwords .template-common{padding: 0 .7rem;}
|
||||
.template-5 .template-common .t-icon{border-radius: 25px; margin-left: auto; margin-right: auto;}
|
||||
.template-5 .template-common .t-name-wrap{width: auto; margin-left: 0;}
|
||||
.template-5 .template-common .t-name{margin-top: 0;}
|
||||
.template-5 .template-common .t-name .tit{margin-bottom: 10px;}
|
||||
.template-5 .template-common .t-name .name-info{text-align: center; line-height: 20px;}
|
||||
.template-5 .template-common .t-name-wrap .template-btn{padding: 0 38px; margin: 20px auto 10px;}
|
||||
.template-5 .template-common .t-apply{padding-left: 0; text-align: center;}
|
||||
.template-5 .template-common hr{margin-top: 25px;}
|
||||
.template-5 .template-common .t-introduce .tit{margin-top: 25px;}
|
||||
.template-5 .template-common .t-contact{margin-top: .4rem;}
|
||||
.template-5.passwords+.template-footer{margin-top: 30px;}
|
||||
.template-5+.template-footer{margin-top: 15px;}
|
||||
|
||||
.template-5 .template-pwd{margin-top: 15px;}
|
||||
.template-5 .template-pwd .decoding{width: 3.6rem; margin-top: 0; margin-bottom: 25px;}
|
||||
|
||||
.template-5 .template-common{padding: 0;}
|
||||
/*/模板5*/
|
||||
|
||||
/*模板6*/
|
||||
.template-6 .t-bg-6{background-color: #f4f4f4; height: auto; line-height: normal; padding: 10px 0;}
|
||||
.template-6 .t-top .tit{font-size: .4rem; color: #333; float: none; text-align: center;}
|
||||
.template-6 .t-top .name-info{font-size: .28rem; background-color:#a54adea061bafe6f;color: #999; float: none; text-align: center; margin-top: 3px;}
|
||||
.template-6 .t-top .name-info span{margin-left: 10px;}
|
||||
.template-6 .t-top .name-info span:last-of-type{display: block;}
|
||||
|
||||
.template-6 .content{margin-top: 25px;}
|
||||
.template-6 .template-common .template-btn{margin-bottom: 15px;}
|
||||
.template-6 .template-common .show-hr{display: none;}
|
||||
.template-6 .template-common .t-code{margin-top: 25px;}
|
||||
/*/模板6*/
|
||||
|
||||
/*密码显示控制*/
|
||||
.passwords .pc-pwd{display: none !important;}
|
||||
.passwords .phone-pwd{display: none;}
|
||||
/*----------/分发落地页----------*/
|
||||
.xuanfutishi span {
|
||||
float: left;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.xuanfutishi img {
|
||||
float: right;
|
||||
height: 15px;
|
||||
margin: 12px 15px 0 0;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.appicon {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 24px;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.appicon2 {
|
||||
border: 1px solid #fff;
|
||||
border-radius: 24px;
|
||||
height: 140px;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
#codeico {
|
||||
background-color: #efefef;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 10px;
|
||||
margin: 77px;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.one-key-report {
|
||||
background-color: #32b2a7;
|
||||
color: #fff;
|
||||
margin-top: -5px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||||
left: 50%;
|
||||
margin-left: -225px;
|
||||
margin-top: -275px;
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
width: 450px;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .dialog-close {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .dialog-close i.icon-close {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .dialog-close i.icon-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .dialog-close .icon-return {
|
||||
color: #32b2a7;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .custom-checkbox {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 24px;
|
||||
margin-right: 40px;
|
||||
padding: 2px 2px 2px 25px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .custom-checkbox:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .custom-checkbox::after, .one-key-report-dialog .custom-checkbox::before {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .custom-checkbox::after {
|
||||
background-color: #1989fa;
|
||||
border-radius: 10px;
|
||||
display: none;
|
||||
height: 10px;
|
||||
left: 1px;
|
||||
top: 7px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .custom-checkbox::before {
|
||||
background-color: #fff;
|
||||
border: 1px solid #1989fa;
|
||||
border-radius: 12px;
|
||||
height: 12px;
|
||||
left: 0;
|
||||
top: 6px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .custom-checkbox.active::after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .content-row {
|
||||
display: table;
|
||||
padding: 10px 10px 5px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .content-row input, .one-key-report-dialog .content-row label {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .content-row label {
|
||||
font-size: 18px;
|
||||
line-height: 34px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .content-row input {
|
||||
border: 1px solid #a9b1b3;
|
||||
border-radius: 5px;
|
||||
height: 34px;
|
||||
outline: 0 none;
|
||||
padding: 5px 10px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .content-row .checkbox-list {
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .content-row textarea {
|
||||
border: 1px solid #a9b1b3;
|
||||
border-radius: 10px;
|
||||
color: #a9b1b3;
|
||||
font-size: 16px;
|
||||
height: 200px;
|
||||
outline: 0 none;
|
||||
padding: 10px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .content-row .btn-report {
|
||||
background-color: #32b2a7;
|
||||
border-radius: 15px;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-top: -5px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback, .one-key-report-dialog .report-form {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-sending {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
color: #000;
|
||||
cursor: default;
|
||||
display: none;
|
||||
font-size: 20px;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
line-height: 450px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-form {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback {
|
||||
cursor: default;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks {
|
||||
background-color: #efefef;
|
||||
border-radius: 20px;
|
||||
height: 160px;
|
||||
margin: 50px auto 20px;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks .brace-content {
|
||||
color: #000;
|
||||
margin: 0 auto;
|
||||
padding-top: 30px;
|
||||
text-align: center;
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks .thanks {
|
||||
color: #000;
|
||||
cursor: default;
|
||||
font-family: "Roboto Slab", "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.8px;
|
||||
margin: 15px 0 0;
|
||||
min-height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks .face, .one-key-report-dialog .report-feedback .feedback-thanks .icon-brace-left, .one-key-report-dialog .report-feedback .feedback-thanks .icon-brace-right {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks .icon-brace-left, .one-key-report-dialog .report-feedback .feedback-thanks .icon-brace-right {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks .face .icon-comma-eye {
|
||||
font-size: 22px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks .face .icon-comma-eye.right {
|
||||
display: inline-block;
|
||||
margin-left: 16px;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-thanks .face .icon-mouth {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
transform: rotateZ(-19deg) translateX(2px);
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-message {
|
||||
color: #1989fa;
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-feedback .feedback-content {
|
||||
color: #858585;
|
||||
font-size: 18px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-error {
|
||||
color: red;
|
||||
cursor: default;
|
||||
line-height: 34px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.one-key-report-dialog .report-error > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ad_section {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
margin-bottom: 0;
|
||||
left: 50%;
|
||||
margin-left: -365px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.ad_section {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.floadAd {
|
||||
position: absolute;
|
||||
z-index: 999900;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.floadAd .item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.floadAd .item img {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
/*safari打开提示*/
|
||||
.click_opacity{ width:100%; height:100%; background:#000; opacity:0.6; position:fixed; z-index:10000; top:0px;}
|
||||
.to_btn{ position:fixed; top:10px; right:10px; text-align:right; z-index:10001; font-family:"微软雅黑";}
|
||||
.to_btn span{ display:block;}
|
||||
.to_btn img{ width:20%; height:auto; display:inline-block;}
|
||||
.to_btn .span1{ font-size:1.6rem; color:#fff; margin-top:5px;}
|
||||
.to_btn{ color:#fff;}
|
||||
.to_btn .span2{ display:inline-block; line-height:36px; width:80%; margin-bottom:12px; text-align:left; font-size:16px;}
|
||||
.to_btn .span2 em{ display:inline-block; width:16px; height:16px; background:#009dd9; color:#fff; font-size:12px; text-align:center; line-height:16px; border:1px solid #fff; border-radius:50%; margin-right:3px;}
|
||||
.to_btn .span2 img{ display:inline-block; width:30px; height:30px; margin:0px 5px;}
|
||||
.to_btn span{ display:block; float:right;}
|
||||
.to_btn .android_open img{ display:inline-block; width:150px; height:34px;}
|
||||
3
tools/downloadWeb2/css/umi.css
Normal file
BIN
tools/downloadWeb2/img/logo.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
211
tools/downloadWeb2/index.html
Normal file
@@ -0,0 +1,211 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="./img/logo.png">
|
||||
<link rel="stylesheet" type="text/css" href="./css/umi.css">
|
||||
<link rel="stylesheet" type="text/css" href="./css/download.css">
|
||||
<script src="./js/jquery-1.9.1.min.js"></script>
|
||||
<link rel="stylesheet" href="./css/main.css">
|
||||
<title>下载</title>
|
||||
</head>
|
||||
|
||||
<body _c_t_j1="1">
|
||||
<div id="root">
|
||||
<div class="mobile___3touz">
|
||||
<div class="top-content___1-px4">
|
||||
<div class="title___vZvj6">
|
||||
下载客户端
|
||||
</div>
|
||||
<div class="desc___1bUF- shuoming">
|
||||
适应系统:HarmonyOS 2.0+ ,Android 5.0+ ,iOS 9.0+
|
||||
</div>
|
||||
<div class="logo___2JBdG">
|
||||
<img alt="" src="./img/logo.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-content___25WPV">
|
||||
<a href="" type="ios" class="ms-btn template-btn clearfix pc-pwd down_load">
|
||||
<div class="btn___37Kse">
|
||||
立即下载
|
||||
</div>
|
||||
</a>
|
||||
<div class="update-info___1KZ0J">
|
||||
<div class="title___vZvj6">
|
||||
游戏声明
|
||||
</div>
|
||||
<div class="content___3LVDL">
|
||||
<div>
|
||||
- 游戏仅供娱乐
|
||||
</div>
|
||||
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: 100px;">
|
||||
</div>
|
||||
<div class="footer___31nRH">
|
||||
© 2024
|
||||
<!-- <a href="http://www.beian.miit.gov.cn"> -->
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="weixin_ios" style="display:none;">
|
||||
<div class="click_opacity">
|
||||
</div>
|
||||
<div class="to_btn">
|
||||
<span class="span1">
|
||||
<img src="./picture/click_btn.png">
|
||||
</span>
|
||||
<span class="span2">
|
||||
<em>
|
||||
1
|
||||
</em>
|
||||
点击右上角
|
||||
<img src="./picture/menu.png">
|
||||
打开菜单
|
||||
</span>
|
||||
<span class="span2">
|
||||
<em>
|
||||
2
|
||||
</em>
|
||||
选择
|
||||
<img src="./picture/safari.png">
|
||||
在默认浏览器打开
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="weixin_android" style="display: none;">
|
||||
<div class="click_opacity">
|
||||
</div>
|
||||
<div class="to_btn">
|
||||
<span class="span1">
|
||||
<img src="./picture/click_btn.png">
|
||||
</span>
|
||||
<span class="span2">
|
||||
<em>
|
||||
1
|
||||
</em>
|
||||
点击右上角
|
||||
<img src="./picture/menu_android.png">
|
||||
打开菜单
|
||||
</span>
|
||||
<span class="span2 android_open">
|
||||
<em>
|
||||
2
|
||||
</em>
|
||||
选择
|
||||
<img src="./picture/android.png">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
|
||||
|
||||
var Sys = {};
|
||||
var s;
|
||||
(s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] : 0;
|
||||
|
||||
// Get base URL (protocol + domain)
|
||||
var baseUrl = window.location.protocol + "//" + window.location.host;
|
||||
|
||||
// Links
|
||||
var iosUrl = "itms-services://?action=download-manifest&url=" + baseUrl + "/plist/gamehall_jinxianv2.plist";
|
||||
var androidUrl = baseUrl + '/apk/gamehall_jinxianv2.apk';
|
||||
var androidUrlBak = baseUrl + '/apk/gamehall_jinxianv2_bak.apk';
|
||||
var harmonyUrl = baseUrl + '/harmonyos/gamehall_jinxian_harmonyosv2.apk';
|
||||
|
||||
// Detect Types
|
||||
var isIos = /(iPhone|iPad|iPod|iOS)/i.test(ua);
|
||||
var isHarmony = /(harmonyos|openharmony)/i.test(ua);
|
||||
var isAndroid = /(Android|Adr)/i.test(ua);
|
||||
|
||||
// Detect WX/QQ (使用更稳健的 indexOf 检测)
|
||||
var isWeChat = ua.indexOf("micromessenger") > -1;
|
||||
var isQQ = ua.indexOf(' qq') > -1 && ua.indexOf('mqqbrowser') < 0;
|
||||
|
||||
// Logic
|
||||
if (isIos) {
|
||||
$(".shuoming").html('适用于苹果手机');
|
||||
$(".btn___37Kse").text('iOS版下载');
|
||||
|
||||
if (Sys.safari) {
|
||||
if (isWeChat || isQQ) { // iOS下微信我们也拦截一下显示提示
|
||||
$("#weixin_ios").show();
|
||||
} else {
|
||||
$(".down_load").attr("href", iosUrl);
|
||||
$(".down_load").click(function (event) {});
|
||||
}
|
||||
} else {
|
||||
// 非 Safari (Chrome等 或 微信/QQ)
|
||||
$("#weixin_ios").show();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Android / Harmony / PC
|
||||
|
||||
// 优先检查微信/QQ,确保即使系统识别失败(如HarmonyOS UA变动)也能正确拦截
|
||||
if (isWeChat || isQQ) {
|
||||
var labelText = '适用于安卓手机';
|
||||
var btnText = 'Android版下载';
|
||||
|
||||
// 如果能精确识别是鸿蒙,则更新文案
|
||||
if (isHarmony) {
|
||||
labelText = '适用于HarmonyOS系统';
|
||||
btnText = 'HarmonyOS版下载';
|
||||
}
|
||||
|
||||
$(".shuoming").html(labelText);
|
||||
$(".btn___37Kse").text(btnText);
|
||||
$(".down_load").attr("href", '###');
|
||||
$("#weixin_android").show();
|
||||
}
|
||||
else {
|
||||
// 非微信/QQ环境:正常浏览器下载逻辑
|
||||
var downloadLink = androidUrl; // 默认
|
||||
var labelText = '适用于安卓手机';
|
||||
var btnText = 'Android版下载';
|
||||
var isPC = false;
|
||||
|
||||
if (isHarmony) {
|
||||
labelText = '适用于HarmonyOS系统';
|
||||
btnText = 'HarmonyOS版下载';
|
||||
downloadLink = harmonyUrl;
|
||||
} else if (isAndroid) {
|
||||
// 默认即安卓
|
||||
} else {
|
||||
isPC = true;
|
||||
}
|
||||
|
||||
if (!isPC) {
|
||||
$(".shuoming").html(labelText);
|
||||
$(".btn___37Kse").text(btnText);
|
||||
$(".down_load").attr("href", downloadLink);
|
||||
|
||||
if (isAndroid && !isHarmony) {
|
||||
$(".down_load").after('<a href="' + androidUrlBak + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px;"><div class="btn___37Kse">无法安装时备用包下载</div></a>');
|
||||
}
|
||||
} else {
|
||||
// PC Mode
|
||||
$(".shuoming").html('请选择对应系统下载');
|
||||
var btns = '';
|
||||
btns += '<a href="' + androidUrl + '" class="ms-btn template-btn clearfix pc-pwd down_load"><div class="btn___37Kse">Android版下载</div></a>';
|
||||
btns += '<a href="' + androidUrlBak + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px"><div class="btn___37Kse">无法安装时备用包下载</div></a>';
|
||||
btns += '<a href="' + harmonyUrl + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px"><div class="btn___37Kse">HarmonyOS版下载</div></a>';
|
||||
btns += '<a href="' + iosUrl + '" class="ms-btn template-btn clearfix pc-pwd down_load" style="margin-top:20px"><div class="btn___37Kse">iOS版下载</div></a>';
|
||||
|
||||
$(".down_load").replaceWith(btns);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5
tools/downloadWeb2/js/jquery-1.9.1.min.js
vendored
Normal file
BIN
tools/downloadWeb2/picture/android.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
tools/downloadWeb2/picture/click_btn.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
tools/downloadWeb2/picture/menu.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tools/downloadWeb2/picture/menu_android.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
tools/downloadWeb2/picture/safari.png
Normal file
|
After Width: | Height: | Size: 26 KiB |