ioGame 源码完全开放、最新文档阅读完全开放;使用完全自由、免费(遵守开源协议)。
ioGame 是一个轻量级的网络编程框架,适用于网络游戏服务器、物联网、内部系统及各种需要长连接的场景。
文档与更新日志
https://github.com/iohao/ioGame/releases/tag/21.8
版本更新汇总
-
[light-game-room] #278 桌游类、房间类游戏的扩展模块,简化与规范化房间管理相关的、开始游戏流程相关的、玩法操作相关的相关扩展
-
[core] #290 新增广播文档构建器,简化生成广播对接文档
-
[示例集合整理] 将 SimpleExample(文档中所有功能点的示例)、SpringBootExample(综合示例)、ioGameWeb2Game(web 转游戏 – 示例理解篇)、fxglSimpleGame(移动同步 FXGL + netty)合并成一个示例项目。
[core]
#290 新增广播文档构建器,简化生成广播对接文档
下面是使用示例
public class MyLogicServer extends AbstractBrokerClientStartup { @Override public BarSkeleton createBarSkeleton() { // 业务框架构建器 BarSkeletonBuilder builder = ... // 错误码、广播、推送对接文档生成 extractedDco(builder); return builder.build(); } private void extractedDco(BarSkeletonBuilder builder) { // 错误码 Arrays.stream(GameCode.values()).forEach(builder::addMsgExceptionInfo); // UserCmd builder.addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.enterSquare)) .setDataClass(SquarePlayer.class) .setDescription("新玩家加入房间,给房间内的其他玩家广播") ).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.move)) .setDataClass(SquarePlayerMove.class) .setDescription("其他玩家的移动") ).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.offline)) .setDataClass(LongValue.class) .setDescription("有玩家下线了。userId") ); // room builder.addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.roomUpdateBroadcast)) .setDataClass(FightRoomNotice.class) .setDescription("房间更新通知") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.playerEnterRoomBroadcast)) .setDataClass(FightPlayer.class) .setDescription("有新玩家加入房间") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.enterRoom)) .setDataClass(FightEnterRoom.class) .setDescription("玩家自己进入房间") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.dissolveRoomBroadcast)) .setDescription("解散房间") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.quitRoom)) .setDataClass(LongValue.class) .setDescription("有玩家退出房间了。userId") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.ready)) .setDataClass(PlayerReady.class) .setDescription("有玩家准备或取消准备了") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.nextRoundBroadcast)) .setDataClass(IntValue.class) .setDescription("对局开始,通知玩家开始选择。round 当前对局数") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.operationBroadcast)) .setDataClass(LongValue.class) .setDescription("通知其他玩家,有玩家做了选择。userId") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.littleSettleBroadcast)) .setDataClassList(FightRoundPlayerScore.class) .setDescription("广播玩家对局分数") ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.gameOverBroadcast)) .setDescription("游戏结束") ); } }
其他扩展阅读
下面是生成后的对接文档预览
==================== 游戏文档格式说明 ==================== https://www.yuque.com/iohao/game/irth38#cJLdC ==================== FightHallAction 大厅(类似地图) ==================== 路由: 1 - 1 --- 【登录】 --- 【FightHallAction:67】【loginVerify】 方法参数: LoginVerify loginVerify 登录验证 方法返回值: UserInfo 玩家信息 路由: 1 - 2 --- 【进入大厅】 --- 【FightHallAction:95】【enterSquare】 方法参数: EnterSquare enterSquare 进入大厅 方法返回值: ByteValueList<SquarePlayer> 所有玩家 广播推送: SquarePlayer 新玩家加入房间,给房间内的其他玩家广播 路由: 1 - 4 --- 【玩家移动】 --- 【FightHallAction:131】【move】 方法参数: SquarePlayerMove squarePlayerMove 玩家移动 方法返回值: void 广播推送: SquarePlayerMove 其他玩家的移动 路由: 1 - 5 --- 【玩家下线】 --- 【FightHallAction:155】【offline】 方法返回值: void 广播推送: LongValue 有玩家下线了。userId ==================== FightRoomAction ==================== 路由: 2 - 1 --- 【玩家创建新房间】 --- 【FightRoomAction:63】【createRoom】 方法返回值: void 路由: 2 - 2 --- 【玩家进入房间】 --- 【FightRoomAction:96】【enterRoom】 方法参数: LongValue roomId 房间号 方法返回值: void 房间信息 广播推送: FightEnterRoom 玩家自己进入房间 路由: 2 - 3 --- 【玩家退出房间】 --- 【FightRoomAction:120】【quitRoom】 方法返回值: void 广播推送: LongValue 有玩家退出房间了。userId 路由: 2 - 4 --- 【玩家准备】 --- 【FightRoomAction:146】【ready】 方法参数: BoolValue ready true 表示准备,false 则是取消准备 方法返回值: void 广播推送: PlayerReady 有玩家准备或取消准备了 路由: 2 - 5 --- 【房间列表】 --- 【FightRoomAction:222】【listRoom】 方法返回值: ByteValueList<FightRoomNotice> 房间列表 路由: 2 - 6 --- 【玩家在游戏中的操作】 --- 【FightRoomAction:191】【operation】 方法参数: FightOperationCommand command 玩家操作数据 方法返回值: void 路由: 2 - 7 --- 【开始游戏】 --- 【FightRoomAction:162】【startGame】 方法返回值: void ==================== 其它广播推送 ==================== 路由: 2 - 51 --- 广播推送: FightRoomNotice (房间更新通知) 路由: 2 - 50 --- 广播推送: FightPlayer (有新玩家加入房间) 路由: 2 - 52 --- 广播推送: IntValue (对局开始,通知玩家开始选择。round 当前对局数) 路由: 2 - 53 --- 广播推送: LongValue (通知其他玩家,有玩家做了选择。userId) 路由: 2 - 56 --- 广播推送: none (解散房间) 路由: 2 - 54 --- 广播推送: ByteValueList<FightRoundPlayerScore> (广播玩家对局分数) 路由: 2 - 55 --- 广播推送: none (游戏结束) ==================== 错误码 ==================== -1008 : 绑定的游戏逻辑服不存在 -1007 : 强制玩家下线 -1006 : 数据不存在 -1005 : class 不存在 -1004 : 请先登录 -1003 : 心跳超时相关 -1002 : 路由错误 -1001 : 参数验错误 -1000 : 系统其它错误 1 : 玩家在房间里 3 : 房间不存在 4 : 非法操作 6 : 开始游戏需要的最小人数不足 7 : 请等待其他玩家准备 8 : 房间空间不足,人数已满
[light-game-room]
room 模块相关文档 – room 桌游、房间类 (yuque.com)
#278 桌游类、房间类游戏的扩展模块,简化与规范化房间管理相关的、开始游戏流程相关的、玩法操作相关的相关扩展
light-game-room 房间,是 ioGame 提供的一个轻量小部件 – 可按需选择的模块。
light-game-room + 领域事件 + 内置 Kit = 轻松搞定桌游类游戏
该模块是桌游类、房间类游戏的解决方案。比较适合桌游类、房间类的游戏基础搭建,基于该模型可以做一些如,炉石传说、三国杀、斗地主、麻将 …等类似的桌游。或者说只要是房间类的游戏,该模型都适用。比如,CS、泡泡堂、飞行棋、坦克大战 …等。
如果你计划做一些桌游类的游戏,那么推荐你基于该模块做扩展。该模块遵循面向对象的设计原则,没有强耦合,可扩展性强。且帮助开发者屏蔽了很多重复性的工作,并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。
主要解决的问题与职责
桌游、房间类的游戏在功能职责上可以分为 3 大类,分别是
-
房间管理相关的
-
管理着所有的房间、查询房间列表、房间的添加、房间的删除、房间与玩家之间的关联、房间查找(通过 roomId 查找、通过 userId 查找)。
-
-
开始游戏流程相关的
-
通常桌游、房间类的游戏都有一些固定的流程,如创建房间、玩家进入房间、玩家退出房间、解散房间、玩家准备、开始游戏 …等。
-
开始游戏时,需要做开始前的验证,如房间内的玩家是否符足够 …等,当一切符合业务时,才是真正的开始游戏。
-
-
玩法操作相关的
-
游戏开始后,由于不同游戏之间的具体操作是不相同的。如坦克的射击,炉石的战前选牌、出牌,麻将的吃、碰、杠、过、胡,回合制游戏的普攻、防御、技能 …等。
-
由于玩法操作的不同,所以我们的玩法操作需要是可扩展的,并用于处理具体的玩法操作。同时这种扩展方式更符合单一职责,使得我们后续的扩展与维护成本更低。
-
以上功能职责(房间管理相关、流程相关、玩法操作相关)属于相对通用的功能。如果每款游戏都重复的做这些工作,除了枯燥之外,还将浪费巨大的人力成本。
而当前模块则能很好的帮助开发者屏蔽这些重复性的工作,并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。更重要的是有相关文档,将来当你的团队有新进成员时,可以快速的上手。
room 实战简介
文档中,我们基于该 room 模块做一个实战示例,该示例整体比较简单,多名玩家在房间里猜拳(石头、剪刀、布)得分。实战示例包括了前后端,前端使用 FXGL 引擎,这样开发者在学习时,只需 JDK 环境就可以了,而不需要安装更多的环境。启动游戏后玩家会将加入大厅(类似地图),多名玩家相互可见,并且玩家可以在大厅内移动。
下面是基于 room 模块的扩展实战 demo
[示例集合整理]
将 SimpleExample(文档中所有功能点的示例)、SpringBootExample(综合示例)、ioGameWeb2Game(web 转游戏 – 示例理解篇)、fxglSimpleGame(移动同步 FXGL + netty)合并成一个示例项目。
github | gitee |
---|---|
ioGame 示例集合 | ioGame 示例集合 |
ioGame 使用趋势数据
关注 ioGame 的游戏服务器开发者持续增多,2022-09 ~ 至今各月的统计数据;
这里的统计信息是关于开发者关注 ioGame 框架相关的,从统计数据中可以看出,由于 ioGame 上手简单,功能强大等优点,得到了众多开发者的关注。如果你想知道 ioGame 有没有人在使用,可以先到这里看下统计数据、开发者的评价与讨论。
https://www.yuque.com/iohao/game/gpxk93#TwVa8
这里展示了每月的统计数据,统计数据来源于语雀后台,这些数据都是真实的、客观存在的、活的。
通过统计数据,我们可以看到每天会有很多开发者在访问 ioGame 的在线文档,并且这些统计数据不是来源于口嗨的,也不是主观创造的。
所以,还在犹豫要不要使用 ioGame 的开发者们,更应该讨论的是 “为什么这些开发者会选择使用 ioGame”,而不是 ioGame 有没有人在使用的问题。
ioGame 网络游戏服务器框架简介
- 无锁异步化、事件驱动的架构设计;轻量级,无需依赖任何第三方中间件或数据库就能支持集群、分布式
- 通过 ioGame 可以很容易的搭建出一个集群无中心节点、集群自动化、多进程的分布式游戏服务器
- 包体小、启动快、内存占用少、更加的节约、无需配置文件、提供了优雅的路由访问权限控制
- 可同时支持多种连接方式:WS、UDP、TCP… 等;框架已支持全链路调用日志跟踪特性
- 让开发者用一套业务代码,能轻松切换和扩展不同的通信协议:Protobuf、JSON
- 近原生的性能;业务框架在单线程中平均每秒可以执行 1152 万次业务逻辑
- 代码即联调文档、JSR380 验证、断言 + 异常机制 = 更少的维护成本
- 框架具备智能的同进程亲和性;开发中,业务代码可定位与跳转
- 架构部署灵活性与多样性:既可相互独立,又可相互融合
- 可同时与同类型的多个游戏逻辑服通信并得到数据
- 逻辑服之间可相互跨进程、跨机器进行通信
- 支持玩家对游戏逻辑服进行动态绑定
- 能与任何其他框架做融合共存
- 对 webMVC 开发者友好
- 无 spring 强依赖
- 零学习成本
- javaSE
ioGame 的组成
ioGame 由 [网络通信框架] 和 [业务框架] 组成
- 网络通信框架:职责是各服务器之间的网络通信
- 业务框架:职责是业务逻辑的处理方式和编写方式
网络通信框架
SOFABolt 是蚂蚁金融服务集团开发的一套基于 Netty 实现的网络通信框架。
- 为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上,而不是过多的纠结于网络底层 NIO 的实现以及处理难以调试的网络问题,Netty 应运而生。
- 为了让中间件开发者能将更多的精力放在产品功能特性实现上,而不是重复地一遍遍制造通信框架的轮子,SOFABolt 应运而生。
Bolt 名字取自迪士尼动画 – 闪***,是一个基于 Netty 最佳实践的轻量、易用、高性能、易扩展的通信框架。
业务框架
如果说 sofa-bolt 是为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上。而业务框架正是解决业务逻辑如何方便实现这一问题上。业务框架是游戏框架的一部分,职责是简化程序员的业务逻辑实现,业务框架使程序员能够快速的开始编写游戏业务。
业务框架对于每个 action (即业务的处理方法) 都是通过 asm 与 Singleton、Flyweight 、Command 等设计模式结合,对 action 的获取上通过 array 来得到,是一种近原生的方式。
单线程中,业务框架平均每秒可以执行 1152 万次业务逻辑。
无锁异步化与事件驱动的架构设计、集群无中心节点、自带负载均衡、分布式支持、可动态增减机器、避免类爆炸的设计;
名称 |
扩展方式 |
职责 |
游戏对外服 |
分布式 |
与玩家连接、交互 |
游戏逻辑服 |
分布式 |
处理具体业务逻辑 |
Broker(游戏网关) |
集群 |
调度和转发任务; |
通过 ioGame 可以使得游戏编程变得简单,下面是一个业务示例
@ActionController(1)public class DemoAction { @ActionMethod(0) public HelloReq here(HelloReq helloReq) { // 业务数据 var newHelloReq = new HelloReq(); newHelloReq.name = helloReq.name + ", I'm here "; return newHelloReq; } // 注意,这个方法只是为了演示而写的;(ioGame21 开始支持) // 效果与上面的方法一样,只不过是用广播(推送)的方式将数据返回给请求方@ActionMethod(0) public void here(HelloReq helloReq, FlowContext flowContext) { // 业务数据 var newHelloReq = new HelloReq(); newHelloReq.name = helloReq.name + ", I'm here "; flowContext.broadcastMe(newHelloReq); } // 跨服调用示例,下面分别展示了同步与异步回调的写法 void testShowInvokeModule(FlowContext flowContext) { /* * 框架为跨服请求提供了同步、异步、异步回调的编码风格 api。(ioGame21 开始支持) */ var cmdInfo = CmdInfo.of(1,0); var yourData = ... 你的请求参数 // 跨服请求(异步回调 - 无阻塞)-- 路由、请求参数、回调。 flowContext.invokeModuleMessageAsync(cmdInfo, yourData, responseMessage -> { var helloReq = responseMessage.getData(HelloReq.class); // --- 此异步回调,具备全链路调用日志跟踪 --- log.info("异步回调 : {}", helloReq); }); // 跨服请求(同步 - 阻塞)-- 路由、请求参数。 ResponseMessage responseMessage = flowContext.invokeModuleMessage(cmdInfo, yourData); var helloReq = responseMessage.getData(HelloReq.class); log.info("同步调用 : {}", helloReq); }}
有了以上信息,游戏开发者可以很快的定位问题。如果没有可视化的信息,开发中会浪费很多时间在前后端的沟通上。问题包括:
- 是否传参问题 (游戏前端说传了)
- 是否响应问题(游戏后端说返回了)
- 业务执行时长问题 (游戏前端说没收到响应, 游戏后端说早就响应了)
其中代码导航可以让开发者快速的跳转到业务类对应代码中,在多人合作的项目中,可以快速的知道业务经过了哪些方法的执行,使得我们可以快速的进行阅读或修改;
框架内置的其他功能
内置多种可选模块,可按需选择,以方便应用开发:
- 领域事件 (轻量级单机最快 MQ — disruptor;通过领域事件模块,可为你的系统实现类似 Guava-EventBus、Spring 事件驱动模型 ApplicationEvent、业务解耦、规避并发、不阻塞主线程… 等,各种浪操作)
- 任务延时器 (将来某个时间可对任务进行执行、暂停、取消等操作,并不是类似 Quartz 的任务调度)
- 多环境切换 (不同运行环境下的配置支持)
- light-jprotobuf (补足 jprotobuf 不能让多个对象在单个 .proto 源文件中生成的需求,并简化 jprotobuf 对源文件的注释)
- 分布式锁 (基于 Redisson 的简单实现)
内置的其他功能:
- 心跳相关
- 用户上线、离线相关的钩子方法
- UserSessions (对所有用户 UserSession 的管理,统计在线用户等)
- UserSession (与 channel 是 1:1 的关系,可取到对应的 userId、channel 等信息。)
- 登录相关(提供重复登录、顶号等相关增强功能)
- 业务参数基础类型 自动装箱、拆箱(解决协议碎片)
适合人群?
- 长期从事 web 内部系统开发人员, 想了解游戏的
- 刚从事游戏开发的
- 未从事过游戏开发,但却对其感兴趣的
- 对设计模式在实践中的应用和 sofa-bolt 有兴趣的学习者
- 可以接受新鲜事物的
- 想放弃祖传代码的
推荐实际编程经验一年以上的人员。
ioGame 提供了丰富的在线高质量使用文档,为你的团队助力,带上你们的小伙伴一起,这样就不用手把手的教了。