Unity Sqlite和JSON 本地数据存储
本人对Unity中Sqlite和JSON本地数据存储方面的知识不算精通,因此在此分享他人的帖子内容,希望能为有相关需求的人提供帮助。
基本思路
在Unity项目里,对于不同类型的数据可以采用不同的存储方式:
- 游戏基础配置数据:像怪物的属性、装备模板属性、关卡怪物等这类数据,推荐使用SQLite进行存储。可以使用Unity插件SQLiteUnityKit(GitHub地址),同时推荐使用客户端SQLite Expert Personal 3来管理数据,这样便于数据的管理和维护。
- 玩家核心数据:如玩家的属性、装备、技能等数据,采用JSON格式进行存储,并加密保存在
Application.persistentDataPath路径下。这种方式可以避免玩家数据在软件每次升级时被覆盖。
插件及文件准备
- litJson:需要将
litJson.dll文件放置在项目的Plugins目录下。 - SQLite相关文件:
- 将
libsqlite3.so文件存放到Assets/Plugins/Android目录中。 - 把自定义的SQLite DB数据文件放置在
Assets/StreamingAssets目录下。
SQLite部分
数据文件与代码拷贝
将准备好的DB数据文件复制到Assets/StreamingAssets目录。同时,把从SQLiteUnityKit GitHub仓库下载的压缩包中的DataTable.cs、SqliteDatabase.cs文件,复制到项目中的任意位置。最终项目结构大致如下。
SQLiteUnityKit框架特点
SQLiteUnityKit框架运用Dictionary数据结构模拟了DataTable和DataRow。所以在执行查询语句时,返回的是DataTable,这和平时使用ado.net提供的查询模式类似。
调用示例
以下是一个简单的调用示例代码:
SqliteDatabase sqlDB = new SqliteDatabase("config.db");
string query = "INSERT INTO User(UserName) VALUES( 'Santiago')";
sqlDB.ExecuteNonQuery(query);
此外,我制作了一个unitypackage的例子,大家可以下载导入项目使用。
JSON部分
存储格式选择
原本数据存储采用XML格式,因为XML具有跨平台的特性。但JSON同样能实现跨平台,而且有LitJson这个强大的格式转换工具,所以本文以JSON格式为例进行本地文件存储。
数据加密
数据加密可以使用C#提供的加密类,需要自定义加密密钥。以下是加密相关的代码:
using System.Security.Cryptography;
using System.Text;
public class GlobalDataHelper
{
private const string DATA_ENCRYPT_KEY = "a234857890654c3678d77234567890O2";
private static RijndaelManaged _encryptAlgorithm = null;
public static RijndaelManaged DataEncryptAlgorithm()
{
_encryptAlgorithm = new RijndaelManaged();
_encryptAlgorithm.Key = Encoding.UTF8.GetBytes(DATA_ENCRYPT_KEY);
_encryptAlgorithm.Mode = CipherMode.ECB;
_encryptAlgorithm.Padding = PaddingMode.PKCS7;
return _encryptAlgorithm;
}
}
破解版检测
针对安卓机子上泛滥的各种破解版软件问题,可以利用Unity提供的唯一机器ID。在写入玩家数据时,将该ID一同写入数据中。读取数据后,对比该ID和本机ID,如果不一致,则判定为破解版。获取唯一机器ID的代码如下:
SystemInfo.deviceUniqueIdentifier
不过本例子是以基础配置数据为例,代码中未提供该功能的实现。
避免存档覆盖
Unity提供了一个只读路径Application.persistentDataPath,将文件存放在该路径下,不会受到软件更新的影响。
Json Helper类
以下是一个用于处理JSON数据存储和加载的辅助类DataStoreProcessor:
using System.Security.Cryptography;
using System.Text;
using System;
using System.IO;
using LitJson;
public class DataStoreProcessor
{
private static DataStoreProcessor _dataStoreProcessor = null;
public static DataStoreProcessor SharedInstance
{
get
{
if (_dataStoreProcessor == null)
_dataStoreProcessor = new DataStoreProcessor();
return _dataStoreProcessor;
}
}
/// <summary>
/// 加密数据
/// </summary>
/// <returns>加密后的数据</returns>
/// <param name="dataToEncrypt">待加密的数据</param>
public string EncryptData(string dataToEncrypt)
{
byte[] dataToEncryptArray = Encoding.UTF8.GetBytes(dataToEncrypt);
byte[] dataAfterEncryptArray = GlobalDataHelper.DataEncryptAlgorithm().CreateEncryptor()
.TransformFinalBlock(dataToEncryptArray, 0, dataToEncryptArray.Length);
return Convert.ToBase64String(dataAfterEncryptArray, 0, dataAfterEncryptArray.Length);
}
/// <summary>
/// 解密数据
/// </summary>
/// <returns>解密后的数据</returns>
/// <param name="dataToDecrypt">待解密的数据</param>
public string DecryptData(string dataToDecrypt)
{
byte[] dataToDecryptArray = Convert.FromBase64String(dataToDecrypt);
byte[] dataAfterDecryptArray = GlobalDataHelper.DataEncryptAlgorithm().CreateDecryptor()
.TransformFinalBlock(dataToDecryptArray, 0, dataToDecryptArray.Length);
return Encoding.UTF8.GetString(dataAfterDecryptArray);
}
/// <summary>
/// 数据保存
/// </summary>
/// <param name="tobject">要保存的数据对象</param>
/// <param name="path">保存路径</param>
/// <param name="isEncrypt">是否加密,默认为true</param>
/// <typeparam name="T">数据对象的类型</typeparam>
public void Save(Object tobject, string path, bool isEncrypt = true)
{
string serializedString = JsonMapper.ToJson(tobject);
using (StreamWriter sw = File.CreateText(path))
{
if (isEncrypt)
sw.Write(EncryptData(serializedString));
else
sw.Write(serializedString);
}
}
/// <summary>
/// 载入数据
/// </summary>
/// <param name="path">数据文件路径</param>
/// <param name="isEncrypt">是否加密,默认为true</param>
/// <typeparam name="T">数据对象的类型</typeparam>
public T Load<T>(string path, bool isEncrypt = true)
{
if (File.Exists(path) == false)
return default(T);
using (StreamReader sr = File.OpenText(path))
{
string stringEncrypt = sr.ReadToEnd();
if (string.IsNullOrEmpty(stringEncrypt))
return default(T);
if (isEncrypt)
return JsonMapper.ToObject<T>(DecryptData(stringEncrypt));
else
return JsonMapper.ToObject<T>(stringEncrypt);
}
}
}
调用示例
下面的代码实现了一个自定义窗体,允许开发者自行定义用户在等待界面时显示的本地配置文字。按照常规,游戏基础配置类的数据应该使用SQL方式进行数据交互,本文只是为了演示功能。只有玩家数据才采用本地文件存储的方式,存储在永久路径中。
using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
public class LoadingDataConfigWindow : ScriptableWizard
{
public List<string> NotifyString;
// 改成 Application.persistentDataPath 永久存储
private readonly string LOADING_DATA_CONFIG_URL = Application.dataPath + @"/Resources/Setting/LoadNotify.data";
public LoadingDataConfigWindow()
{
NotifyString = DataStoreProcessor.SharedInstance.Load<List<string>>(LOADING_DATA_CONFIG_URL, false);
}
[MenuItem("GameObject/Data Setting/Loading text")]
static void CreateWizard()
{
LoadingDataConfigWindow window = DisplayWizard<LoadingDataConfigWindow>("配置登陆提示文字", "确认", "取消");
window.minSize = new Vector2(1024, 768);
}
// This is called when the user clicks on the Create button.
void OnWizardCreate()
{
DataStoreProcessor.SharedInstance.Save(NotifyString, LOADING_DATA_CONFIG_URL, false);
Debug.Log(string.Format(" 保存成功,共计录入 {0} 数据", NotifyString.Count));
}
// Allows you to provide an action when the user clicks on the
// other button "Apply".
void OnWizardOtherButton()
{
Debug.Log("取消");
}
}
通过上述内容,我们可以在Unity项目中合理地使用SQLite和JSON进行本地数据存储,确保数据的安全和稳定性。