sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

Unity SQLite (Insert, Update, Select) のテスト

Unity で SQLite を使う例として「SQLiteUnityKit」を利用するプロジェクトの環境構築までは、前の記事で説明しました。

f:id:shikaku_sh:20200206133654p:plain
SQLite

今回はその続きで、基本的な SQL 文を書いてみて、ちゃんと実行できるかをテストしてみました。

こんな感じで、うまくできました。

f:id:shikaku_sh:20200207143319g:plain:w500
INSERT UPDATE SELECT をゲーム中に実行してみる

スクリプトの例

SQL を呼び出すスクリプト部分を、次のようにまとめてみました。

SQL 文(SQLiteUnityKit のクラス)をいろんなクラスが直接呼び出すのは、低レベルのコンポーネントと密接になりすぎるため、好ましいと思えなかった(not 疎結合)ので、アダプターみたいな役割のクラスを用意したところです。

インターフェースを挟むことで DB との結合も緩やかなものになっていいんだろうと思います。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SqlTester
{
    private static string _fileName = "sample.db";

    public static void Insert()
    {
        try
        {
            var db = new SqliteDatabase(_fileName);

            var rand = UnityEngine.Random.Range(2, 100);
            var name = $"athena No.{rand}";
            var data1 = rand;
            var data2 = "arrow";

            db.ExecuteQuery($"INSERT INTO charactors (name, data1, data2) VALUES ('{name}', {data1}, '{data2}')");
        }
        catch
        {
            throw;
        }
    }

    public static void Update()
    {
        try
        {
            var db = new SqliteDatabase(_fileName);

            var power = UnityEngine.Random.Range(100, 1000);

            db.ExecuteQuery($"UPDATE charactors SET data1 = {power} WHERE id = 1");
        }
        catch
        {
            throw;
        }
    }

    public static IEnumerable<string> Select()
    {
        var charactors = new List<string>();

        try
        {
            var db = new SqliteDatabase(_fileName);
            var query = db.ExecuteQuery("SELECT * FROM charactors");

            foreach (var row in query.Rows)
            {
                var id = row["id"];
                var name = row["name"];
                var data1 = row["data1"];
                var data2 = row["data2"];

                var text = $"ID:{id}, Name:{name}, DATA1:{data1}, DATA2:{data2}";

                charactors.Add(text);
            }

        }
        catch
        {
            throw;
        }

        return charactors;
    }
}

テスト

ボタンを押下したときに、それぞれに対応する SqlTester クラスのメソッドを実行します。

f:id:shikaku_sh:20200207144245p:plain:w500
シンプル

ボタンに設定したクラス

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestButton : MonoBehaviour
{
    public event EventHandler<IEnumerable<string>> OccurredSelect;

    public void InsertSQL()
    {
        Debug.Log("Insert Called.");
        SqlTester.Insert();
    }

    public void UpdateSQL()
    {
        Debug.Log("Update Called.");
        SqlTester.Update();
    }

    public void SelectSQL()
    {
        Debug.Log("Select Called.");
        var charactors = SqlTester.Select();

        OccurredSelect.Invoke(this, charactors);
    }
}

テキストに設定したクラス

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    public Text OwnerObject;
    private List<string> Charactors = new List<string>();
    private string ExceptionText = "";

    // Start is called before the first frame update
    void Start()
    {
        var test = GameObject.Find("SelectButton").GetComponent<TestButton>();

        test.OccurredSelect += (owner, args) => 
        {
            Debug.Log($"Called OccurredSelect Event at {nameof(Test)} Class.");

            var charactors = SqlTester.Select();

            AddCharactors(charactors, true);
        };

        try
        {
            var charactors = SqlTester.Select();

            AddCharactors(charactors, true);
        }
        catch (Exception ex)
        {
            ExceptionText = ex.Message;
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (OwnerObject == null) return;

        string text = "";

        if (ExceptionText == "")
        {
            if (Charactors.Count > 0)
            {
                text = string.Join("\r\n", Charactors);
            }
            else
            {
                text = "キャラクターが存在しません";
            }
        }
        else
        {
            text = ExceptionText;
        }

        OwnerObject.text = text;

    }

    private void AddCharactors(IEnumerable<string> charactors, bool isReset)
    {
        if (isReset)
        {
            Charactors.Clear();
        }

        foreach (var chara in charactors)
        {
            Charactors.Add(chara);
        }
    }
}

すこし小ネタとして、Select 文はイベント OccurredSelect 経由でデータをテキスト用のスクリプトクラスに渡しています。やり方はいろいろあると思います。最初に思いつきそうな Charactors プロパティを公開 (public) にして、別クラスが参照を得るのはイマイチに思ってこうしました。

OOP のポイント1

  • ユーザーには必要最小限の権限しか与えない
  • 書き換えが不要なら、読み取り専用に作る

だと思います。なんで、Test クラスの Charactors プロパティは、いろんな他クラスに見えてたら(勝手にイジられる可能性があるのは)私の意図にありません。

テスト結果

Android でも問題なく動作しているようです。

f:id:shikaku_sh:20200207143529j:plain:w500
できた

なお、Windows 環境だと Unity Editor で利用している DB はどこにあるのか認識に注意が必要かもしれません。AppData 内にあるため %LOCALAPPDATA%\..\LocalLow\ といったショートカットが便利かもしれません。

%LOCALAPPDATA%..\LocalLow\"Player.CompanyName"\"プロジェクト名"

f:id:shikaku_sh:20200207150910p:plain:w500
LocalLow なんてところにコピーされてました

サンプル

今回作成したプログラムは GitHub に公開しています。

参考

Using SQLite: Small. Fast. Reliable. Choose Any Three.

Using SQLite: Small. Fast. Reliable. Choose Any Three.


  1. 未確認飛行のブログ (URL) やクリーンアーキテクチャなども参考になった。