一个基于 Erlang/OTP 的高性能、开箱即用的游戏服务器框架。以斗地主为示例,展示了完整的游戏服务器架构。
Erlang 作为构建游戏服务器(尤其是大型多人在线游戏 MMO)的后端技术,拥有几个非常突出的优势,这些优势恰好解决了游戏服务器的核心痛点。
- 轻量级进程:Erlang 的进程非常轻量(内存占用小,创建销毁快),一个服务器可以轻松管理数百万个并发进程。这完美契合游戏服务器中"每个玩家连接对应一个处理逻辑"的模型。
- 无锁并发:进程间通过消息传递通信,避免了复杂的锁竞争。这意味着在高并发下,服务器性能不会因锁争用而急剧下降,能更平稳地处理玩家峰值。
- "任其崩溃"哲学:Erlang 的核心设计是"让错误局部化"。单个玩家进程崩溃(比如因非法数据)不会影响整个服务器。监督树机制能自动重启失败的进程,确保服务持续可用。而且服务器极少会宕机。
- 热代码升级:可以在不重启服务器的情况下动态更新游戏逻辑代码。这对需要 7x24 小时运行的在线游戏至关重要,可以实现无缝更新、修复 Bug 或添加新活动,避免停机损失。
Erlang 被设计用于电信系统,对延迟有严格要求。其调度器和垃圾回收机制针对低延迟场景进行了优化,能保证在高负载下依然有可预测的响应时间。这对于需要快速响应的实时战斗、交互类游戏非常重要。
Erlang 语言和 OTP 框架原生支持分布式计算。你可以轻松地将游戏世界的不同区域或功能(如聊天、战斗、经济系统)部署到多台服务器上,它们能像在同一台机器上一样透明地通信。天然支持水平扩展,可以通过增加节点来应对玩家数量的增长。
Erlang 的异步非阻塞 I/O 模型能高效处理海量 TCP 连接(如玩家连接)。对于需要长连接的实时游戏来说,这是基础保障。
OTP 提供了经过工业验证的构建块,如 gen_server(用于状态管理)、gen_statem(用于状态机,非常适合游戏逻辑如技能状态)、supervisor(监督进程树)等。这大大减少了从零开始构建复杂、可靠系统的难度。
- 状态管理:Erlang 进程天然就是状态机,非常适合管理玩家状态、游戏房间状态等。
- 消息传递:游戏中的各种事件(玩家移动、攻击、聊天)可以封装成消息,在不同进程间传递,架构清晰,易于调试和扩展。
Erlang 是构建高并发、高可用、分布式的游戏服务器(特别是后端逻辑、网关、匹配系统等)的顶级选择之一。它用"轻量级进程 + 消息传递 + 容错"的哲学,优雅地解决了游戏服务器面临的并发、稳定性和扩展性难题。如果你正在开发一款需要处理海量实时玩家交互的在线游戏,尤其是对服务器稳定性和扩展性要求极高的项目,Erlang 绝对是一个值得深入评估的技术选项。
Erlang 的核心是轻量级进程(Actor 模型):
| 对比项 | C++ / Java | Erlang |
|---|---|---|
| 并发单位 | 线程(贵,要池化) | 进程(便宜,随便开) |
| 单节点支撑 | 数千线程 | 几十万 ~ 上百万进程 |
| 资源映射 | 玩家 → 线程池中的一个任务 | 玩家 → 一个独立进程 |
| 状态隔离 | 共享内存,需加锁 | 完全隔离,无共享内存 |
为什么 Actor 模型适合游戏?
传统方案的问题:
┌─────────────────────────────────────────────────────┐
│ 共享内存 + 锁 │
│ ┌─────┐ lock ┌─────────┐ │
│ │线程A│ ────────> │ 玩家数据 │ <─────── 线程B │
│ └─────┘ unlock └─────────┘ unlock │
│ │
│ 问题: │
│ 1. 死锁风险 - 多个锁的获取顺序不一致 │
│ 2. 竞态条件 - 锁粒度难以把握 │
│ 3. 性能瓶颈 - 锁竞争导致串行化 │
│ 4. 调试困难 - 并发 bug 难以复现 │
└─────────────────────────────────────────────────────┘
Erlang Actor 方案:
┌─────────────────────────────────────────────────────┐
│ 独立进程 + 消息传递 │
│ ┌─────────┐ ┌─────────┐ │
│ │ PlayerA │ ─── 消息 ───────> │ PlayerB │ │
│ │ (进程1) │ │ (进程2) │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 状态A │ │ 状态B │ │
│ │ (独立) │ │ (独立) │ │
│ └─────────┘ └─────────┘ │
│ │
│ 优势: │
│ 1. 无锁 - 每个玩家状态独立,无竞争 │
│ 2. 隔离 - 单个玩家崩溃不影响其他玩家 │
│ 3. 简单 - 消息传递模型直观易懂 │
│ 4. 可扩展 - 进程可分布到多台机器 │
└─────────────────────────────────────────────────────┘
游戏中的自然映射:
玩家登录 → 创建一个 player 进程
进入房间 → 创建一个 game_room 进程
建立连接 → 创建一个 gateway 进程
聊天频道 → 创建一个 channel 进程
每个实体 = 一个进程,状态天然隔离,无需操心并发问题!
BEAM 虚拟机的抢占式调度:
| 特性 | 说明 | 游戏场景 |
|---|---|---|
| 抢占式调度 | 类似 OS,每个进程执行一小段就切走 | 不会被某个大任务卡死 |
| 公平调度 | 所有进程平等获得 CPU 时间 | 延迟稳定可预测 |
| 优先级支持 | 可设置进程优先级 | 关键逻辑优先执行 |
非常适合:
┌─────────────────────────────────────────────────────┐
│ 战斗 Tick → 每帧计算,延迟稳定 │
│ 技能计算 → 复杂公式,不会卡住其他玩家 │
│ 定时器 → buff / cd 精确触发 │
│ AI 决策 → 复杂寻路,不影响主线程 │
└─────────────────────────────────────────────────────┘
性能数据:
| 指标 | Erlang 表现 | 说明 |
|---|---|---|
| 进程创建 | 100万/秒 | 轻量级进程,创建开销极小 |
| 进程内存 | ~2KB/进程 | 单机可支撑数十万玩家 |
| 消息延迟 | 微秒级 | 进程间通信无需序列化 |
| GC 停顿 | 进程级 | 单进程 GC,无全局停顿 |
GC 对比:
C++/Java GC (Stop-The-World):
┌─────────────────────────────────────────────────────┐
│ 时间线 ─────────────────────────────────────────> │
│ ┌──┐ ┌──┐ ┌──┐ ┌─────────────────┐ ┌──┐ │
│ │工│ │工│ │工│ │ Stop-The-World │ │工│ │
│ │作│ │作│ │作│ │ GC 暂停 │ │作│ │
│ └──┘ └──┘ └──┘ └─────────────────┘ └──┘ │
│ ↑ │
│ 游戏卡顿! │
└─────────────────────────────────────────────────────┘
Erlang GC (进程级):
┌─────────────────────────────────────────────────────┐
│ 时间线 ─────────────────────────────────────────> │
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
│ │P1│ │P2│ │P3│ │P1│ │P4│ │P2│ │P5│ │P3│ │P1│ │
│ │GC│ │ │ │ │ │GC│ │ │ │GC│ │ │ │GC│ │GC│ │
│ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ │
│ ↑ │
│ 各进程独立 GC,互不影响,游戏流畅 │
└─────────────────────────────────────────────────────┘
Erlang 的哲学:Let it crash(让它崩) 而且erlang游戏服极少会宕机
传统思维:
┌─────────────────────────────────────────────────────┐
│ try { │
│ // 各种异常处理 │
│ // 生怕程序崩溃 │
│ } catch (Exception e) { │
│ // 记录日志,尝试恢复 │
│ } │
│ │
│ 问题:异常处理代码比业务代码还多! │
└─────────────────────────────────────────────────────┘
Erlang 思维:
┌─────────────────────────────────────────────────────┐
│ %% 正常写业务逻辑,不用 try-catch │
│ %% 崩了就崩了,Supervisor 会自动重启 │
│ │
│ handle_info({attack, Target}, State) -> │
│ %% 如果 Target 不存在,会崩溃 │
│ %% 但没关系,Supervisor 会重启这个玩家进程 │
│ %% 其他玩家完全不受影响! │
│ NewState = do_attack(Target, State), │
│ {noreply, NewState}. │
└─────────────────────────────────────────────────────┘
Supervisor 监控树架构:
┌──────────────┐
│ agw_sup │ ← 顶层监控
│ (supervisor)│
└──────┬───────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ gatewaySup │ │ system_sup │ │ 其他服务 │
│ (supervisor) │ │ (supervisor) │ │ (supervisor) │
└──────┬───────┘ └──────┬───────┘ └──────────────┘
│ │
┌──────┴──────┐ ┌──────┴──────┐
▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│gateway│ │gateway│ │player │ │game │
│ Tcp1 │ │ Ws1 │ │ proc │ │server │
└───────┘ └───────┘ └───────┘ └───────┘
│ │
▼ ▼
崩溃! 正常运行
│
└──> Supervisor 自动重启
其他玩家完全不受影响!
游戏里的好处:
| 场景 | 传统方案 | Erlang 方案 |
|---|---|---|
| 单个玩家异常 | 可能拖垮全服 | 只影响自己,自动恢复 |
| 模块有 bug | 整个服务重启 | 局部恢复,热修复 |
| 热更新时出错 | 服务挂掉 | 回滚到旧版本继续运行 |
容错策略配置:
%% supervisor 配置
SupFlags = #{
strategy => one_for_one, %% 一个进程崩溃,只重启它自己
intensity => 10, %% 60秒内最多重启10次
period => 60 %% 超过则整个 supervisor 崩溃
},MMO 神器:不停服更新版本
传统方案:
┌─────────────────────────────────────────────────────┐
│ 1. 编译新版本 │
│ 2. 停止服务器 ──> 玩家掉线 │
│ 3. 部署新版本 │
│ 4. 启动服务器 ──> 玩家重连 │
│ 5. 等待玩家恢复 │
│ │
│ 停机时间: 数分钟 ~ 数十分钟 │
│ 玩家体验: 差,每次更新都掉线 │
└─────────────────────────────────────────────────────┘
Erlang 热更新:
┌─────────────────────────────────────────────────────┐
│ 1. 编译新模块 │
│ 2. l(player). %% 热加载 │
│ 3. 完成! │
│ │
│ 玩家无感知,游戏继续运行 │
│ 停机时间: 0 │
│ 玩家体验: 丝滑,完全无感 │
└─────────────────────────────────────────────────────┘
热更新的核心能力:
| 能力 | 说明 |
|---|---|
| 不中断服务 | 代码更新期间服务持续运行 |
| 状态不丢失 | 玩家在线状态、游戏进度完整保留 |
| 新旧共存 | 新旧代码可以共存一段时间,平滑过渡 |
| 修 bug 不踢人 | 紧急修复无需踢掉在线玩家 |
实际代码:
%% 开发模式下,eSync 自动监听文件变化
%% 修改 player.erl 并保存后,自动:
1> c(player).
{ok, player}
%% 生产环境手动热更新
1> l(player).
{module, player}
%% 所有正在运行的玩家进程,下次消息处理时自动使用新代码Erlang 节点之间天然互联:
单节点 → 多节点扩展:
┌─────────────────┐ ┌─────────────────┐
│ Node A │ │ Node B │
│ (网关节点) │ │ (战斗节点) │
│ │ │ │
│ ┌─────────┐ │ │ ┌─────────┐ │
│ │gateway │ │ │ │game_room│ │
│ │process │<───┼─────┼────│ process │ │
│ └─────────┘ │ │ └─────────┘ │
│ │ │ │
│ 天然 RPC: │ │ │
│ gen_server:call│─────┼───> 正常工作 │
│ 完全透明! │ │ │
└─────────────────┘ └─────────────────┘
│ │
└───────────┬───────────┘
▼
┌──────────────┐
│ Node C │
│ (中心节点) │
│ │
│ 全局注册表 │
│ 全局锁 │
│ 跨服匹配 │
└──────────────┘
游戏架构天然拆分:
| 节点类型 | 职责 | 特点 |
|---|---|---|
| 网关节点 | 处理连接、协议解析 | 可水平扩展 |
| 战斗节点 | 游戏逻辑计算 | CPU 密集 |
| 场景节点 | 地图、AOI | 状态密集 |
| 中心节点 | 全局服务、匹配 | 协调中心 |
分布式代码示例:
%% 单节点调用
gen_server:call({player, PlayerId}, get_info).
%% 跨节点调用 - 只需指定节点名
gen_server:call({player, PlayerId}, 'game_node_1@192.168.1.10', get_info).
%% 或者使用全局注册
gen_server:call({global, {player, PlayerId}}, get_info).
%% 节点连接
net_kernel:connect('game_node_2@192.168.1.11').TCP 连接 ≈ 一个进程,写起来极其直观:
传统方案 (C++/Java):
┌─────────────────────────────────────────────────────┐
│ epoll / IOCP / reactor 模式 │
│ │
│ 1. 创建 socket │
│ 2. 注册到 epoll/IOCP │
│ 3. 事件循环中处理读写 │
│ 4. 状态机管理连接状态 │
│ 5. 处理半包、粘包 │
│ 6. 线程池处理业务逻辑 │
│ ... │
│ │
│ 代码复杂,容易出错 │
└─────────────────────────────────────────────────────┘
Erlang 方案:
┌─────────────────────────────────────────────────────┐
│ {ok, Socket} = gen_tcp:accept(ListenSocket), │
│ spawn(fun() -> handle_client(Socket) end), │
│ │
│ handle_client(Socket) -> │
│ receive │
│ {tcp, Socket, Data} -> │
│ %% 直接处理,不用管 epoll/IOCP │
│ handle_data(Data), │
│ handle_client(Socket); │
│ {tcp_closed, Socket} -> │
│ %% 连接关闭,进程自动结束 │
│ ok │
│ end. │
│ │
│ 简单直观,一个连接一个进程 │
└─────────────────────────────────────────────────────┘
特别适合:
| 场景 | 说明 |
|---|---|
| MMO | 大量长连接,每个玩家独立进程 |
| 聊天系统 | 实时消息推送,WebSocket 天然支持 |
| 实时对战 | 低延迟要求,进程间通信微秒级 |
| 推送服务 | 百万连接,单机支撑 |
| 方面 | 传统方案 | Erlang 方案 |
|---|---|---|
| 代码量 | 大量样板代码 | OTP 行为模式封装 |
| 调试 | printf / gdb | 内置调试器、追踪器 |
| 测试 | 单元测试框架 | EUnit、Common Test |
| 文档 | 需要额外维护 | Edoc 从代码生成 |
| 重构 | IDE 支持 | 编译器检查 |
| 项目 | 类型 | 说明 |
|---|---|---|
| 即时通讯 | 2百万连接/服务器,被 Facebook 190亿收购 | |
| RabbitMQ | 消息队列 | 最流行的开源消息中间件之一 |
| Discord | 游戏语音 | 500万并发用户,后来部分迁移到 Rust |
| League of Legends | 游戏 | 聊天服务器使用 Erlang |
| Call of Duty | 游戏 | 匹配服务器使用 Erlang |
| 魔兽世界 | 游戏 | 早期聊天和部分后台系统 |
| 皇室战争 | 游戏 | 后端服务器大量使用 Erlang |
状态管理: Erlang 进程天然就是状态机,非常适合管理玩家状态、游戏房间状态等。
消息传递: 游戏中的各种事件(玩家移动、攻击、聊天)可以封装成消息,在不同进程间传递,架构清晰,易于调试和扩展。
强大的 OTP 框架: OTP 提供了经过工业验证的构建块:
| OTP 组件 | 用途 | 游戏场景 |
|---|---|---|
gen_server |
状态管理 | 玩家进程、房间进程 |
gen_statem |
状态机 | 技能状态、战斗流程 |
supervisor |
监督树 | 进程崩溃自动恢复 |
application |
应用管理 | 模块化部署 |
| 场景 | 推荐度 | 说明 |
|---|---|---|
| MMORPG | ⭐⭐⭐⭐⭐ | 大量玩家独立状态,完美匹配 |
| 卡牌/棋牌 | ⭐⭐⭐⭐⭐ | 本框架示例,斗地主完整实现 |
| MOBA/FPS | ⭐⭐⭐⭐ | 匹配、聊天用 Erlang,战斗用 C++ |
| 休闲游戏 | ⭐⭐⭐⭐⭐ | 高并发、低延迟需求 |
| 实时对战 | ⭐⭐⭐⭐⭐ | WebSocket 支持良好,长连接天花板 |
| SLG 策略 | ⭐⭐⭐⭐⭐ | 复杂状态管理,热更新需求 |
| IM 聊天 | ⭐⭐⭐⭐⭐ | 长连接 + 高并发,天然优势 |
| 挑战 | 说明 | 应对策略 |
|---|---|---|
| 学习曲线 | 函数式编程和 Erlang 特有语法需要时间适应 | 但是本框架提供完整示例,边学边用,开箱即可用,简单上手,完善的工具依赖 |
| 生态系统 | 相比 C++/Java/Go,游戏特定领域库较少 | 与其他技术栈配合,Erlang 做核心逻辑 |
| 人才储备 | 精通 Erlang 的开发者相对较少 | 文档完善,降低上手门槛 |
┌─────────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ Web Client (React) │ Mobile │ Desktop │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 网关层 (Gateway) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ gatewayTcp │ │ gatewayWs │ │ gatewayHttp │ │
│ │ (TCP) │ │ (WebSocket) │ │ (HTTP) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ gatewaySup │ 监控树 │
│ └─────────────┘ │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 玩家进程层 │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Player │ │ Player │ │ Player │ 玩家进程层 │
│ │ Process │ │ Process │ │ Process │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Game Server 游戏逻辑层 │ │
│ │ room_manager │ game_server │ ai_agent │ │
│ └───────────────────┬─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 数据持久化层 │ │
│ │ score_system │ eArango (ArangoDB) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ agw 框架 │
└─────────────────────────────────────────────────────────────────┘
客户端连接流程:
客户端 Gateway Player GameServer
│ │ │ │
│──── TCP/WS 连接 ────────>│ │ │
│ │ │ │
│──── cs_handshake ───────>│ │ │
│<─── sc_handshake ────────│ │ │
│ │ │ │
│──── cs_login ───────────>│ │ │
│ │── start_link ──────────>│ │
│ │ │── join_game ───────────>│
│<─── sc_login ────────────│ │ │
│ │ │ │
│ │<────── 监控 ────────────│ │
│ │ │<────── 监控 ────────────│
│ │ │ │
│──── 游戏消息 ────────────>│─── eCliMsg ────────────>│─── 游戏逻辑 ───────────>│
│<─── 游戏响应 ────────────│<── eSendCli ────────────│<── 游戏结果 ────────────│
断线重连流程:
客户端A GatewayA Player 新客户端A
│ │ │ │
│──── 连接断开 ───────────>│ │ │
│ │─── DOWN 信号 ──────────>│ │
│ │ │ │
│ │ │── 启动 3 分钟定时器 ────>│
│ │ │ │
│ │ │<──── 新连接 ────────────│
│ │ │<──── cs_login ──────────│
│ │ │ │
│ │ │── 取消定时器 │
│ │ │── 更新 GatewayPid ─────>│
│ │ │ │
│ │ │── 恢复游戏状态 ─────────>│
│ │ │ │
│ │ │<─── 重连成功 ───────────│
挤下线流程:
老客户端 GatewayA Player 新客户端
│ │ │ │
│ │ │<──── cs_login ──────────│
│ │ │ │
│ │<── eKickByRelogin ─────│ │
│<─── 连接关闭 ────────────│ │ │
│ │ │ │
│ │ │── 更新 GatewayPid ─────>│
│ │ │ │
│ │ │<─── 登录成功 ───────────│
agw/
├── config/ # 配置文件
│ ├── devopsGame.cfg # 游戏服务器配置
│ ├── devopsCenter.cfg # 中心服务器配置
│ ├── devopsCross.cfg # 跨服服务器配置
│ ├── sys.config # OTP 系统配置
│ └── vm.args # Erlang VM 参数
│
├── include/ # 头文件
│ ├── common.hrl # 通用宏定义
│ ├── game.hrl # 游戏记录定义
│ ├── gateway.hrl # 网关记录定义
│ ├── protoMsg.hrl # 协议消息定义
│ └── server.hrl # 服务器定义
│
├── proto/ # 协议定义
│ ├── 0_common.mpdf # 公共类型
│ ├── 1_login.mpdf # 登录协议
│ └── 2_role.mpdf # 角色协议
│
├── src/ # 源代码
│ ├── gateway/ # 网关模块
│ │ ├── gateway.erl # 网关启动入口
│ │ ├── gatewayTcp.erl # TCP 网关
│ │ ├── gatewayWs.erl # WebSocket 网关
│ │ ├── gatewayHttp.erl # HTTP 网关
│ │ └── gatewaySup.erl # 网关监控树
│ │
│ ├── player/ # 玩家模块
│ │ └── player.erl # 玩家进程
│ │
│ ├── handler/ # 消息处理器
│ │ ├── loginHer.erl # 登录消息处理
│ │ └── roleHer.erl # 角色消息处理
│ │
│ ├── game/ # 游戏逻辑
│ │ ├── game_server.erl # 游戏房间服务
│ │ ├── room_manager.erl # 房间管理器
│ │ ├── score_system.erl # 积分系统
│ │ ├── card/ # 牌型判断
│ │ │ ├── cards.erl # 发牌洗牌
│ │ │ ├── card_rules.erl # 牌型规则
│ │ │ └── card_checker.erl# 出牌检测
│ │ └── ai/ # AI 托管
│ │ ├── ai_agent.erl # AI 代理
│ │ ├── ai_logic.erl # AI 逻辑
│ │ └── ai_supervisor.erl
│ │
│ ├── proto/ # 协议编解码
│ │ └── protoMsg.erl # 协议消息处理
│ │
│ └── server/ # 服务器管理
│ └── devops.erl # 运维配置加载相关
│
├── priv/ # 静态资源
│ ├── static/ # 前端静态文件
│
├── cli/ # 前端客户端
│ └── src/ # React 游戏客户端
│
├── rebar.config # Rebar3 配置
├── Emakefile # Emake 配置
└── start.bat # Windows 启动脚本
| 库名 | 类型 | 功能说明 |
|---|---|---|
| eNet | 网络 | TCP/UDP 高并发网络库 |
| eWSrv | 网络 | WebSocket/Http 服务器 |
| eArango | 存储 | ArangoDB 数据库驱动 |
| ePtDirty | 存储 | 脏数据自动检测与维护 |
| eGLock | 并发 | 全局锁用于避免大量需要单进程顺序处理的数据在单进程处理瓶颈 |
| gTimer | 定时 | 全局定时器用于管理全服的定时器 |
| ranks | 游戏 | 排行榜系统 |
| eAccess | 安全 | 敏感词过滤 |
| eGbh | 行为 | 通用行为模式 |
| eFaw | 并行 | 工厂/工人模式用于处理繁重也与玩家状态不强相关的业务 |
| eSync | 开发 | 代码自动编译,热更新配置表自动加载 |
| genProto | 开发 | 协议生成工具支持生成Erlang/Lua/C#/Ts语言 |
| genCfgs | 开发 | 配置导表工具支持xlsx文件转换为erlang,elixir,lua,json,xml |
| eLog | 基础 | 简单易用高效的日志系统 |
| eUtils | 基础 | 通用工具函数 |
| jiffy | 基础 | JSON 编解码 |
| 其他 | 基础 | 本github还有其他一些游戏相关的工具可以多多探索 |
- Erlang/OTP 27+
- Rebar3/eMake (构建工具)
- Python 3.7+ (配置导表工具) release版本不需要安装python
- Node.js 18+ (前端/GM工具) release版本不需要安装node
# 编译项目
rebar3 compile
本项目依赖库有一些nif 如果要编译的话依赖于c/c++编译环境 需要安装gcc或者c++编译器, windows平台可以安装Visual Studio的C++编译工具,然后需要添加环境变量 参见 eUtils库的'erlangVS环境编译nifdll.md' 这个文档
然后还会依赖本github下 eNpc库 需要先将eNpc库rebar3 escripte 然后将生成的执行文件 eNpc eNpc.cmd 复制到elrang安装目录下的bin目录下 以便编译eNpc依赖库的nif文件
如果闲nif编译麻烦 可以将有nif编译的库的rebar.config文件中带"eNpc compile" 这样的都注释掉
% {pre_hooks,
% [{"", compile, "eNpc compile"}]}.
%
% {post_hooks,
% [{"", clean, "eNpc clean"}]}.
# 安装前端依赖
cd cli && npm install
# 启动开发服务器
cd cli && npm run dev
# 打包生产版本
cd cli && npm run build
# 然后将 dist 文件复制到 priv/static 目录下
# 访问 http://127.0.0.1:9200 会自动加载游戏页面
# GM 工具依赖
gmSrv:start(Port).# Windows
start.bat
# Linux
./config/startGame.sh| 服务 | 地址 |
|---|---|
| TCP 端口 | 9100 |
| WebSocket | ws://127.0.0.1:9300 |
| HTTP API | http://127.0.0.1:9200 |
| 游戏客户端 | http://127.0.0.1:9200 |
%% 服务器类型 [game | center | cross]
{type, game}.
%% 服务器 ID
{server_id, 1}.
%% 网络端口
{port, 9100}. %% TCP
{ws_port, 9300}. %% WebSocket
{web_port, 9200}. %% HTTP{eArango, [
{sisDb, {
[{baseUrl, <<"http://127.0.0.1:8529">>},
{dbName, <<"sis">>},
{user, <<"root">>},
{password, <<"******">>},
{poolSize, 128}],
[{reconnect, true},
{backlogSize, 1024}]
}}
]}协议使用自定义 .mpdf 格式,位于 proto/ 目录:
%% 登录请求
cs_login {
string name;
}
%% 登录响应
sc_login {
int32 result;
string playerId;
playerInfo player;
}
%% 出牌请求
cs_play {
repeated card cards;
}
%% 出牌响应
sc_player_played {
int32 playerIdx;
repeated card cards;
}生成协议代码:
genProto.bat # Windows具体参见 genCfgs 工具说明文档。
欢迎提交 Issue 和 Pull Request!
如有问题,请提交 Issue 或联系作者。