Um truque para mostrar o seletor pop-up de nomes de camadas de classificação para seu componente

Hoje eu tive um pequeno problema com o Unity:
eu tinha um componente simples que muda meu renderizador sortingLayerNamebaseado em alguma condição. Algo assim:

class MyComp : Monobehavior {
void Update() {
if (somecondition) {
renderer
.sortingLayerName = "Some";
} else {
renderer
.sortingLayerName = "Another";
}
}
}

Realmente, eu tive que lidar com esse tipo de script todos os dias para Oh! Estou ficando mais alto .
Por exemplo, para ajustar a ordem de classificação do jogador quando está dentro ou fora de um edifício. Ou a posição do item quando é agarrado pelo jogador. É muito comum em jogos 2D manipular as ordens de classificação e odeio escrever a string exata da camada de classificação manualmente. Se eu cometer um erro de digitação no nome da camada, ele será interrompido silenciosamente. É muito irritante.

Então eu refatorei assim:

Em primeiro lugar, movi os nomes das duas camadas em duas variáveis ​​públicas, para poder alterná-los em meu editor. Ainda é muito inconveniente.

Eu poderia criar um Editorscript específico para personalizá-lo, mas é muito chato escrever um componente para esse tipo de problema. Eu quero algo mais genérico …

Em primeiro lugar, defini um novo System.Attributecomo este:

using System;

[AttributeUsage(AttributeTargets.Class,Inherited = true)]
public class HasSortingLayer : Attribute
{
string[] _names;
public string[] Names { get { return _names; } }
public HasSortingLayer(params string[] names) { _names = names; }
}

Eu usei para decorar meu componente:

[HasSortingLayerName('ActiveLayerName', 'InactiveLayerName')]
class MyComp : Monobehavior {
public string ActiveLayerName;
public string InactiveLayerName;

void Update() {
if (somecondition) {
renderer
.sortingLayerName = "Some";
} else {
renderer
.sortingLayerName = "Another";
}
}
}

Finalmente, defini um CustomEditorscript genérico para a MonoBehavior(= todos os tipos de scripts):

using UnityEditor;
using UnityEngine;
using System;
using System.Reflection;
using System.Linq;
using UnityEditorInternal;

[CustomEditor(typeof(MonoBehaviour), true)]
public class SortingLayerEditor : Editor {

SerializedProperty[] properties;
string[] sortingLayerNames;

void OnEnable(){
if (Attribute.IsDefined (target.GetType (), typeof(HasSortingLayerName))) {
var sortingLayer = (HasSortingLayerName)Attribute.GetCustomAttribute(target.GetType (),typeof(HasSortingLayerName));
properties
= sortingLayer.Names.Select (s => {
return serializedObject.FindProperty (s);
}).ToArray ();
sortingLayerNames
= GetSortingLayerNames ();
}
}

public override void OnInspectorGUI() {
base.OnInspectorGUI ();
if (properties != null && sortingLayerNames != null) {
foreach (var p in properties) {
if (p == null) {
continue;
}
int index = Mathf.Max (0, Array.IndexOf (sortingLayerNames, p.stringValue));
index
= EditorGUILayout.Popup (p.displayName, index, sortingLayerNames);

p
.stringValue = sortingLayerNames [index];
}

if (GUI.changed) {
serializedObject
.ApplyModifiedProperties ();
}
}
}

public string[] GetSortingLayerNames() {
Type internalEditorUtilityType = typeof(InternalEditorUtility);
PropertyInfo sortingLayersProperty = internalEditorUtilityType.GetProperty("sortingLayerNames", BindingFlags.Static | BindingFlags.NonPublic);
var sortingLayers = (string[])sortingLayersProperty.GetValue(null, new object[0]);
return sortingLayers;
}
}

E TA-DA!
Exemplo

Como funciona

O GetSortingLayerNamesmétodo extrai a lista de nomes de camadas de classificação.
No OnEnablemétodo, verifico se o componente atual possui um Attributedo tipo HasSortingLayerName. Se sim, ele extrai um SerializablePropertyby Names. Finalmente, ele usa um EditorGUILayout.Popuppara mostrar o pop-up no editor!

Muito simples, mas muito útil: apenas definir o HasSortingLayerNameatributo em um componente, faz com que o editor desenhe um pop-up para escolher a camada de classificação!

Respostas relacionadas:

PlayerPrefs aprimorados para Unity