Unity + SQLite

UnitySQLITE

Hoje aprenderemos como usar um database local em Unity. Isso é útil para poder realizar busca e armazenamento de informações de forma eficiente. Obviamente, nem todo game necessita de um banco de dados, e PlayerPrefs cumpre o papel de salvar configurações simples com tuplas de chaves e valores, mas não é nada ideal para grandes conjuntos  de informações. Sem demoras, vamos lá!

Dentro do diretório Assets, criar uma pasta chamada Plugins

Copiar as dlls Mono.Data.Sqlite e System.Data localizadas em C:\Program Files\Unity\Editor\Data\Mono\lib\mono\2.0. Colar também a dll sqlite3.dll obtida em https://www.sqlite.org/download.html (usar versão x64: Precompiled Binaries for Windows – sqlite-dll-win64-x64[…].zip).

Criar um script chamado AccessTest, para realizar os comandos de acesso ao database.

Usando o SQLite Expert Personal ou outro editor SQLite, criar um database chamado unity_database.

Criar uma tabela com campos: Id – integer, primary key, auto incremento, Name – string, Age – integer, Background – string.

CREATE TABLE [Character] (
  [Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 
  [Name] CHAR NOT NULL, 
  [Age] INT NOT NULL, 
  [Background] CHAR NOT NULL);

sqlite

Após criado o banco, copiá-lo para a pasta Assets (ou crie nela de uma vez).

Definir uma string de conexão com o banco dentro de AccessTest:

string connectionString = "URI=file:" + Application.dataPath + "/unity_database.db";

Adicione as seguintes usings ao cabeçalho da classe:

using Mono.Data.Sqlite;
using System.Data;
using System;

Dentro de AccessTest, crie o método ReadCharacters(), que fará a leitura e impressão de todos os dados da tabela Characters. Não se esqueça de chamar esse método dentro de Start(), por exemplo. Crie e abra a conexão sqlite com a string de conexão, crie um novo comando e informe a consulta. Em seguida, o comando executa a leitura no banco de dados, e retorna os valores ao datareader, que possui índices de acordo com as colunas da tabela que foram informadas na consulta. Para acessar cada coluna, basta então especificar o índice da mesma.

using (var connection = new SqliteConnection(connectionString))
{
       connection.Open();
       using (var command = connection.CreateCommand())
       {
             command.CommandText = "SELECT * FROM Character";
             command.CommandType = CommandType.Text;

             using (var reader = command.ExecuteReader())
             {
                    while (reader.Read())
                    {
                        print((string)reader[1] + "\n" +
                              (int)reader[2] + "\n" +
                              (string)reader[3]);
                    }
             }
       }
}

Crie agora o método void Save(), que permitirá a inserção de um novo personagem no banco de dados. Os objetos this.name, this.age e this.backgroud são InputFields que devem se referenciados para se obter a informação digitada pelo usuário na UI.

Cada um deles é armazenado numa variável a parte (nesse momento, verificações de valores inválidos podem ser realizadas) e  o valor digitado em age é convertido para int para ser compatível com a coluna do banco.

Após criada e aberta a conexão, a conexão cria uma transação com o banco em modo de Begin, indicando o início da transação. Em seguida, cria-se um comando como anteriormente. O comando é preenchido com um insert para a tabela. O primeiro parâmetro é passado como null pois foi definido como auto-incremento no banco de dados, e o SQLite se encarregará de inserir seu valor automaticamente. Os próximos valores são na verdade parâmetros, pois iniciam com o caractere ‘@’. O comando então adiciona novos objetos SqliteParameter indicando o nome do parâmetro definido anteriormente e qual o seu valor. A transação atual é atribuída ao comando, que em seguida é executado e a quantidade de registros alterados/inseridos é retornada e armazenada em rows. Finalmente a transação é comitada se tudo ocorrer corretamente. Do contrário, a exceção gerada será capturada em catch, a transação é desfeita e o erro será impresso.

public void Save()
{
        string connectionString = "URI=file:" + Application.dataPath + "/unity_database.db";
        string pName = this.name.text;
        int pAge = int.Parse(age.text);
        string pBackground = background.text;

        using (var connection = new SqliteConnection(connectionString))
        {
            connection.Open();
            using (var transaction = connection.BeginTransaction())
            {
                using (var command = connection.CreateCommand())
                {
                    try
                    {
                        command.CommandText = "INSERT INTO Character VALUES (null, @name, @age, @background);";
                        command.CommandType = CommandType.Text;
                        command.Parameters.Add(new SqliteParameter("name", pName));
                        command.Parameters.Add(new SqliteParameter("age", pAge));
                        command.Parameters.Add(new SqliteParameter("background", pBackground));

                        command.Transaction = transaction;

                        var rows = command.ExecuteNonQuery();

                        transaction.Commit();

                        print(rows + " affected");
                    }
                    catch(Exception e)
                    {
                        transaction.Rollback();
                        print(e.Message);
                    }
                }
            }
        }

A interface de usuário com os InputFields pode facilmente ser criada por você. Não se esqueça de inserir um botão cujo evento Click ative o método Save(), e também não se esqueça de adicionar o script AccessTest em algum game object ativo.

unity

A consulta será exibida buscando o que existir no banco de dados:

consulta

Após essa configuração e acesso iniciais, os comandos de DELETE e UPDATE são bem transparentes de serem criados: é uma questão de SQL, e não mais Unity. Existem técnicas mais eficientes e que reduzem a quantidade de código a ser escrito para se acessar um database, como frameworks ORM (Object Relational Mapping) que inclusive traduzem as tabelas em classes C#, facilitando a manipulação de dados como objetos, mas isso é assunto para outra hora.

O projeto pode ser encontrado aqui.

Até a próxima!

Resposta