Unity3D: Criando singletons de ScriptableObjects automaticamente

(For the English version, click here)

Olá! Compartilho aqui uma pequena classe que estou usando para facilitar a minha vida com Unity3D: um singleton baseado em um ScriptableObject que vai sobreviver a carregamentos do jogo e do editor, o que pode ser bem útil para dados que precisam ser compartilhados pelas scenes como por exemplo parâmetros do jogador/inimigos ou o estado global do jogo.

É baseada nessa ótima palestra sobre ScriptableObjects na Unite '16 Europa, mostrada abaixo (em inglês); não é um vídeo curto, mas se você quiser se tornar um desenvolvedor Unity3D profissa, é incrivelmente instrutivo. Além disso, se for possível, compareça às conferências Unite, um monte de coisas bacanas tanto pra desenvolvedores quanto designers (eu fui pra Unite '16 São Paulo e a minha mente explodiu!)

Então, sem mais delongas, vamos ao código:

using System.Linq;  
using UnityEngine;

/// <summary>
/// Classe abstrata para fazer singletons de ScriptableOBject que sobrevivam recarregamentos
/// Retorna o asset criado no editor, null se não existe nenhum
/// Baseado em https://www.youtube.com/watch?v=VBA1QCoEAX4
/// </summary>
/// <typeparam name="T">Tipo do singleton</typeparam>

public abstract class SingletonScriptableObject<T> : ScriptableObject where T : ScriptableObject {  
    static T _instance = null;
    public static T Instance
    {
        get
        {
            if (!_instance)
                _instance = Resources.FindObjectsOfTypeAll<T>().FirstOrDefault();
            return _instance;
        }
    }
}

Também disponível como um gist do GitHub. Perceba que esta classe irá retornar apenas assets que já existam, criados pelo menu Assets ou criados e salvos via código; não irá criar automaticamente um novo asset sob demanda.

Como um exemplo, vamos supor que você tenha um ScriptableObject PlayerData que possui informações básicas sobre o jogador; para transformá-lo em um singleton, apenas faça com que o mesmo herde de SingletonScriptableObject:

using UnityEngine;

// Adicione uma entrada no menu Assets para criar um asset deste tipo
[CreateAssetMenu(menuName = "ScriptableObjects/PlayerData")]
public class PlayerData: SingletonScriptableObject<PlayerData> {  
    public int maximumPlayerHealth;
    public int startingLives;
}

Importante: antes de usar o asset, você deve ter uma variável pública em qualquer script em uso dentro da scene e associar manualmente o ScriptableObject a essa variável; caso contrário, só vai funcionar dentro do editor. Isso é necessário devido a maneira como o Unity lida com assets em tempo de execução, ignorando assets que não são referenciados. Poderíamos também criar um GameObject cuja única função é ter um script cheio de variáveis públicas, cada uma associada a um ScriptableObject, e guardar como prefab pra reaproveitar nas outras scenes.

Finalmente, para usar o singleton, apenas use a instância diretamente ou ponha uma referência em alguma variável:

public void SomeFunction() {  
    // Diretamente
    Debug.Log("Maximum health: " + PlayerData.Instance.maximumPlayerHealth);

    // guardando uma referência, e.g. em Start()
    PlayerData playerData = PlayerData.Instance;
    Debug.Log("Starting lives: " + playerData.startingLives);
}

Bom, é isso. ScriptableObjects são um recurso poderoso que podem ser um pouco complicados de se entender, mas que podem potencialmente melhorar bastante o seu workflow - não apenas para desenvolvedores como também para game designers.