Como criar uma cena de loading com barra de progresso na Unity

Este é um artigo convidado escrito por Leonardo Thurler sobre como criar uma cena de loading com barra de progresso na Unity.

Leonardo é cientista da computação e trabalha com análise de sistemas, desenvolvendo jogos em seu tempo livre.

Ele também é aluno da Academia de Produção de Jogos, nosso treinamento online com cursos, entrevistas e uma comunidade incrível de desenvolvedores de games.

O Leonardo já escreveu outros dois mega posts aqui no Produção de Jogos:

Você pode conferir os artigos acima quando terminar este aqui. Agora, continua aqui comigo…

Assim como o último artigo do Leonardo, este post é um tutorial extremamente detalhado que vai te permitir entender todos os passos do processo.

Sem mais delongas, fique aí com esse excelente tutorial de Unity.

Nota para os iniciantes: Se você não sabe do que estou falando, confira nesse artigo o que é e para que serve uma game engine e depois descubra neste artigo o que é e como utilizar a Unity

Com vocês, Leonardo Thurler

Olá pessoal,

Todo mundo que já jogou qualquer jogo digital já se deparou com as telas de Loading, elas servem para informar ao usuário quando uma outra fase ou algo pesado está sendo carregado e para isso geralmente elas possuem uma barra ou uma porcentagem de carregamento.

Neste artigo eu busco explicar como criar uma cena de Loading na Unity. A tela que iremos criar apresenta tanto a barra de progresso como um texto com a porcentagem, esta forma de tela de Loading pode ser utilizada tanto em jogos 2D como em jogos 3D. Abaixo, segue um gif do comportamento que produziremos neste artigo.

telaLoading

O código produzido neste artigo é compatível com o Unity 5.3 ou posterior, pois utiliza o novo recurso SceneManager já que o Application.LoadLevel se tornou obsoleto.

É possível adaptar o código para que funcione nas versões anteriores do Unity para isto basta modificar os pontos em que é utilizado o SceneManager e colocar para utilizar o método equivalente no Application. Caso queira baixar o projeto ou o exe construído neste artigo, confira os links abaixo:

Criando as Scenes e animações

Primeiro crie uma imagem toda preta que servirá para fazer um efeito nas transições das cenas. Esse efeito será escurecer a tela quando for carregar uma nova tela e clarear a tela quando ela for carregada.

A imagem criada pode ser em qualquer resolução já que será totalmente preta não irá perder qualidade ao esticar. A imagem pode ser semelhante à da imagem abaixo.

Unity - Tela de loading

Agora vamos criar um novo projeto no Unity, no caso do artigo eu criei um projeto 2D, porém este artigo se aplica tanto a jogos 2D quanto 3D.

Ao iniciar o projeto crie uma pasta chamada “Images” e importe a imagem criada para dentro do projeto e crie uma Imagem utilizando o sistema UI na Unity, para criar a imagem basta ir no menu Game Object > UI > Image.

Renomeie o game object para “FadeImage”, coloque para utilizar a imagem que foi criada e redimensione para que ela ocupe todo o espaço do canvas. A sua tela deve ficar semelhante à da imagem abaixo.

loading-unity-2

Feito isso vamos criar uma animação para esta imagem aparecer e sumir, este será o nosso efeito antes e depois de realizar a transição entre as cenas. Crie uma pasta chamada “Animations” e dentro dela crie um AnimatorControler com o nome de “FadeImageAC”.

Adicione o Animator Controller ao objeto “FadeImage”. Na aba animator, para habilitar esta aba basta ir no menu Window > Animator, adicione um parâmetro do tipo Trigger com o nome “show”. Com isso sua tela deve ficar semelhante a imagem abaixo.

loading-unity-3

Agora vamos criar 3 animações para este objeto, para isto vamos na aba animation, para habilitar esta aba basta ir no menu Window > Animation, e criamos as animações para este Animator Controller. Salve todas as animações dentro da pasta Animations.

A primeira animação terá o nome de “Visible” e terá as propriedades indicas na imagem abaixo.

loading-unity-4

A segunda animação terá o nome de “FadeIn” e terá as propriedades indicas nas imagens abaixo.

loading-unity-5

loading-unity-6

E a última terá o nome de “FadeOut” e terá as propriedades indicadas nas imagens abaixo.

loading-unity-7

loading-unity-8

Com as três animações criadas vamos voltar a aba animator e configurar o fluxo entre as animações. Primeiro vamos apagar o State default e definir o State “Visible” como o default, além disso vamos criar uma transição dele para o State “FadeOut”, está transição irá servir para quando a tela carregar a imagem preta sumir automaticamente. A configuração da transição deve ficar conforme demostrado na imagem abaixo.

loading-unity-9

Agora vamos criar uma transição entre o State “Any State” para o “Fade In”, está transição irá servir para fazer a imagem preta aparecer antes de carregar a cena de loading.  A configuração da transição deve ficar conforme demonstrado na imagem abaixo.

loading-unity-10

Agora que já criamos as animações e transições entre elas, vamos criar uma pasta chamada “Scenes” e salvar a nossa cena com o nome “MainScene” dentro dela.

Além disso, vamos criar um botão, dentro do canvas, e alterar o texto dele para “Go To Next Scene”, este botão servirá para carregar a nova cena. Caso você dê play no jogo nesse momento você pode ver que a imagem preta some, mas o botão sempre está à frente dela.

Para corrigir isto basta colocar o game object do botão acima do objeto da imagem na aba de hierarquia, este é um detalhe bem importante pois queremos que essa imagem fique à frente de qualquer coisa então qualquer outra coisa que formos adicionar ao canvas devem ficar acima do objeto que contém a imagem.

Além dessa alteração vamos deixar a imagem preta invisível para que possamos ver o botão no editor, para fazer isto basta selecionar a imagem e na cor dela diminuir todo o alpha. Com isso, a sua tela deve ficar semelhante à da imagem abaixo.

loading-unity-11

Caso o Unity não tenha criado o game object “Event System” automaticamente, você deve ir no menu Game Object > UI > Event System. Esse objeto é muito importante pois ele vai garantir que os cliques no botão sejam interpretados pelo Unity.

Caso você dê play agora, verá que a imagem preta irá aparecer e sumir, isso acontece por conta da nossa animação “Visible”. Ela faz a imagem aparecer e logo depois ela troca automaticamente para a animação “FadeOut” que escurece e desativa o objeto da imagem.

Agora vamos criar duas novas cenas com base nesta que já está criada. Vamos no menu File > Save Scene as… e criamos duas cópias dessa cena, uma com o nome de “LoadingScene” e outro com o nome de “GameScene”.

Na “GameScene” vamos alterar o texto do botão para “Go To Game Scene” e vamos criar vários cubos na tela, isso vai servir para que a cena fique um pouco pesada e seja possível ver a barra de progresso sendo carregada na cena de Loading. Agora sua tela deve estar semelhante a imagem abaixo.

loading-unity-12

Na “LoadingScene” vamos remover o game object “FadeImage” pois está tela não terá efeito depois de carregada. Vamos remover o botão e criar dois game object de texto no canvas, um terá o texto “Loading:” e o outro terá o nome “Percent” e o texto “0%”.

Além de criar os game object de texto, vamos criar também um Slider que será a barra de progresso. Ao criar o Slider vamos remover o objeto “Handle Slide Area” que fica dentro dele, além disso vamos mudar o nome do game object “Slider” para “ProgessBar” e configurar seus atributos conforme demonstrado na imagem abaixo.

loading-unity-13

Agora que temos as telas configuradas vamos criar os scripts e depois colocar os botões para ativar as funções de trocar de cenas.

Criando os Scripts

Para que as transições entre as cenas controladas de forma mais fácil, vamos utilizar um padrão de projeto chamado Singleton.

Este padrão é muito utilizado em desenvolvimento de softwares, basicamente ele serve para quando você quer ter somente uma instância de um determinado objeto em toda sua aplicação, caso queriam saber mais aconselho a pesquisarem sobre este padrão no Google pois tem bastante material legal sobre isso na internet.

A utilização desse padrão se deve ao fato de que teremos um objeto controlador de cenas, este objeto será responsável por indicar ao Unity como as transições deverão ser feitas. Com isso só precisamos de um objeto desse no nosso jogo inteiro e esse é exatamente o motivo de utilizarmos este padrão.

Então, vamos criar uma pasta chamada “Scripts” e criar o nosso primeiro script, esse script vai indicar que o objeto que fizer referência a ele através de herança (conceito de orientação a objeto)  utilizará o padrão mencionado.

Segue o código abaixo.

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

//Singleton<T> o T é uma espécie de template que será definido pelas outras classes/scripts que herdarem
//essa classe. Para entender melhor como usar esse T basta olhar a classe SceneController.
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    //Usado para armazenar a única instância que deve existir desse objeto no jogo.
    private static T instance;

    //Usado para coletar a instância dessa classe e conseguir utilizar os métodos.
    public static T getInstance()
    {
        if (instance == null)
            instance = (T)FindObjectOfType(typeof(T));

        return instance;
    }

    //Se for sobrescrever este método em alguma classe filha, lembrar de utilizar o comando base.SingletonStart();
    void Start()
    {
        SingletonStart();
    }

    //Usado para manter somente uma instância do objeto que utiliza essa classe. Caso seja carregada 
    //uma scene em que esse objeto já exista, o método irá destruir o objeto da scene e manter o mais antigo.
    public bool SingletonStart()
    {
        //Caso a variável instance já esteja preenchida e o gameobject for diferente do que está sendo executado, indica que esse é um novo objeto que deve ser destruído,
        //caso não esteja preenchido indica que é o primeiro objeto e ele deve ser mantido em todas as scenes.
        if (instance && !instance.gameObject.Equals(gameObject))
        {
            Destroy(gameObject);

            //Retorna falso indicando que esse objeto foi destruído e que as variáveis não devem ser inicializadas.
            return false;
        }
        else
        {
            //Usado para indicar que esse gameo bject não deve ser destruído ao fazer as transiçoes da tela.
            DontDestroyOnLoad(gameObject);


            //Caso a instance esteja preenchida não é preciso localizar ela novamente, isso pode acontecer caso algum script
            //chame a função getInstance antes do unity executar o start da classe que está herdando a Singleton. Isso pode acontecer
            //caso você utilize o valor de uma classe Singleton na função Start de uma outra classe. Caso você opte por utilizar algum método de uma classe singleton
            //dentro do Start de uma outra classe, você deve utilizar somente valores da classe singleton que você preencher via inspector.
            if (instance == null)
                //Usado para encontrar o objeto do tipo do template na scene. 
                //Deve existir somente um desse objeto na scene, e não será preciso existir ele em nenhuma scene posterior
                //pois esse objeto será mantido.
                instance = (T)FindObjectOfType(typeof(T));

            //Retorna verdadeiro indicando que esse objeto não foi destruído e que as variáveis devem ser inicializadas.
            return true;
        }
    }
}

Agora vamos criar o script do objeto que irá servir para controlar as cenas, note que na definição da classe ele é Filho da classe Singleton e define o template mencionado no script anterior. Segue o código abaixo.

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System;
//Herda a classe Singleton para indicar que deve existir somente um desse objeto em todo o jogo.
public class SceneController : Singleton<SceneController>
{
  //Usado para indicar que começou a fazer a transição entre as scenes.
  private bool isLoading;
  //Usado para indicar que deve ser iniciada a transição entre as scenes.
  private bool startLoading;
  //Usado para guarda o nome da scene de destino.
  private string targetSceneName;
  //Usado para indicar o tempo mínimo que a tela de loading deve se manter ativa.
  //Caso o valor seja 0 o tempo mínimo será o tempo de carregamento da nova cena.
  private float minLoadingTime;
  void Start()
  {
      //Usado para ativar o método que está na classe Singleton.
      if (base.SingletonStart())
      {
          //Inicializa as váriaveis
          startLoading = false;
          isLoading = false;
          targetSceneName = "";
          minLoadingTime = 1;
      }
  }
  void Update()
  {
      // Começa a carregar a nova scene caso esteja indicado para carregar e ainda não esteja carregando.
      if (startLoading && !isLoading)
          StartCoroutine(StartLoadScene());
  }
  //Retorna um IEnumerator para ser utilizado no StartCoroutine, isso serve para que seja possível parar a execução
  //somente deste método durante um determinado período de tempo e voltar da onde parou. Como pode ser visto mais adiante.
  public IEnumerator StartLoadScene()
  {
      //Indica que começou a carregar e com isso evita de ficar chamando esse método repetidamente
      isLoading = true;
  
      //Ativa a Trigger "show" do objeto FadeImage indicando que a animação "FadeIn" deve ser iniciada
      GameObject.Find("FadeImage").GetComponent<Animator>().SetTrigger("show");
      //Interrompe a execução até o final do frame, isso é feito para que dê tempo para que a animação seja trocada.
      //antes de executar o resto do código.
      yield return new WaitForEndOfFrame();
      //Se estiver rodando a animação FadeIn indica para esperar a quantidade de segundos que a animação dura.
      if (GameObject.Find("FadeImage").GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("FadeIn"))
          yield return new WaitForSeconds(GameObject.Find("FadeImage").GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).length);
      //Começa a carregar a cena de loading de forma assíncrona, com isso a execução do jogo não fica travada.
      AsyncOperation op = SceneManager.LoadSceneAsync("LoadingScene");
      //Espera até que a cena de loading termine de carregar. OBS: A cena de loading deve ser leve para que seja
      //carregada rápida.
      while (!op.isDone)
          yield return new WaitForEndOfFrame();
    
      //Utilizado para garantir que a tela de load vai ficar ativa durante o tempo mínimo que foi definido.
      float timeLoading = Time.time + minLoadingTime;
      //Começa a carregar a cena indicada como destino de forma assíncrona.
      op = SceneManager.LoadSceneAsync(targetSceneName);
      //Utilizado para que não ative a cena automaticamente após terminar de carregar, isso é feito para que possamos
      //mostrar a barra de progresso e porcentagem do carregamento.
      op.allowSceneActivation = false;
      //Enquanto não terminou de carregar, repete esses comandos frame a frame.
      while (!op.isDone)
      {
          //Usado para calcular e guardar a porcentagem de carregamento da nova scene.
          //Obs: o op.progress retorna um valor de 0 até 1 indicando a porcentagem de carregamento e a ativação da scene,
          //porém quando utilizamos o op.allowSceneActivation = false. Ele carrega a scene mas não chega ativar.
          //Nesse caso quando ele retorna o valor 0.9 indica que a cena foi totalmente carregada e que devemos ativar ela.
          //Por isso o valor é dividido por 0.9 para determinar a porcentagem.
          float percent = (op.progress / 0.9f) * 100;
          //Atualiza os valores na scene de Loading para mostrar a porcentagem em forma de texto e preencher a barra.
          GameObject.Find("ProgessBar").GetComponent<Slider>().value = percent;
          GameObject.Find("Percent").GetComponent<Text>().text = String.Format("{0:0}", percent) + "%";
          //Quando a porcetagem chegar a 100 para de repetir.
          if (percent == 100)
              break;
          //Usado para esperar até o outro frame antes de repetir os comandos.
          yield return new WaitForEndOfFrame();
      }
      //Caso tenha carregado a cena mais rápido que o tempo mínimo que a tela de Loading deve ficar ativa
      //espera até passar o tempo desejado.
      while( Time.time < timeLoading )
          yield return new WaitForEndOfFrame();
      //A scene já foi totalmente carregada e aqui indica para o Unity ativar ela.
      op.allowSceneActivation = true;
      //Reseta as variáveis que controla se deve ou não carregar uma nova scene, isso é feito para que possamos chamar este
      //método novamente.
      startLoading = false;
      isLoading = false;
      targetSceneName = "";
  }
  //Esse método será chamado por outros scripts para indicar que deve carregar uma nova scene.
  public void LoadScene( string targetSceneName)
  {
      //Indica o nome da scene de destino e indica que deve iniciar a transição das telas.
      this.targetSceneName = targetSceneName;
      startLoading = true;
  }
}

E agora vamos criar mais um script, esse script servirá para que os botões que criamos nas cenas ativem a função de trocar de cena. Segue o código abaixo.

using UnityEngine;
using System.Collections;
public class GuiController : MonoBehaviour
{
  //Método utilizado pelos Botões da cena para indicar qual fase deve ser carregada.
  public void GoToScene(string sceneName)
  {
      //Utiliza o método da classe SceneController para carregar a nova scene.
      //Note que estamos acessando a classe de uma forma stática, isso ocorre por conta da herança com a classe Singleton.
      SceneController.getInstance().LoadScene(sceneName);
  }
}

Configurando os botões e trocando as cenas na Unity

Bem agora falta pouco, vamos voltar as cenas para criar os objetos que irão controlar as ações e colocar os botões para ativar essas ações.

A “MainScene” é a nossa cena inicial, então ela deve conter o objeto do controlador de cenas. As outras não precisarão desse objeto pois ele vai ser mantido ao trocar as cenas.

Então nessa Scene vamos criar 2 objetos: um com o nome de “SceneController” e vamos adicionar o script “SceneController” a ele; e o outro objeto terá o nome de “GuiController” e vamos adicionar o script “GuiController” a ele.

Além disso, vamos colocar para que o botão que existe na tela ative o método “Go To Scene”, do game object “GuiController”, passando o nome da cena “GameScene”. Conforme pode ser visto na imagem abaixo.

loading-unity-14

Agora vamos à “GameScene”, nela vamos criar apenas o objeto “GuiController” e vamos adicionar o script “GuiController” a ele. Além disso vamos colocar para que o botão que existe na tela ative o método “Go To Scene”, do game object “GuiController”, passando o nome da cena “MainScene”:

loading-unity-15

Corrigindo possíveis erros e testando

Agora se executarmos o jogo e clicarmos no botão, podemos receber 3 erros. Um erro se deve ao fato de que você tem que executar primeiro a “MainScene” pois ela é a única que cria o objeto “SceneController”.

É possível resolver este erro copiando o objeto “SceneController” da “MainScene” para esta cena, mesmo fazendo isso não serão criadas duas instâncias do objeto pois o script “Singleton” que criamos tem um tratamento para resolver isto, mas caso não queria fazer isso é só iniciar a execução através da “MainScene”.

O segundo erro que pode acontecer é porque as 3 cenas não estão configuradas no build do jogo e para que seja possível navegar entre elas precisamos realizar essa configuração. Para isso basta ir no menu File > Build Settings… e configurar as cenas como mostra na imagem abaixo.

loading-unity-16

O terceiro não é bem um erro e não trava a execução. A princípio, parece que o texto e a barra da cena de Loading não estão funcionando. Isso se deve ao fato de que o editor da Unity nem sempre consegue emular o build do jogo perfeitamente, e com isso ele várias vezes acaba carregando a cena toda de uma vez só.

Para realizar o teste corretamente você deve realizar o Build do projeto para alguma plataforma, e quando você testar nessa plataforma verá que está correto. Porém, se a cena for bem leve ele continuará carregando toda a cena de uma vez só e a barra de load vai do 0 direto para o 100.

Conclusão

Acredito que este artigo possa ajudar a vocês a entenderem como é feito uma cena Loading na Unity, além dos scripts servirem como um template que vocês podem aproveitar em seus projetos.

Da forma que a cena de Loading foi criada você consegue modificar ela da maneira que quiser sem que ela afete as outras cenas.

Além disso, espero que tenham aprendido uma forma de utilizar o Singleton no Unity, ele pode ser utilizado em muitos casos relacionados ao desenvolvimento de jogos.

Espero que este artigo tenha sido útil e que tenham gostado da leitura. Um abraço e até o próximo artigo!

Comentários Finais (por Raphael Dias)

E aí, o que achou do artigo do Leonardo?

Agora, como sempre, eu queria te deixar uma dica e uma pergunta.

A dica é conferir os outros excelentes artigos que o Leonardo Thurler escreveu para o Produção de Jogos:

E antes de dar uma olhada nos materiais acima, a minha pergunta para você é:

Você já implementou uma tela de loading no seu game? Se ainda não, qual foi o motivo?

Fico esperando sua resposta abaixo.

Abraços,

Raphael Dias

6 Comentários


  1. Mais um artigo muito bem explicado pelo Leonardo Thurler! Parabéns!!! 🙂

    Responder

  2. Eu estou fazendo uma equipe de desenvolvimento de jogos para tablet para computador se você quiser participar entre em contato comigo eu trabalho na “Gracom” fazer jogos para Android e quero fazer jogos para Xbox 360

    Responder

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *