Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
C#chevron-right

Salvando dados no Unity3D usando SQLite

Dominic Frei13 min read • Published Apr 12, 2022 • Updated Sep 07, 2022
UnitySQLMongoDBC#
SNIPPET
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty
social-githubVer código
(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 4: SQL (este tutorial)
  • Parte 5: Realm Unity SDK (em breve)
  • Parte 6: Comparação de todas essas opções
Semelhante às partes anteriores, este tutorial também pode ser encontrado em nosso repositório de exemplos do Unity na ramificação persistência-comparação.
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.

Exemplo de jogo

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, apresentando os diferentes métodos, terão a mesma estrutura básica que pode ser encontrada em HitCountExample.cs.
1using UnityEngine;
2
3/// <summary>
4/// This script shows the basic structure of all other scripts.
5/// </summary>
6public class HitCountExample : MonoBehaviour
7{
8 // Keep count of the clicks.
9 [SerializeField] 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.

SQLite

(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:
1Unity/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 à cápsula. 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).
Ali, primeiro definimos um nome para nosso arquivo de banco de dados.Por enquanto , vamos chamá-loMyDatabase. Consequentemente, o URI deve ser "URI=file:MyDatabase.sqlite" (4). Então podemos criar uma conexão com este banco de dados utilizando new SqliteConnection(dbUri) (5) e abri-lo com dbConnection.Open() (6).
1using Mono.Data.Sqlite; // 1
2using System.Data; // 1
3using UnityEngine;
4
5public class SqliteExampleSimple : MonoBehaviour
6{
7 // Resources:
8 // https://www.mono-project.com/docs/database-access/providers/sqlite/
9
10 [SerializeField] 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 )"
Afinal, o que significa essa declaração? Primeiro, precisamos declarar o que queremos fazer, que é CREATE TABLE IF NOT EXISTS. Em seguida, precisamos nomear esta tabela, que será o mesmo 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 é a nossa 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 novamente? Bem, a maneira mais fácil seria apenas procurar no banco de dados. Existem muitas ferramentas disponíveis para conseguir isso. Uma das opções de código aberto seria 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 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.

Exemplo estendido

(Consulte SqliteExampleExtended.cs no repositório para ver a versão finalizada).
Na seção anterior, vimos 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. Embora 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, analisaremos 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[SerializeField] private int hitCountUnmodified = 0;
2[SerializeField] private int hitCountShift = 0;
3[SerializeField] private int hitCountControl = 0;
A detecção de qual tecla está 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 é 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:
1private KeyCode modifier = default;
Agora, a função Update() pode ser assim:
1private 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 estadounmodified e apenas configuramos modifier de volta para seu default (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 podermos comparar as duas versões posteriormente, mudaremos o nome da tabela e a chamaremos de HitCountTableExtended:
1dbCommandCreateTable.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:
1private 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 LeftShift de 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.
1void 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 essencialmente inalterada criando um IDbConnection (1) e um IDbCommand (2) e então lendo todas as linhas novamente com SELECT * (3) mas desta vez de HitCountTableExtended, finalizado executando o comando com ExecuteReader() (4).
Para a próxima parte, agora 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 contra o KeyCode (8) e o atribuímos ao hitCountcorrespondente (9).
Agora reinicie o jogo e experimente!

Conclusão

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.
Forneça feedback e faça qualquer pergunta no fórum da comunidade do Realm.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Artigo

Como usar o Realm de forma eficaz em um aplicativo Xamarin.Forms


Sep 09, 2024 | 18 min read
Notícias e Anúncios

Apresentamos o MongoDB Analyzer para .NET


Aug 05, 2024 | 6 min read
Início rápido

Crie seu primeiro aplicativo .NET Core com o MongoDB Atlas


Jun 04, 2024 | 6 min read
Tutorial

Integrar o Azure Key Vault com a criptografia em nível de campo no lado do cliente do MongoDB


May 24, 2022 | 9 min read
Tecnologias Utilizadas
Linguagens
Tecnologias
Produtos
Sumário
  • Exemplo de jogo