Unity 通过Sqlite3和lua-protubuf制作游戏存档

过年啦,新年快乐~

作为一个独立游戏开发者(其实也没啥做出来的东西),做一个单机游戏,那必然要有存档。
存档的方式有很多,xml,json,PlayerPrefs,sqlite等等。
在和大佬做了充分的交流了之后,决定用sqlite来做,总归上会比其他方式好一些。(至于为啥,我已经忘了)。

那么……
Unity 通过Sqlite3和lua-protubuf制作游戏存档_第1张图片

先说一下我的版本搭配:
Unity2019.4.10f1
Xlua
Sqlite3
lua-protobuf

然后说一下存档流程。
使用Sqlite3做一个数据库。
在这里插入图片描述
分别是存档ID,创建时间戳,上次存档时间戳,存档数据。其中存档数据使用的是Blob类型来存储。
Unity 通过Sqlite3和lua-protubuf制作游戏存档_第2张图片

存档数据用PB转为二进制流,然后存储。

小插曲1:
数据库的blob类型的数据可以用多个,比如说role_data,money_data,equip_data等。
proto的数据也可以用多个消息类型,比如说RoleRoot,MoneyRoot,EquipRoot等。
到底该怎么设计,数据库的属性,proto的消息类型该怎么定义,因项目和人而异。
大佬就这个存档制作方式和我交流了很久,0.5天wasted。

那么就开始正文吧。

PB部分

google出的protobuf并不支持lua,所以就去网上找了已经构建好的做好了第三方库的xlua的git项目。把他的Plugins目录弄到自己的项目中。
具体操作就看这篇文章即可,就不赘述了。
xLua下使用lua-protobuf

在C#中读取文件的二进制流,然后传给lua,通过pb.load来加载。

//PB文件路径
public static byte[] PB_SCHEMA
{
    get
    {
#if UNITY_EDITOR
        string filePath = Application.dataPath + "/../ExternalData/Proto/saveproto.pb";
#elif UNITY_STANDALONE
        string filePath = Application.dataPath + "/Resources/Proto/saveproto.pb";
#elif UNITY_ANDROID
        string filePath = Application.persistentDataPath + "/Proto/saveproto.pb";
#else
        string filePath = Application.dataPath + "/../ExternalData/Proto/saveproto.pb";
#endif

        byte[] pbBytes;
        if (File.Exists(filePath))
        {
            FileInfo fileInfo = new FileInfo(filePath);
            pbBytes = new byte[fileInfo.Length];
            FileStream file = fileInfo.OpenRead();
            file.Read(pbBytes, 0, Convert.ToInt32(file.Length));
            file.Close();
        }
        else
        {
            TextAsset file = ResourceMgr.Ins.LoadTextAsset("Proto/saveproto.pb", true);
            pbBytes = file.bytes;
        }

        return pbBytes;
    }
}

这里的路径,是针对我的项目的路径来读的。我也不打算搞IOS,所以我也不知道要是突然想出个IOS版本,又得调试多久……

接下来是Lua方面,就很简单粗制了。

--加载pb.dll来转二进制和解二进制
local pb = require "pb"

if pb.load(GlobalDefine.PB_SCHEMA) then
    Debug("pb文件加载成功")
end

小插曲2:
一开始的时候,做的是和文章示例一样,用loadfile来加载pb文件。
然而,我并不知道这个路径的相对路径是咋回事。网上也搜不到(梯子跪了,百度搜到的嘛emmmm)
所以就在C#方面做了静态变量来获取绝对路径,倒是可以用。
又然而,打出了PC包才想起来,我的pb文件是放在resource目录下的,这读起来就难受的很。
所以就改了一下,变成了直接读文件的二进制流,用load方法来加载。难受,0.8天wasted。

小插曲3:
建议没有编译经验的老铁们,不要想着自己编译这个东西。
我自己尝试过用lua-protobuf 使用说明里所讲的来编译个pb.dll出来。
编译倒是编译出来了,但是一扔进去,啥啥都不对…… 所以看到已经有了xlua继承第三方库的时候,我直接就把Plugins拿过来用了,完全不想自己编译。因为之前去编译那个东西,2.2天wasted。

pb部分这样就完成了。
其实想来,如果存档是只有一张表来存放所有的值的话,我觉得可以直接把pb.encode出来的二进制流存为一个文件,也可以当做存档来使用了。

但是吧,因项目而异,我还是弄得比较全套一些比较好。所以噩梦开始了……

Sqlite3部分

首先要保证Editor和PC是能成功跑起来的。
先去sqlite3的官网上把PC需要的dll下载下来。
SQLite Download Page 需要下载哪个,自行评断。
Unity 通过Sqlite3和lua-protubuf制作游戏存档_第3张图片
除此之外,还需要一个Mono.Data.Sqlite.dll,这个文件在Unity安装目录下
2018:C:\Program Files\Unity\Unity2018\Editor\Data\Mono\lib\mono\2.0\Mono.Data.Sqlite.dll
2019:C:\Program Files\Unity\Unity2019\Editor\Data\MonoBleedingEdge\lib\mono\2.0-api\Mono.Data.Sqlite.dll
把这个库放到Plugins文件夹下,PC就可以了。

有的文章会提到要把System.Data.dll也放进去,这个看实际情况吧,我的加进去反而出现了各种麻烦。

小插曲4:
当时出现了"System.InvalidOperationException:Cannot set CommandText while a DataReader is active"。百度和必应都找了,根本找不到原因。后来在一篇不起眼的文章看到说把2018的复制过去,因为2019已经是老兼容性问题了,2018会稳一些。嗯,我试了,打不出包,告诉我冲突。我干脆就删了,没想到就解决了…… 2.5天wasted。

这里贴一下开启数据库的代码

#if UNITY_EDITOR
    private readonly static string saveFilePath = Application.dataPath + "/../SaveData/";
#elif UNITY_STANDALONE
    private readonly static string saveFilePath = Application.dataPath + "/../SaveData/";
#elif UNITY_ANDROID
    private readonly static string saveFilePath = Application.persistentDataPath + "/SaveData/";
#endif

public void OpenConnect()
{
    try
    {
        //存档存放路径
        if (!Directory.Exists(saveFilePath))
        {
            Directory.CreateDirectory(saveFilePath);
        }

        //基本内容数据库
        string basicStr = "Data Source = " + saveFilePath + fileName;
        connection = new SqliteConnection(basicStr);
        connection.Open();
        Debug.Log("Connect to SaveDatabase Succ");
    }
    catch(Exception e)
    {
        Debug.LogError(e.ToString());
    }
}

有一些文章提到了SqliteConnection传入的字符串要以“URI = file:”开头,但是我去看了源码,用“Data Source = ”是可行的。

这里说一下关于Blob的问题。
针对简单一些的语句,直接传字符串就可以了。

public void SetGameSaveValue(int id, string key, int value)
{
    string sql = string.Format("update basic set {1} = {2} where id == {0}", id, key, value);
    ExecuteQuery(sql);
}

但是blob不一样,虽然说比较适合的是char[],但是pb已经转成二进制流了,那就用byte[]也是没有问题的。但是这个数据是不能拼接字符串来做处理的,所以

public void SetGameSaveValue(int id, string key, byte[] value)
{
    SqliteCommand command = connection.CreateCommand();
    command.CommandText = string.Format("update basic set {1} = @value where id == {0}", id, key);
    command.Parameters.Add("value", DbType.Binary).Value = value;
    ExecuteQuery(command);
}

这样就可以了,然后再说一下ExecureQuery这个方法。

private DataRow ExecuteQuery(string sql)
{
    SqliteCommand command = connection.CreateCommand();
    command.CommandText = sql;
    return ExecuteQuery(command);
}

private DataRow ExecuteQuery(SqliteCommand command)
{
    SqliteDataReader reader = command.ExecuteReader();
    command.Dispose();

    //获取数据
    DataTable dTable = new DataTable();
    dTable.Load(reader);
    reader.Close();

    //只返回查询到的第一行数据
    if (dTable.Rows.Count > 0)
    {
        return dTable.Rows[0];
    }

    return null;
}

不过这里有个问题要注意,DataRow的值是一个object,所以你要拆箱才行,这时候你就要注意空值的问题。

Sqlite3 安卓部分

如果要做安卓的话,那就一定需要libsqlite3.so文件。可以百度一个下来用,或者这里提供一个别人的帖子拿过来的下载链接。

如果编不过去的话,看一下是不是需要把System.data.dll加到Plugins里,不过可能会出现小插曲4的事情,不过我没有用2018尝试,我也不知道。

编出来的包,如果不出意外的话,会在LogCat里告诉你“Unable to find advapi32”。这是因为这个so有点旧了,System.Data.DataTable找不到。所以这一段还得改一下

private object ExecuteQuery(SqliteCommand command, string resultKey = null)
{
    SqliteDataReader reader = command.ExecuteReader();
    command.Dispose();

    object result = null;

    //获取数据 只以第一条搜索到的数据为准
    if ((resultKey != null) && reader.HasRows)
    {
        reader.Read();
        for (int index = 0; index < reader.FieldCount; index++)
        {
            if (resultKey == reader.GetName(index))
            {
                result = reader[index];
                break;
            }
        }
    }

    reader.Close();
    return result;
}

这个写法因项目而异,因为我是固定主键查找的,所以只需要一条数据。

小插曲5:
Sqlite的官网,我下了android需要的aar文件(只有这一个文件)。又按照百度到的做法,然后告诉我“Unable to find sqlite3.so” “Unable to find libsqlite3.so”,然后我解压包一看,里面的名字叫做“libsqliteX.so”,我就改成libsqlite3。然后彻底崩了= =。算了,还是在网上下一个能用的吧。1天wasted。

这些都完成了之后,我的Sqlite+pb做存档终于在PC和Android上都能跑的通了。花了将近10天的时间。十分艰难。
希望你要是也有这个需求的话,会顺利很多。
Unity 通过Sqlite3和lua-protubuf制作游戏存档_第4张图片

你可能感兴趣的:(Unity,unity,sqlite)