[游戏开发] Unity中使用FlatBuffer

什么是FlatBuffer

官网:

GitHub - google/flatbuffers: FlatBuffers: Memory Efficient Serialization LibraryFlatBuffers: Memory Efficient Serialization Library - google/flatbuffershttps://github.com/google/flatbuffers

为什么用FloatBuffer,优势在哪?

下图是常规使用的各种数据存储类型的性能对比。

  1. 对序列化数据的访问不需要打包和拆包------它将序列化数据存储在缓存中,这些数据既可以存储在文件中,又可以通过网络原样传输,而没有任何解析开销;(这是最主要的原因,ProtoBuffer、JSON等均需要拆包和解包)
  2. 内存效率和速度------访问数据时的唯一内存需求就是缓冲区,不需要额外的内存分配。 这里可查看详细的基准测试;
  3. 扩展性、灵活性------它支持的可选字段意味着不仅能获得很好的前向/后向兼容性(对于长生命周期的游戏来说尤其重要,因为不需要每个新版本都更新所有数据);
  4. 最小代码依赖------仅仅需要自动生成的少量代码和一个单一的头文件依赖,很容易集成到现有系统中。再次,看基准部分细节;
  5. 强类型设计------尽可能使错误出现在编译期,而不是等到运行期才手动检查和修正;
  6. 使用简单------生成的C++代码提供了简单的访问和构造接口;而且如果需要,通过一个可选功能可以用来在运行时高效解析Schema和类JSON格式的文本;
  7. 跨平台------支持C++11、Java,而不需要任何依赖库;在最新的gcc、clang、vs2010等编译器上工作良好;

除了性能上的优势,FlatBuffer还支持把数据序列化成明文Json供开发者校验数据正确性。

游戏开发什么情况选择使用FlatBuffer

大量的数据配表,想要高效加载大量数据

FlatBuffer使用流程

  1. 定义FlatBuffer数据格式文件schema
  2. 序列化数据成bytes文件
  3. Unity工程中加入FlatBuffer解析源码
  4. 使用FlatC.exe工具生成C#数据解析代码
  5. 加载bytes数据解析成数据对象。

Schema

FlatBuffer的自定义数据格式文件叫Schema。文件后缀是.fbs,和protobuffer的.proto后缀类似。

Schema支持的语法有

创建MyTestData.fbs文件,放到新建文件夹TestFlatBuffer内

bash 复制代码
//统计一下所有使用类型
//namespace的作用:1生成C#代码有命名空间  2生成文件夹
namespace MyGame;

//attribute字段暂时没看啥作用
attribute "priority";
 
//枚举使用方式1
enum Color : byte { Red = 1, Green, Blue }

//枚举使用方式2
enum PhoneType : int {
  MOBILE = 0,
  HOME = 1,
  WORK = 2,
}

table Monster {
  number:string (required);
  type:int;
}

table Weapon {
  number:string (required);
  type:int;
}

table Pickup {
  number:string (required);
  type:int;
}

//数据对象可以是Monster、Weapon、Pickup中的任何一个
union TestUnion { Monster, Weapon, Pickup }

//自定义三维数据
struct Vec3 {
  x:float;
  y:float;
  z:float;
}

//Monster结构中展示了常规值类型数据的使用方法
//展示了在table内使用struct、数组、union、枚举
table DataTable {
    pos:Vec3;

    //常规值类型
    damage:int = 500;
    hp:short = 100;
    name:string;
    friendly:bool = false;
	
    //数组的使用方式
    intArr:[int];
    
    //枚举的使用方式,支持写默认值
    color:Color = Blue;

    unionTarget:TestUnion;
}

table MyTestData {
	dataTable:[DataTable];
}

//root_type字段非常重要
root_type MyTestData;

Flatc.exe文件

flatc文件是干嘛的?它是把schema语法文件生成目标语言代码的程序。

Releases · google/flatbuffers · GitHubFlatBuffers: Memory Efficient Serialization Library - Releases · google/flatbuffershttps://github.com/google/flatbuffers/releases

新建一个批处理文件exportCSharp.bat去运行这个exe程序

bash 复制代码
flatc.exe --csharp -o Sample MyTestData.fbs

--csharp代表生成目标语言

-o代表输出文件路径为 Sample文件夹

在PowerSheel中运行这个bat,不在powerSheel运行就双击运行Bat文件,发现有语法错误

修改53行语法错误继续运行bat,成功后没啥日志

到输出文件夹去看一下

通过图片可以看出,我们在Schema文件里定义的数据类型都生成了一份C#文件,把MyGame文件夹拷贝到Unity项目中就可以解析这些数据啦

打开MyTestData文件简单看一下,反正都是数据格式咱们不需要太关心。

下载FlatBuffer源码

为何前面已经导出了schema数据代码,还要下载FlatBuffer源码??

因为schema生成的代码是纯数据相关的代码,需要源码去驱动数据代码序列化和反序列化功能。

https://github.com/google/flatbuffershttps://github.com/google/flatbuffers

把这些C#脚本拷贝到Unity项目中,多余文件可以删掉。

把源码拷贝到项目中后,编译报错

原因是我的FlatBuffer源码使用的是老版本,和flatc.exe代码的版本号不配套,你们的Flatc和FlatBuffers源码都从官网下载,肯定是配套的。

在Unity中使用FlatBuffer

完成前面的工作就可以正式使用FlatBuffer啦

这是官方文档里推荐的Unity使用FlatBuffer案例

Flatbuffers for Unity + Sample Code | eXiinhttp://exiin.com/blog/flatbuffers-for-unity-sample-code/

测试工程就只有FlatBuffers源码和我们生成的MyGame数据脚本

整俩按钮测试

创建一个测试文件UseFlatBuffer.cs

cs 复制代码
using System.IO;
using UnityEngine;
using MyGame;
using Google.FlatBuffers;
using Color = MyGame.Color;

public class UseFlatBuffer : MonoBehaviour
{
    public void Serilized()
    {
        //MyTestDatas是个数组,我们假定数据有5条
        int dataCount = 5;
        FlatBufferBuilder builder = new FlatBufferBuilder(1);
        //创建数组对象
        Offset<DataTable>[] dataTables = new Offset<DataTable>[dataCount];

        for (int i = 0; i < dataCount; i++)
        {
            StringOffset testName = builder.CreateString(i.ToString());
            VectorOffset inventoryVector = DataTable.CreateIntArrVector(builder,new int[]{i * 10 + 1,i * 10 + 2,i * 10 + 3,i * 10 + 4});
            
            //创建Union类型和对象
            TestUnion unionType = TestUnion.Monster;
            Offset<Monster> monster = Monster.CreateMonster(builder, builder.CreateString("怪物的名字"), 100);
            
            //---------------------开始写入数据-------------------------------------------------
            DataTable.StartDataTable(builder);
            //创建、写入pos
            Offset<Vec3> pos = Vec3.CreateVec3(builder, 100 * i, 100 * i, 100 * i);
            DataTable.AddPos(builder,pos);
            DataTable.AddHp(builder, 2);//short类型
            DataTable.AddFriendly(builder,true);
            DataTable.AddName(builder,testName);//string类型
            //数组的用法
            DataTable.AddIntArr(builder,inventoryVector);//添加整数数组到对象
            //枚举的用法
            DataTable.AddColor(builder,Color.Red);
            //Union的使用方法
            DataTable.AddUnionTargetType(builder,unionType);
            DataTable.AddUnionTarget(builder, monster.Value);
            dataTables[i] = DataTable.EndDataTable(builder);
        }
    
        //把5条数据塞入VectorOffset,因为最终数据是一个数组
        VectorOffset dtArr = MyTestData.CreateDataTableVector(builder, dataTables);

        //开始写入数据
        MyTestData.StartMyTestData(builder);
        MyTestData.AddDataTable(builder,dtArr);
        //结束写入
        Offset<MyTestData> dtOffset = MyTestData.EndMyTestData(builder);
        
        //序列化数据
        MyTestData.FinishMyTestDataBuffer(builder,dtOffset);
        byte[] bytes = builder.DataBuffer.ToSizedArray();
        File.WriteAllBytes("E:\\MyTestData.bytes",bytes);
    }

    //反序列化,把数据都打印出来
    public void Deserilized()
    {
        byte[] datas = File.ReadAllBytes("E:\\MyTestData.bytes");
        ByteBuffer buffer = new ByteBuffer(datas);
        MyTestData myTestData = MyTestData.GetRootAsMyTestData(buffer);
        int dtCount = myTestData.DataTableLength;
        Debug.Log("数据量:" + dtCount.ToString());

        for (int i = 0; i < dtCount; i++)
        {
            MyGame.DataTable dt = myTestData.DataTable(i).Value;
            Vec3 targetPos = dt.Pos.Value;
            Debug.Log(string.Format("x:{0},y:{1},z{2}",targetPos.X,targetPos.Y,targetPos.Z));
            for (int j = 0; j < dt.IntArrLength; j++)
                Debug.Log("intArr: " + dt.IntArr(j).ToString());
            MyGame.Color color = dt.Color;
            Debug.Log("color: " + color);
        }
    }
}

注意点

1、数据结构支持嵌套自身

bash 复制代码
table MyTestData {
	damage:int = 500;
    data:MyTestData;
}

2、如果你只有一个数据对象,用下面的写法,根数据就是这个结构体,如果你有多个重复对象,则用上面的写法,把它写成一个数组。

bash 复制代码
table MyTestData {
	dataTable:[DataTable];
}
root_type MyTestData;
bash 复制代码
table MyTestData {
	damage:int = 500;
    hp:short = 100;
    name:string;
}
root_type MyTestData;

3、三维数组有简写方式,官网里有写

复制代码
struct Vec3 {
    v:[float:3];
}
相关推荐
PersistJiao37 分钟前
使用 Temporal 管理和调度 Couchbase SQL 脚本的实际例子
数据库·sql
it界的哈士奇1 小时前
麒麟系统+达梦数据库+MybatisPlus+Redis+SpringBoot
数据库·spring boot·redis·达梦·mybatisplus
罗伯特_十三1 小时前
SQL Server 中,`timestamp` 和 `rowversion`类型特性
数据库·sqlserver
JhonKI1 小时前
【MySQL】表的约束(上)详解
android·数据库·mysql
woshilys2 小时前
sql server log文件
数据库·sqlserver
_向前2 小时前
SQL Having用法
数据库·sql
do_you_like_van_游戏3 小时前
geoserver(1) 发布sql 图层 支持自定义参数
数据库·sql
爱技术的小伙子3 小时前
【Oracle11g SQL详解】日期和时间函数:SYSDATE、TO_DATE、TO_CHAR 等
数据库·sql
Navicat中国3 小时前
Navicat 17 功能简介 | SQL 开发
数据库·sql·mysql·mongodb·mariadb·navicat·polardb
东阳马生架构3 小时前
Redis原理—2.单机数据库的实现
数据库·redis·nosql