很早之前我已經講了如何生成表格解析代碼點這裏,本次講一下如何將表格數據生成二進制流。
一、添加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
至此,我們生成二進制流文件就大功告成了,下一講我會在此基數上講解如何解析二進制文件,最終達到我們遊戲項目中要使用數據的目的。