iohao / ioGame

无锁异步化、事件驱动架构设计的 java netty 网络编程框架; 轻量级,无需依赖任何第三方中间件或数据库就能支持集群、分布式; 适用于网络游戏服务器、物联网、内部系统及各种需要长连接的场景; 通过 ioGame 你可以很容易的搭建出一个集群无中心节点、集群自动化、分布式的网络服务器;FXGL、Unity、UE、Cocos Creator、Godot、Netty、Protobuf、webSocket、tcp、socket;java Netty 游戏服务器框架;

Home Page:http://game.iohao.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[light-game-room] 重构 room 扩展模块 - 适用桌游类的游戏

iohao opened this issue · comments

计划重构 room 模块,简化相关 api 的使用,并提供相关文档。在此之前,由于该模块较偏特定业务,且示例流程涉及较多,所以一直没有提供相关文档与示例。

room 模块比较适合桌游类的游戏基础搭建,基于该模型可以做一些如,炉石传说、三国杀、斗地主、麻将 ...等类似的桌游。或者说只要是房间类的游戏,该模型都适用。比如,CS、泡泡堂、飞行棋、坦克大战 ...等。


简介 - light-game-room 房间

light-game-room 房间,是 ioGame 提供的一个轻量小部件 - 可按需选择的模块。

该模块的主要职责有三个方面,分别是房间、玩家、操作。

  • 房间相关的包括,房间的创建、房间的管理及对房间内玩家的管理(如玩家退出房间、加入房间,控制房间内的人数 ...等)。
  • 玩家方面相关的包括,玩家的信息及在房间内所持有的资源(资源指的是玩家所持有的子弹、牌、道具 ...等,需根据游戏来确定)。
  • 操作方面相关,由于不同游戏之间的具体操作是不相同的,如坦克的射击,飞行棋的投骰子行走,炉石的战前选牌、出牌,麻将的吃、碰、杠、过、胡,回合制游戏的普攻、防御、技能 ...等。上述这些不同的操作,我们可以通过扩展 OperationHandler 接口,来处理具体的业务玩法;同时这种扩展方式更符合单一职责,使得我们后续的扩展与维护成本更低。

基于该模块的扩展,可以减少大量的重复工作,基本只需关注自身游戏的特定业务。

桌游戏类游戏的大致流程

  • 房间创建
  • 玩家创建
  • 进入房间
  • 退出房间
  • 解散房间
  • 游戏开始

游戏开始后,就是房间内的各种操作了,即上面提到的,如坦克的射击,飞行棋的投骰子行走,炉石的战前选牌、出牌,麻将的吃、碰、杠、过、胡,回合制游戏的普攻、防御、技能 ...等。


该模块是一个较综合的实战模块,通常在制作一个游戏时,至少可能涉及以下几个点

后续计划做一个相关实战的 demo,将包括前后端。前端计划使用 FXGL 引擎,这样开发者在学习时,只需 JDK 环境就可以了,而不需要安装更多的环境。

重构之后,将提供 room 模块的详细使用文档。

基于 room 模块的实战简介

现在,我们基于该 room 模块做一个实战示例,该示例整体比较简单,多名玩家在房间里猜拳(石头、剪刀、布)得分。

实战示例包括了前后端,前端使用 FXGL 引擎,这样开发者在学习时,只需 JDK 环境就可以了,而不需要安装更多的环境。


源码统计与功能介绍

image

基于 room 模块的扩展,只需要数百行代码就完成了该实战示例,包含的功能如下:

  • 登录
  • 玩家进入大厅(地图)
  • 玩家可在大厅移动
  • 玩家移动时相互可见
  • 玩家离开大厅(玩家下线)
  • 查询房间列表
  • 房间信息实时变更通知(房间内有玩家数量变化,等待中、游戏中 ...等状态)
  • 玩家创建房间
  • 玩家进入房间
  • 玩家退出房间
  • 解散房间
  • 玩家准备
  • 开始游戏
  • 玩家在房间内的玩法操作
  • 对接文档生成..等

正如开头介绍所说的,room 模块很好的帮助开发者屏蔽这些重复性的工作,并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。更重要的是有相关文档,将来当你的团队有新进成员时,可以快速的上手。


大厅

启动游戏后玩家会将加入大厅(类似地图),多名玩家相互可见,并且玩家可以在大厅内移动。

图中左边是玩家的可移动区域,右边房间列表信息。

image


大厅的玩家可以看见当前的房间列表信息,房间列表信息会实时的变化,比如游戏开始后,会由等待中变成游戏中。房间内的玩家数有变更时,也会实时的更新。

玩家可以创建一个房间,等待其他玩家的加入,也可以加入一个已经存在的房间与其他玩家共同游戏。

image

image

准备、游戏开始

image

游戏开始后,每个玩家可以选择要出的(石头、剪刀、布),选择好后会显示到左边的面板中;右边是当前房间内的得分排行,得分最高的排在前。

游戏时,每局有一定的时间供玩家选择,倒计时结束后会公布结算结果。如果倒计时结束玩家没有选择,那么服务器会强制帮未操作的玩家做出随机选择。

image

image

游戏结束

游戏结束后,玩家可以离开房间。

image

小结、扩展玩法操作

现在我们知道了,基于 room 模块的扩展,可以帮助开发者屏蔽大量重复性的工作,减少大量的代码。并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。

此外,该模型还可以帮助我们在编码时,减少心智上的负担。以玩家的操作玩法为例,下面是我们扩展的一个操作玩法,即选择(石头、剪刀、布)其中一个。

该扩展将验证与业务逻辑分离,这样可以使得我们的代码更加的清晰。

代码之所以清晰,是因为我们的验证使用了断言机制(断言 + 异常机制 = 清晰简洁的代码)。传统的框架需要开发者疯狂 if else,且每次需要手动手动传输错误码,相关讨论可阅读传统写法或祖传代码写法

// 验证与实际业务逻辑分离
// 将玩家的选择(石头、剪刀、布)广播给房间内的其他玩家。
public class ChooseOperationHandler implements OperationHandler {
    @Override
    public void verify(PlayerOperationContext context) {
        // 玩家操作的数据
        FightOperationCommand command = context.getCommand();
        int elementIndex = command.elementIndex;
        // 验证玩家的操作数据是否合法(石头、剪刀、布)
        GameCode.illegalOperation.assertTrue(FiveElementKit.verify(elementIndex));
    }

    @Override
    public void process(PlayerOperationContext context) {
        FightRoomEntity room = context.getRoom();
        long userId = context.getUserId();

        // 将玩家的元素选择(石头、剪刀、布)保存到当前对局中
        var fightRound = room.getCurrentFightRound();
        var item = fightRound.getItemByUserId(userId);

        FightOperationCommand command = context.getCommand();
        item.setElementIndex(command.elementIndex);

        // 房间内广播 - 有玩家做了选择
        room.ofRangeBroadcast()
                .setResponseMessage(RoomCmd.of(RoomCmd.operationBroadcast), LongValue.of(userId))
                .execute();
    }
}