这里写自定义目录标题
前置介绍
系列相关内容
当前系列是基于 netty 分布式网络游戏服务器框架开发。
相关系列:
ioGame 简单介绍
ioGame 是一个轻量级的网络编程框架,适用于网络游戏服务器、物联网、内部系统及各种需要长连接的场景;
ioGame 是基于 java netty 的高性能游戏服务器框架,是一款真.轻量级的网络编程框架。
ioGame 源码完全开放、最新文档阅读完全开放;使用完全自由、免费(遵守开源协议)。
你是否想要开发一个高性能、稳定、易用、自带负载均衡、避免类爆炸设计、可跨进程跨机器通信、集群无中心节点、集群自动化、有状态多进程的分布式的网络编程服务器呢?如果是的话,这里向你推荐一个由 java 语言编写的网络编程框架 ioGame。下面将会从多个方面来对框架做一些简单的介绍。
特点:
- 无锁异步化、事件驱动的架构设计;轻量级,无需依赖任何第三方中间件或数据库就能支持集群、分布式
- 通过 ioGame 可以很容易的搭建出一个集群无中心节点、集群自动化、多进程的分布式游戏服务器
- 包体小、启动快、内存占用少、更加的节约、无需配置文件、提供了优雅的路由访问权限控制
- 可同时支持多种连接方式:WS、UDP、TCP…等;框架已支持全链路调用日志跟踪特性
- 让开发者用一套业务代码,能轻松切换和扩展不同的通信协议:Protobuf、JSON
- 近原生的性能;业务框架在单线程中平均每秒可以执行 1152 万次业务逻辑
- 代码即联调文档、JSR380验证、断言 + 异常机制 = 更少的维护成本
- 框架具备智能的同进程亲和性;开发中,业务代码可定位与跳转
- 架构部署灵活性与多样性:既可相互独立,又可相互融合
- 可同时与同类型的多个游戏逻辑服通信并得到数据
- 逻辑服之间可相互跨进程、跨机器进行通信
- 支持玩家对游戏逻辑服进行动态绑定
- 能与任何其他框架做融合共存
- 对 webMVC 开发者友好
- 无 spring 强依赖
- 零学习成本
系列描述
本系列将介绍网络游戏开发中,桌游、房间类的游戏设计及实现流程。使用通用的模型来扩展桌游、房间类游戏的实现。
桌游、房间类的游戏通常指的是,如炉石传说、三国杀、斗地主、麻将 …等类似的桌游。或者说只要是房间类的游戏,该模型都适用。比如,CS、泡泡堂、飞行棋、坦克大战 …等。
桌游、房间类的游戏设计描述
light-game-room 房间,是 ioGame 提供的一个轻量小部件 - 可按需选择的模块。
light-game-room + 领域事件 + 内置 Kit = 轻松搞定桌游类游戏
该模块是桌游类、房间类游戏的解决方案。比较适合桌游类、房间类的游戏基础搭建,基于该模型可以做一些如,炉石传说、三国杀、斗地主、麻将 …等类似的桌游。或者说只要是房间类的游戏,该模型都适用。比如,CS、泡泡堂、飞行棋、坦克大战 …等。
如果你计划做一些桌游类的游戏,那么推荐你基于该模块做扩展。该模块遵循面向对象的设计原则,没有强耦合,可扩展性强。且帮助开发者屏蔽了很多重复性的工作,并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。
达成目标
现在,我们基于该 room 模块做一个实战示例,该示例整体比较简单,多名玩家在同一个房间里猜拳(石头、剪刀、布)得分。
实战示例包括了前后端,前端使用 FXGL 引擎,这样开发者在学习时,只需 JDK 环境就可以了,从而避免安装其他环境。
基于 room 模块的扩展,只需要数百行代码就完成了下述功能:
- 登录
- 玩家进入大厅(地图)
- 玩家可在大厅移动
- 玩家移动时相互可见
- 玩家离开大厅(玩家下线)
- 查询房间列表
- 房间信息实时变更通知(房间内有玩家数量变化,等待中、游戏中 …等状态)
- 玩家创建房间
- 玩家进入房间
- 玩家退出房间
- 解散房间
- 玩家准备
- 开始游戏
- 玩家在房间内的玩法操作
- 游戏对接文档的生成
- 及各种验证(断言 + 异常机制 = 清晰简洁的代码)
正如开头介绍所说的,room 模块很好的帮助开发者屏蔽这些重复性的工作,并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。更重要的是有相关文档,将来当你的团队有新进成员时,可以快速的上手。
目标部分截图展示
启动游戏后玩家会将加入大厅(类似地图),多名玩家相互可见,并且玩家可以在大厅内移动。
图中左边是玩家的可移动区域,右边房间列表信息。
正文开始
上篇,我们介绍了房间管理相关的内容了,本篇将介绍【开始游戏流程 - 相关】相关内容。
开始游戏流程 - 相关
GameFlowService 开始游戏流程相关的。通常桌游、房间类的游戏都有一些固定的流程,如创建房间、玩家进入房间、玩家退出房间、解散房间、玩家准备、开始游戏 …等。
自定义扩展
GameFlowService 接口基本由开发者自己实现,因为游戏类型不同、玩法不同,所以框架只能抽象出一些流程,而具体的实现则由开发者实现。
public class TankGameFlowService implements GameFlowService {
@Override
public Room createRoom(RoomCreateContext createContext) {
// 创建房间
TankRoom room = new TanRoom();
// return YourGameRoom
return room;
}
@Override
public Player createPlayer(GameFlowContext gameFlowContext) {
// 创建玩家
TankPlayer player = new TankPlayer();
// return YourGameRoomPlayer
return player;
}
@Override
public void enterRoom(GameFlowContext gameFlowContext) {
// 玩家进入房间 (重连)
}
@Override
public void startGameVerify(GameFlowContext gameFlowContext) {
// 游戏开始前的逻辑校验
}
@Override
public void startGameVerifyAfter(GameFlowContext gameFlowContext) {
// 游戏开始,会在 startGameVerify 校验成功后执行
}
@Override
public void dissolveRoom(GameFlowContext gameFlowContext) {
// 解散房间
}
@Override
public void quitRoom(GameFlowContext gameFlowContext) {
// 玩家退出房间
}
@Override
public void ready(boolean ready, GameFlowContext gameFlowContext) {
// 玩家准备
}
}
RoomCreateContext
RoomCreateContext 是创建房间时的上下文,提供了以下 api
下面是一个创建 RoomCreateContext 的示例,并与 GameFlowService 接口配合使用。因为创建房间的逻辑是由开发者自定义的,如果创建过程中需要更复杂的游戏规则,可以通过动态属性来扩展。
AttrOption<YourRoomRule> ruleAttr = AttrOption.valueOf("rule");
// 创建房间
RoomCreateContext roomCreateContext = RoomCreateContext.of(flowContext)
// 设置房间空间大小:最大可容纳 3 人,开始游戏时最少需要 2 人
.setSpaceSize(3, 2)
// 动态属性扩展 - 自定义房间玩法规则
.option(ruleAttr, rule);
// 创建房间
GameFlowService gameFlowService = ...
Room room = gameFlowService.createRoom(roomCreateContext);
// 保存房间
RoomService roomService = ...
roomService.addRoom(room);
public class MyGameFlowService implements GameFlowService {
... ...
@Override
public TankRoom createRoom(RoomCreateContext createContext) {
long roomId = roomIdCounter.incrementAndGet();
var room = new TankRoom();
room.setRoomId(roomId);
room.setRoomCreateContext(createContext);
room.setSpaceSize(createContext.getSpaceSize());
return room;
}
}
GameFlowContext
GameFlowService 是与开始游戏流程相关的,除了房间创建的方法使用了 RoomCreateContext 作为参数外,其余方法(玩家进入房间、玩家退出房间、解散房间、玩家准备、开始游戏)的参数则都是使用的 GameFlowContext。
GameFlowContext 也具备动态属性的特性。
以玩家准备来举例,来说明 GameFlowContext 的使用。
// 创建 GameFlowContext
GameFlowContext context = GameFlowContext.of(room, flowContext);
// 玩家准备
GameFlowService gameFlowService = ...
gameFlowService.ready(true, context);
// 准备与取消准备的协议
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class PlayerReady {
/** userId */
long userId;
/** true 表示准备 */
boolean ready;
}
public class MyGameFlowService implements GameFlowService {
... ...
@Override
public void ready(boolean ready, GameFlowContext gameFlowContext) {
// 当前操作的玩家
Player player = gameFlowContext.getPlayer();
player.setReady(ready);
// 通知房间内的玩家,有玩家准备或取消准备了
PlayerReady playerReady = new PlayerReady();
playerReady.userId = gameFlowContext.getUserId();
playerReady.ready = ready;
// 房间内广播
gameFlowContext.getRoom().ofRangeBroadcast()
.setResponseMessage(RoomCmd.of(RoomCmd.ready), playerReady)
.execute();
}
}
小结
现在,我们已经介绍了 RoomService(房间管理相关的)和 GameFlowService(开始游戏流程相关的)。现在,玩家除了不能在房间里面操作,其他的流程已经通了。
还介绍了 RoomCreateContext 和 GameFlowContext 上下文,两者都是服务于游戏流程的;同时,两者都具备动态属性的特性,如果有特殊业务的,可以通过此扩展来实现。
下篇预告
本期先介绍到这,后续将介绍【玩法操作 - 相关】相关内容。
房间内的玩法操作业务,也就是玩家的房间内具体的玩法操作。如,坦克的射击,炉石的战前选牌、出牌,麻将的吃、碰、杠、过、胡,回合制游戏的普攻、防御、技能 …等。
.
由于玩法操作的不同,所以我们的玩法操作需要是可扩展的,并用于处理具体的玩法操作。通过实现 OperationHandler 接口,开发者可以扩展不同的玩法操作;同时这种扩展方式更符合单一职责,使得我们后续的扩展与维护成本更低。