20 KiB
20 KiB
友乐游戏框架基础概述
文档目标:帮助开发者理解友乐游戏平台的整体架构、前后端分离机制、模块化设计和基础开发规范。
📚 目录
1. 友乐游戏平台架构
1.1 整体架构
友乐游戏平台采用模块化、可扩展的架构设计,支持多个子游戏的独立开发和部署。
┌─────────────────────────────────────────────────┐
│ 友乐游戏平台(Node.js) │
├─────────────────────────────────────────────────┤
│ 应用层(youle_app) │
│ ├─ 房间管理(youle_room) │
│ ├─ 玩家管理(youle_player) │
│ ├─ 通信服务(youle_socket) │
│ └─ 数据存储(youle_database) │
├─────────────────────────────────────────────────┤
│ 游戏模块层(mod_*) │
│ ├─ mod_jinxianmahjong(进贤麻将) │
│ ├─ mod_other_game1(其他游戏1) │
│ └─ mod_other_game2(其他游戏2) │
├─────────────────────────────────────────────────┤
│ 网络层(packet.js) │
│ ├─ WebSocket通信 │
│ ├─ HTTP通信 │
│ └─ RPC路由分发 │
└─────────────────────────────────────────────────┘
↕ WebSocket/HTTP
┌─────────────────────────────────────────────────┐
│ 客户端(浏览器环境) │
│ ├─ 游戏界面渲染 │
│ ├─ 用户交互处理 │
│ ├─ 本地状态管理 │
│ └─ 网络通信封装 │
└─────────────────────────────────────────────────┘
1.2 核心组件
| 组件名称 | 职责说明 | 部署位置 |
|---|---|---|
| youle_app | 应用级服务提供者,管理所有游戏模块 | 服务端 |
| youle_room | 房间管理服务,处理房间创建、加入、解散 | 服务端 |
| packet.js | 数据包路由分发器,实现RPC调用 | 服务端 |
| mod_*(游戏模块) | 子游戏具体实现,独立封装游戏逻辑 | 服务端 |
| 客户端界面 | 游戏前端界面和交互逻辑 | 浏览器 |
1.3 关键设计理念
- 模块独立性:每个游戏模块独立开发、测试、部署
- 接口标准化:所有游戏模块遵循统一的接口规范
- 双向解耦:框架和游戏通过export/import接口解耦
- 状态同步:服务端为权威状态源,客户端被动接收
2. 前后端分离部署
2.1 部署架构
⚠️ 重要:友乐游戏采用真正的前后端物理分离,而非同进程模块调用。
┌──────────────────────────────────────────────┐
│ 用户浏览器(客户端) │
│ 环境:Chrome/Firefox/Safari等浏览器 │
│ 语言:JavaScript ES5(不使用Node.js) │
│ 部署:静态HTML/JS/CSS文件 │
│ 运行:浏览器JavaScript引擎 │
└──────────────────────────────────────────────┘
↕
WebSocket/HTTP协议通信
↕
┌──────────────────────────────────────────────┐
│ 游戏服务器(服务端) │
│ 环境:Node.js运行时 │
│ 语言:JavaScript ES5 │
│ 部署:服务器进程(pm2/systemd等) │
│ 运行:Node.js引擎 │
└──────────────────────────────────────────────┘
2.2 前后端通信特点
客户端特点(浏览器环境)
- 运行环境:运行在用户浏览器中(Chrome、Firefox、Safari等)
- 语言规范:使用原生JavaScript ES5标准,不使用Node.js特性
- 部署方式:作为静态资源部署(HTML、JS、CSS文件)
- 模块系统:不使用npm包管理,不使用require/import
- 依赖管理:通过
<script>标签按顺序加载
服务端特点(Node.js环境)
- 运行环境:运行在服务器Node.js进程中
- 语言规范:使用JavaScript ES5,遵循Node.js模块规范
- 部署方式:作为Node.js应用部署
- 模块系统:使用Node.js的
require()进行模块加载 - 依赖管理:使用npm管理依赖包
通信机制
| 特性 | 说明 | 影响 |
|---|---|---|
| 物理分离 | 客户端和服务端是完全独立的进程 | 不能直接调用函数,必须通过网络通信 |
| 网络通信 | 使用WebSocket/HTTP协议 | 存在网络延迟,需要异步处理 |
| 数据序列化 | 所有数据必须JSON序列化 | 不能传递函数、对象引用等 |
| 异步交互 | 所有请求都是异步的 | 需要回调或事件机制处理响应 |
| 状态同步 | 服务端主动推送状态变化 | 客户端需要维护本地状态副本 |
2.3 代码共享机制
由于前后端部署环境不同,代码共享通过文件复制方式实现:
server/games2/jinxianmahjong/shared/
├── core/ # 核心算法(前后端共享)
│ ├── JingAlgorithm.js # 精牌算法
│ ├── HandEvaluator.js # 牌型评估
│ └── ScoringEngine.js # 计分引擎
├── constants/ # 常量定义(前后端共享)
├── dataStructures/ # 数据结构(前后端共享)
└── utils/ # 工具函数(前后端共享)
↓ 文件复制(构建时)↓
client/js/shared/ # 复制到客户端目录
├── core/
├── constants/
├── dataStructures/
└── utils/
共享代码编写要求:
- ES5语法:不使用任何ES6+特性(箭头函数、class、let/const等)
- 无副作用:纯函数设计,不依赖全局状态
- 环境兼容:同时兼容浏览器和Node.js环境
- 无依赖:不依赖Node.js特定API或浏览器特定API
3. 三文件架构规范
每个子游戏必须遵循三文件架构规范:
3.1 架构概览
server/games2/jinxianmahjong/
├── mod.js # 【1】模块主入口
├── export.js # 【2】输出接口(框架→子游戏)
└── import.js # 【3】输入接口(子游戏→框架)
3.2 三个核心文件
1️⃣ mod.js - 模块主入口
职责:
- 创建游戏模块实例
- 按顺序加载依赖文件
- 初始化模块状态
- 定义RPC方法
关键代码:
// 创建模块实例
var mod_jinxianmahjong = cls_mod.new(
"mod_jinxianmahjong", // 模块名称
"jinxianmahjong", // 游戏ID
youle_app // 父级应用
);
// 加载依赖(按顺序)
require('./export.js'); // 输出接口
require('./import.js'); // 输入接口
require('./rpc/RpcHandler.js'); // RPC处理器
// ... 其他依赖
// 定义RPC方法
mod_jinxianmahjong.player_draw = function(pack) {
// 处理玩家摸牌请求
};
2️⃣ export.js - 输出接口
职责:框架调用子游戏的标准接口
必需的8个接口:
get_needroomcard- 创建房间所需房卡get_asetcount- 游戏局数get_needroomcard_joinroom- 加入房间所需房卡makewar- 开战(游戏开始)get_deskinfo- 获取牌桌信息(断线重连)get_disbandRoom- 解散房间player_enter- 玩家进入player_leave- 玩家离开
实现模式:
var cls_jinxianmahjong_export = {
new: function() {
var exp = {};
exp.get_needroomcard = function(roomtype, o_game_config) {
// 根据roomtype返回所需房卡数
return 1; // 示例
};
exp.makewar = function(o_room, o_game_config) {
// 创建游戏桌对象
var o_desk = createDesk(o_room);
o_room.o_desk = o_desk;
o_desk.o_room = o_room;
// 返回开战数据包
return {
app: "youle",
route: "jinxianmahjong",
rpc: "makewar",
data: { /* 开战数据 */ }
};
};
// ... 其他6个接口
return exp;
}
};
// 挂载到模块
mod_jinxianmahjong.export = cls_jinxianmahjong_export.new();
3️⃣ import.js - 输入接口
职责:子游戏调用框架服务
核心的4个接口:
check_player- 验证玩家身份和位置deduct_roomcard- 扣除房卡save_grade- 保存游戏成绩finish_gametask- 完成游戏任务
实现模式:
var cls_jinxianmahjong_import = {
new: function() {
var imp = {};
imp.check_player = function(agentid, gameid, roomcode, seat, playerid, conmode, fromid) {
// 调用框架验证服务
return mod_jinxianmahjong.app.youle_room.export.check_player(
agentid, gameid, roomcode, seat, playerid, conmode, fromid
);
};
imp.deduct_roomcard = function(o_room) {
// ⚠️ 必须在第一小局结算时调用
return mod_jinxianmahjong.app.youle_room.export.deduct_roomcard(o_room);
};
imp.save_grade = function(o_room, o_gameinfo1, o_gameinfo2, freeroomflag) {
// ⚠️ 必须在大局结束时调用
mod_jinxianmahjong.app.youle_room.export.save_grade(
o_room, o_gameinfo1, o_gameinfo2, freeroomflag
);
};
// ... 其他接口
return imp;
}
};
// 挂载到模块
mod_jinxianmahjong.import = cls_jinxianmahjong_import.new();
3.3 文件加载顺序
严格的加载顺序(不可变更):
1. mod.js # 首先加载,创建模块实例
↓
2. export.js # 加载输出接口
↓
3. import.js # 加载输入接口
↓
4. RpcHandler.js # 加载RPC处理器
↓
5. 其他业务文件 # 加载游戏逻辑
为什么顺序重要:
export.js和import.js在模块初始化时就需要- 后续文件可能依赖这两个接口
- 错误的顺序会导致
undefined错误
4. 模块加载机制
4.1 双环境加载支持
友乐游戏框架支持两种运行环境:
Node.js环境(服务器/测试)
if (typeof require !== 'undefined' && typeof module !== 'undefined') {
// Node.js环境:使用require同步加载
require('./export.js');
require('./import.js');
require('./rpc/RpcHandler.js');
// ...
}
友乐平台环境(生产服务器)
else if (typeof min_loadJsFile !== 'undefined') {
// 友乐平台:使用min_loadJsFile异步加载
min_loadJsFile("games2/jinxianmahjong/export.js", function() {
min_loadJsFile("games2/jinxianmahjong/import.js", function() {
min_loadJsFile("games2/jinxianmahjong/rpc/RpcHandler.js", function() {
// 嵌套回调加载
});
});
});
}
4.2 模块初始化
function initializeModule() {
console.log("[mod_jinxianmahjong] 模块初始化开始");
// 1. 验证必需接口
if (!mod_jinxianmahjong.export) {
throw new Error("export接口未加载");
}
if (!mod_jinxianmahjong.import) {
throw new Error("import接口未加载");
}
// 2. 设置就绪状态
mod_jinxianmahjong.isReady = true;
mod_jinxianmahjong.loadTime = new Date();
console.log("[mod_jinxianmahjong] ✅ 模块加载完成");
}
5. 数据包协议基础
5.1 统一包结构
所有数据包必须遵循以下结构:
{
"app": "youle", // 应用标识(固定为"youle")
"route": "jinxianmahjong", // 路由模块名(游戏ID)
"rpc": "player_draw", // RPC方法名
"data": { // 业务数据
"agentid": "agent001",
"playerid": 12345,
"gameid": "jinxianmahjong",
"roomcode": 100001,
"seat": 0,
// ... 其他业务参数
}
}
5.2 三层路由机制
客户端发送数据包
↓
【1】packet.js(网络层)
↓ 根据app字段路由
【2】youle_app(应用层)
↓ 根据route字段路由
【3】mod_jinxianmahjong(模块层)
↓ 根据rpc字段调用
【4】mod_jinxianmahjong.player_draw(pack)
↓
执行游戏逻辑
路由过程:
- packet.js:接收网络数据包,解析app字段
- youle_app:根据route字段找到对应游戏模块
- mod_jinxianmahjong:根据rpc字段调用对应方法
- RPC方法:执行具体游戏逻辑
5.3 RPC方法标准模板
mod_jinxianmahjong.player_draw = function(pack) {
// 1. 提取参数
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);
// 2. 验证玩家(必须)
var o_room = mod_jinxianmahjong.import.check_player(
agentid, gameid, roomcode, seat, playerid,
pack.conmode, pack.fromid
);
if (!o_room) {
return; // 验证失败
}
// 3. 获取游戏桌对象
var o_desk = o_room.o_desk;
// 4. 执行业务逻辑
var result = o_desk.gameService.playerDraw(seat);
// 5. 构造响应包
var msg = {
app: "youle",
route: "jinxianmahjong",
rpc: "player_draw_result",
data: {
seat: seat,
card: result.card,
// ...
}
};
// 6. 发送响应
o_room.method.sendpack_toall(msg);
};
6. 开发环境与兼容性
6.1 ES5语法规范
必须遵循ES5语法,不使用任何ES6+特性:
✅ 允许使用:
// 函数声明
function myFunction() { }
// 变量声明
var myVar = 123;
// 对象字面量
var obj = {
key: 'value',
method: function() { }
};
// 数组操作
var arr = [1, 2, 3];
arr.push(4);
arr.forEach(function(item) { });
// 原型继承
function MyClass() { }
MyClass.prototype.method = function() { };
❌ 禁止使用:
// 箭头函数(ES6)
const func = () => { };
// let/const(ES6)
let myVar = 123;
const MY_CONST = 456;
// 类语法(ES6)
class MyClass { }
// 模板字符串(ES6)
var str = `Hello ${name}`;
// 解构赋值(ES6)
var {x, y} = point;
// async/await(ES7)
async function func() { }
6.2 环境检测
// 检测Node.js环境
if (typeof require !== 'undefined' && typeof module !== 'undefined') {
// Node.js环境
}
// 检测浏览器环境
if (typeof window !== 'undefined') {
// 浏览器环境
}
// 检测友乐平台环境
if (typeof min_loadJsFile !== 'undefined') {
// 友乐平台环境
}
6.3 兼容性要求
| 特性 | Node.js环境 | 浏览器环境 | 要求 |
|---|---|---|---|
| 语法 | ES5 | ES5 | 不使用ES6+特性 |
| 模块系统 | require() | <script>标签 |
双环境兼容 |
| 全局对象 | global | window | 使用条件判断 |
| 文件系统 | fs模块 | ❌ 不可用 | 共享代码不能依赖 |
| DOM API | ❌ 不可用 | document等 | 共享代码不能依赖 |
7. 关键概念总结
7.1 核心术语
| 术语 | 含义 | 重要性 |
|---|---|---|
| mod | 游戏模块实例(如mod_jinxianmahjong) | ⭐⭐⭐⭐⭐ |
| export | 框架调用子游戏的接口集合 | ⭐⭐⭐⭐⭐ |
| import | 子游戏调用框架的接口集合 | ⭐⭐⭐⭐⭐ |
| RPC | 远程过程调用,客户端调用服务端方法 | ⭐⭐⭐⭐⭐ |
| o_room | 房间对象,管理房间状态和玩家 | ⭐⭐⭐⭐ |
| o_desk | 游戏桌对象,管理具体游戏状态 | ⭐⭐⭐⭐ |
| roomtype | 房间类型配置数组(局数、玩法等) | ⭐⭐⭐ |
| pack | 数据包对象,包含app、route、rpc、data | ⭐⭐⭐⭐⭐ |
7.2 开发要点
- 严格遵循三文件架构:mod.js、export.js、import.js
- 加载顺序不可变更:export → import → 其他文件
- 使用ES5语法:确保浏览器和Node.js双环境兼容
- 理解前后端分离:通过网络通信,不是函数调用
- 掌握RPC机制:packet.js → app → mod → RPC方法
- 注意调用时机:deduct_roomcard(第一局结算)、save_grade(大局结束)
8. 相关文档
- 01-Export接口说明 - 详细的8个必需接口说明
- 02-Import接口说明 - 详细的4个框架服务接口
- 03-RPC处理机制 - RPC路由和处理流程
- 08-游戏流程概述 - 完整游戏流程说明
9. 快速上手
9.1 创建新游戏模块的步骤
- 创建游戏目录:
server/games2/yourgame/ - 创建mod.js:定义模块实例和RPC方法
- 创建export.js:实现8个必需接口
- 创建import.js:封装4个框架接口
- 创建RPC处理器:处理客户端请求
- 创建游戏逻辑:实现具体游戏功能
- 测试验证:确保接口正确调用
9.2 常见问题
Q: export和import的区别? A: export是框架调用子游戏(框架→游戏),import是子游戏调用框架(游戏→框架)。
Q: 为什么必须使用ES5语法? A: 客户端部署在浏览器中,需要兼容老版本浏览器。ES5语法兼容性最好。
Q: 什么时候调用deduct_roomcard? A: 必须在第一小局结算时调用,不是开战时。
Q: o_room和o_desk的关系?
A: o_room是房间对象(框架管理),o_desk是游戏桌对象(子游戏管理),通过o_room.o_desk关联。
下一步:阅读01-Export接口说明了解8个必需接口的详细实现。