数据库 数据存储对于游戏开发来说是必不可少的,在常规的游戏开发中,后端服务器会采用MySQL(关系型数据库) ,**MongoDB(非关系型数据库)**等作为数据库存储。
关于关系型和菲关系型数据库的学习理解参考:
网络通信和服务器学习 
本章节是在没有后端但又有玩家数据存储需求的前提下诞生的,通过调研**SQLite(关系型数据库)**正是定位本地高效数据库而非CS结构的数据库,所以本章节通过深入学习SQLite,弄懂如何正确高效的把本地玩家数据存储到本地数据库。
SQLite What 首先来看看SQLite官网的一个介绍:SQLite is not directly comparable to client/server SQL database engines such as MySQL, Oracle, PostgreSQL, or SQL Server since SQLite is trying to solve a different problem. 
Why 那么什么时候适用于SQLite?什么时候适用于MySQL(CS)了?SQLite 
Client/Server Applications(CS结构的程序–需要大量客户端服务器通信存储) 
High-volume Websites(高吞吐量的网站) 
Very large datasets(数据存储量大–数据大) 
High Concurrency(高并发–很多人同时写入) 
 
How 这里本人明确了暂时是打算用于单机版游戏的本地数据库,所以倾向于用SQLite来做。
Unity(游戏开发引擎) 
SQLite4Unity3d(第三方给Unity封装好的SQLite库–支持Window,Android(ARM64貌似也支持了)和IOS) 
Navicat(可视化数据库工具) 
SQL(数据库查询语言) 
 
目标 :
编写一个数据库操作UI界面,支持数据库的增删改查操作(方便学习使用SQLite4Unity3d提供的接口访问SQLite数据库) 
利用Navicat查看Window本地存储的数据库数据(学会Navicat的基本使用和数据库SQL相关操作) 
打包编译Window和Android版本到真机上查看效果(确保多平台的兼容性) 
 
SQLite4Unity3d实战 环境准备 :SQLite4Unity3d Navicat Premium SQL学习1  or SQL学习2 
Note:
SQLite4Unity3d uses only the synchronous part of sqlite-net, so all the calls to the database are synchronous. SQLite4Unity3d有这么一句官方提示,可以看出所有的数据库操作都是同步的。这里考虑到都是单机本地存储同时数据量也不会很大,所以同步异步不是很重要,所以并不影响个人使用。 ) 
PC实战 接下来结合SQLite4Unity3d来实战学习数据库的增删改查等操作。第1步 
第2步 
第3步 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  Player {     [PrimaryKey ]     public  int  UID { get ; set ; }     public  string  FirstName { get ; set ; }     public  string  LastName { get ; set ; }     public  int  Age { get ; set ; }     public  override  string  ToString ()     {         return  $"[Player: UId={UID} , FirstName={FirstName} ,  LastName={LastName} , Age={Age} ]" ;     } } 
第4步 
1 2 3 DatabaseFolderPath = $"{Application.persistentDataPath} /" ; mSQLiteConnect = new  SQLiteConnection($"{DatabaseFolderPath} /{DatabaseName} " , SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create); 
可以看到本来StreamingAssets目录下没有数据库文件的这一下就创建成功了。
第5步 
1 mSQLiteConnect.CreateTable<Player>(); 
第6步 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 mSQLiteConnect.Insert(new  Player         {             UID = 1 ,             FirstName = "Huan" ,             LastName = "Tang" ,             Age = 29 ,         });         mSQLiteConnect.Insert(new  Player         {             UID = 3 ,             FirstName = "XiaoYun" ,             LastName = "Zhou" ,             Age = 28 ,         });         mSQLiteConnect.Insert(new  Player         {             UID = 2 ,             FirstName = "Jiang" ,             LastName = "Fan" ,             Age = 28 ,         });         mSQLiteConnect.Insert(new  Player         {             UID = 5 ,             FirstName = "ZhenLiang" ,             LastName = "Li" ,             Age = 29 ,         });         mSQLiteConnect.Insert(new  Player         {             UID = 4 ,             FirstName = "XiaoLin" ,             LastName = "Kuang" ,             Age = 28 ,         }); 
第7步 
1 mSQLiteConnect.Delete<Player>(deleteuid); 
第7步 
1 2 valideplayer.Age = newage; mSQLiteConnect.Update(valideplayer); 
第8步 
1 var  rownumber = mSQLiteConnect.DeleteAll<Player>();
第9步 
1 var  rownumberaffected = mSQLiteConnect.DropTable<Player>();
1 2 3 4 5 6 7 8 9 10 11 public  int  DropTable <T >(){     var  map = GetMapping (typeof  (T));     var  query = string .Format("drop table if exists \"{0}\"" , map.TableName);     return  Execute (query); } 
关于SQL更多学习参考:SQL 教程 SQL Tutorial 
第10步 
Android实战 为了验证SQLite4Unity3d的跨平台能力,接下来打包验证真机:
Android真机数据库文件查看(本人使用的ES文件浏览器 ):
Android真机数据库详细信息PC查看:
可以看到Android真机上数据库的一些基础操作都成功了的。IOS这里就暂时不验证了,根据Github上的介绍理论上是支持的。
Note:
真机可读取目录修改为Application.persistentDataPath,因为Application.streamingAssetsPath在真机上是只读目录。 
真机使用ES文件浏览器 来查看程序目录下的数据库文件 
 
SQLite4Unity3d进阶 学习了SQLite4Unity3d的基础使用,接下来要考虑的是实战使用时,如何封装一层数据库管理,方便游戏里的所有数据库数据都成功的加载读取修改保存。
这里主要是对于SQLite.cs的简单封装,用于支持多数据库创建,以及数据库创建路径管理,以及自定义数据库表的封装访问。
上代码之前,先简单看下封装的代码设计:
DatabaseManager.cs(对多数据库创建访问以及路径规划进行封装支持 ) 
BaseDatabase.cs(数据库基类抽象 –实现对数据库操作进行封装) 
BaseTableData.cs(数据库表数据基类抽象 –实现对数据库表操作进行封装) 
BaseIntTableData.cs(int做主键的表数据抽象 –实现对int主键表的操作封装) 
BaseStringTableData.cs(string做主键的表数据抽象 –实现对string主键表的操作封装) 
 
DatabaseManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 namespace  TH.Modules.Data {                                        public  class  DatabaseManager  : SingletonTemplate <DatabaseManager >     {                                    private  readonly  static  string  DatabaseFolderPath = Application.persistentDataPath + "/Database/" ;                                             private  Dictionary<string , SQLiteConnection> ConnectedDatabaseMap;         public  DatabaseManager ()         {                          if (!Directory.Exists(DatabaseFolderPath))             {                 Directory.CreateDirectory(DatabaseFolderPath);             }             ConnectedDatabaseMap = new  Dictionary<string , SQLiteConnection>();         }                                                               public  void  openDatabase (string  databasename, SQLiteOpenFlags openflags = SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, bool  storeDateTimeAsTicks = false          {             if (!isDatabaseConnected(databasename))             {                 var  databasepath = DatabaseFolderPath + databasename;                 var  connection = new  SQLiteConnection(databasepath, openflags, storeDateTimeAsTicks);                 ConnectedDatabaseMap.Add(databasename, connection);                 Debug.Log($"连接数据库:{databasename} " );             }             else              {                 Debug.Log($"数据库:{databasename} 已连接,请勿重复连接!" );             }         }                                                      public  SQLiteConnection getDatabaseConnection (string  databasename         {             SQLiteConnection connection;             if  (ConnectedDatabaseMap.TryGetValue(databasename, out  connection))             {                 return  connection;             }             else              {                 return  null ;             }         }                                             public  void  closeDatabase (string  databasename         {             SQLiteConnection connection;             if (ConnectedDatabaseMap.TryGetValue(databasename, out  connection))             {                 connection.Close();                 ConnectedDatabaseMap.Remove(databasename);                 Debug.Log($"关闭数据库:{databasename} " );             }             else              {                 Debug.LogError($"未连接数据库:{databasename} ,关闭失败!" );             }         }                                    public  void  closeAllDatabase ()         {             foreach  (var  databasename in  ConnectedDatabaseMap.Keys)             {                 closeDatabase(databasename);             }         }                                    public  void  clear ()         {             closeAllDatabase();         }                                                               private  bool  isDatabaseConnected (string  databasename         {             return  ConnectedDatabaseMap.ContainsKey(databasename);         }         #region  辅助方法                                                                public  string  getTableAllDatasInOneString <T >(string  databasenamewhere  T : new ()         {             SQLiteConnection sqliteconnection = getDatabaseConnection(databasename);             if  (sqliteconnection != null )             {                 var  querytable = sqliteconnection.Table<T>();                 if  (querytable != null )                 {                     var  result = string .Empty;                     foreach  (var  data in  querytable)                     {                         result += data.ToString();                         result += "\n" ;                     }                     return  result;                 }                 else                  {                     return  string .Empty;                 }             }             else              {                 return  string .Empty;             }         }         #endregion       } } 
TODO:
现阶段是默认存在包外的persistentDataPath,对于数据库如何做好加密管理避免被轻易修改,这个问题还有待学习思考。 数据库表是否存在还没找到方式判定(暂时是从流程上确保表存在的)  
BaseDatabase.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 namespace  TH.Modules.Data {                                   public  abstract  class  BaseDatabase      {                                    protected  SQLiteConnection mSQLiteConnect;                                    public  virtual  string  DatabaseName         {             get              {                 return  $"{GetType().Name} .db" ;             }         }         protected  BaseDatabase ()         {         }                                    public  void  LoadDatabase ()         {             mSQLiteConnect = DatabaseManager.Singleton.openDatabase(DatabaseName);         }                                             public  bool  IsConnected ()         {             return  mSQLiteConnect != null ;         }                                                                                                                                                                                             public  bool  CreateTable <T >() where  T : BaseTableData, new ()         {             if (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,创建类名:{typeof (T).Name} 表失败!" );                 return  false ;             }                                                                              mSQLiteConnect.CreateTable<T>();             return  true ;         }                                                      public  bool  InsertDataI <T >(T data ) where  T : BaseIntTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,插入类名:{typeof (T).Name} 表数据失败!" );                 return  false ;             }                                                                 mSQLiteConnect.Insert(data);             return  true ;         }                                                      public  bool  InsertOrReplaceDataI <T >(T data ) where  T : BaseIntTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,插入类名:{typeof (T).Name} 表数据失败!" );                 return  false ;             }                                                                 mSQLiteConnect.InsertOrReplace(data);             return  true ;         }                                                      public  bool  InsertDataS <T >(T data ) where  T : BaseStringTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,插入类名:{typeof (T).Name} 表数据失败!" );                 return  false ;             }                                                                 mSQLiteConnect.Insert(data);             return  true ;         }                                                      public  bool  InsertOrReplaceDataS <T >(T data ) where  T : BaseStringTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,插入类名:{typeof (T).Name} 表数据失败!" );                 return  false ;             }                                                                 mSQLiteConnect.InsertOrReplace(data);             return  true ;         }                                                      public  bool  DeleteDataByUIDI <T >(int  uidwhere  T : BaseIntTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,删除类名:{typeof (T).Name} 表UID:{uid} 数据失败!" );                 return  false ;             }             var  deletedNumber = mSQLiteConnect.Delete<T>(uid);             Debug.Log($"数据库:{DatabaseName} ,删除类名:{typeof (T).Name} 表UID:{uid} 数量:{deletedNumber} " );             return  deletedNumber > 0 ;         }                                                      public  bool  DeleteDataByUIDS <T >(string  uidwhere  T : BaseStringTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,删除类名:{typeof (T).Name} 表数据失败!" );                 return  false ;             }             var  deletedNumber = mSQLiteConnect.Delete<T>(uid);             Debug.Log($"数据库:{DatabaseName} ,删除类名:{typeof (T).Name} 表UID:{uid} 数量:{deletedNumber} " );             return  deletedNumber > 0 ;         }                                                      public  bool  UpdateDataI <T >(T data ) where  T : BaseIntTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,插入类名:{typeof (T).Name} 表数据失败!" );                 return  false ;             }                                                                 var  updatedNumber = mSQLiteConnect.Update(data);             Debug.Log($"数据库:{DatabaseName} ,更新类名:{typeof (T).Name} 表UID:{data.UID} 数量:{updatedNumber} " );             return  updatedNumber > 0 ;         }                                                      public  bool  UpdateDataS <T >(T data ) where  T : BaseStringTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,插入类名:{typeof (T).Name} 表数据失败!" );                 return  false ;             }                                                                 var  updatedNumber = mSQLiteConnect.Update(data);             Debug.Log($"数据库:{DatabaseName} ,更新类名:{typeof (T).Name} 表UID:{data.UID} 数量:{updatedNumber} " );             return  updatedNumber > 0 ;         }                                                      public  T GetDataByUIDI <T >(int  uidwhere  T : BaseIntTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,获取类名:{typeof (T).Name} 表数据失败!" );                 return  null ;             }             var  data = mSQLiteConnect.Find<T>((databaseTable) => databaseTable.UID == uid);             if  (data == null )             {                 Debug.LogError($"数据库:{DatabaseName} ,获取类名:{typeof (T).Name} 的UID:{uid} 表数据不存在!" );                 return  null ;             }             return  data;         }                                                      public  T GetDataByUIDS <T >(string  uidwhere  T : BaseStringTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,获取类名:{typeof (T).Name} 表数据失败!" );                 return  null ;             }             var  data = mSQLiteConnect.Find<T>((databaseTable) => databaseTable.UID == uid);             if  (data == null )             {                 Debug.LogError($"数据库:{DatabaseName} ,获取类名:{typeof (T).Name} 的UID:{uid} 表数据不存在!" );                 return  null ;             }             return  data;         }                                                      public  TableQuery <T > GetAllData <T >() where  T : BaseTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,获取类名:{typeof (T).Name} 表数据失败!" );                 return  null ;             }             var  tbData = mSQLiteConnect.Table<T>();             if  (tbData == null )             {                 Debug.LogError($"数据库:{DatabaseName} ,获取类名:{typeof (T).Name} 表所有数据不存在!" );                 return  null ;             }             return  tbData;         }                                             public  int  DeleteTableAll <T >() where  T : BaseTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,删除类名:{typeof (T).Name} 表所有数据失败!" );                 return  0 ;             }                                                                              var  deletedNumber = mSQLiteConnect.DeleteAll<T>();             Debug.Log($"数据库:{DatabaseName} ,删除类名:{typeof (T).Name} 表所有数据数量:{deletedNumber} " );             return  deletedNumber;         }                                             public  int  DeleteTable <T >() where  T : BaseTableData, new ()         {             if  (!IsConnected())             {                 Debug.LogError($"数据库:{DatabaseName} 未连接,删除类名:{typeof (T).Name} 表失败!" );                 return  0 ;             }             var  deletedNumber = mSQLiteConnect.DropTable<T>();             Debug.Log($"数据库:{DatabaseName} ,删除类名:{typeof (T).Name} 表数量:{deletedNumber} " );             return  deletedNumber;         }                                    public  void  CloseDatabase ()         {             DatabaseManager.Singleton.closeDatabase(DatabaseName);             mSQLiteConnect = null ;         }     } } 
BaseTableData.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 namespace  TH.Modules.Data {                         public  abstract  class  BaseTableData      {                                    public  string  TableName         {             get              {                 return  GetType().Name;             }         }         public  BaseTableData ()         {         }                                    public  override  string  ToString ()         {             return  $"[TableName:{TableName} ]" ;         }     } } 
BaseIntTableData.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 namespace  TH.Modules.Data {                         public  abstract  class  BaseIntTableData  : BaseTableData      {                                    [PrimaryKey ]         public  int  UID         {             get ;             set ;         }         public  BaseIntTableData () : base ()         {         }                                             public  BaseIntTableData (int  uidbase ()         {             UID = uid;         }                                    public  override  string  ToString ()         {             return  $"[TableName:{TableName}  UID:{UID} ]" ;         }     } } 
BaseStringTableData.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 namespace  TH.Modules.Data {                         public  abstract  class  BaseStringTableData  : BaseTableData      {         [PrimaryKey ]         public  string  UID         {             get ;             set ;         }         public  BaseStringTableData () : base ()         {         }                                             public  BaseStringTableData (string  uidbase ()         {             UID = uid;         }                                    public  override  string  ToString ()         {             return  $"[TableName:{TableName}  UID:{UID} ]" ;         }     } } 
GameDatabase.cs(自定义游戏数据库)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public  class  GameDatabase  : BaseDatabase {                    public  static  GameDatabase Singleton     {         get          {             if (mSingleton != null )             {                 return  mSingleton;             }             mSingleton = new  GameDatabase();             return  mSingleton;         }     }     private  static  GameDatabase mSingleton;     public  GameDatabase () : base ()     {     } } 
Player.cs(自定义玩家数据表结构)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public  class  Player  : BaseIntTableData {                    public  string  FirstName     {         get ;         set ;     }                    public  string  LastName     {         get ;         set ;     }                    [Indexed ]     public  int  Age     {         get ;         set ;     }     public  Player () : base ()     {     }                                        public  Player (int  uid, string  firstName, string  lastName, int  agebase (uid )     {         FirstName = firstName;         LastName = lastName;         Age = age;     }                         public  override  string  ToString ()     {         return  $"[Player: UID={UID} , FirstName={FirstName} ,  LastName={LastName} , Age={Age} ]" ;     } } 
进阶版的实战代码见Github源码:
SQLiteStudy 
结论 
SQLite适用于非CS结构,存储数据量不大,不会多人并发操作数据库的情况(好比单机游戏的数据库)。 数据量大或者多人操作频繁或者需要CS架构支持,更适合MySQL+Redis SQLite4Unity3d通过反射和Linq对SQLite的封装实现了对于SQL无感和ORM(Object Relational Mapping)数据库访问方式。 SQLite4Unity3d支持多平台,适合单机游戏数据存储  
Reference SQLite4Unity3d 
Github地址 SQLiteStudy