Skip to main content

Command Palette

Search for a command to run...

Make a multiplayer card game - Episode 3 | Change JSON to Protocol Buffers

Published
L

Full stack web 3D developer

Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data. It has many advantages such as "Lesser in Size and Better in Performance", like this article Why Google moved from JSON to Protocol Buffers? introduce.

I used it on all the projects I've worked on over the years. Besides the better performance, what make me impressed is that the clean communication between client and server with protocol buffers.

You can clone demo for episode3 which including all the bellow content.

According to the official tutorial, two steps should be done:

  1. Install the runtime library google-protobuf:npm install google-protobuf.
  2. Get the Protocol Compiler protoc: download from https://github.com/protocolbuffers/protobuf/releases

Then run a command like protoc --js_out=import_style=commonjs,binary:. messages.proto base.proto, you can get the .js file from .proto.

When the .js file generated, the protocol buffers environment is ready.

Back to our game, we should create a .proto file, named card-game.proto.

The first line in .proto file should be:

syntax = "proto3";

which means which version of Protobuf we are using.

Define the enum of Cmd:

enum Cmd{
    NONE = 0;
    READY_C2S = 1;
    DEALCARDS_S2C = 2;
    COMPETEFORLANDLORDROLE_C2S = 3;
    PLAYTURN_S2C = 4;
    PLAYCARDS_C2S = 5;
    PLAYCARDS_S2C = 6;
    ILLEGALCARDS_S2C = 7;
    GAMEEND_S2C = 8;
}

enum Cmd is for routing, will be introduced later.

Define MainMessage message:

message MainMessage{
    uint32 cmd_id = 1;
    bytes data =2;
}

In order to encapsulate the header and data body, we should assign serialized message to the data attribute.

Define data message like:

message DealCards_S2C{
    repeated uint32 cards = 1;
    uint32 seat_number = 2;
}

Then, generate our own .js file.

As the episode3 demo show, run command bellow in directory proto.

  • Windows .\protoc.exe --js_out=import_style=commonjs,binary:out card-game.proto

  • MacOS ./protoc --js_out=import_style=commonjs,binary:out card-game.proto

Now, we have our own .js file from the .proto in which we define message.

Let's encoding and decoding message with protocol buffers.

Encoding:

enum Cmd is used to map message type here

function encodeData(cmd, data) {
    let _proto_struct_obj;
    switch (cmd) {
        case card_game_pb.Cmd.DEALCARDS_S2C:
            _proto_struct_obj = new card_game_pb.DealCards_S2C();
            _proto_struct_obj.setCardsList(data.cards);
            _proto_struct_obj.setSeatNumber(data.seatNumber);
            break;
        case card_game_pb.Cmd.PLAYCARDS_S2C:
            _proto_struct_obj = new card_game_pb.PlayCards_S2C();
            _proto_struct_obj.setCardsList(data.cards);
            _proto_struct_obj.setSeatNumber(data.seatNumber);
            break;
        case card_game_pb.Cmd.ILLEGALCARDS_S2C:
            _proto_struct_obj = new card_game_pb.IllegalCards_S2C();
            _proto_struct_obj.setSeatNumber(data.seatNumber);
            break;
        case card_game_pb.Cmd.GAMEEND_S2C:
            _proto_struct_obj = new card_game_pb.GameEnd_S2C();
            _proto_struct_obj.setSeatNumber(data.seatNumber);
            break;
        case card_game_pb.Cmd.PLAYTURN_S2C:
            _proto_struct_obj = new card_game_pb.PlayTurn_S2C();
            _proto_struct_obj.setHandCardsList(data.handCards);
            _proto_struct_obj.setSeatNumber(data.seatNumber);
            break;
        default:
            console.log("no message matched.")
    }
    if (_proto_struct_obj) {
        let _mainMsg = new card_game_pb.MainMessage();
        _mainMsg.setCmdId(cmd);
        let _data = _proto_struct_obj.serializeBinary();
        _mainMsg.setData(_data);
        let _completeData = _mainMsg.serializeBinary();
        return _completeData;
    }
    return null;
}

Decoding:

enum Cmd is used to map message type here

function decodeData(buffer) {
    let _mainMsg = card_game_pb.MainMessage.deserializeBinary(buffer);
    let _cmd = _mainMsg.getCmdId();
    let _bytesData = _mainMsg.getData();
    let _data;
    switch (_cmd) {
        case card_game_pb.Cmd.READY_C2S:
            _data = card_game_pb.Ready_C2S.deserializeBinary(_bytesData);
            _data = {
                seatNumber: _data.getSeatNumber()
            }
            if (_this.ready_C2S) _this.ready_C2S(_data);
            break;
        case card_game_pb.Cmd.PLAYCARDS_C2S:
            _data = card_game_pb.PlayCards_C2S.deserializeBinary(_bytesData);
            _data = {
                cards: _data.getCardsList(),
                seatNumber: _data.getSeatNumber()
            }
            if (_this.playCards_C2S) _this.playCards_C2S(_data);
            break;
        case card_game_pb.Cmd.COMPETEFORLANDLORDROLE_C2S:
            _data = card_game_pb.CompeteForLandLordRole_C2S.deserializeBinary(_bytesData);
            _data = {
                score: _data.getScore(),
                seatNumber: _data.getSeatNumber()
            }
            if (_this.competeForLandLordRole_C2S) _this.competeForLandLordRole_C2S(_data);
            break;
        default:
            console.log("no message matched.")
    }
}

Caution:

  1. Attributes defined as seat_number in .proto file should be call like getSeatNumber and setSeatNumebr.
  2. Attributes defined as repeated type in .proto should call getAttributeNameList and setAttributeList instead of the attribute self name.

More from this blog

如何将静态页面部署到Github Page,并绑定自定义域名

在仓库主页选择setting 点击pages 选择分支branch 输入自定义域名custom domain(设置好后点击save,系统将自动在项目根目录生成CNAME文件,文件内容为设置的当前域名) 在域名提供商处,添加DNS设置(以阿里云为例) 配置CNAME 配置IP,创建一个A类记录指向185.199.108.153 github将根据访问域名路由项目仓库(仓库名称为iddz.fun,并且根目录有CNAME文件) 最后,通过访问自定义域名,完成静态网站的部署...

Oct 4, 2023
如何将静态页面部署到Github Page,并绑定自定义域名

熟悉熟悉官方文档,逐步深入Babylon.js

文档的组织结构 通过前一篇文章的了解,我们会对Babylon.js有了一个大致的了解,整个文档主要是想带你逐步深入掌握这个Babylon.js所提供的所有内容。 内容主要分为一个概览和9个主要部分,这些部分包含章节,还有API详解和强大的文档和playground搜索功能。 - 1.Babylon.js特性 - Babylon.js是一个功能完备的游戏和渲染引擎,具有广泛的特性。这个部分将带你了解这些特性,并帮助你编码和使用它们。- 2.将Babylon.js添加到你的Web项目中 - 有多种...

Sep 25, 2023
熟悉熟悉官方文档,逐步深入Babylon.js

Lizhiyu's Blog

33 posts

Bringing the world closer together through play