2014-05-04更新
SqliteDatabase.cs這個文件的初始方法有問題,具體是如果指定URL已經存在了DB文件,就不會重新覆蓋DB文件。
這導致我們修改之后的DB文件無法產生效果。
本人的解決辦法是在游戲啟動的界面,通過對比本地的Resources目錄下的文件A,和玩家核心數據B里面的數據庫版本號,
如果A>B,則判定本地的DB文件版本較老,需要更新。
具體代碼請到目錄SQLite篇下下載
2014-04-30更新
剔除了使用網絡上爛大街的SQLite使用方法(原因android下無法讀取數據),使用libSQLite3.so,通過DLLImport,在C#代碼里直接調用C接口
這種原生調用SQLite的方式,我在pc、android上親測無誤,ios沒測過,但是stackoverflow上有兄弟試過,沒問題。園子的朋友如果可以測IOS的,歡迎提供結果
基本思路,游戲基礎配置數據,比如怪物的屬性、裝備模板屬性、關卡怪物等,使用SQLite(Unity插件SQLiteUnityKit-GitHub地址,推薦客戶端SQLite Expert Personal 3),管理方便
玩家核心數據(屬性、裝備、技能)使用JSON格式,加密保存在Application.persistentDataPath路徑里,避免每次升級被覆蓋
插件本地下載地址
准備工作
1 litJson.dll放在Plugins目錄下
2 libsqlite3.so文件放到 Assets/Plugins/Android目錄下
3 自定義的SQLite DB數據文件放到 Assets/StreamingAssets目錄下
SQLite篇
將准備好的DB數據文件拷貝到Assets/StreamingAssets
把SQLiteUnityKit GitHub下載的壓縮包里的 DataTable.cs、SqliteDatabase.cs,拷貝到項目中的任意位置
最終項目結構看起來是這樣子的

SQLiteUnityKit框架用Dictionary數據結構模擬了DataTable,DataRow,因此我們執行查詢語句的時候,返回的是DataTable,就像平時使用ado.net提供的查詢模式一樣。
調用方式
SqliteDatabase sqlDB = new SqliteDatabase(“config.db”); string query = “INSERT INTO User(UserName) VALUES( ‘Santiago’)”; sqlDB.ExecuteNonQuery(query);
我做了個unitypackge的例子,大家可以下載導入
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; } }
關於破解版軟件
安卓機子上泛濫各種XXX破解版,關於破解版的問題,我們可以通過Unity提供的唯一機器ID,在寫入玩家數據的時候,將其一並寫入到數據中去,在讀取數據之后,對比該ID和本機ID,如果不一致,則認為是破解版
SystemInfo.deviceUniqueIdentifier
本例子是以基礎配置數據為例,因此代碼中不提供該功能。
關於避免更新之后,玩家存檔被覆蓋
Unity提供了一個只讀路徑,放在該路徑下的文件,不會被軟件更新所影響。
Application.persistentDataPath
Json Helper類~~
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>The data.</returns> /// <param name="dataToEncrypt">Data to encrypt.</param> public string EncryptData (string dataToEncrypt) { //給明文加密用GetBytes 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>The data.</returns> /// <param name="dataToDecrypt">Data to decrypt.</param> public string DecryptData (string dataToDecrypt) { //給密文解密用FromBase64String 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">Tobject.</param> /// <param name="path">Path.</param> /// <typeparam name="T">The 1st type parameter.</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">Path.</param> /// <typeparam name="T">The 1st type parameter.</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 ("取消"); } }
