Menu

09 – Tutorial Intermediário – Salvar e Carregar

E então pessoal, pronto para aprender a salvar e carregar o seu jogo de uma forma diferente?

“Mas Carlos, nós não já vimos um Save com o PlayerPrefs?”

Já vimos sim, mas lá nós guardamos apenas os atributos, então imagine se sua classe tiver 30 atributos e você ter que pegar e carregar uma por uma lá no PlayerPrefs, um trabalhão não é mesmo?

Então a ideia aqui será transformar a classe em uma espécie de texto, através do serialize, para só então salvar a classe por completo. Quem já trabalha com programação e já viu o Serialize, já deve ter uma noção do que iremos fazer então.

Mas relaxem, o PlayerPrefs é algo bem simples, porém mais trabalhoso. Todavia nós usando o Serializable em nossas classes para salva-la não será um processo difícil, só precisamos tomar alguns cuidados.

Bom, então bora continuar nosso tutorial de onde paramos lá na scene MenuFases onde criamos o script GCSelecaoFase, se lembram?

Bom, aqui a primeira coisa será desabilitar PainelSelecaoFases e criar um novo Painel (UI >> Panel) chamado PainelSalvar, o deixa centralizado e opcionalmente escolhe um fundo para este painel:

01- Tutorial Salva

Posteriormente dentro do PainelSalvar vamos criar 3 botões que serão os Save Slots (BotaoSlot1, BotaoSlot2, BotaoSlot3):

02- Tutorial Salva

Próximo passo é criar um campo de texto em cima dos botões, para informar ao jogador se o jogo está salvando e se já foi salvo. Chamaremos esse campo de TextoInformacao e o deixaremos com o campo texto vazio:

03- Tutorial Salva

Agora criaremos um novo Script (C#) chamado CGSalvar dentro da pasta scripts/controllers.

Nesse script vamos precisar de 3 variáveis:

Script: CGSalvar.cs

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class GCSalvar : MonoBehaviour {

    public GameObject painelSelecaoFase; //Ativa o painel ao apertar o botão voltar
    public GameObject painelSalvar;      //Desativa o painel ao apertar o botão salvar
    public Text textoInformativo;        //Informa o jogador o andamento do save

}

Os painéis servem apenas para a gente troca-lo ao apertar em um botão voltar. O textoInformativo como falei será as informações passadas para o jogador. Reparem que o textoInformativo é um script Text do tipo UI, ou seja, tem que chamar lá em cima o UnityEngine.UI.

O próximo passo é criar os métodos dos botões de save e voltar:

Script: CGSalvar.cs

    public void botaoSlot(int slot) {

    }

    public void botaoVoltar () {
        painelSalvar.SetActive(false);
        painelSelecaoFase.SetActive(true);
    }

O botão voltar vocês já entendem o que ele fará. Apenas desativa o painelSalvar e ativa o painelSelecaoFase, bem simples. Já o botaoSlot ele recebe como parâmetro um valor inteiro com o nome slot. Isso servirá para a gente saber se ele apertou o botão 1, 2 ou 3. Ao adicionar o método lá no Unity no On Click, ele vai perguntar qual é esse valor ai que estamos passando como parâmetro.

Agora vamos as novidades. Lembram –se de que eu havia dito que o nosso script GameStatus não poderia herdar de MonoBehaviour? Pois bem, é porque temos que dizer que ele será serializável  (Acho que essa palavra nem existe). Com isso ele transforma todos os atributos em espécie de textos que podem ser guardados, porém como ficaria os da classe MonoBehaviour que não são serializável? Não dá né? Daria bronca, por isso que ele não pode herdar de MonoBehaviour. Para tornar uma classe assim, basta a gente por [System.Serializable] antes da declaração da classe:

Script: GameStatus.cs

using UnityEngine;
using System.Collections;

[System.Serializable]
public class GameStatus {

    public int faseLiberada;            //Guarda a ultima fase liberada
    public Status status;               //Guarda o Status do personagem
    public string prefabPersonagem;     //Guarda o nome do prefab do personagem
}

Ou, o System nada mais é um é do que um namespace igual aos lá de cima e o UI que usamos. Então se quisermos podemos jogar ele lá para cima em um using e só usar o Serializable:

Script: GameStatus.cs

using UnityEngine;
using System.Collections;

[System.Serializable]
public class GameStatus {

    public int faseLiberada;            //Guarda a ultima fase liberada
    public Status status;               //Guarda o Status do personagem
    public string prefabPersonagem;     //Guarda o nome do prefab do personagem
}

Estou passando as duas formas, para que vocês não estranhem caso vejam algo na internet diferente. O mesmo valor para os UI. Ao invés de chamar o UnityEngine.UI, vocês podem usar UI.Text ou UI.Button… Eu prefiro chamar os namespaces da classe no using.

Mas voltando ao nosso script, ao usar o Serializable ele já consegue converter/serializar a nossa classe com as variáveis primitivas int, string, bool, float… Porém Status é uma classe, ou seja, não é um tipo primitivo (nativo). Com isso precisamos também serializa-lo.

Abram o script Status e adicionem o Serializable:

Script: Status.cs

using UnityEngine;
using System.Collections;
using System;

[Serializable]
public class Status {

    private int hp;          //Total de vida atual do personagem
    private int hpMax;       //Total de vida máxima do personagem
    private int mp;         //Total de pontos de mágia atual do personagem
    private int mpMax;      //Total de pontos de mágia máxima do personagem
    private int ataque;     //Poder do ataque
    private int exp = 1;    //Total de experiencia
    private int nivel = 1;  //Nível atual do personagem
...

Como todas as nossas variáveis são de tipo primitivo, então já acabamos por aqui. Entendem agora o problema que teríamos com o MonoBehaviour? Ela tem várias classes que nos não temos acesso com o GameObject, logo não teríamos como transforma-los em Serializable.

E caso tivesse alguma variável que você não quisesse salvar, bastava por [NonSerialized] sobre essa variável:

Script: GameStatus.cs

using UnityEngine;
using System.Collections;
using System;

[Serializable]
public class GameStatus {

    public int faseLiberada;            //Guarda a ultima fase liberada
    [NonSerialized]
    public Status status;               //Guarda o Status do personagem
    public string prefabPersonagem;     //Guarda o nome do prefab do personagem
}

Mas este NÃO é o nosso caso, então não coloquem o NonSerialized sobre o Status, pois nós queremos salva-lo, beleza?

Bom, agora é só voltar ao script GCSalvar e no método botaoSlot vamos fazer o seguinte:

Script: GCSalvar.cs

	public void botaoSlot(int slot) {
        textoInformativo.text = "Salvando no Slot " + slot;

        //Salvando
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath + "/save"+slot+".dat");
        var gameStatus = FindObjectOfType().getGameStatus();
        bf.Serialize(file, gameStatus);
        file.Close();

        textoInformativo.text = "Jogo salvo no slot " + slot;

        var textoBotao = GameObject.Find("BotaoSlot" + slot).transform.GetChild(0).GetComponent();
        textoBotao.text = "Personagem: " + gameStatus.prefabPersonagem + " | Fases Liberadas: " + gameStatus.faseLiberada;
    }

“Nooooossa que método gigante e complicado!”

Nem é, é bem facinho na realidade.  Vamos por parte:

textoInformativo.text = “Salvando no Slot ” + slot;

Pegamos o objeto textoInformativo e passamos a mensagem que o jogo está sendo salvo no slot de número informado.

BinaryFormatter bf = new BinaryFormatter();

Instanciamos uma class do tipo BinaryFormatter. Essa classe permite serializar as classes no formato binário dentro de um arquivo. Ou seja, será como salvaremos nossos dados.

Para usar essa classe, temos que chamar o namespace dela lá no inicio do script:

using System.Runtime.Serialization.Formatters.Binary;

FileStream file = File.Create(Application.persistentDataPath + “/save”+slot+”.dat”);

Aqui estamos criando um novo Stream de arquivo (FileStream), que permite ler e escrever determinado arquivo. O Arquivo que ele terá acesso, é o arquivo criado (ou substituído) através do File.Create. O Application.persistentDataPath é o local onde o Unity guarda os nossos dados. E o nome do arquivo será saveNUMERO.data. Se for o slot 1 então será: save1.dat. Se for o 2, será save2.dat.

Para usa-lo também precisamos chamar um namespace especifico do C#:

using System.IO;

var gameStatus = FindObjectOfType<GCJogo>().getGameStatus();

Aqui é fácil, né? Buscamos o GameStatus do objeto que tiver o script GCJogo.

bf.Serialize(file, gameStatus);

Aqui, através da variável BinaryFormatter, transformamos o nosso gameStatus e seus atributos (Que tem o Serializable) em binário e o adicionamos dentro do arquivo file que criamos agora a pouco.

file.Close();

Em seguida encerramos nossas atividades com esse arquivo (Vocês estão ligados aqueles arquivos que você pede para excluir, editar ou mover e ele dá erro dizendo que o arquivo não pode ser excluído, pois está em uso por outro programa? É isso o que acontece quando você não fecha seu acesso ao arquivo u.u).

textoInformativo.text = “Jogo salvo no slot ” + slot;

Aqui informamos ao jogador que o jogo já foi salvo.

Var textoBotao = GameObject.Find(“BotaoSlot” + slot).transform.GetChild(0).GetComponent<Text>();

E essa linha gigante? Bora transforma-la em coisas menores:
GameObject.Find(“BotaoSlot”+slot) – Vai buscar o objeto com o nome BotaoSlot1/BotaoSlot2/BotaoSlot3

Transform.GetChild(0) – Vai buscar o primeiro objeto filho desse objeto (No caso o Text)

GetComponent<Text> – Vai buscar o script UI Text do objeto Text.

Ou seja já conhecemos tudinho, só que ao invés de criarmos variáveis e executar o código em 3 linhas, o fizemos em apenas uma.

textoBotao.text = “Personagem: ” + gameStatus.prefabPersonagem + ” | Fases Liberadas: ” + gameStatus.faseLiberada;

Mudamos o texto do botão de “Slot 0*” para “Personagem: PREFAB | Fases Liberadas: N”.

Que tal testarmos agora isso? Então salva ai e vamos voltar ao Unity.

Lá crie um botão para voltar apara a seleção de fases:

04- Tutorial Salva

Adicione o script GCSalva ao objeto GC, informando quem são os painéis e o texto informativo:

05- Tutorial Salva

No Botão Voltar, chame o método botaoVoltar.

Nos BotaoSlot1, adicione o método botaoSlot do script GCSalvar no objeto GC e informa o número do Slot:

06- Tutorial Salva

07- Tutorial Salva

08- Tutorial Salva

Desative o PainelSalvar e ative o PainelSelecaoFases.

No script GCSelecaoFase adicione as variáveis para os objetos painéis e faça a troca no botaoSalvar:

Script: GCSelecaoFase.cs

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections;

public class GCSelecaoFase : MonoBehaviour {

    public GameObject painelSelecaoFase; //Desativa o painel ao apertar o botão salvar
    public GameObject painelSalvar;      //Ativa o painel ao apertar o botão salvar

    public void botaoSalvar() {
        painelSalvar.SetActive(true);
        painelSelecaoFase.SetActive(false);
    }
...

Agora no objeto GC lá no Unity se lembre de informar ao script GCSelecaoFase quem são os painéis:

09- Tutorial Salva

E no BotaoSalvar adicionar o método caso ainda não tenha feito.

Pronto, agora é só iniciar o jogo na scene Main (Se lembrem de salvar antes). Ao iniciar, vá direto salvar e clique em um dos slots e veja a mágica acontecer:

10- Tutorial Salva

E o que acontece se você parar e tentar jogar de novo?

11- Tutorial Salva

“Ué? Cadê o jogo salvo Carlos? O.o”

Está lá salvo, mas você em algum momento disse no script para atualizar o texto dos slots sem ser no save? Não né? Então fazer isso agora no script GCSalva no método Start:

Script: GCSalva.cs

    void Start() {
        painelSalvar.SetActive(true);
        for (int slot = 1; slot <= 3; slot++) {
            BinaryFormatter bf = new BinaryFormatter();
            if (File.Exists(Application.persistentDataPath + "/save" + slot + ".dat")) {
                FileStream file = File.Open(Application.persistentDataPath + "/save" + slot + ".dat", FileMode.Open);
                var gameStatus = (GameStatus)bf.Deserialize(file);
                file.Close();

                var textoBotao = GameObject.Find("BotaoSlot" + slot).transform.GetChild(0).GetComponent();
                textoBotao.text = "Personagem: " + gameStatus.prefabPersonagem + " | Fases Liberadas: " + gameStatus.faseLiberada;
            }
        }
        painelSalvar.SetActive(false);
    }

Vamos entender o código agora. Bom, primeiro eu ativei o painel, do contrário o GameObject.Find não vai conseguir encontrar os botões. E no final do script e o desativei, para afinal ele começa desativado.

“Carlos e se eu quiser chamar o objeto por referência criando um public GameObject BotaoSlot1, posso?”

Eu diria que não só pode, como deve. No nosso jogo que é pequeno a gente nem sente. Mas pense comigo o que é melhor, você saber quem você já quer e ir lá busca-lo, ou sair perguntando de um por um se ele se chama BotaoSlot1 como o Find faz? Em projetos pequenos com o nosso a gente nem sente, mas imagina ele fazer isso em uma scene que tem 100 a 200 objetos? Tenso né? Mas para esse projeto pequeno não teremos problema.

A maior novidade para vocês vem na estrutura de repetição:

for (int slot = 1; slot <= 3; slot++)

Essa é a estrutura de repetição padrão na maioria das linguagens. Ela é dividida em 3 partes:
FOR  (ANTES DE INICIAR A REPETIÇÃO; CONDIÇÃO; APÓS COMPLETAR UM LOOP)

Ou seja, no inicio criamos uma variável que inicial no valor 1 (Normalmente usamos o “i” como nome dessa variável de repetição), depois dizemos que a repetição vai continuar enquanto slot for menor que 3. Por fim dizemos que ao final de cada repetição, slot vai ganhar + 1.

Em outras palavras, isso:

for (int slot = 1; slot <= 3; slot++) {
}

É igual a isso:

int slot = 1
for (; slot <= 3;) {
slot++
}

Quando Slot for 4, ou seja, maior que 3, então ele sai da repetição.

E o que acontece dentro dessa repetição? O seguinte código:

BinaryFormatter bf = new BinaryFormatter();
            if (File.Exists(Application.persistentDataPath + "/save" + slot + ".dat")) {
                FileStream file = File.Open(Application.persistentDataPath + "/save" + slot + ".dat", FileMode.Open);
                var gameStatus = (GameStatus)bf.Deserialize(file);
                file.Close();

                var textoBotao = GameObject.Find("BotaoSlot" + slot).transform.GetChild(0).GetComponent();
                textoBotao.text = "Personagem: " + gameStatus.prefabPersonagem + " | Fases Liberadas: " + gameStatus.faseLiberada;
            }

BinaryFormatter já vimos para que serve.

No if (File.Exists(Application.persistentDataPath + "/save" + slot + ".dat")) verificamos se existe algum save no slot informado.

O FileStream agora não irá receber um arquivo criado, mas sim aberto através do File.Open.

Após pegar esse arquivo, nós fazemos o caminho inverso e transformando o file de binário para a classe GameStatus com todos os seus dados salvos:

var gameStatus = (GameStatus)bf.Deserialize(file);

Usamos o (GameStatus) para dizer que a classe que acabou de ser deserializada do binário é do tipo GameStatus.

Para encerra nossas ações com esse arquivo, o fechamos através do método Close.

E nas duas linhas seguintes, nós pegamos o texto no objeto filho do objeto BotaoSlot e informamos o texto do objeto!

Com isso ao carregar a tela já deverá estará lá o nome do save, quando o Slot não estiver vazio.

E então você já sabe como criar o seu Save não é mesmo? E o Load? Bora fazer?

Então antes de iniciar é salvar tudo o que foi modificado e depois voltar para a scene Main. Lá irá criar um Painel (UI>>Panel) que pode ser até mesmo uma copia da Scene MenuFases (porém sem o texto informativo e com o nome PainelCarregar):

13- Tutorial Salva

No Script GCMenuPrincipal iremos adicionar uma nova variável:

public GameObject painelCarregar;

E no método botaoVoltar, adicionamos esse o painel sendo desativado:

Script: GCMenuPrincipal.cs

    public void botaoVoltar() {
        painelPrincipal.SetActive(true);//Ativa o Painel Principal
        painelConfiguracoes.SetActive(false); //Desativa o Painel de Configurações
        painelSelecaoPersonagem.SetActive(false); //Desativa o Painel de Seleção de Personagens
        painelCarregar.SetActive(false); //Desativa o Painel de Carregar jogo
    }

No método botaoCarregarJogo, fazemos as trocas de painéis:

Script: GCMenuPrincipal.cs

    public void botaoCarregarJogo() {
        painelPrincipal.SetActive(false);//Desativa o Painel Principal
        painelCarregar.SetActive(true); //Ativa o Painel de Carregar
    }

Agora iremos fazer o mesmo trabalho que fizemos na outra Scene, para buscar os textos, só que aqui iremos também verificar se em algum momento ele entrou dentro do if File.Exists, pois caso tenha, iremos ativar o botão CarregarJogo:

Script: GCMenuPrincipal.cs

    void Start() {
        painelCarregar.SetActive(true);
        var botaoCarregarAtivo = false;

        for (int slot = 1; slot <= 3; slot++) {
            BinaryFormatter bf = new BinaryFormatter();
            if (File.Exists(Application.persistentDataPath + "/save" + slot + ".dat")) {
                botaoCarregarAtivo = true;
                FileStream file = File.Open(Application.persistentDataPath + "/save" + slot + ".dat", FileMode.Open);
                var gameStatus = (GameStatus)bf.Deserialize(file);
                file.Close();

                var textoBotao = GameObject.Find("BotaoSlot" + slot).transform.GetChild(0).GetComponent();
                textoBotao.text = "Personagem: " + gameStatus.prefabPersonagem + " | Fases Liberadas: " + gameStatus.faseLiberada;
            }
        }
        painelCarregar.SetActive(false);

        if (botaoCarregarAtivo) {
            var botaoCarregar = GameObject.Find("CarregarJogo").GetComponent

Já que estamos acessando scripts de outros objetos, vamos trabalhar no Start (Mas como é padrão do Unity, não teria bronca em usar o Awake).

O Script é basicamente o mesmo do anterior sem mudar nada, apenas acrescentar. Nele adicionamos uma nova variável chamada botaoCarregarAtivo, que irá se tornar true caso existe algum arquivo de save.

E caso ela se torne true, então buscamos o objeto “CarregarJogo”, recuperando o seu script Button, para alterar a interatividade (interactable) para true. E sim, poderíamos ter feito tudo em uma linha só sim, fim à vontade.

Com isso pronto, basta voltar ao Unity, informar ao script GCMenuPrincipal no objeto GC quem é o painelCarregar:

14- Tutorial Salva

Atribuir ao botão CarregarJogo no PainelPrincipal o método botaoCarregarJogo e no botão Voltar no PainelCarregar o método botaoVoltar.

15- Tutorial Salva

16- Tutorial Salva

Agora já pode testar, para ver se o botão fica ativo e aparece se os nomes dos botões mudam:

17- Tutorial Salva

18- Tutorial Salva

Para terminar esse script agora só falta a gente fazer o método do botão carregar e atribuir esse valor ao GameStatus no script GCJogo.

Antes de tudo, o que precisamos é no script GCJogo, criar um método para receber o GameStatus e atribuir ao que temos:

Script: GCJogo.cs

    public void carregarJogo(GameStatus gameStatus) {
        this.gameStatus = gameStatus;
    }

Bem simples não é? É a mesma coisa que um set, só que usamos outro nome para deixar mais claro do que se trata.

Agora voltando ao script GCMenuPrincipal, vamos criar um método para os botões de carregar o jogo:

    public void botaoCarregarSlot(int slot) {
        if (File.Exists(Application.persistentDataPath + "/save"+slot+".dat")) { //Existe um save
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/save" + slot + ".dat", FileMode.Open); //Abre arquivo
            var gameStatus = (GameStatus) bf.Deserialize(file);        //Transforma de binário para GameStatus
            file.Close();

            var GCJogo = FindObjectOfType();
            GCJogo.carregarJogo(gameStatus);

            SceneManager.LoadScene("MenuFases");
        }
    }

Já fizemos tanta vez esse script que vocês já devem ter até decorado. Primeiro verificamos se existe algo naquele slot informado através do File.Exists. Depois buscamos o arquivo e o convertemos de binário para GameStatus. Por fim, buscamos o objeto com o script GCJogo e passamos o GameStatus que acabou de recuperar para o script GCJogo (Ou seja, o GameStatus ativo do jogo) e só então carregamos a scene de seleção de fases. Melhor do que sair caçando tudo que é variável através do PlayerPrefs, não é?.

Agora para finalizar esse tutorial, é só ir nos botões do PainelCarregar e adicionar o método botaoCarregarSlot com o seu número correspondente:

19- Tutorial Salva

Pronto! Sistema de Salvar e Carregar jogo terminado com sucesso o/

Não foi tão difícil, né? No post que vem iremos aprender algo ainda mais fácil. Criar um HUD onde mostre o HP, MP, experiência e as skills de nossos personagens! Então bora que bora!

Criador do Jogos Indie, amante de jogos, terror, música, anime e programação. Estudante de mestrado com foco em jogos na educação. Louco por Resident Evil e... sei lá, acho que é isso O.o

17 comments

  1. Fabio Pimenta disse:

    Carlos, boa noite. Excelente Tutorial, espero que seja fácil adaptá-lo para Mobile ou que as ações de Salvar sejam independentes de plataforma.

    Testei o seu Projeto e não dá problema algum mas o meu quando executo e escolho qualquer slot para Salvar retorna o erro abaixo :

    UnauthorizedAccessException: Access to the path C:\save1.dat is denied.

    Não consegui descobrir porque ele está tentando escrever dentro do FileSystem e no Raiz da Unidade C: ao invés da estrutura do Jogo.

    O que pode estar ocorrendo ?

    • Você está usando o Application.persistentDataPath no File.Open? Caso não esteja, tudo que iniciar com “/” irá para o diretório raiz.

      O Application.persistentDataPath ele deve retornar C:\Users\NOMEDOUSUARIO\AppData\LocalLow\NOMEDACOMPANIADOSEUJOGO\NOMEDOJOGO

      O Nome da Compania do Jogo, você alterar lá no Building Settigs na opções de preferências que fica perto do botão Build.

      Confirmar se está usando de fato o nome correto. Senão faz um
      Debug.Log(Application.persistentDataPath); e ver se ele retorna o diretório certinho.

      • Fabio Pimenta disse:

        Carlos, estou usando sim, segue trecho do GCSalvar.cs

        FileStream file = File.Open(Application.persistentDataPath + “/save” + slot + “.dat”, FileMode.Open);

        Tentei usar o Debug.Log e até print mas a execução do Jogo trava e retornam uma série de erros no Console.

        UnauthorizedAccessException: Access to the path “C:\save1.dat” is denied.
        System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/FileStream.cs:320)
        System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
        (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare,int)
        System.IO.File.Create (System.String path, Int32 bufferSize) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/File.cs:135)
        System.IO.File.Create (System.String path) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/File.cs:130)
        GCSalvar.botaoSlot (Int32 slot) (at Assets/Scripts/Controllers/GCSalvar.cs:37)
        UnityEngine.Events.InvokableCall`1[System.Int32].Invoke (System.Object[] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:175)
        UnityEngine.Events.CachedInvokableCall`1[System.Int32].Invoke (System.Object[] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:293)
        UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:621)
        UnityEngine.Events.UnityEventBase.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:756)
        UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:53)
        UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:35)
        UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:44)
        UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:52)
        UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:269)
        UnityEngine.EventSystems.EventSystem:Update()

        • O erro está relacionado ao tentar salvar algo na raiz, o que não pode, por não haver permissão.

          Tenta usar o Debug.Log(Application.persistentDataPath); no inicio do método, antes dele tentar criar algo através do File.Open. Pode até por um return; após para ele não executar o resto do código.

          Vi o link que você mandou, mas não tem nenhuma solução ali para o seu caso. Vi outros cantos e recomendaram reiniciar a maquina.

      • Fabio Pimenta disse:

        Fiz uma pesquisa e parece que existe um Bug com o Application.persistentDataPath que em alguns casos e devices retorna vazio. Segue link no final.

        Como contornar ? Como referenciar um Path relativo ?

        http://answers.unity3d.com/questions/998752/unauthorizedaccessexception-access-to-the-path-is-1.html

        • Outra solução que eu vi, que acho que resolverá o seu problema, uma vez que você falou que o meu jogo funcionou normal, será recriar o ProjectSettings.asset.

          O arquivo fica dentro da pasta do projeto em ProjectSettings. Se não me falha a memoria para recriar, você fecha o Unity, vai na pasta e deleta esse arquivo. Depois abre o Unity de novo e ele vai informar que não achou as informações e recria novamente.

          Maaaas eu não tenho certeza, pois só aconteceu uma vez isso comigo de recriar o ProjetSettings, então ao invés de excluir, apenas o mova para uma pasta diferente

          • Fabio Pimenta disse:

            Muito Obrigado Carlos !!! Funcionou apagando o ProjectSettings.asset, quando o Unity abre o Projeto ele nem diz nada e simplesmente recria o arquivo.

            [ Debug ] : Estava retornando vazio, mas agora retorna o Path C:/Users/Fabio/AppData/LocalLow/DefaultCompany/NomeDoJogo

            [ Reestart ] : Reiniciei o Windows mas não resolveu.

            Só para constar e informação para outros que possam tem o Problema que tive :
            – Windows 10 64 bits, Unity Versão 5.3.3f1 (Última Lançada dia 23/02/2016)

  2. Deyvid Lira disse:

    As vezes esse erro também é causado por conta do antivírus (já aconteceu no meu caso).

  3. julio disse:

    Uma dúvida como faz para inserir som no inimigo, tentei declarar como publico o AudioClip no script do zumbi e tentar pegar pelo GetComponent. o som na hora que o zumbi surge e adicionei o som porém não funcionou.

  4. Daniel H.S. disse:

    Estou com um bug bem doido e espero que possa me ajudar, quando aperto novo jogo sempre vai pro jogo que estava rodando e nao pra um novo jogo, e o save fica gravando sempre que inicia o jogo, caso eu grave e continue jogando tudo bem, mas se reiniciar ele volta pra 1, então meus problemas são —-> Um novo jogo que não inicia um novo jogo(inicia oque estiver rodando) e um save que salva sempre que o jogo inicia, se puder me dar uma ajudinha com pelo menos o novo jogo agradeço 😛

  5. Daniel H.S. disse:

    Eu ja estou com o jogo praticamente pronto(diferente desse ai, uma mecânica diferente), mas tava dando alguns bugs, vou refazer os scripts, tava meio bagunçado, acho que era isso.
    desde já obrigado.

  6. Thiago lourenço disse:

    eae Carlos seus tutoriais são ótimos, mas uma pergunta estou fazendo um jogo runner e quero q ele salve o valor do meu dinheiro . como posso fazer

Deixe uma resposta

Parceiros

Steam Brasil LoboLimão Centro RPG Lab Indie
Mundo Gamer PodTerror

Anunciantes

Aglomerando - Agregador de conteúdo
Uêba - Os Melhores Links GeraLinks - Agregador de links Piadas Idiotas - São idiotas mas o faz rir Tedioso: Os melhores links LinkLog MeusLinks.com - Informação e conteúdo todos os dias para você! Agregador de Links - Madruga Links 4Blogs - Agregador de conteúdo Está no seu momento de descanso né? Entao clique aqui!