Protobuf 使用(unity)

工具&環(huán)境

1.vscode https://code.visualstudio.com
2.node.js http://nodejs.cn/download/
3.npm(node.js)
4.Google.Protobuf https://github.com/protocolbuffers/protobuf
5.protoc (windows平臺編譯工具)https://github.com/protocolbuffers/protobuf/releases/download/v3.17.3/protoc-3.17.3-win64.zip
6.protobuf Csharp https://github.com/protocolbuffers/protobuf/tree/master/csharp
7.Protoc Gen Typescript (轉(zhuǎn)ts配置)https://www.npmjs.com/package/protoc-gen-ts

無關(guān)知識點

1)package.json npm項目配置 文檔

npm init //創(chuàng)建默認(rèn)package.json配置

以下是我本地項目配置

{
  "name": "protobufdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "google-protobuf": "^3.17.0",
    "ts-node": "^10.2.1",
    "typescript": "^4.3.5"
  },
  "devDependencies": {
    "source-map-support": "^0.5.19",
    "@types/google-protobuf": "^3.15.2",
    "ts-protoc-gen": "^0.15.1-pre.a71b34e",
    "@swc-node/core": "^1.3.0",
    "fs-extra": "^9.1.0",
    "uglify-js": "^3.13.1"
  }
}

注意兩種依賴

dependencies鸯乃,在生產(chǎn)環(huán)境中需要用到的依賴
devDependencies,在開發(fā)、測試環(huán)境中用到的依賴

在開發(fā)新項目時 運行 npm install 就會根據(jù)配置自動下載安裝依賴文件。
在配置外還有兩種安裝方式
npm install xxx 本地安裝
npm install -g xxx 全局安裝 (默認(rèn)會安裝到npm全局目錄下:C:\Users\jsfa\AppData\Roaming\npm\node_modules
如果安裝時同時還想將module添加到package依賴 npm install xxx -save
添加到Dev的命令類似 npm install xxx -save -dev

自定義npm 命令 script

image.png

在package目錄控制臺命令 npm run test 就會輸出一個我們定義的報錯信息
通過這樣的方式可以做很多事情在孝,比如快捷打包,自動執(zhí)行运沦,復(fù)制文件
image.png

2)Vscode配置文件 settings.json

自定義一些設(shè)置击孩,文件篩選啊之類的

調(diào)試(debug)和啟動(run)的配置項 launch.json

image.png

如果我想調(diào)試typescript代碼,添加下面的代碼

   "version": "0.2.0",
    "configurations": [
        {
            "name": "Current TS File",
            "type": "node",
            "request": "launch",
            "args": [
                "${workspaceRoot}/TsProj/Test/Test.ts"
            ],
            "runtimeArgs": [
                "--nolazy",
                "-r",
                "ts-node/register"
            ],
            "sourceMaps": true,
            "cwd": "${workspaceRoot}/TsProj",
            "protocol": "inspector",
            "console": "integratedTerminal",
            "internalConsoleOptions": "neverOpen"
        }
}

點擊調(diào)試就會自動編譯運行工作空間下Test.ts腳本
如果新項目沒有這個腳本挟冠,添加的方式很多比如
Create a launch.json file

image.png

如果想調(diào)試Unity 可以安裝Vscode 插件Debugger for Unity

image.png

安裝好之后 選擇創(chuàng)建json.file 在下拉列表中選擇unityDebugger
image.png

然后當(dāng)當(dāng)當(dāng)當(dāng)(三聲)于购,多了一堆東西
image.png

調(diào)試
image.png

運行調(diào)試 運行unity 就可以愉快打斷點調(diào)試了
有可能還要先Attach一下
image.png

3)TypeScript 配置文件 tsconfig.json 文檔鏈接

初始化配置文件
1.在跟目錄手動創(chuàng)建tsconfig.json文件
2控制臺運行 tsc --init命令
這是我本地項目配置

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "jsx": "react",
    "sourceMap": true,
    "noImplicitAny": true,
    "typeRoots": [
      "../Assets/Puerts/Typing",
      "../Assets/Gen/Typing",
      "./node_modules/@types"
    ],
    "outDir": "dist",
    "suppressImplicitAnyIndexErrors": true,
    "experimentalDecorators": true
  }
}

還記得上面package配置里的Scripts: build

image.png

執(zhí)行npm run build就可以以我配置的選項執(zhí)行編譯typescript腳本。

Protobuf 使用(本文主題)

protobuf的概念上面有文檔鏈接

  1. 創(chuàng)建proto文件


    image.png

    TestData.proto 這就是我們需要的protobuf源文件了
    可以下載這個插件,方便編寫protobuf協(xié)議


    image.png
syntax = "proto3";
option csharp_namespace =  "Dx"; 
message TestData {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}
  1. 將.proto生成特定語言能夠使用的數(shù)據(jù)結(jié)構(gòu)
    這是我們下載好的編譯文件知染,將bin目錄添加到環(huán)境變量中肋僧,然后控制臺測試 protoc --version
    image.png

    image.png

    在vs目錄也好,控制臺也好
    a.C#
    protoc --csharp_out=輸出目錄 源文件目錄\*.proto
    b.js
    protoc --js_out=輸出目錄 源文件目錄\*.proto
    c.TypeScript
    這個比較特殊 我還沒找到原有的生成方式控淡,所以這里要用到 Protoc Gen Typescript
    可以通過npm直接安裝
npm install ts-protoc-gen

然后就可以執(zhí)行

protoc -I=sourcedir --ts_out=dist myproto.proto \\生成ts的protobuf結(jié)構(gòu)

這里有可能會報錯嫌吠,找不到google.protobuf 模塊,但項目里明明有掺炭,可以通過全局安裝
npm install -g google-protobuf 解決 (這里的問題我還沒搞明白辫诅,但暫時這么解決了)
ts-protoc-gen有兩種轉(zhuǎn)化方式

protoc  --ts_out=.\Assets\Proto .\Assets\Proto\TestData.proto
protoc --plugin=protoc-gen-ts=.\node_modules\ts-protoc-gen\bin\protoc-gen-ts.cmd --proto_path=. --ts_out=.\Assets\Proto .\Assets\Proto\TestData.proto

這兩種方式生成的ts結(jié)構(gòu)是不同的

TestData.ts ts的協(xié)議文件包含所有的變量 類型 序列化 反序列化方法。涧狮。炕矮。

/**
 * Generated by the protoc-gen-ts.  DO NOT EDIT!
 * compiler version: 3.7.1
 * source: Assets/Proto/TestData.proto
 * git: https://github.com/thesayyn/protoc-gen-ts
 * buymeacoffee: https://www.buymeacoffee.com/thesayyn
 *  */
import * as pb_1 from "google-protobuf";
export class TestData extends pb_1.Message {
    constructor(data?: any[] | {
        name?: string;
        id?: number;
        email?: string;
        phones?: TestData.PhoneNumber[];
    }) {
        super();
        pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [4], []);
        if (!Array.isArray(data) && typeof data == "object") {
            if ("name" in data && data.name != undefined) {
                this.name = data.name;
            }
            if ("id" in data && data.id != undefined) {
                this.id = data.id;
            }
            if ("email" in data && data.email != undefined) {
                this.email = data.email;
            }
            if ("phones" in data && data.phones != undefined) {
                this.phones = data.phones;
            }
        }
    }
    get name() {
        return pb_1.Message.getField(this, 1) as string;
    }
    set name(value: string) {
        pb_1.Message.setField(this, 1, value);
    }
    get id() {
        return pb_1.Message.getField(this, 2) as number;
    }
    set id(value: number) {
        pb_1.Message.setField(this, 2, value);
    }
    get email() {
        return pb_1.Message.getField(this, 3) as string;
    }
    set email(value: string) {
        pb_1.Message.setField(this, 3, value);
    }
    get phones() {
        return pb_1.Message.getRepeatedWrapperField(this, TestData.PhoneNumber, 4) as TestData.PhoneNumber[];
    }
    set phones(value: TestData.PhoneNumber[]) {
        pb_1.Message.setRepeatedWrapperField(this, 4, value);
    }
  
...還有好長好長的代碼...

        serializeBinary(): Uint8Array {
            return this.serialize();
        }
        static deserializeBinary(bytes: Uint8Array): PhoneNumber {
            return PhoneNumber.deserialize(bytes);
        }
    }
}

TestData_pb.ts ts 協(xié)議的聲明文件 同樣包含所有的 變量 類型 序列化反序列化方法

// package: 
// file: TestData.proto

import * as jspb from "google-protobuf";

export class TestData extends jspb.Message {
  getName(): string;
  setName(value: string): void;

  getId(): number;
  setId(value: number): void;

  getEmail(): string;
  setEmail(value: string): void;

  clearPhonesList(): void;
  getPhonesList(): Array<TestData.PhoneNumber>;
  setPhonesList(value: Array<TestData.PhoneNumber>): void;
  addPhones(value?: TestData.PhoneNumber, index?: number): TestData.PhoneNumber;

  serializeBinary(): Uint8Array;
  toObject(includeInstance?: boolean): TestData.AsObject;
  static toObject(includeInstance: boolean, msg: TestData): TestData.AsObject;
  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
  static serializeBinaryToWriter(message: TestData, writer: jspb.BinaryWriter): void;
  static deserializeBinary(bytes: Uint8Array): TestData;
  static deserializeBinaryFromReader(message: TestData, reader: jspb.BinaryReader): TestData;
}

export namespace TestData {
  export type AsObject = {
    name: string,
    id: number,
    email: string,
    phonesList: Array<TestData.PhoneNumber.AsObject>,
  }

  export class PhoneNumber extends jspb.Message {
    getNumber(): string;
    setNumber(value: string): void;

    getType(): TestData.PhoneTypeMap[keyof TestData.PhoneTypeMap];
    setType(value: TestData.PhoneTypeMap[keyof TestData.PhoneTypeMap]): void;

    serializeBinary(): Uint8Array;
    toObject(includeInstance?: boolean): PhoneNumber.AsObject;
    static toObject(includeInstance: boolean, msg: PhoneNumber): PhoneNumber.AsObject;
    static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
    static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
    static serializeBinaryToWriter(message: PhoneNumber, writer: jspb.BinaryWriter): void;
    static deserializeBinary(bytes: Uint8Array): PhoneNumber;
    static deserializeBinaryFromReader(message: PhoneNumber, reader: jspb.BinaryReader): PhoneNumber;
  }

  export namespace PhoneNumber {
    export type AsObject = {
      number: string,
      type: TestData.PhoneTypeMap[keyof TestData.PhoneTypeMap],
    }
  }

  export interface PhoneTypeMap {
    MOBILE: 0;
    HOME: 1;
    WORK: 2;
  }

  export const PhoneType: PhoneTypeMap;
}

在import 對應(yīng)路徑下的 TestData之后 ,就可以開始使用了

import { TestData } from "./Assets/Proto/TestData_pb";//這里要指向使用的協(xié)議文件者冤,我這里指向的協(xié)議文件
class main{
    public x:TestData=new TestData();

    constructor(id:number,name:string){
        this.x.setId(id);
        this.x.setName(name);
    }
}

function Read(data:ArrayBuffer){
    let ret = TestData.deserializeBinary(new Uint8Array(data));
    console.log(ret.getId());
    console.log(ret.getName());
}

let m = new main(12,"dx");
Read(m.x.serializeBinary());

我們可以直接在ts腳本中創(chuàng)建對應(yīng)的協(xié)議數(shù)據(jù)類型肤视,讀取寫入,序列化之后傳輸涉枫。
但當(dāng)我們將ts編譯為js文件時邢滑,兩種方式的流程有所不同
TestData.ts
main.ts通過tsc 編譯后,將自動將生成TestData.js腳本,里面包含全部的協(xié)議信息拜银,所以編譯過后殊鞭,能正常調(diào)用,程序正常運行尼桶。
TestData_pb.ts由于它只是聲明文件操灿,
所以我們在編程階段可以正常使用方法,添加數(shù)據(jù)而不會報錯泵督,但將主程序編譯之后趾盐,并不會生成對應(yīng)的協(xié)議.js文件,所以運行程序會出現(xiàn)

image.png

所以需要我們主動生成對應(yīng)的.js腳本:
在生成協(xié)議時同時生成js腳本

D:\SoftWare\protoc-3.7.1-win64\bin\protoc.exe --plugin=protoc-gen-ts=D:\Project\UnityProject\ProtobufDemo\node_modules/.bin/protoc-gen-ts.cmd --proto_path=. --ts_out=. --js_out=import_style=commonjs,binary:. .\TestData.proto

程序目錄\protoc.exe
--plugin=protoc-gen-ts=protoc-gen-ts.cmd目錄\protoc-gen-ts.cmd
--proto_path=依賴的proto文件目錄(import"**.proto"
--ts_out=聲明文件輸出目錄
--js_out=import_style=commonjs,binary:js文件輸出格式及目錄
協(xié)議文件輸入路徑

這樣我們就同時得到聲明文件.d.ts.js文件了,可以正常編譯使用

一般在應(yīng)用場景中,我們更多的使用第二種方式來在ts部分使用protobuf協(xié)議,因為聲明文件更簡潔明了,諸如此類巴拉巴拉(反正用就完事了)

d. 其他語言請查看官方文檔
3 ) proto文件有了,對應(yīng)平臺轉(zhuǎn)化的協(xié)議也有了,現(xiàn)在就是使用了藤滥,以Unity為例
將轉(zhuǎn)換的協(xié)議 TestData.cs導(dǎo)入Unity

image.png

然后報錯
image.png

因為我們項目中缺少 Google.Protobuf
打開github,選擇C#,download
image.png

image.png

bulidall
image.png

image.png

將以下文件導(dǎo)入unity plugins\protobuf 目錄
image.png

image.png

編寫測試代碼
main.cs

using Google.Protobuf; //數(shù)據(jù)轉(zhuǎn)二進制要用到 ToByteArray()  方法
public class Main : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        TestData data = new TestData();
        data.Name="dx";
        data.Id=1234;
        var s = data.ToByteArray();
        SendS(s);
    }
    public void SendS(byte[] data){
        Debug.Log(data);
        RecvS(data);
    }
    public void RecvS(byte[] data){
        TestData rData = TestData.Parser.ParseFrom(data);
        Debug.Log(rData.Name);
        Debug.Log(rData.Id);
    }
}

結(jié)果


image.png

成功融虽!
其他語言也類似,只是序列化和反序列化的方法可以稍微有差別

補充(大概率是我數(shù)據(jù)轉(zhuǎn)換處理基礎(chǔ)太差造成的原因)
這里出現(xiàn)了Cs端Ts端傳輸byte[] 數(shù)據(jù) 協(xié)議轉(zhuǎn)化失敗截亦,沒法正常取數(shù)據(jù)的情況,最后通過PuertsArrayBuffer將cs端的二進制數(shù)據(jù)先轉(zhuǎn)為ArrayBuffer,最后在ts部分轉(zhuǎn)換為協(xié)議數(shù)據(jù)解決

public static Puerts.ArrayBuffer GetPlayerRecord()
        {
            Protocol.PlayerRecord data = PlayerRecord.Instance.GetPlayerRecordPb();
           
            if (data != null) return new Puerts.ArrayBuffer(data.ToByteArray());
            return null;
        }
export function InitPlayerRecord (data: ArrayBuffer) {
        let playerRecord  = PlayerRecord .deserializeBinary(new Uint8Array(data));
    }

拋開協(xié)議也能通過在ts端通過jsonobj的方式解析數(shù)據(jù),前提是你完全知道協(xié)議內(nèi)容

let obj = JSON.parse(data);
        let mrlist = obj['mapRecords'];
        for (let i = 0; i < mrlist.length; i++) {
            let record=mrlist[i];
            UpdateRecordByJson(record["area"],record["floor"],record["status"],record["time"],record["resetTime"],record["boughttimes"],record["weeklyboughttimes"]);
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稀颁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子楣黍,更是在濱河造成了極大的恐慌匾灶,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件租漂,死亡現(xiàn)場離奇詭異阶女,居然都是意外死亡,警方通過查閱死者的電腦和手機哩治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門秃踩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锚扎,你說我怎么就攤上這事吞瞪。” “怎么了驾孔?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵芍秆,是天一觀的道長。 經(jīng)常有香客問我翠勉,道長妖啥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任对碌,我火速辦了婚禮荆虱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朽们。我一直安慰自己怀读,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布骑脱。 她就那樣靜靜地躺著菜枷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叁丧。 梳的紋絲不亂的頭發(fā)上啤誊,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天岳瞭,我揣著相機與錄音,去河邊找鬼蚊锹。 笑死瞳筏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牡昆。 我是一名探鬼主播姚炕,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迁杨!你這毒婦竟也來了钻心?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤铅协,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后摊沉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狐史,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年说墨,在試婚紗的時候發(fā)現(xiàn)自己被綠了骏全。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡尼斧,死狀恐怖姜贡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棺棵,我是刑警寧澤楼咳,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站烛恤,受9級特大地震影響母怜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缚柏,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一苹熏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧币喧,春花似錦轨域、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至怜浅,卻和暖如春铐然,著一層夾襖步出監(jiān)牢的瞬間蔬崩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工搀暑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沥阳,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓自点,卻偏偏與公主長得像桐罕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桂敛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Protobuf[https://github.com/protocolbuffers/protobuf]是goo...
    gogoingmonkey閱讀 9,267評論 0 3
  • 簡介 和http中常用的json協(xié)議一樣功炮,protobuf也是用來傳輸數(shù)據(jù)的,但是它使用二進制格式术唬,傳輸效率更高薪伏。...
    StormZhu閱讀 9,660評論 5 7
  • 1. Protobuf 介紹 Protobuf 是 Protocol Buffers 的簡稱 ,是Google開源...
    楚江云閱讀 892評論 0 2
  • 0、什么是protoBuf protoBuf是一種靈活高效的獨立于語言平臺的結(jié)構(gòu)化數(shù)據(jù)表示方法粗仓,與XML相比嫁怀,pr...
    Javen205閱讀 7,384評論 1 11
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭借浊,有人歡樂有人憂愁塘淑,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,523評論 28 53