Files
youlegames/codes/games/server/docs/guides/framework/00-框架基础概述.md
2026-02-04 23:47:45 +08:00

20 KiB
Raw Blame History

友乐游戏框架基础概述

文档目标:帮助开发者理解友乐游戏平台的整体架构、前后端分离机制、模块化设计和基础开发规范。

📚 目录

  1. 友乐游戏平台架构
  2. 前后端分离部署
  3. 三文件架构规范
  4. 模块加载机制
  5. 数据包协议基础
  6. 开发环境与兼容性

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 关键设计理念

  1. 模块独立性:每个游戏模块独立开发、测试、部署
  2. 接口标准化:所有游戏模块遵循统一的接口规范
  3. 双向解耦框架和游戏通过export/import接口解耦
  4. 状态同步:服务端为权威状态源,客户端被动接收

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/

共享代码编写要求

  1. ES5语法不使用任何ES6+特性箭头函数、class、let/const等
  2. 无副作用:纯函数设计,不依赖全局状态
  3. 环境兼容同时兼容浏览器和Node.js环境
  4. 无依赖不依赖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个接口

  1. get_needroomcard - 创建房间所需房卡
  2. get_asetcount - 游戏局数
  3. get_needroomcard_joinroom - 加入房间所需房卡
  4. makewar - 开战(游戏开始)
  5. get_deskinfo - 获取牌桌信息(断线重连)
  6. get_disbandRoom - 解散房间
  7. player_enter - 玩家进入
  8. 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个接口

  1. check_player - 验证玩家身份和位置
  2. deduct_roomcard - 扣除房卡
  3. save_grade - 保存游戏成绩
  4. 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.jsimport.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)
      ↓
执行游戏逻辑

路由过程

  1. packet.js接收网络数据包解析app字段
  2. youle_app根据route字段找到对应游戏模块
  3. mod_jinxianmahjong根据rpc字段调用对应方法
  4. 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/constES6
let myVar = 123;
const MY_CONST = 456;

// 类语法ES6
class MyClass { }

// 模板字符串ES6
var str = `Hello ${name}`;

// 解构赋值ES6
var {x, y} = point;

// async/awaitES7
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 开发要点

  1. 严格遵循三文件架构mod.js、export.js、import.js
  2. 加载顺序不可变更export → import → 其他文件
  3. 使用ES5语法确保浏览器和Node.js双环境兼容
  4. 理解前后端分离:通过网络通信,不是函数调用
  5. 掌握RPC机制packet.js → app → mod → RPC方法
  6. 注意调用时机deduct_roomcard第一局结算、save_grade大局结束

8. 相关文档


9. 快速上手

9.1 创建新游戏模块的步骤

  1. 创建游戏目录server/games2/yourgame/
  2. 创建mod.js定义模块实例和RPC方法
  3. 创建export.js实现8个必需接口
  4. 创建import.js封装4个框架接口
  5. 创建RPC处理器:处理客户端请求
  6. 创建游戏逻辑:实现具体游戏功能
  7. 测试验证:确保接口正确调用

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个必需接口的详细实现。