1. 程式人生 > >Unity3D遊戲開發之SQLite讓資料庫開發更簡單

Unity3D遊戲開發之SQLite讓資料庫開發更簡單

各位朋友大家好,歡迎大家關注我的部落格,我是秦元培,我是部落格地址是http://blog.csdn.net/qinyuanpei。在經歷了一段時間的忙碌後,博主終於有時間來研究新的東西啦,今天部落格向和大家一起交流的內容是在Unity3D遊戲開發中使用SQLite進行資料庫開發,坦白來講,在我的技術體系中Web和資料庫是相對薄弱的兩個部分,因此正好這段時間專案需要和伺服器、資料庫進行互動,因此在接下來的文章中博主可能會更加傾向於講解這方面的內容,希望大家能夠喜歡啊!

一、什麼是SQLite?

SQLite是一款輕型的資料庫,是遵守ACID的關係型資料庫管理系統,它包含在一個相對小的C庫中,以嵌入式作為它的設計目標,它佔用資源非常的低,因此適合在嵌入式裝置如Android、Ruby on Rails等中使用。它能夠支援Windows/Linux/Unix等等主流的作業系統,同時能夠跟和C、C++、Ruby、Python、C#、PHP、Java等程式語言相結合。SQLite是一個以檔案形式存在的關係型資料庫,儘管無法實現分散式和橫向擴充套件,可是作為一個輕量級的嵌入式資料庫,它不需要系統提供服務支援,通過SDK直接操作檔案避免了對資料庫維護的相關事務,從這個角度來講它是一個出色的資料庫。

二、為什麼要選擇SQLite

好了,在瞭解了SQLite後,我們來了解下SQLite有哪些讓我們心動的特性,或者說我們為什麼要選擇SQLite,因為在這個世界上我們有太多的資料庫可以選擇,諸如Oracle、MySQL、SQLServer、DB2、NoSQL、MongoDB等等:

  • ACID事務
  • 零配置 – 無需安裝和管理配置
  • 儲存在單一磁碟檔案中的一個完整的資料庫
  • 資料庫檔案可以在不同位元組順序的機器間自由的共享
  • 支援資料庫大小至2TB
  • 足夠小, 大致13萬行C程式碼, 4.43M
  • 簡單, 輕鬆的API
  • 包含TCL繫結, 同時通過Wrapper支援其他語言的繫結
  • 良好註釋的原始碼, 並且有著90%以上的測試覆蓋率
  • 獨立: 沒有額外依賴
  • 原始碼完全的開源, 你可以用於任何用途, 包括出售它
  • 支援多種開發語言,C, C++, PHP, Perl, Java, C#,Python, Ruby等

三、Unity3D中的SQLite

在Unity3D中使用SQLite,我們首先要明白這樣一件事情,即我們這裡的使用的SQLite並非是通常意義上的SQLite.NET,而是經過移植後的Mono.Data.Sqlite。因為Unity3D基於Mono,因此使用移植後的Mono.Data.Sqlite能夠減少我們的專案在不同平臺上出現各種各樣的問題。在Unity3D中使用的SQLite以Mono.Data.Sqlite.dll即動態連結庫的形式給出,因此我們需要將這個檔案放置在專案目錄下的Plugins資料夾中,此外我們需要System.Data.dll或者Mono.Data.dll這兩個檔案新增到Plugins目錄中,因為我們需要的部分資料相關的API或者類都定義在這兩個檔案當中,這些檔案可以從

這裡直接下載。

PS:博主注意到在網上有使用Mono.Data.SQLiteClient.dll這個庫實現在Unity3D操作SQLite資料庫的相關文章,博主大概看了下,感覺和使用Mono.Data.Sqlite.dll這個庫大同小異,大家喜歡哪個就用哪個吧!哈哈!博主在開源社群找到一個版本庫,據說可以同時支援.NET和Mono,如果大家感興趣歡迎大家去測試啊,哈哈!

在正式開始寫程式碼前,我們首先來回顧下通常情況下資料庫讀寫的基本流程吧!

  • 定義資料庫連線字串(ConnectionString)完成資料庫連線的構造,建立或者開啟一個數據庫。
  • 定義相關的SQL命令(Command)通過這些命令實現對資料庫的增加、刪除、更新、讀取四種基本功能。
  • 在完成各種資料庫操作後及時關閉資料庫連線,解除對資料庫的連線和引用。

SQLite作為一款優秀的資料庫,在為其編寫資料庫相關程式碼時同樣遵循這樣的流程,考慮到對資料庫的增加、刪除、更新、讀取四種操作具有類似性和統一性,因此在動手寫Unity3D指令碼前,首先讓我們來編寫一個SQLite的輔助類SQLiteHelper.cs。該類程式碼定義如下:

using UnityEngine;
using System.Collections;
using Mono.Data.Sqlite;
using System;

public class SQLiteHelper
{
    /// <summary>
    /// 資料庫連線定義
    /// </summary>
    private SqliteConnection dbConnection;

    /// <summary>
    /// SQL命令定義
    /// </summary>
    private SqliteCommand dbCommand;

    /// <summary>
    /// 資料讀取定義
    /// </summary>
    private SqliteDataReader dataReader;

    /// <summary>
    /// 建構函式    
    /// </summary>
    /// <param name="connectionString">資料庫連線字串</param>
    public SQLiteHelper(string connectionString)
    {
        try{
            //構造資料庫連線
            dbConnection=new SqliteConnection(connectionString);
            //開啟資料庫
            dbConnection.Open();
        }catch(Exception e)
        {
            Debug.Log(e.Message);
        }
    }

    /// <summary>
    /// 執行SQL命令
    /// </summary>
    /// <returns>The query.</returns>
    /// <param name="queryString">SQL命令字串</param>
    public SqliteDataReader ExecuteQuery(string queryString)
    {
        dbCommand = dbConnection.CreateCommand();
        dbCommand.CommandText = queryString;
        dataReader = dbCommand.ExecuteReader();
        return dataReader;
    }

    /// <summary>
    /// 關閉資料庫連線
    /// </summary>
    public void CloseConnection()
    {
        //銷燬Command
        if(dbCommand != null){
            dbCommand.Cancel();
        }
        dbCommand = null;

        //銷燬Reader
        if(dataReader != null){
            dataReader.Close();
        }
        dataReader = null;

        //銷燬Connection
        if(dbConnection != null){
            dbConnection.Close();
        }
        dbConnection = null;
    }

    /// <summary>
    /// 讀取整張資料表
    /// </summary>
    /// <returns>The full table.</returns>
    /// <param name="tableName">資料表名稱</param>
    public SqliteDataReader ReadFullTable(string tableName)
    {
        string queryString = "SELECT * FROM " + tableName;
        return ExecuteQuery (queryString);
    }

    /// <summary>
    /// 向指定資料表中插入資料
    /// </summary>
    /// <returns>The values.</returns>
    /// <param name="tableName">資料表名稱</param>
    /// <param name="values">插入的數值</param>
    public SqliteDataReader InsertValues(string tableName,string[] values)
    {
        //獲取資料表中欄位數目
        int fieldCount=ReadFullTable(tableName).FieldCount;
        //當插入的資料長度不等於欄位數目時引發異常
        if(values.Length!=fieldCount){
            throw new SqliteException("values.Length!=fieldCount");
        }

        string queryString = "INSERT INTO " + tableName + " VALUES (" + values[0];
        for(int i=1; i<values.Length; i++)
        {
            queryString+=", " + values[i];
        }
        queryString += " )";
        return ExecuteQuery(queryString);
    }

    /// <summary>
    /// 更新指定資料表內的資料
    /// </summary>
    /// <returns>The values.</returns>
    /// <param name="tableName">資料表名稱</param>
    /// <param name="colNames">欄位名</param>
    /// <param name="colValues">欄位名對應的資料</param>
    /// <param name="key">關鍵字</param>
    /// <param name="value">關鍵字對應的值</param>
    public SqliteDataReader UpdateValues(string tableName,string[] colNames,string[] colValues,string key,string operation,string value)
    {
        //當欄位名稱和欄位數值不對應時引發異常
        if(colNames.Length!=colValues.Length) {
            throw new SqliteException("colNames.Length!=colValues.Length");
        }

        string queryString = "UPDATE " + tableName + " SET " + colNames[0] + "=" + colValues[0];
        for(int i=1; i<colValues.Length; i++) 
        {
            queryString+=", " + colNames[i] + "=" + colValues[i];
        }
        queryString += " WHERE " + key + operation + value;
        return ExecuteQuery(queryString);
    }

    /// <summary>
    /// 刪除指定資料表內的資料
    /// </summary>
    /// <returns>The values.</returns>
    /// <param name="tableName">資料表名稱</param>
    /// <param name="colNames">欄位名</param>
    /// <param name="colValues">欄位名對應的資料</param>
    public SqliteDataReader DeleteValuesOR(string tableName,string[] colNames,string[] operations,string[] colValues)
    {
        //當欄位名稱和欄位數值不對應時引發異常
        if(colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length) {
            throw new SqliteException("colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length");
        }

        string queryString = "DELETE FROM " + tableName + " WHERE " + colNames[0] + operations[0] + colValues[0];
        for(int i=1; i<colValues.Length; i++) 
        {
            queryString+="OR " + colNames[i] + operations[0] + colValues[i];
        }
        return ExecuteQuery(queryString);
    }

    /// <summary>
    /// 刪除指定資料表內的資料
    /// </summary>
    /// <returns>The values.</returns>
    /// <param name="tableName">資料表名稱</param>
    /// <param name="colNames">欄位名</param>
    /// <param name="colValues">欄位名對應的資料</param>
    public SqliteDataReader DeleteValuesAND(string tableName,string[] colNames,string[] operations,string[] colValues)
    {
        //當欄位名稱和欄位數值不對應時引發異常
        if(colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length) {
            throw new SqliteException("colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length");
        }

        string queryString = "DELETE FROM " + tableName + " WHERE " + colNames[0] + operations[0] + colValues[0];
        for(int i=1; i<colValues.Length; i++) 
        {
            queryString+=" AND " + colNames[i] + operations[i] + colValues[i];
        }
        return ExecuteQuery(queryString);
    }

    /// <summary>
    /// 建立資料表
    /// </summary> +
    /// <returns>The table.</returns>
    /// <param name="tableName">資料表名</param>
    /// <param name="colNames">欄位名</param>
    /// <param name="colTypes">欄位名型別</param>
    public SqliteDataReader CreateTable(string tableName,string[] colNames,string[] colTypes)
    {
        string queryString = "CREATE TABLE " + tableName + "( " + colNames [0] + " " + colTypes [0];
        for (int i=1; i<colNames.Length; i++) 
        {
            queryString+=", " + colNames[i] + " " + colTypes[i];
        }
        queryString+= "  ) ";
        return ExecuteQuery(queryString);
    }

    /// <summary>
    /// Reads the table.
    /// </summary>
    /// <returns>The table.</returns>
    /// <param name="tableName">Table name.</param>
    /// <param name="items">Items.</param>
    /// <param name="colNames">Col names.</param>
    /// <param name="operations">Operations.</param>
    /// <param name="colValues">Col values.</param>
    public SqliteDataReader ReadTable(string tableName,string[] items,string[] colNames,string[] operations, string[] colValues)
    {
        string queryString = "SELECT " + items [0];
        for (int i=1; i<items.Length; i++) 
        {
            queryString+=", " + items[i];
        }
        queryString += " FROM " + tableName + " WHERE " + colNames[0] + " " +  operations[0] + " " + colValues[0];
        for (int i=0; i<colNames.Length; i++) 
        {
            queryString+=" AND " + colNames[i] + " " + operations[i] + " " + colValues[0] + " ";
        }
        return ExecuteQuery(queryString);
    }
}

SQLiteHelper類主要實現了資料庫、資料表的建立以及資料表中記錄的增加、刪除、更新、讀取四種基本功能。該類最初由國外的Unity3D開發者釋出在Unity3D官方論壇,後來經宣雨鬆使用C#進行重寫,我在此基礎上進行了完善,再此對兩位大神的無私付出表示感謝。這裡要說明的有三點:

  • 一、在Unity3D編輯器下生成資料庫檔案(.db)預設位於和Assets目錄同級的位置,即專案的工程資料夾中。我們可以通過修改路徑在改變資料庫檔案的儲存位置,具體來講:
    Windows平臺:data source=Application.dataPath/資料庫名稱.db
    IOS平臺:data source=Application.persistentDataPath/資料庫名稱.db
    Android平臺:URL=file:Application.persistentDataPath/資料庫名稱.db(我想說Android平臺就是個奇葩,搞什麼特殊化嘛)

  • 二、確保Unity3D編輯器中的.NET版本和MonoDevelop中的.NET版本都為2.0版本,在Unity3D中打包匯出的程式可能不會保留資料庫檔案,因此需要手動將資料庫檔案拷貝到相應的位置,當然更加合理的方案是將資料庫檔案存放到StreamingAssets資料夾下,然後在第一次載入遊戲的時候將資料庫檔案複製到對應平臺上的存放位置。

  • 三、在使用InsertValues方法時請參考SQLite中欄位型別與C#中資料型別的對應關係,博主目前測試了int型別和string型別都沒有什麼問題,更多型別的資料請大家自行測試然後告訴博主測試的結果,如果大家有興趣擴充套件這個輔助類的話可以自行去擴充套件哦,嘿嘿!

好了,千呼萬喚始出來的時候到了,下面我們以一個例項來完成今天的專案講解,因為我們已經定義好了SQLite的輔助類,因此我們可以快速地編寫出下面的指令碼程式碼:

using UnityEngine;
using System.Collections;
using System.IO;
using Mono.Data.Sqlite;

public class SQLiteDemo : MonoBehaviour 
{
    /// <summary>
    /// SQLite資料庫輔助類
    /// </summary>
    private SQLiteHelper sql;

    void Start () 
    {
        //建立名為sqlite4unity的資料庫
        sql = new SQLiteHelper("data source=sqlite4unity.db");

        //建立名為table1的資料表
        sql.CreateTable("table1",new string[]{"ID","Name","Age","Email"},new string[]{"INTEGER","TEXT","INTEGER","TEXT"});

        //插入兩條資料
        sql.InsertValues("table1",new string[]{"'1'","'張三'","'22'","'[email protected]'"});
        sql.InsertValues("table1",new string[]{"'2'","'李四'","'25'","'[email protected]'"});

        //更新資料,將Name="張三"的記錄中的Name改為"Zhang3"
        sql.UpdateValues("table1", new string[]{"Name"}, new string[]{"'Zhang3'"}, "Name", "=", "'張三'");

        //插入3條資料
        sql.InsertValues("table1",new string[]{"3","'王五'","25","'[email protected]'"});
        sql.InsertValues("table1",new string[]{"4","'王五'","26","'[email protected]'"});
        sql.InsertValues("table1",new string[]{"5","'王五'","27","'[email protected]'"});

        //刪除Name="王五"且Age=26的記錄,DeleteValuesOR方法類似
        sql.DeleteValuesAND("table1", new string[]{"Name","Age"}, new string[]{"=","="}, new string[]{"'王五'","'26'"});

        //讀取整張表
        SqliteDataReader reader = sql.ReadFullTable ("table1");
        while(reader.Read()) 
        {
            //讀取ID
            Debug.Log(reader.GetInt32(reader.GetOrdinal("ID")));
            //讀取Name
            Debug.Log(reader.GetString(reader.GetOrdinal("Name")));
            //讀取Age
            Debug.Log(reader.GetInt32(reader.GetOrdinal("Age")));
            //讀取Email
            Debug.Log(reader.GetString(reader.GetOrdinal("Email")));
        }

        //讀取資料表中Age>=25的所有記錄的ID和Name
        reader = sql.ReadTable ("table1", new string[]{"ID","Name"}, new string[]{"Age"}, new string[]{">="}, new string[]{"'25'"});
        while(reader.Read()) 
        {
            //讀取ID
            Debug.Log(reader.GetInt32(reader.GetOrdinal("ID")));
            //讀取Name
            Debug.Log(reader.GetString(reader.GetOrdinal("Name")));
        }

        //自定義SQL,刪除資料表中所有Name="王五"的記錄
        sql.ExecuteQuery("DELETE FROM table1 WHERE NAME='王五'");

        //關閉資料庫連線
        sql.CloseConnection();
    }
}

在上面的程式碼中我們是在Start方法中建立了資料庫和資料表,然而在實際使用中我們需要判斷資料庫和資料表是否存在,因此如果你使用這段指令碼提示錯誤資訊,請確保資料庫和資料表是否已經存在。好了,下面的截圖展示了程式執行的結果:

資料庫效果演示

Unity3D效果展示

作為一個強大的資料庫怎麼能沒有圖形化的資料庫管理工具呢?所以這裡博主向大家推薦一個免安裝的小工具SqliteStudio,使用這個工具可以幫助我們方便地管理Sqlite資料庫裡的資料,這樣是不是比較方便呢?哈哈!這個工具可以從這裡下載哦!

SQLiteStudio介面演示

好了,今天的內容就是這樣了,為了寫這篇文章花了三個晚上準備,希望大家喜歡啊!如果大家覺得這篇文章有用,請繼續關注我的部落格,我是秦元培,我的部落格地址是http://blog.csdn.net/qinyuanpei

2015年11月3日更新內容:在不同的平臺上資料庫的儲存位置是不同的,在這裡給出一個參考的路徑,希望大家在處理移動端的時候注意這些問題啊!

        //各平臺下資料庫儲存的絕對路徑(通用)
        //PC:sql = new SQLiteHelper("data source=" + Application.dataPath + "/sqlite4unity.db");
        //Mac:sql = new SQLiteHelper("data source=" + Application.dataPath + "/sqlite4unity.db");
        //Android:sql = new SQLiteHelper("URI=file:" + Application.persistentDataPath + "/sqlite4unity.db");
        //iOS:sql = new SQLiteHelper("data source=" + Application.persistentDataPath + "/sqlite4unity.db");

        //PC平臺下的相對路徑
        //sql = new SQLiteHelper("data source="sqlite4unity.db");
        //編輯器:Assets/sqlite4unity.db
        //編譯後:和AppName.exe同級的目錄下,這裡比較奇葩
        //當然可以用更隨意的方式sql = new SQLiteHelper("data source="D://SQLite//sqlite4unity.db");
        //確保路徑存在即可否則會發生錯誤

        //如果是事先建立了一份資料庫
        //可以將這個資料庫放置在StreamingAssets目錄下然後再拷貝到
        //Application.persistentDataPath + "/sqlite4unity.db"路徑即可