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);
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.
A consulta será exibida buscando o que existir no banco de dados:
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!