UnityでSQLiteを使う 【SQLiteUnityKitの問題修正と拡張】
SQLite Unity Kitには致命的な問題があります!
数日前からUnityでSQLiteを使ってのデータベースの読み込み/書き込みを実装していたのですが、利用させてもらっていた「SQLite Unity Kit」というソースコードに致命的な問題があったため、当初考えていたよりも実装に手間がかかってしまいました。
その問題とは、アプリのバージョンアップなどでデータベースファイルを更新した場合、端末内にある既存のデータを全て新しい方のデータベースファイルの内容で上書きしてしまうというものです。
敵のステータスとかそういった変更されることのないデータ(マスターデータ)が上書きされる分には何も問題は無いのですが、現在の所持ゴールドとかパーティー編成のようなユーザーデータまで上書き(つまり初期化)されてしまうので、何も考えずにSQLite Unity Kitを使い続けると痛い目に合う時がやって来ます。
そこで本記事ではSQLite Unity Kitを安心安全かつちょっとだけ便利に使うための方法について書いていきたいと思います。
ちょっと長くなりますが、最後までお付き合いください。
お話を始める前に、前提
本記事は以下の記事の通りに環境構築をしている人向けの内容になっています。
>> Windows64bit&Unity5(64bit)でSQLite。 – いまきたこうぎょう。
とても分かりやすく素晴らしい記事ですので、もしこれからUnity上にSQLiteの環境を作ろうと思っているのでしたらぜひ「いまきたこうぎょう。」さんの記事を参考にしてください。
SQLite Unity Kitの修正・拡張方針
まずは修正方針です。
- 修正前の動作(問題がある動作)
- データベースファイルのタイムスタンプを比較し、新しいファイルが存在する場合にデータベースを更新する。
- 更新時は古いデータベースファイルが新しいデータベースファイルで強制的に上書きされる。
- 修正後の動作(安心安全な動作)
- タイムスタンプ及びバージョン情報の2つを比較し、新しいファイルまたはバージョンが存在する場合にデータベースを更新する。
- 更新時はテーブルによって上書きするかマージするかを選択できる。
まず更新のトリガーがタイムスタンプだけだと色々不便なので、タイムスタンプとは別に独自にバージョン情報を持ちたいですね。
バージョン情報についてはデータベースではなく、PlayerPrefsを利用して管理したいと思います。
そして更新時の処理ですが、上でも述べたように古いデータを上書きしたいテーブルと、古いデータとマージをしたいテーブルとがあると思いますので、この2つを選べるようにしましょう。
修正プログラムの利用においての前提条件
今回のプログラムは、以下のルールに従ってデータベースが運用されているという前提で組まれています。
- 主キーがある。
- 主キーはint型で、カラム名は”id”である。
- テーブルはマスターデータ(終始、値は変更されない)とユーザーデータ(ユーザーの操作によって値が変更される)に分かれて設計されている。
ある程度データベースを組みなれた方なら、まあまあ納得の行く条件ではないかと思います。
修正プログラムと修正手順
以下の手順に従い、プログラムを修正・拡張していきます。
- データベースファイルとバージョンファイルを作成する
- PlayerPrefsにデータベースバージョンを保存できるようにする
- SqliteDatabaseクラスのコンストラクタを修正する
- データベースのテーブルクラスを作成する
- それら全てを管理するクラスを作成する
- 出来上がったものを使ってみる
なお、今回使用するプログラムはこちらに固めて置いてありますので、どうぞご自由にお持ちください。
※ただし本プログラムを使用したことにより発生した問題および損害について、当方は一切の責任を負いません。ご自分のプログラム上でのテストをきちんと行った上でご利用ください。
1. データベースファイルとバージョンファイルを作成する
まず下準備としてデータベースファイルとデータベースのバージョンファイルを作成し、Assets/StreamingAssetsに置きます。
今回はとりあえずこちらで用意したものを置いてもらえれば大丈夫です。
- database.db
- database_ver.txt
※こんな感じになると思います。
2. PlayerPrefsにデータベースバージョンを保存できるようにする
次にこちらのCSファイルをAssets/Scriptに置いてください。
- UserData.cs
※これは前回の記事「Unity PlayerPrefsの使い方に関する簡単なアイデア」で紹介したクラスです。
3. SqliteDatabaseクラスのコンストラクタを修正する
さらにSQLite Unity KitのメインファイルであるSqliteDatabase.csを開き、コンストラクタの処理を以下のように修正します。
※いまきたこうぎょう。さんの手順通りに構築しているなら、SqliteDatabase.csはすでにAssets/Scriptの中にあると思います。
- 修正前
public SqliteDatabase (string dbName) { pathDB = System.IO.Path.Combine (Application.persistentDataPath, dbName); //original path string sourcePath = System.IO.Path.Combine (Application.streamingAssetsPath, dbName); //if DB does not exist in persistent data folder (folder "Documents" on iOS) or source DB is newer then copy it if (!System.IO.File.Exists (pathDB) || (System.IO.File.GetLastWriteTimeUtc(sourcePath) > System.IO.File.GetLastWriteTimeUtc(pathDB))) {
- 修正後
public SqliteDatabase (string dbName, bool isCreate) { pathDB = System.IO.Path.Combine (Application.persistentDataPath, dbName); //original path string sourcePath = System.IO.Path.Combine (Application.streamingAssetsPath, dbName); // if (isCreate) {
修正点は、コンストラクタの引数が増えた(1行目)のと、最初のif文の条件が変更になった(8行目)、この2点だけです。簡単ですよね?
4. データベースのテーブルクラスを作成する
テーブルクラスは全てのテーブルの親となる基底クラスと、それを継承したサブクラスとに分かれます。
こちらのCSファイルをやはりAssets/Scriptに置いてください。
- AbstractDbTable.cs
- DummyMasterTable.cs
- DummyCaptureTable.cs
なお「DummyMasterTable.cs」「DummyCaptureTable.cs」は基底クラスを継承したサブクラスを定義してあるファイルなのですが、本来ならこれは利用する皆さんがそれぞれ用意してもらうものになります。
が、ひとまずビルドを通したいのでダミーとして用意させていただきました。
実際にこのプログラムを利用する際は、サブクラスは自分で作ってください。
サブクラス自動生成バッチ
一々コーディングするのも面倒臭いので、サブクラスの自動生成バッチを作りました。
- create_table.py
このバッチはテキストエディタで開けるので、使用するテーブルの構成に応じて適宜変更してください。
変更方法はファイル内のコメントを見てもらえば分かると思います。変更には1分もかかりません。
なお言語はpythonを使用しているので、pythonの実行環境を別途用意してください。
5. それら全てを管理するクラスを作成する
データベース周りを管理してくれるクラスを用意します。
こちらのCSファイルをAssets/Scriptに置いてください。
- MyDatabase.cs
6. 出来上がったものを使ってみる
最後に、以下のCSファイルをAssets/Scriptに置き、シーン内の適当なオブジェクトにAdd Componentしてください。
- GameController.cs
ここまでの手順を滞り無く進めると、Assets/Script下は以下のような構成になっていると思います。
実際の使い方ですがGameController.cs内にサンプルを記載しているので、ここで説明するよりもそちらを見てもらった方が早いでしょう。
思想としては、データベース操作の要であるSQLiteUnityKitを完全に隠蔽して直接触らせないように、またインスタンスがいくつも作られないようにしています。
テーブル内に格納されているデータも、使用する時に毎回型を指定してキャストするのではなくテーブルクラス内にて最初から定義してあるためコーディング中のうっかりミスによるバグが防げます。
また、主キーによるSelectのような基本的なクエリは基底クラスで実装済みですし、Insert/Updateについても自動生成バッチを使えば勝手に生成してくれますので、この辺りのクエリを一々書く必要もありません。
仮にINNER JOINなどを使った独自のクエリを作りたければ、自動生成されたサブクラスに自分でメソッドを追加していくことも可能です。
データベースにテーブルを追加したい場合は、database.dbに追加したいテーブルを定義して、AbstractDbTableクラスを継承した新しいサブクラスを作成して、それらをMyDatabaseクラスからアクセスできるようにインターフェースを追加すればできます。
それでは、良いSQLiteライフを!
今回は所々で微妙にプログラマー寄りな内容だったので、非プログラマーの方は理解できない箇所もあったかもしれません。
ただ、データベースのような技術を使おうとすると、どうしても一歩二歩踏み込んだ知識が必要になってきますね。それは仕方のないことだと思います。
ちなみに今回、やってることはそんなに難しくはありません。
ですので、非プログラマーの方は暇があったらプログラムの中身を見てその内容を理解してみてください。
逆にバリバリ本業のプログラマーの方は、ぜひ自分の使いやすいようにカスタマイズしてみてください。そしてそれをこちらにも教えてください(笑)
とりあえず、現時点ではそれなりに使えているので個人的に満足はしています(*´ェ`*)
[2016/01/23追記]
SQLite Unity Kitには他にも、日本語を扱おうとした時にエラーになる問題があるようですね。
詳しくはこちらの記事をご覧になってください。
本家にPR出したのか?
> とおりすがりさんへ
コメントありがとうございます。
PullRequestについてですが、こちらは出しておりません。
理由は本文にもあります通り、マスターデータのみ取り扱う場合は特に問題にはならないからです。
ですのでまあ本修正は、私自身が使いやすいようにするだけの魔改造であるとご認識ください。