Unity3D: uma visão geral de ScriptableObjects

(For the English version, click here)

Introdução

Em um dos posts que eu escrevi por aqui eu expliquei uma maneira de criar singletons com ScriptableObjects, algo que pode ser bem útil em determinadas situações, mas depois fiquei pensando se seria interessante fazer um post falando algumas coisas sobre ScriptableObjects em si. Sendo assim, ei-lo. As informações deste post foram obtidas do vídeo abaixo, da documentação do Unity3D e da minhas experiências pessoais com o tema, recomendo que assistam o vídeo abaixo (~1h de duração) quando tiverem um tempo:

Se você já sabe o que é um ScriptableObject e quer ir direto aos exemplos, clique aqui.

Dito isso, o que seria, exatamente, um ScriptableObject (SO, daqui em diante)? Aqui vai a explicação da documentação oficial e um live training, mas de maneira simplificada podemos imaginar SOs como objetos "clássicos" do paradigma da Programação Orientada a Objetos, ao invés de componentes, como normalmente se usa no Unity3D. Não sendo componentes, não precisam necessariamente estar associados a um GameObject para que possam ser utilizados.

Pra criar um SO, basta criar uma subclasse de ScriptableObject:

using UnityEngine;

public class MyScriptableObject : ScriptableObject {  
    public int someNumber;
}

Para usar um SO no projeto primeiro criamos uma instância do mesmo, onde são inicializados os campos públicos, e depois é só utilizar em qualquer componente. Também é fácil criar uma hierarquia de classes, como demonstrado no vídeo: uma classe HealthPowerUp derivada de uma classe base PowerUp, onde o componente poderia chamar PowerUp.apply() que iria chamar o método da classe derivada.

As instâncias de SOs se tornam assets no projeto, da mesma maneira que uma textura ou um arquivo de som também se tornam assets quando adicionados ao projeto. Podem ser referenciadas por variáveis públicas de um componente, transformadas em singletons ou ainda podem ser carregadas em tempo de execução da pasta Resources. Para guardar em algum repositório, basta armazenar o arquivo .asset da instância e nada mais.

Existem outros atributos no SOs como por exemplo a função OnEnable(), que é chamada quando o objeto é carregado, não irei entrar em detalhes sobre tais recursos mas os mesmos estão listados na documentação oficial aqui.

Caso de uso: Tomato Hunter

Falei do Tomato Hunter em outro post, mas se você quiser dar uma conferida, só clicar aí embaixo:

No Tomato Hunter foram utilizados SOs com o propósito de guardar os parâmetros configuráveis de diversas entidades do jogo, de uma maneira que fosse ao mesmo tempo fácil de aplicar nestas entidades e acessível para que os outros membros do time pudessem ajustar o que fosse necessário sem precisar abrir o código. Como exemplo, listo abaixo o SO com os parâmetros do jogador:

using UnityEngine;  
using System.Collections;

// Adicionando uma entrada no menu Assets
[CreateAssetMenu(menuName = "ScriptableObjects/PlayerConfig")]
public class PlayerConfig : ScriptableObject {  
    [Header("Movement")]
    public float runSpeed;
    public float stealthSpeed;
    public float groundDamping;

    [Header("Attributes")]
    public int healthPoints;
    public float invincibilityTime;
}

Nesta classe estão os parâmetros de movimentação e outros atributos básicos do jogador, que afetam diretamente a jogabilidade. O menu para criar uma instância de PlayerConfig é mostrado abaixo:
Menu com os SOs

Depois de criado pelo menu Assets e configurado, é assim que aparece no Inspector:

Instância de PlayerConfig no Inspector

O nome da instância pode ser qualquer um, neste caso ficou PlayerConfig mesmo pra facilitar. Este asset pode ser configurado livremente no editor, em qualquer scene, com o jogo rodando ou não. Desta forma o game designer pode facilmente ajustar os parâmetros que julgar necessário e depois só é necessário salvar o único arquivo .asset gerado e nada mais.

Outro SO utilizado é o EnemyConfig, que possui os parâmetros dos inimigos. Abaixo estão as duas instâncias utilizadas para os dois inimigos do jogo. Desta vez foi preciso dar nomes diferentes para cada uma:

Por fim, para utilizar estas instâncias no jogo, basta criar uma variável pública do tipo correto e preencher no Inspector, como por exemplo no objeto Player, onde temos uma varíavel do tipo PlayerConfig:

Para acessar os parâmetros nesta variável, basta referenciar diretamente, como por exemplo playerConfig.runSpeed ou playerConfig.healthPoints.

Esta é apenas uma pequena amostra do que é possível fazer com ScriptableObjects; fazendo uso daquele clichê mais batido do que saco de pancadas, o único limite é a sua imaginação. Em próximos projetos pretendo explorar melhor estes objetos e usá-los de maneiras diferentes, e depois volto aqui pra contar como foi.