[Unity] Salvando e carregando dados com serialização (Parte 2)

Bem vindos de volta! Neste tópico nós vamos entender um outro conceito importante para nossa estruturação de save/load com serialização que é a ferramenta: PlayerPrefs.


O que é PlayerPrefs?
PlayerPrefs é uma ferramenta bem antiga da Unity, que permite que você salve valores de variáveis de forma rápida, sem complicação nenhuma.
Como o próprio nome sugere, essa tool é responsável por armazenar preferências do usuário, ou seja, valores esporádicos e simples, como por exemplo, aspect ratio que o jogador prefira, preferência de idioma, e coisas assim mais simples, que não precisem de uma estrutura mais complexa para serem consumidos.
Nativamente, podemos armazenar três tipos de valores com PlayerPrefs: strings, floats e ints.



Armazenando valores com PlayerPrefs:
Para armazenar um valor é bem simples, basta declarar a variável desejada, e através do PlayerPrefs, setá-la para que possa ser usada posteriormente.
O PlayerPrefs trabalha com armazenamento em chave, onde você passa uma string para servir como chave daquele valor.

Vamos supor que desejamos salvar um simples texto no PlayerPrefs, pra isso, vamos criar uma variável do tipo string em nosso script, e criar uma chave com o nome "simpleTextKey":

Código:
using UnityEngine;

public class Example : MonoBehaviuor{
public string myText = "Texto de exemplo";
public const string playerPrefKey = "simpleTextKey";
}

Para armazenar o valor da variável "myText" no PlayerPrefs, basta utilizar o método "SetString", passando a chave do valor, e a variável desejada, e depois chamar o método "Save", assim:

Código:
using UnityEngine;

public class Example : MonoBehaviuor{
public string myText = "Texto de exemplo";
public const string playerPrefKey = "simpleTextKey";

private void Start(){
PlayerPrefs.SetString(playerPrefKey, myText);
PlayerPrefs.Save();
}
}

Para resgatar um valor armazenado no PlayerPrefs, você só precisa chamar o método "GetString" e passar a chave do valor que deseja carregar.
O valor retornado do GetString, é exatamente o valor que estava salvo, por isso, é interessante armazenar esse retorno numa variável que você deseja utilizar na sua classe que contenha esse dado, no nosso caso, a própria variável "myText" vai resgatar seu valor anteriormente salvo.

É uma boa prática também fazer uma validação antes, para testar se aquela chave já existe no PlayerPrefs para não receber um erro de referência nula:

Código:
private void OnEnable(){
if(!string.IsNullOrEmpty(PlayerPrefs.GetString(playerPrefKey)))
myText = PlayerPrefs.GetString(playerPrefKey);
}

*Esse trecho de código está inserido no script "Example", já mostrado nesse tópico.

Desvantagem:
Parece tudo muito simples e rápido carregar e salvar dados com o PlayerPrefs, não? É intensão ser simples, porque ele justamente permite armazenar dados simples, mas a grande desvantagem da utilização desse recurso para salvar e carregar dados, é exatamente a questão de que não é possível realizar essas ações para estruturas mais complexas, pois ele só aceita três tipos de valores, como já citado anteriormente.

Então se por exemplo quiséssemos guardar a struct que criamos no tutorial anterior "PlayerData", não poderíamos, pois aqui teríamos uma struct, e não um dado simples.

Serialização + PlayerPrefs
Frente a isso, uma solução bem interessante para esse tipo de situação seria unir a simplicidade do PlayerPrefs com a versatilidade da serialização.

Lembre-se que o processo de serialização é você simplesmente converter uma estrutura de dado mais complexa para uma única string, então o que podemos fazer é serializar uma estrutura ou classe, e armazenar essa string no PlayerPrefs (já que ele aceita strings, como vimos no exemplo acima).
Dessa forma, vamos fazer com que o PlayerPrefs nos sirva como um "container" de informações, que seriam nossos dados serializados, convertidos para string.

Para serializar um objeto na Unity, utilizamos o método "ToJson" da classe "JsonUtility":

Código:
using UnityEngine;

public class Example2 : MonoBehaviour{
private PlayerData _playerData; // variável que vai conter os dados persistentes

private const string playerDataKey = "Player_Data_Key"; // chave do playerprefs

private void Start(){
string saveData = JsonUtility.ToJson(_playerData);

PlayerPrefs.SetString(playerDataKey, saveData);
PlayerPrefs.Save();
}
}

Observe no script acima que criamos uma variável do tipo da nossa struct para que ela armazene os valores carregados posteriormente, e também para que possamos passar essa estrutura para o processo de serialização.
Também criamos uma chave para o PlayerPrefs.

Depois definimos dentro do Start uma variável do tipo string, para armazenar a nossa estrutura serializada (convertida para string).
Serializamos nosssa struct "_playerData", utilizando o método "ToJson" do JsonUtility.
Após isso, teremos nossa estrutura convertida para string, então podemos armazenar essa string no PlayerPrefs, utilizando a chave que definimos.

Assim, estamos salvando todas aquelas informações do jogador, como: nome, vida, pontos, moedas, e tudo mais o que tínhamos na struct do tutorial anterior.

Uma observação importante a se fazer é que: uma classe ou estrutura só pode ser serializada se possuir o atributo "Serializable" definido em seu escopo, assim:

Código:
[System.Serializable]
public struct PlayerData{...}

*Estrutura retirada da postagem anterior.

Desserialização + PlayerPrefs:
Já o processo de carregar esses valores, é através da desserialização.

Para desserializar uma estrutura no Unity, basta utilizar o método "FromJson" e definir o tipo da estrutura para o qual aquele objeto serializado deverá ser convertido novamente, assim:

Código:
private void OnEnable(){
if(!string.IsNullOrEmpty(PlayerPrefs.GetString(playerDataKey))){
string loadedData = PlayerPrefs.GetString(playerDataKey);
_playerData = JsonUtility.FromJson<PlayerData>(loadedData);
}
}

Neste código, estamos validando se a chave existe no PlayerPrefs para evitar erros de referência nula.
Depois, estamos pegando o valor vindo do PlayerPrefs através da chave fornecida, e armazenando esse valor numa string.
Por fim, realizamos o processo de desserialização deste objeto, armazenando o valor retornado na variável de referência da nossa struct. Para isso, utilizamos o método "FromJson", especificamos o tipo de estrutura que deve ser desserializado esse objeto, e passamos o objeto serializado pra ele (a string retornada do PlayerPrefs).

Pronto, com este processo, temos um carregamento e save de estrutura e classes, de uma forma rápida e simples.

Importante:
Mas não é só isso! Ainda falta uma parte deste tutorial, onde irei mostrar como criar uma arquitetura robusta, simples e segura em seu código, para que você carregue e salve objetos do seu jogo, sem precisar chamar esses comandos diversas vezes!
Por isso, é muito importante acompanhar a última parte deste tutorial, pois lá eu mostro diversos conceitos avançados do C# e como manter seu código reutilizável e seguro. Nos Vemos lá!

Comentários