Всем привет, данная публикация будет посвящена работе с встраиваемой реляционной базой данных SQLite в Unity. Данная статья написана новичком для новичков с целью показания работы с SQLite, предполагается, что вы знаете основы SQL. Так как в интернете нет ясного тутора для новичков, я решил занять эту нишу. В данной статье мы напишем простенький класс для работы с данной СУБД, который можно использовать для решения широкого круга задач (локализация, сохранение данных, ведение разных таблиц).
SQLite – компактная встраиваемая реляционная СУБД, которая является довольно таки популярной. Важный плюс SQLite – это кроссплатформенность, по этому мы можем использовать SQLite для различных платформ. SQLite можно использовать когда нужна скорость и компактность, по этому, при возникновении проблемы хранения данных я надумал решить её использованием данной СУБД.
Для создания и редактирование нашей БД есть большое количество бесплатных утилит и плагинов для браузеров, лично я буду использовать DB Browser (SQLite), меня он зацепил своей простотой, а работа с различными плагинами в браузере, мне показалась не очень удобной. В общем, кто как хочет, так и работает. Использую DB Browser можно спокойно создать таблицы, сделать между ними связи и заполнить их данными не прибегая к использованию SQL. Так же, в DB Browser вы можете делать всё ручками с помощью SQLite, так что, тут уже кому как удобнее.
Создаём базу данных в Assets/StreamingAssets нашего проекта (у меня это db.bytes, так как Unity понимает только *.bytes для баз данных мы будем использовать именно это расширение). Чисто для примера я создал такую БД со следующими таблицами:
1) Таблица «Player», которая описывает сущность игрока:
Заполнил её следующими данными:
2) Таблица «Scores», которая введена для повышения уровня нормализации БД
Заполнил её следующими данными:
Создаём базу данных в 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 приватные переменные и константу с названием файла БД. У нас должно выйти, что-то такое:
Это всё конечно хорошо, но всё же работать с БД мы не сможем. Для работы с БД мы должны получить путь к ней, предлагаю сделать статический конструктор, который как раз и будет получать путь к БД (Напомню, что БД лежит в StreamingAssets).
Примечание. Нам нужно распаковывать БД в указанные пути (Application.dataPath/db.bytes для Windows и Application.persistentDataPath/db.bytes для Android) так как папка StreamingAssets, после сборки, имеет атрибут ReadOnly (кроме Android) и мы не сможем записывать что-то в БД. Собственно, для того, что б можно было записывать что либо в БД, мы и распаковываем нашу базу данных. Подробно сказано какие пути, под какую платформу нужно использовать в этой статье.
Напишем методы открытия подключения и закрытия, а так же метод, который будет выполнять запрос, который не требует возврата значений, допустим, INSERT, UPDATE, CREATE, DELETE, DROP.
Чудесно, теперь наш скрипт может выполнять запросы на модификацию данных. Но как же быть с очень важным SELECT? Я решил, что возвращаемое значение метода, который должен выполнять запрос на выборку данных, должен иметь тип DataTable или же string, если требуется получить 1 значение. Для этого напишем 2 метода:
Готово, теперь у нас есть простой скрипт, который может делать запросы на модификацию и выборку данных. Давайте сейчас напишем скрипт ScoreManager. Который будет получать таблицу лучших результатов отсортированных по убыванию. И, для проверки, отобразим в Debug.Log ник лидера и его очки.
Вот что получаем при запуске:
Спасибо за внимание, с удовольствием приму конструктивную критику.
Что такое 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()} очков.");
}
}
Вот что получаем при запуске:
Спасибо за внимание, с удовольствием приму конструктивную критику.
Комментарии (6)
p4p
08.03.2019 17:29Только sqilte и использую. Удобно выстраивать логику. Но как заметили некоторые комментаторы, есть подводные камни с правами доступа и проблемы на некоторых китайцах. Допустим игрок может изменить место храните по умолчанию уже после установки игры и база будет недоступна.
MrMureno
простите, но мало чем отличается от статьи https://habr.com/ru/post/181239/ (хотя может я что упустил бегло проглядывая)
плюс вы пропустили важный момент (из старой доброй статьи) — права на чтение только, если говорить не только про андроид, а про кроссплатформенность.
Так что папка стриминг ассетов — хороша для чтения, а вот если что-то записывать хотим — лучше так не хранить. (у вас оно и не хранится, а заготовка базы данных распаковывается в другое место, просто как то пропущен этот момент в пояснениях, а это довольно важно, как по мне)
Leopotam
Ну и если уж говорить про кроссплатформенность — автор может ради интереса попробовать переключить платформу на iOS и удивиться ошибкам компиляции.
best_programmer Автор
Просмотрел я статью, указанную вами, в ней упор больше на работу с БД через Linq. После прочтения этой статьи нужно будет ещё гуглить как работать с той библиотекой более детально, например те же виды атрибутов в этой библиотеки, в статье ничего не сказано про них. В статье показан пример кода и классов, которых мало для нормального понимания (хотя б потому что не показано как устанавливать внешние ключи). В статье максимум теории что, куда, зачем.
Моя статья ориентирована, что б человек, знающий основы SQL, мог работать с SQLite сразу же после прочтения этой статьи, без необходимости дополнительной гуглёжки. И в моей статье упор на код.
К отличиям статей, можно ещё отнести, что у меня описана ошибка про System.Data, по своему опыту могу сказать, что даже такие ошибки могут потратить пару часов у новичков. И в той статье, указана ссылка на нерабочую библиотеку для работы с Android, лично у меня не вышло подружить ту библиотеку, а библиотека на оф. сайте SQLite уже формата *.arm для AndroidStudio и в Unity не работает.
Я считаю, что Ваше замечание по поводу отсутствие пояснения с распаковкой БД достаточно важно, по этому сейчас это поправлю.
Спасибо большое Вам.)
MrMureno
на офф саите sqlite-android-3270200.aar
а формат aar — давно юнити переваривает адекватно.
Хотя надо конечно пробовать подставить, может и вправду намудрили что-то внутри, но маловероятно.
best_programmer Автор
Сейчас проверил всё, если я ничего не упустил, для работы с *.aar файлами в Unity нужно использовать AndroidJavaClass. Т.е., если работать с *.aar файлом SQLite придётся переписывать полностью скрипт работы с БД под Android, что слегка усложняет разработку всего приложения в целом. А при использовании моего *.so файла, ничего переписывать не нужно и код на этих двух платформах будет абсолютно одинаковым, за исключением путей.