My Framework by Nefisto - 1

Frameworks

My unity framework

Unity 2019.3.4f1Unknown LicenseUpdated 34 days agoCreated on April 7th, 2020
Go to source

Framework

Framework para otimizar o fluxo de desenvolvimento quando se usa unity

Sumario
Arquitetura de variaveis
Arquitetura de eventos
Runtime sets
Reload-proof singletons
Audio events
FlexibleUI
Extra
Atributos
Editor

Notes:

  • O código se difere da sua versão original a qual o credito é dado, o mesmo representa apenas o local onde eu ouvi falar sobre o conceito pela primeira vez.
  • A parte de como funciona expressa apenas a ideia central de como o código esta estruturado, para mais informações leia o código.

Créditos:


Arquitetura de variáveis

Ryan Hipple - Unite 2017

Conceito

​ Variáveis como scriptable objects para criar uma camada de comunicação entre os objetos que dependem desse estado compartilhado

Quando usar

​ Caso uma variável local qualquer seja necessária para o comportamento de algum outro objeto, ao invés de criar uma referencia entre os objetos em questão, torne a variável um SO e os objetos se referenciam a ele pelos assets

Como usar

  1. Crie um novo SO do tipo de variável desejada
  2. Dentro do script crie a variável do tipo especifico: ex public IntReference myVar
  3. No inspector selecione “use variable” (veja imagem mais abaixo)
  4. Insira a variável criada no passo 1

Como funciona

​ A estrutura é dividida em 3 partes

  1. Uma classe base genérica
public abstract class BaseVariable<T> : ScriptableObject
{
    public bool haveDefaultValue = false;
    
    [SerializeField]
    private T value;
    private T runTimeValue;
    
    public T Value
    {
        get => haveDefaultValue ? runTimeValue : value;
        set
        {
            if (haveDefaultValue)
                runTimeValue = value;
            else
                this.value = value;
        }
    }

    private void OnEnable()
    {
        if (haveDefaultValue)
            runTimeValue = value;
    }
    
    private void OnDisable()
    {
        if (haveDefaultValue)
            runTimeValue = value;
    }
}

​ E um SO para cada tipo de variável que for criada

[CreateAssetMenu(fileName = "IntVariable", menuName = "Variables/Int")]
public class IntVariable : BaseVariable<int>
{ }
  1. Uma referencia que será criada nos monobehaviours que forem compartilhar a variável em questão. Essa classe também tem como objetivo criar um sistema mais designer-friendly tornando possível que os estados compartilhados sejam alterados sem a necessidade de alteração no código, tudo pelo inspector que funciona como um injetor
using System;

[Serializable]
public class IntReference
{
    public bool UseConstant = true;
    public int ConstantValue;
    public IntVariable Variable;

    public IntReference()
    { }

    public IntReference(int value)
    {
        UseConstant = true;
        ConstantValue = value;
    }

    public int Value
    {
        get => UseConstant ? ConstantValue : Variable.Value; 
        set
        {
            if (UseConstant)
                ConstantValue = value;
            else
                Variable.Value = value;
        }
    }

    public static implicit operator int(IntReference reference)
        => reference.Value;
}

E para melhorar a experiência, é criado uma PropertyDrawer

Imgur

to up


Arquitetura de eventos

Ryan Hipple - Unite 2017

Conceito

​ Destacar eventos que acontecem durante a execução, como por exemplo um pause, e criar uma camada que separa os objetos que são responsáveis por disparar o evento (triggers) e aqueles que respondem a esse evento (listeners)

Quando usar

​ Sempre que possível

Como usar

  1. Crie um novo SO do tipo de evento desejado

  2. Dentro do objeto que ira atuar como trigger, crie a variável e faça seu invoke no lugar desejado

    ...
    public GameEvent onDeath;
    ...
    public void Death()
        => onDeath.Raise()
    
  3. Nos objetos que atuarão como listeners, adicione o componente GameEventListener (do mesmo tipo do evento) Imgur

  4. Arraste o evento ao qual o objeto ira responder e insira os comportamentos a serem executados na lista de response

OBS: Não há garantia de ordem de execução para a resposta (até onde eu sei)

Como funciona

  1. Uma classe que representa o evento e expõe maneiras de se registrar e se remover como listener do evento.

    OBS: Essa classe é recriada para cada evento de tipo distinto

[CreateAssetMenu(fileName = "GameEvent", menuName = "Events/GameEvent(void)")]
public class GameEvent : ScriptableObject
{
    private readonly List<GameEventListener> eventListeners = new List<GameEventListenerInt>();

    public void Raise()
    {
        for (int i = eventListeners.Count - 1; i >= 0; i--)
            eventListeners[i].OnEventRaised();
    }

    public void RegisterListener(GameEventListener listener)
    {
        if (!eventListeners.Contains(listener))
            eventListeners.Add(listener);
    }

    public void UnregisterListener(GameEventListener listener)
    {
        if (eventListeners.Contains(listener))
            eventListeners.Remove(listener);
    }
}

​ Para facilitar o trabalho de depuração é escrito um editor que permite que o evento seja invocado direto do inspector

Imgur

OBS: por razões obvias o evento só pode ser chamado em runtime

  1. Há um componente de monobehaviour que atua como listener para um evento qualquer, do mesmo tipo, e executa os eventos quando o trigger é ativado

    OBS: Para eventos tipados é necessário criar uma classes serializavel que herde de UnityEvent

    ex: Para um evento de int

    [Serializable]
    public class MyIntEvent : UnityEvent<int> {}
    
public class GameEventListener : MonoBehaviour
{
    [Tooltip("Event to register with.")]
    public GameEvent Event;

    [Tooltip("Response to invoke when Event is raised.")]
    public UnityEvent Response;

    private void OnEnable()
    {
        Event.RegisterListener(this);
    }

    private void OnDisable()
    {
        Event.UnregisterListener(this);
    }

    public void OnEventRaised()
    {
        Response.Invoke();
    }
}

to up


Runtime sets

Ryan Hipple - Unite 2017

Conceito

​ As vezes é necessário manter um registro de quais objetos específicos existem em cena em um momento qualquer ou talvez criar categorias para esses objetos e ter acesso a diferentes grupos em tempo de execução. Para solucionar esse tipo de problema sem usar variáveis globais de controle (AKA: Singletons, que causam dependências na comunicação) é proposto o uso de um SO que faça o papel de armazenar as referencias aos objetos guardados e que exista independentemente de cena

Quando usar

​ Sempre que for necessário categorizar e/ou acompanhar objetos em tempo de execução

Como usar

  1. Crie o SO de RuntimeSet

  2. Aos objetos que farão parte desse grupo, insira o componente Runtime Item e adicione o grupo a qual o item pertence

  3. No script em que for necessário fazer referencia ao grupo, adicione ele como uma variável publica a ser injetada pelo inspector (O uso com LINQ é apenas um exemplo)

    ...
    public RuntimeSet trackableGroup;
    ...
    trackableGroup.Items.Find(...);
    

OBS: Quem acessar o componente é responsável por coletar as informações necessárias

Como funciona

  1. Uma classe abstrata que representa um grupo de “coisas”
[CreateAssetMenu(fileName= "RuntimeSet", menuName= "RuntimeSet")]
public class RuntimeSet : ScriptableObject
{
    public List<RuntimeItem> Items = new List<RuntimeItem>();

    public void Add(RuntimeItem thing)
    {
        if (!Items.Contains(thing))
            Items.Add(thing);
    }

    public void Remove(RuntimeItem thing)
    {
        if (Items.Contains(thing))
            Items.Remove(thing);
    }
}
  1. Um componente que marca um objeto como “rastreável”
public class RuntimeItem : MonoBehaviour
{
    public RuntimeSet runtimeSet;

    private void OnEnable()
        => runtimeSet.Add(this);

    private void OnDisable()
        => runtimeSet.Remove(this);
}

to up


Reload-proof Singleton

Richard Fine - Unite 2016

Conceito

​ Em algumas situações precisamos de objetos que existam independentemente de qual cena está em uso atualmente, para esses casos é proposto o uso de padrões singletons que existem apenas nos assets por meio de SO. Essa abordagem evita o uso de preload-scenes

Como usar

  1. Crie um script que herde de ScriptableSingleton
[CreateAssetMenu(fileName = "SceneController", menuName = "Managers/Scene")]
public class SceneController : ScriptableSingleton<SceneController>
{   
    public void ChangeScene(int sceneIndex)
       => SceneManager.LoadScene(sceneIndex);
}
  1. Crie uma pasta chamada “Resources” em qualquer parte do seu diretório e deixe o singleton ali dentro. Caso seja necessário testar diferentes “controladores”, troque o controlador que está dentro da pasta
  2. A partir de qualquer ponto do código de qualquer script, chame o singleton por meio de sua instancia: SceneController.Instance.ChangeScene(0)

Como funciona

  1. Uma classe genérica que procura o singleton dentro da pasta resource

    using System.Linq;
    using UnityEngine;
    
    public abstract class ScriptableSingleton<T> : ScriptableObject 
        where T : ScriptableObject
    {
        protected static T _instance;
        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    var type = typeof(T);
                    var instances = Resources.LoadAll<T>(string.Empty);
                    _instance = instances.FirstOrDefault();
                    if (_instance == null)
                    {
                        Debug.LogErrorFormat("[ScriptableSingleton] No instance of {0} found!", type.ToString());
                    }
                    else if (instances.Count() > 1)
                    {
                        Debug.LogErrorFormat("[ScriptableSingleton] Multiple instances of {0} found!", type.ToString());
                    }
                }
                return _instance;
            }
        }
    }
    

to up


Audio events

Richard Fine - Unite 2016

Conceito

​ Para controlar a adição de áudio no projeto e melhorar a experiência de controle, é utilizado um padrão de delegate para criar comportamentos distintos entre arquivos que controlam os áudios

Quando usar

​ É possível criar comportamentos distintos para grupos distintos de áudio, como por exemplo, um comportamento que randomiza um áudio dentro de uma array de AudioClips com ou sem peso, que alterna entre áudio, etc… e todos por ser injetados da mesma maneira pelo inspector e/ou chamados por código. Enfim, use sempre que possível

Como usar

  1. Crie e configure um novo SO do evento de áudio desejado (botão direito -> Áudio events)

  2. Na classe responsável por executar o áudio crie uma referencia para a classe base e chame seu método Play() pelo código OU pelo inspector por meio de eventos

    ...
    public AudioEvent burp;
    ...
    
  3. Arraste o áudio criado no passo 1

Como funciona

  1. Uma classe base que garante a existência do método play (note que é usado uma classe abstrata pois o inspector do unity não permite que interfaces sejam injetadas por ele, não de maneira nativa pelo menos)
  2. Agora é criado diferentes tipos de áudio que herdem da classe acima, implementa comportamentos distintos para cada um e permitindo que eles sejam criados por meio de SO
  3. É implementado um custom editor para permitir que o som seja testado a qualquer momento

Imgur

OBS 1: A variável de volume e pitch são customizados para possuir um mínimo e máximo

OBS 2: A imagem é apenas um exemplo, cada classe vai ter seu próprio meio de aparecer no inspector

to up


Atributos

Richard Fine - Unite 2016

Imgur

​ Permite que você escolha um mínimo e um máximo para um variável qualquer

Como usar

​ Cria uma variável do tipo (custom) FloatRange (ou IntRange), por padrão ela oscila de 0 a 1, para valores diferentes insira o atributo MaxMinRange

...
[MaxMinRange(0, 2)]
public FloatRange volume;
Métodos
GetRandom() Retorna um valor aleatório entre os especificados no atributo
Show all projects by Nefisto