Salvando dados no Unity3D usando SQLite
(Parte 4 da série de comparação de persistência)
Nossa jornada de explorar as opções oferecidas para uso quando se trata de persistência no Unity levará, nesta parte, aos bancos de dados. Mais especificamente, ao SQLite.
O SQLite é um banco de dados baseado em C e usado em muitas áreas. Ele existe há muito tempo e também encontrou seu caminho no mundo do Unity. Durante esta série de tutoriais, vimos opções como
PlayerPrefs
no Unity e, por outro lado, File
e BinaryWriter
/BinaryReader
fornecidas pelo framework .NET subjacente.
Aqui está uma visão geral da série completa:
- Parte 1: PlayerPrefs
- Parte 2: Arquivos
- Parte 3: BinaryReader e BinaryWriter
- Parte 4: SQL (este tutorial)
- Parte 5: Realm Unity SDK (em breve)
- Parte 6: Comparação de todas essas opções
Assim como nas partes anteriores, este tutorial tambĂ©m está disponĂvel no nosso repositĂłrio de exemplos do Unity no branch comparação de permanĂŞncia.

Cada parte Ă© classificada em uma pasta. Os trĂŞs scripts que veremos neste tutorial estĂŁo na subpasta
SQLite
. Mas, primeiro, vamos dar uma olhada no jogo de exemplo em si e no que temos de preparar no Unity antes de começarmos a codificação propriamente dita.Observe que, se já tiver trabalhado em qualquer um dos outros tutoriais desta série, você pode pular esta seção, pois estamos usando o mesmo exemplo em todas as partes da série, para que seja mais fácil ver as diferenças entre as abordagens.
O objetivo desta série de tutoriais é mostrar a você uma maneira rápida e fácil de dar os primeiros passos nas várias formas de persistir os dados em seu jogo.
Portanto, o exemplo que usaremos será o mais simples possĂvel no prĂłprio editor para que possamos nos concentrar totalmente no cĂłdigo real que precisamos escrever.

Uma cápsula simples na cena será usada para que possamos interagir com um objeto de jogo. Em seguida, registramos os cliques na cápsula e mantemos a contagem de ocorrências.

Quando vocĂŞ abre um modelo de 3D limpo, basta escolher
GameObject
-> 3D Object
-> Capsule
.Em seguida, você pode adicionar scripts à cápsula ativando-a na hierarquia e usando
Add Component
no inspetor.

Os scripts que adicionaremos a esta cápsula mostrando os diferentes mĂ©todos terĂŁo a mesma estrutura básica que está disponĂvel em
HitCountExample.cs
.1 using UnityEngine; 2 3 /// <summary> 4 /// This script shows the basic structure of all other scripts. 5 /// </summary> 6 public class HitCountExample : MonoBehaviour 7 { 8 // Keep count of the clicks. 9 [private int hitCount; // 1 ] 10 11 private void Start() // 2 12 { 13 // Read the persisted data and set the initial hit count. 14 hitCount = 0; // 3 15 } 16 17 private void OnMouseDown() // 4 18 { 19 // Increment the hit count on each click and save the data. 20 hitCount++; // 5 21 } 22 }
A primeira coisa que precisamos adicionar é um contador para os cliques na cápsula (1). Adicione um
[SerilizeField]
aqui para que você possa observá-lo ao clicar na cápsula no editor Unity.Sempre que o jogo for iniciado (2), queremos ler a contagem atual de acertos da persistência e inicializar
hitCount
de acordo (3). Isso é feito no método Start()
que é chamado sempre que uma cena é carregada para cada objeto de jogo ao qual esse script está anexado.A segunda parte disso é salvar as alterações, o que queremos fazer sempre que registrarmos um clique do mouse. A mensagem do Unity para isso é
OnMouseDown()
(4). Esse método é chamado toda vez que o GameObject
ao qual esse script está anexado é clicado (com um clique do botão esquerdo do mouse). Nesse caso, incrementamos o hitCount
(5) que, por fim, será salvo pelas várias opções mostradas nesta série de tutoriais.(Consulte
SqliteExampleSimple.cs
no repositĂłrio para ver a versĂŁo finalizada).Agora vamos garantir que nossa contagem de acertos seja persistente para que possamos continuar jogando da prĂłxima vez que iniciarmos o jogo.
O SQLite nĂŁo está incluĂdo por padrĂŁo em um novo projeto Unity e tambĂ©m nĂŁo está disponĂvel diretamente por meio do gerenciador de pacotes Unity. Temos que instalar dois componentes para começar a usá-lo.
Primeiro, acesse https://sqlite.org/download.html e escolha os
Precompiled Binaries
para seu sistema operacional. Descompacte-o e adicione os dois arquivos—sqlite3.def
e sqlite3.dll
— à pasta Plugin
em seu projeto Unity.Em seguida, abra um explorador de arquivos no diretório de instalação do Unity Hub e vá para o seguinte subdiretório:
1 Unity/Hub/Editor/2021.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/unity
Lá, você encontrará o arquivo
Mono.Data.Sqlite.dll
que também precisa ser movido para a pasta Plugins
em seu projeto Unity. O resultado ao voltar para o Editor deve ser parecido com este:
Agora que os preparativos terminaram, queremos adicionar nosso primeiro script Ă capsula. Semelhante ao
HitCountExample.cs
, crie um novo C# script
e nomeie-o .
SqliteExampleSimple
Ao abri-lo, a primeira coisa que queremos fazer Ă© importar SQLite adicionando
using Mono.Data.Sqlite;
e using System.Data;
no topo do arquivo (1).A seguir, veremos como salvar sempre que a contagem de ocorrĂŞncias for alterada, o que acontece durante
OnMouseDown()
. Primeiro precisamos abrir uma conexĂŁo com o banco de dados. Isso Ă© oferecido pela biblioteca SQLite por meio da classe IDbConnection
(2), que representa uma conexão aberta com o banco de dados. Como precisaremos de uma conexão para carregar os dados mais tarde, extrairemos a abertura de uma conexão de banco de dados em outra função e a chamaremos private IDbConnection CreateAndOpenDatabase()
(3).Lá, primeiro definimos um nome para nosso arquivo de banco de dados. Vou chamá-lo de
MyDatabase
por enquanto. Por consequĂŞncia, o URI deve ser "URI=file:MyDatabase.sqlite"
(4). EntĂŁo podemos criar uma conexĂŁo com esse banco de dados usando new SqliteConnection(dbUri)
(5) e abri-lo com dbConnection.Open()
(6).1 using Mono.Data.Sqlite; // 1 2 using System.Data; // 1 3 using UnityEngine; 4 5 public class SqliteExampleSimple : MonoBehaviour 6 { 7 // Resources: 8 // https://www.mono-project.com/docs/database-access/providers/sqlite/ 9 10 [private int hitCount = 0; ] 11 12 void Start() // 13 13 { 14 // Read all values from the table. 15 IDbConnection dbConnection = CreateAndOpenDatabase(); // 14 16 IDbCommand dbCommandReadValues = dbConnection.CreateCommand(); // 15 17 dbCommandReadValues.CommandText = "SELECT * FROM HitCountTableSimple"; // 16 18 IDataReader dataReader = dbCommandReadValues.ExecuteReader(); // 17 19 20 while (dataReader.Read()) // 18 21 { 22 // The `id` has index 0, our `hits` have the index 1. 23 hitCount = dataReader.GetInt32(1); // 19 24 } 25 26 // Remember to always close the connection at the end. 27 dbConnection.Close(); // 20 28 } 29 30 private void OnMouseDown() 31 { 32 hitCount++; 33 34 // Insert hits into the table. 35 IDbConnection dbConnection = CreateAndOpenDatabase(); // 2 36 IDbCommand dbCommandInsertValue = dbConnection.CreateCommand(); // 9 37 dbCommandInsertValue.CommandText = "INSERT OR REPLACE INTO HitCountTableSimple (id, hits) VALUES (0, " + hitCount + ")"; // 10 38 dbCommandInsertValue.ExecuteNonQuery(); // 11 39 40 // Remember to always close the connection at the end. 41 dbConnection.Close(); // 12 42 } 43 44 private IDbConnection CreateAndOpenDatabase() // 3 45 { 46 // Open a connection to the database. 47 string dbUri = "URI=file:MyDatabase.sqlite"; // 4 48 IDbConnection dbConnection = new SqliteConnection(dbUri); // 5 49 dbConnection.Open(); // 6 50 51 // Create a table for the hit count in the database if it does not exist yet. 52 IDbCommand dbCommandCreateTable = dbConnection.CreateCommand(); // 6 53 dbCommandCreateTable.CommandText = "CREATE TABLE IF NOT EXISTS HitCountTableSimple (id INTEGER PRIMARY KEY, hits INTEGER )"; // 7 54 dbCommandCreateTable.ExecuteReader(); // 8 55 56 return dbConnection; 57 } 58 }
Agora podemos trabalhar com este banco de dados SQLite. No entanto, antes de podermos realmente adicionar dados a ele, precisamos configurar uma estrutura. Isso significa criar e definir tabelas, que Ă© a maneira como a maioria dos bancos de dados sĂŁo organizados. A captura de tela a seguir mostra o estado final que criaremos neste exemplo.

Ao acessar ou modificar o banco de dados, usamos
IDbCommand
(6), que representa uma declaração SQL que pode ser executada em um banco de dados.Vamos criar uma nova tabela e definir algumas colunas usando o seguinte comando (7):
1 "CREATE TABLE IF NOT EXISTS HitCountTableSimple (id INTEGER PRIMARY KEY, hits INTEGER )"
Então, o que essa declaração significa? Primeiro, precisamos declarar o que queremos fazer, que é
CREATE TABLE IF NOT EXISTS
. Em seguida, precisamos nomear esta tabela, que será igual ao script em que estamos trabalhando agora: .
HitCountTableSimple
Por Ăşltimo, mas nĂŁo menos importante, precisamos definir como essa nova tabela deve ficar. Isso Ă© feito nomeando todas as colunas como uma tupla:
(id INTEGER PRIMARY KEY, hits INTEGER )
. A primeira define uma coluna id
do tipo INTEGER
que Ă© o nosso PRIMARY KEY
. A segunda define uma coluna hits
do tipo .
INTEGER
Depois de atribuir essa instrução como
CommandText
, precisamos chamar ExecuteReader()
(8) em dbCommandCreateTable
para executá-lo.Agora, de volta a
OnMouseClicked()
. Com o dbConnection
criado, agora podemos definir outro IDbCommand
(9) para modificar a nova tabela que acabamos de criar e adicionar alguns dados. Desta vez, o CommandText
(10) será:1 "INSERT OR REPLACE INTO HitCountTableSimple (id, hits) VALUES (0, " + hitCount + ")"
Vamos decifrar isso também:
INSERT OR REPLACE INTO
adiciona uma nova variável a uma tabela ou a atualiza, se ela já existir. A seguir está o nome da tabela que queremos inserir, HitCountTableSimple
. Isto Ă© seguido por uma tupla de colunas que gostarĂamos de alterar, (id, hits)
. A instrução VALUES (0, " + hitCount + ")
define então valores que devem ser inseridos, também como uma tupla. Nesse caso, apenas escolhemos 0
para a chave e usamos qualquer que seja o hitCount
atual como valor.Em vez de criar a tabela, executamos este comando chamando
ExecuteNonQuery()
(11) nela.A diferença pode ser definida da seguinte forma:
O ExecuteReader é usado para qualquer conjunto de resultados com várias linhas/colunas (por exemplo, SELECT col1, col2 de alguma tabela). ExecuteNonQuery normalmente é usado para instruções SQL sem resultados (por exemplo, UPDATE, INSERT etc.).
Tudo o que resta a fazer Ă©
Close()
corretamente (12) o banco de dados.Como podemos realmente verificar se isso funcionou antes de continuarmos a ler os valores do banco de dados de novo? A maneira mais fácil seria simplesmente conferir o banco de dados. Existem muitas ferramentas disponĂveis para fazer isso. Uma das opções de cĂłdigo aberto Ă© https://sqlitebrowser.org/.
Depois de baixá-lo e instalá-lo, tudo que você precisa fazer é
File -> Open Database
, navegar até seu projeto do Unity e selecionar o arquivo MyDatabase.sqlite
. Se vocĂŞ entĂŁo escolher o Table
HitCountTableSimple
, o resultado deve ser semelhante a este:
Vá em frente e execute o jogo. Clique algumas vezes na cápsula e verifique a alteração no Inspector. Quando você voltar ao navegador do banco de dados e clicar em Atualizar, o mesmo número deverá aparecer na coluna
value
da tabela.Na próxima vez que iniciarmos o jogo, queremos carregar essa contagem de visitas do banco de dados novamente. Usamos a função
Start()
(13) já que ela só precisa ser feita quando a cena carrega. Como antes, precisamos obter um controle do banco de dados com um IDbConnection
(14) e criar um novo IDbCommand
(15) para ler os dados. Como há apenas uma tabela e um valor, é bastante simples por enquanto. Podemos apenas ler all data
usando:1 "SELECT * FROM HitCountTableSimple"
Nesse caso,
SELECT
significa read the following values
, seguido por um *
que indica a leitura de todos os dados. A palavra-chave FROM
entĂŁo especifica a tabela que deve ser lida, que Ă© novamente HitCountTableSimple
. Finalmente, executamos este comando usando ExecuteReader()
(17) pois queremos dados de volta. Esses dados sĂŁo salvos em um IDataReader
, da documentação:Fornece um meio de ler um ou mais fluxos somente de encaminhamento de conjuntos de resultados obtidos pela execução de um comando em uma fonte de dados e é implementado por provedores de dados .NET que acessam bancos de dados relacionais.
IDataReader
aborda seu conteĂşdo de forma de Ăndice, onde a ordem corresponde a uma das colunas na tabela SQL. EntĂŁo, em nosso caso, id
tem Ăndice 0 e hitCount
tem Ăndice 1. A forma como esses dados sĂŁo lidos Ă© linha por linha. Cada vez que chamamos dataReader.Read()
(18), lemos outra linha da tabela. Como sabemos que há apenas uma linha na tabela, podemos apenas atribuir o value
dessa linha ao hitCount
usando seu Ăndice 1. O value
Ă© do tipo INTEGER
entĂŁo precisamos usar GetInt32(1)
para lĂŞ-lo e especificar o Ăndice do campo que queremos ler como um parâmetro, id
sendo 0
e value
sendo .
1
Como antes, no final queremos
Close()
adequadamente o banco de dados (20).Quando vocĂŞ reiniciar o jogo novamente, agora vocĂŞ deve ver um valor inicial para
hitCount
que Ă© lido do banco de dados.(Consulte
SqliteExampleExtended.cs
no repositório para ver a versão finalizada).Na seção anterior, examinamos a versão mais simples de um exemplo de banco de dados que você pode imaginar. Uma tabela, uma linha e apenas um valor no qual estamos interessados. Mesmo que um banco de dados como o SQLite possa lidar com qualquer tipo de complexidade, queremos poder compará-lo com as partes anteriores desta série de tutoriais e, portanto, examinaremos o mesmo
Extended example
, usando três contagens de ocorrências em vez de uma e usando chaves modificadoras para identificá-las: Shift
e .
Control
Vamos começar criando um novo script
SqliteExampleExtended.cs
e anexá-lo à capsula. Copie o código de SqliteExampleSimple
e aplique as seguintes alterações a ele. Primeiro, defina as três contagens de acertos:1 [private int hitCountUnmodified = 0; ] 2 [private int hitCountShift = 0; ] 3 [private int hitCountControl = 0; ]
A detecção de qual tecla foi pressionada (além do clique do mouse) pode ser feita usando a classe
Input
que faz parte do Unity SDK. Chamando Input.GetKey()
, podemos verificar se uma determinada tecla foi pressionada. Isso deve ser feito durante Update()
, que é a função do Unity chamada a cada quadro. A razão para isso está indicada na documentação:Nota: os sinalizadores de entrada não são redefinidos até a atualização. Você deve fazer todas as chamadas de entrada no loop de atualização.
A tecla que foi pressionada precisa ser lembrada ao receber o evento
OnMouseDown()
. Portanto, precisamos adicionar um campo privado para salvá-lo assim:1 private KeyCode modifier = default;
Agora, a função
Update()
pode ser assim:1 private void Update() 2 { 3 // Check if a key was pressed. 4 if (Input.GetKey(KeyCode.LeftShift)) // 1 5 { 6 // Set the LeftShift key. 7 modifier = KeyCode.LeftShift; // 2 8 } 9 else if (Input.GetKey(KeyCode.LeftControl)) // 1 10 { 11 // Set the LeftControl key. 12 modifier = KeyCode.LeftControl; // 2 13 } 14 else // 3 15 { 16 // In any other case reset to default and consider it unmodified. 17 modifier = default; // 4 18 } 19 }
Primeiro, verificamos se a tecla
LeftShift
ou LeftControl
foi pressionada (1) e, em caso afirmativo, salvamos o KeyCode
correspondente em modifier
. Observe que vocĂŞ pode usar o nome string
da chave que está procurando ou o enum KeyCode
, mais seguro para digitação.Caso nenhuma dessas duas teclas tenha sido pressionada (3), definimos isso como o estado
unmodified
e simplesmente definimos modifier
de volta ao seu estadodefault
(4).Antes de prosseguirmos para
OnMouseClicked()
, você pode se perguntar que alterações precisamos fazer na estrutura do banco de dados criada por private IDbConnection CreateAndOpenDatabase()
. Acontece que, na verdade, nĂŁo precisamos mudar nada. Apenas usaremos o id
introduzido na seção anterior e salvaremos o KeyCode
(que é um número inteiro) nele.Para poder comparar as duas versões posteriormente, mudaremos o nome da tabela e a chamaremos de
HitCountTableExtended
:1 dbCommandCreateTable.CommandText = "CREATE TABLE IF NOT EXISTS HitCountTableExtended (id INTEGER PRIMARY KEY, hits INTEGER)";
Agora, vejamos como a detecção de cliques do mouse precisa ser modificada para levar em conta essas chaves:
1 private void OnMouseDown() 2 { 3 var hitCount = 0; 4 switch (modifier) // 1 5 { 6 case KeyCode.LeftShift: 7 // Increment the hit count and set it to PlayerPrefs. 8 hitCount = ++hitCountShift; // 2 9 break; 10 case KeyCode.LeftControl: 11 // Increment the hit count and set it to PlayerPrefs. 12 hitCount = ++hitCountControl; // 2 13 break; 14 default: 15 // Increment the hit count and set it to PlayerPrefs. 16 hitCount = ++hitCountUnmodified; // 2 17 break; 18 } 19 20 // Insert a value into the table. 21 IDbConnection dbConnection = CreateAndOpenDatabase(); 22 IDbCommand dbCommandInsertValue = dbConnection.CreateCommand(); 23 dbCommandInsertValue.CommandText = "INSERT OR REPLACE INTO HitCountTableExtended (id, hits) VALUES (" + (int)modifier + ", " + hitCount + ")"; 24 dbCommandInsertValue.ExecuteNonQuery(); 25 26 // Remember to always close the connection at the end. 27 dbConnection.Close(); 28 }
Primeiro, precisamos verificar qual modificador foi usado no Ăşltimo quadro (1). Dependendo disso, incrementamos a contagem de ocorrĂŞncias correspondente e a atribuĂmos Ă variável local
hitCount
(2). Como antes, contamos qualquer outra chave além de LeftShift
e LeftControl
como unmodified
.Agora, tudo o que precisamos alterar na segunda parte desta função é o
id
que definimos estaticamente como 0
antes e, em vez disso, usar KeyCode
. A declaração SQL atualizada deve ter a seguinte aparência:1 "INSERT OR REPLACE INTO HitCountTableExtended (id, hits) VALUES (" + (int)modifier + ", " + hitCount + ")"
A tupla
VALUES
agora precisa definir (int)modifier
(observe que enum
precisa ser convertido em int
) e hitCount
como seus dois valores.Como antes, podemos iniciar o jogo e ver a parte de salvamento em ação primeiro. Clique algumas vezes até que o Inspector mostre alguns números para as três contagens de acertos:

Agora, vamos abrir o navegador de banco de dados novamente e, desta vez, escolher o
HitCountTableExtended
no menu suspenso:
Como você pode ver, há três linhas, com o
value
sendo igual Ă s contagens de ocorrĂŞncias que vocĂŞ vĂŞ no Inspetor. Na coluna id
, vemos as trĂŞs entradas para KeyCode.None
(0), KeyCode.LeftShift
(304) e KeyCode.LeftControl
(306).Finalmente, vamos ler esses valores do banco de dados ao reiniciar o jogo.
1 void Start() 2 { 3 // Read all values from the table. 4 IDbConnection dbConnection = CreateAndOpenDatabase(); // 1 5 IDbCommand dbCommandReadValues = dbConnection.CreateCommand(); // 2 6 dbCommandReadValues.CommandText = "SELECT * FROM HitCountTableExtended"; // 3 7 IDataReader dataReader = dbCommandReadValues.ExecuteReader(); // 4 8 9 while (dataReader.Read()) // 5 10 { 11 // The `id` has index 0, our `value` has the index 1. 12 var id = dataReader.GetInt32(0); // 6 13 var hits = dataReader.GetInt32(1); // 7 14 if (id == (int)KeyCode.LeftShift) // 8 15 { 16 hitCountShift = hits; // 9 17 } 18 else if (id == (int)KeyCode.LeftControl) // 8 19 { 20 hitCountControl = hits; // 9 21 } 22 else 23 { 24 hitCountUnmodified = hits; // 9 25 } 26 } 27 28 // Remember to always close the connection at the end. 29 dbConnection.Close(); 30 }
A primeira parte funciona basicamente de forma inalterada, criando um
IDbConnection
(1) e um IDbCommand
(2) e, em seguida, lendo todas as linhas novamente com SELECT *
(3), mas dessa vez a partir de HitCountTableExtended
, finalizando com a execução efetiva do comando com ExecuteReader()
(4).Para a prĂłxima parte, precisamos ler cada linha (5) e verificar a qual
KeyCode
ela pertence. Pegamos o id
do Ăndice 0
(6) e o hits
do Ăndice 1
(7) como antes. Em seguida, verificamos o id
em relação ao KeyCode
(8) e o atribuĂmos ao hitCount
(9).Agora reinicie o jogo e experimente!
O SQLite Ă© uma das opções quando se trata de persistĂŞncia. Se vocĂŞ leu os tutoriais anteriores, percebeu que usá-lo pode, a princĂpio, parecer um pouco mais complicado do que o simples
PlayerPrefs
. VocĂŞ precisa aprender uma "linguagem" adicional para poder se comunicar com seu banco de dados. E, como a natureza do SQL nĂŁo Ă© o formato mais fácil de ler, pode parecer um pouco intimidador no inĂcio. Mas o mundo dos bancos de dados oferece muito mais do que pode ser mostrado em um breve tutorial como este!Uma das desvantagens dos arquivos simples ou
PlayerPrefs
que vimos foi ter os dados de forma estruturada, especialmente quando eles se tornam mais complicados ou quando é necessário desenhar relacionamento entre os objeto. Analisamos o JSON como uma forma de melhorar essa situação, mas assim que precisamos alterar o formato e migrar nossa estrutura, isso se torna bastante complicado. A criptografia é outro tópico que pode ser importante para você -PlayerPrefs
e File
não são seguros e podem ser lidos facilmente. Essas são apenas algumas das áreas em que um banco de dados como o SQLite pode ajudá-lo a cumprir os requisitos de persistência dos dados.No próximo tutorial, examinaremos outro banco de dados, o Realm Unity SDK, que oferece vantagens semelhantes às do SQLite e, ao mesmo tempo, é muito fácil de usar.