Что такое SQLite и зачем она нам нужна?
SQLite – компактная встраиваемая реляционная СУБД, которая является довольно таки популярной. Важный плюс SQLite – это кроссплатформенность, по этому мы можем использовать SQLite для различных платформ. SQLite можно использовать когда нужна скорость и компактность, по этому, при возникновении проблемы хранения данных я надумал решить её использованием данной СУБД.
Как работать с SQLite?
Для создания и редактирование нашей БД есть большое количество бесплатных утилит и плагинов для браузеров, лично я буду использовать DB Browser (SQLite), меня он зацепил своей простотой, а работа с различными плагинами в браузере, мне показалась не очень удобной. В общем, кто как хочет, так и работает. Использую DB Browser можно спокойно создать таблицы, сделать между ними связи и заполнить их данными не прибегая к использованию SQL. Так же, в DB Browser вы можете делать всё ручками с помощью SQLite, так что, тут уже кому как удобнее.
Создание и заполнение тестовой БД
Создаём базу данных в Assets/StreamingAssets нашего проекта (у меня это db.bytes, так как Unity понимает только *.bytes для баз данных мы будем использовать именно это расширение). Чисто для примера я создал такую БД со следующими таблицами:
1) Таблица «Player», которая описывает сущность игрока:
CREATE TABLE "Player" ( "id_player" INTEGER NOT NULL, "nickname" TEXT NOT NULL, PRIMARY KEY("id_player") );
Заполнил её следующими данными:

2) Таблица «Scores», которая введена для повышения уровня нормализации БД
CREATE TABLE "Scores" ( "id" INTEGER NOT NULL, "id_player" INTEGER NOT NULL, "score" INTEGER NOT NULL, PRIMARY KEY("id"), FOREIGN KEY("id_player") REFERENCES "Player"("id_player") );
Заполнил её следующими данными:

Подключение библиотек
Создаём базу данных в Assets/StreamingAssets нашего проекта (у меня это db.bytes), далее нам нужно подключить библиотеки для работы с этой БД. Качаем файлик sqlite3.dll с официального сайта для работы с SQLite в Windows. Что б подружить данную СКБД с Android у меня ушло пару дней, так как библиотека указанная в данной статье оказалась не рабочей, лично у меня не вышло с ней работать на Android, постоянно лезли ошибки, по этому заливаю найденную где-то в просторах интернета эту версию библиотеки для Android. Размещаем библиотеки здесь — Assets/Plugins/sqlite.dll и Assets/Plugins/Android/sqlite.so.
После всех этих манипуляций копируем System.Data.dll и Mono.Data.Sqlite.dll с C:\Program Files (x86)\Unity \Editor\Data\Mono\lib\mono\2.0 и вставляем Assets/Plugins вашего Unity проекта. Хочу заметить, что в 2018 версии Unity может писать что System.Data.dll уже подключен и происходит конфликт двух одинаковых файлов. Собственно, решается это просто, не удаляем только что вставленный System.Data.dll.
Структура библиотек должна быть такая:
Assets/Plugins/Mono.Data.Sqlite.dll – просто надо :)
Assets/Plugins/System.Data.dll – аналогичная причина
Assets/Plugins/sqlite3.dll – для работы с SQLite на Windows
Assets/Plugins/Android/libsqlite3.so – для работы с SQLite на Android
Написание скрипта для работы с БД
И наконец то мы можем приступить к написанию скрипта для работы с созданной БД. Для начала, создадим файл MyDataBase и подключим библиотеки System.Data, Mono.Data.Sqlite, System.IO, сделаем класс MyDataBase статическим и, естественно, уберём наследование от MonoBehaviour. Добавим 3 приватные переменные и константу с названием файла БД. У нас должно выйти, что-то такое:
using UnityEngine; using System.Data; using Mono.Data.Sqlite; using System.IO; static class MyDataBase { private const string fileName = "db.bytes"; private static string DBPath; private static SqliteConnection connection; private static SqliteCommand command; }
Это всё конечно хорошо, но всё же работать с БД мы не сможем. Для работы с БД мы должны получить путь к ней, предлагаю сделать статический конструктор, который как раз и будет получать путь к БД (Напомню, что БД лежит в StreamingAssets).
static MyDataBase() { DBPath = GetDatabasePath(); } /// <summary> Возвращает путь к БД. Если её нет в нужной папке на Андроиде, то копирует её с исходного apk файла. </summary> private static string GetDatabasePath() { #if UNITY_EDITOR return Path.Combine(Application.streamingAssetsPath, fileName); #if UNITY_STANDALONE string filePath = Path.Combine(Application.dataPath, fileName); if(!File.Exists(filePath)) UnpackDatabase(filePath); return filePath; #elif UNITY_ANDROID string filePath = Path.Combine(Application.persistentDataPath, fileName); if(!File.Exists(filePath)) UnpackDatabase(filePath); return filePath; #endif } /// <summary> Распаковывает базу данных в указанный путь. </summary> /// <param name="toPath"> Путь в который нужно распаковать базу данных. </param> private static void UnpackDatabase(string toPath) { string fromPath = Path.Combine(Application.streamingAssetsPath, fileName); WWW reader = new WWW(fromPath); while (!reader.isDone) { } File.WriteAllBytes(toPath, reader.bytes); }
Примечание. Нам нужно распаковывать БД в указанные пути (Application.dataPath/db.bytes для Windows и Application.persistentDataPath/db.bytes для Android) так как папка StreamingAssets, после сборки, имеет атрибут ReadOnly (кроме Android) и мы не сможем записывать что-то в БД. Собственно, для того, что б можно было записывать что либо в БД, мы и распаковываем нашу базу данных. Подробно сказано какие пути, под какую платформу нужно использовать в этой статье.
Напишем методы открытия подключения и закрытия, а так же метод, который будет выполнять запрос, который не требует возврата значений, допустим, INSERT, UPDATE, CREATE, DELETE, DROP.
/// <summary> Этот метод открывает подключение к БД. </summary> private static void OpenConnection() { connection = new SqliteConnection("Data Source=" + DBPath); command = new SqliteCommand(connection); connection.Open(); } /// <summary> Этот метод закрывает подключение к БД. </summary> public static void CloseConnection() { connection.Close(); command.Dispose(); } /// <summary> Этот метод выполняет запрос query. </summary> /// <param name="query"> Собственно запрос. </param> public static void ExecuteQueryWithoutAnswer(string query) { OpenConnection(); command.CommandText = query; command.ExecuteNonQuery(); CloseConnection(); }
Чудесно, теперь наш скрипт может выполнять запросы на модификацию данных. Но как же быть с очень важным SELECT? Я решил, что возвращаемое значение метода, который должен выполнять запрос на выборку данных, должен иметь тип DataTable или же string, если требуется получить 1 значение. Для этого напишем 2 метода:
/// <summary> Этот метод выполняет запрос query и возвращает ответ запроса. </summary> /// <param name="query"> Собственно запрос. </param> /// <returns> Возвращает значение 1 строки 1 столбца, если оно имеется. </returns> public static string ExecuteQueryWithAnswer(string query) { OpenConnection(); command.CommandText = query; var answer = command.ExecuteScalar(); CloseConnection(); if (answer != null) return answer.ToString(); else return null; } /// <summary> Этот метод возвращает таблицу, которая является результатом выборки запроса query. </summary> /// <param name="query"> Собственно запрос. </param> public static DataTable GetTable(string query) { OpenConnection(); SqliteDataAdapter adapter = new SqliteDataAdapter(query, connection); DataSet DS = new DataSet(); adapter.Fill(DS); adapter.Dispose(); CloseConnection(); return DS.Tables[0]; }
Готово, теперь у нас есть простой скрипт, который может делать запросы на модификацию и выборку данных. Давайте сейчас напишем скрипт ScoreManager. Который будет получать таблицу лучших результатов отсортированных по убыванию. И, для проверки, отобразим в Debug.Log ник лидера и его очки.
using System.Collections; using System.Collections.Generic; using System.Data; using UnityEngine; public class ScoreManager : MonoBehaviour { private void Start() { // Получаем отсортированную таблицу лидеров DataTable scoreboard = MyDataBase.GetTable("SELECT * FROM Scores ORDER BY score DESC;"); // Получаем id лучшего игрока int idBestPlayer = int.Parse(scoreboard.Rows[0][1].ToString()); // Получаем ник лучшего игрока string nickname = MyDataBase.ExecuteQueryWithAnswer($"SELECT nickname FROM Player WHERE id_player = {idBestPlayer};"); Debug.Log($"Лучший игрок {nickname} набрал {scoreboard.Rows[0][2].ToString()} очков."); } }
Вот что получаем при запуске:

Спасибо за внимание, с удовольствием приму конструктивную критику.

