很早之前我已经讲了如何生成表格解析代码点这里,本次讲一下如何将表格数据生成二进制流。
一、添加FlatBuffers插件
如果我们要使用FlatBuffers技术,那么必须向工程中添加FlatBuffers插件,步骤很简单,我们新建一个Unity3D的空工程,然后在Asset目录下新建Plugins文件夹,并且将之前生成的FlatBuffers.dll文件放到此目录下即可。
二、设计数据表格
我们做开发游戏项目时,经常要为策划设计表格,然后由策划来填写表格内容,程序来解析表格数据 用于业务逻辑。那么我们也从设计一个表格开始,比如我们设计一个怪物表Monster.txt,此文件为普通的文本文件,编码为UTF-8格式,内容如下:
#Id Name Desc Level HP Attack Depense
#INT STRING STRING INT INT INT INT
#怪物ID 怪物名称 描述 等级 血量 攻击力 防御力
1 大地之熊 大地之熊 1 100 2 0
2 烈焰土熊 烈焰土熊 2 180 5 1
3 噬魂蚁王 噬魂蚁王 3 200 8 2
4 玄冰毒蚁 玄冰毒蚁 4 250 12 3
5 九尾天狐 九尾天狐 5 300 18 3
6 幽冥火狐 幽冥火狐 6 380 20 5
7 不死雪狐 不死雪狐 7 420 25 6
8 极地冰狐 极地冰狐 8 480 28 8
9 八翼雷鹰王 八翼雷鹰王 9 600 30 10
这个表每一行的数据之间都以制表符"Tab"相隔,第一行为表的列名,第二行为每一列的数据类型,第三行为对每一列的释义,从第四行开始就是怪物表的属性值了,那么我们就从这样一张表开始讲解如何将此表数据转化为FlatBuffers格式的二进制流文件.
三、制作fbs文件
根据上面Monster的表结构,我们可以制作如下的fbs文件,也可以自己写代码来生成fbs文件,生成后的Table_Monster.fbs文件内容如下:
namespace FlatBuffersDemo;
table Table_Monster {
data:[Monster];
}
table Monster {
Id:int;
Name:string;
Desc:string;
Level:int;
HP:int;
Attack:int;
Depense:int;
}
root_type Table_Monster;
四、生成解析表数据的代码文件
按照上一节中讲的步骤使用flatc.exe工具来生成解析代码文件Table_Monster.cs,使用如下的命令:
flatc.exe --csharp --gen-onefile Table_Monster.fbs
使用上述指令后,会生成如下的代码:
// <auto-generated>
// automatically generated by the FlatBuffers compiler, do not modify
// </auto-generated>
namespace FlatBuffersDemo
{
using global::System;
using global::FlatBuffers;
public struct Table_Monster : IFlatbufferObject
{
private Table __p;
public ByteBuffer ByteBuffer { get { return __p.bb; } }
public static Table_Monster GetRootAsTable_Monster(ByteBuffer _bb) { return GetRootAsTable_Monster(_bb, new Table_Monster()); }
public static Table_Monster GetRootAsTable_Monster(ByteBuffer _bb, Table_Monster obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); }
public void __init(int _i, ByteBuffer _bb) { __p.bb_pos = _i; __p.bb = _bb; }
public Table_Monster __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
public Monster? Data(int j) { int o = __p.__offset(4); return o != 0 ? (Monster?)(new Monster()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; }
public int DataLength { get { int o = __p.__offset(4); return o != 0 ? __p.__vector_len(o) : 0; } }
public static Offset<Table_Monster> CreateTable_Monster(FlatBufferBuilder builder,
VectorOffset dataOffset = default(VectorOffset)) {
builder.StartObject(1);
Table_Monster.AddData(builder, dataOffset);
return Table_Monster.EndTable_Monster(builder);
}
public static void StartTable_Monster(FlatBufferBuilder builder) { builder.StartObject(1); }
public static void AddData(FlatBufferBuilder builder, VectorOffset dataOffset) { builder.AddOffset(0, dataOffset.Value, 0); }
public static VectorOffset CreateDataVector(FlatBufferBuilder builder, Offset<Monster>[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); }
public static VectorOffset CreateDataVectorBlock(FlatBufferBuilder builder, Offset<Monster>[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); }
public static void StartDataVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); }
public static Offset<Table_Monster> EndTable_Monster(FlatBufferBuilder builder) {
int o = builder.EndObject();
return new Offset<Table_Monster>(o);
}
public static void FinishTable_MonsterBuffer(FlatBufferBuilder builder, Offset<Table_Monster> offset) { builder.Finish(offset.Value); }
public static void FinishSizePrefixedTable_MonsterBuffer(FlatBufferBuilder builder, Offset<Table_Monster> offset) { builder.FinishSizePrefixed(offset.Value); }
};
public struct Monster : IFlatbufferObject
{
private Table __p;
public ByteBuffer ByteBuffer { get { return __p.bb; } }
public static Monster GetRootAsMonster(ByteBuffer _bb) { return GetRootAsMonster(_bb, new Monster()); }
public static Monster GetRootAsMonster(ByteBuffer _bb, Monster obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); }
public void __init(int _i, ByteBuffer _bb) { __p.bb_pos = _i; __p.bb = _bb; }
public Monster __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
public int Id { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
public string Name { get { int o = __p.__offset(6); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } }
#if ENABLE_SPAN_T
public Span<byte> GetNameBytes() { return __p.__vector_as_span(6); }
#else
public ArraySegment<byte>? GetNameBytes() { return __p.__vector_as_arraysegment(6); }
#endif
public byte[] GetNameArray() { return __p.__vector_as_array<byte>(6); }
public string Desc { get { int o = __p.__offset(8); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } }
#if ENABLE_SPAN_T
public Span<byte> GetDescBytes() { return __p.__vector_as_span(8); }
#else
public ArraySegment<byte>? GetDescBytes() { return __p.__vector_as_arraysegment(8); }
#endif
public byte[] GetDescArray() { return __p.__vector_as_array<byte>(8); }
public int Level { get { int o = __p.__offset(10); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
public int HP { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
public int Attack { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
public int Depense { get { int o = __p.__offset(16); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
public static Offset<Monster> CreateMonster(FlatBufferBuilder builder,
int Id = 0,
StringOffset NameOffset = default(StringOffset),
StringOffset DescOffset = default(StringOffset),
int Level = 0,
int HP = 0,
int Attack = 0,
int Depense = 0) {
builder.StartObject(7);
Monster.AddDepense(builder, Depense);
Monster.AddAttack(builder, Attack);
Monster.AddHP(builder, HP);
Monster.AddLevel(builder, Level);
Monster.AddDesc(builder, DescOffset);
Monster.AddName(builder, NameOffset);
Monster.AddId(builder, Id);
return Monster.EndMonster(builder);
}
public static void StartMonster(FlatBufferBuilder builder) { builder.StartObject(7); }
public static void AddId(FlatBufferBuilder builder, int Id) { builder.AddInt(0, Id, 0); }
public static void AddName(FlatBufferBuilder builder, StringOffset NameOffset) { builder.AddOffset(1, NameOffset.Value, 0); }
public static void AddDesc(FlatBufferBuilder builder, StringOffset DescOffset) { builder.AddOffset(2, DescOffset.Value, 0); }
public static void AddLevel(FlatBufferBuilder builder, int Level) { builder.AddInt(3, Level, 0); }
public static void AddHP(FlatBufferBuilder builder, int HP) { builder.AddInt(4, HP, 0); }
public static void AddAttack(FlatBufferBuilder builder, int Attack) { builder.AddInt(5, Attack, 0); }
public static void AddDepense(FlatBufferBuilder builder, int Depense) { builder.AddInt(6, Depense, 0); }
public static Offset<Monster> EndMonster(FlatBufferBuilder builder) {
int o = builder.EndObject();
return new Offset<Monster>(o);
}
};
}
五、生成二进制文件
解析表数据的代码文件生成之后,就可以生成二进制文件了。
我们可以先来解析Monster.txt文件的数据,然后通过生成的代码再来生成 二进制文件,主要代码段如下:
public static void Encode()
{
FlatBufferBuilder fbBuilder = new FlatBufferBuilder(1024);
Offset<Monster>[] monsters = new Offset<Monster>[9];
FileStream fileStream = new FileStream("./Assets/DataTable/Monster.txt", FileMode.Open, FileAccess.Read);
StreamReader streamReader = new StreamReader(fileStream, System.Text.Encoding.UTF8);
try
{
fileStream.Seek(0, SeekOrigin.Begin);
int index = 0;
string line = streamReader.ReadLine();
while (!string.IsNullOrEmpty(line))
{
if (!line.StartsWith("#"))
{
string[] array = line.Split(new char[] { '\t' });
int id = int.Parse(array[0]);
StringOffset name = fbBuilder.CreateString(array[1]);
StringOffset desc = fbBuilder.CreateString(array[2]);
int level = int.Parse(array[3]);
int hp = int.Parse(array[4]);
int attack = int.Parse(array[5]);
int depense = int.Parse(array[6]);
monsters[index] = Monster.CreateMonster(fbBuilder, id, name, desc, level, hp, attack, depense);
index++;
}
line = streamReader.ReadLine();
}
}
catch(IOException e)
{
Debug.LogErrorFormat("IO Error: {0}", e.Message);
}
finally
{
streamReader.Close();
streamReader.Dispose();
fileStream.Close();
fileStream.Dispose();
}
VectorOffset table_Monster = Table_Monster.CreateDataVector(fbBuilder, monsters);
Table_Monster.StartTable_Monster(fbBuilder);
Table_Monster.AddData(fbBuilder, table_Monster);
var eab = Table_Monster.EndTable_Monster(fbBuilder);
Table_Monster.FinishTable_MonsterBuffer(fbBuilder, eab);
byte[] buffers = fbBuilder.SizedByteArray();
FileStream fileWrite = new FileStream("./Assets/DataFlatBuffer/Monster.bytes", FileMode.OpenOrCreate, FileAccess.ReadWrite);
fileWrite.Write(buffers, 0, buffers.Length);
fileWrite.Flush();
fileWrite.Close();
fileWrite.Dispose();
}
调用上面的函数之后,我们就把Monster.txt中的源数据生成了二进制字节数据Monster.bytes
至此,我们生成二进制流文件就大功告成了,下一讲我会在此基数上讲解如何解析二进制文件,最终达到我们游戏项目中要使用数据的目的。