unity3d
Rozszerzanie edytora
Szukaj…
Składnia
- [MenuItem (string itemName)]
- [MenuItem (string itemName, bool isValidateFunction)]
- [MenuItem (ciąg itemName, bool isValidateFunction, int priorytet)]
- [ContextMenu (nazwa ciągu)]
- [ContextMenuItem (nazwa ciągu, funkcja ciągu)]
- [DrawGizmo (GizmoType gizmo)]
- [DrawGizmo (GizmoType gizmo, Type drawnGizmoType)]
Parametry
Parametr | Detale |
---|---|
MenuCommand | MenuCommand służy do wyodrębnienia kontekstu dla elementu menu |
MenuCommand.context | Obiekt będący celem polecenia menu |
MenuCommand.userData | Int do przekazywania niestandardowych informacji do elementu menu |
Inspektor celny
Korzystanie z niestandardowego inspektora pozwala zmienić sposób rysowania skryptu w Inspektorze. Czasami chcesz dodać do inspektora dodatkowe informacje o skrypcie, których nie można zrobić z szufladą właściwości niestandardowych.
Poniżej znajduje się prosty przykład niestandardowego obiektu, który przy użyciu niestandardowego inspektora może wyświetlać bardziej przydatne informacje.
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class InspectorExample : MonoBehaviour {
public int Level;
public float BaseDamage;
public float DamageBonus {
get {
return Level / 100f * 50;
}
}
public float ActualDamage {
get {
return BaseDamage + DamageBonus;
}
}
}
#if UNITY_EDITOR
[CustomEditor( typeof( InspectorExample ) )]
public class CustomInspector : Editor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
var ie = (InspectorExample)target;
EditorGUILayout.LabelField( "Damage Bonus", ie.DamageBonus.ToString() );
EditorGUILayout.LabelField( "Actual Damage", ie.ActualDamage.ToString() );
}
}
#endif
Najpierw definiujemy nasze niestandardowe zachowanie za pomocą niektórych pól
public class InspectorExample : MonoBehaviour {
public int Level;
public float BaseDamage;
}
Pola pokazane powyżej są rysowane automatycznie (bez niestandardowego inspektora) podczas przeglądania skryptu w oknie Inspektora.
public float DamageBonus {
get {
return Level / 100f * 50;
}
}
public float ActualDamage {
get {
return BaseDamage + DamageBonus;
}
}
Te właściwości nie są automatycznie rysowane przez Unity. Aby pokazać te właściwości w widoku Inspektora, musimy użyć naszego Inspektora niestandardowego.
Najpierw musimy zdefiniować naszego niestandardowego inspektora w ten sposób
[CustomEditor( typeof( InspectorExample ) )]
public class CustomInspector : Editor {
Niestandardowy inspektor musi pochodzić z edytora i potrzebuje atrybutu CustomEditor . Parametrem atrybutu jest typ obiektu, do którego powinien być używany ten niestandardowy inspektor.
Następna jest metoda OnInspectorGUI. Ta metoda jest wywoływana za każdym razem, gdy skrypt jest wyświetlany w oknie inspektora.
public override void OnInspectorGUI() {
base.OnInspectorGUI();
}
Wykonujemy wywołanie base.OnInspectorGUI (), aby umożliwić Unity obsługę innych pól znajdujących się w skrypcie. Gdybyśmy tego nie nazywali, sami musielibyśmy więcej pracować.
Następnie są nasze niestandardowe właściwości, które chcemy pokazać
var ie = (InspectorExample)target;
EditorGUILayout.LabelField( "Damage Bonus", ie.DamageBonus.ToString() );
EditorGUILayout.LabelField( "Actual Damage", ie.ActualDamage.ToString() );
Musimy utworzyć tymczasową zmienną, która utrzymuje cel rzutowany na nasz niestandardowy typ (cel jest dostępny, ponieważ wywodzimy się z edytora).
Następnie możemy zdecydować, jak narysować nasze właściwości, w tym przypadku wystarczą dwa pola etykiet, ponieważ chcemy tylko wyświetlić wartości, a nie móc ich edytować.
Wynik
Przed
Po
Szuflada właściwości niestandardowych
Czasami masz niestandardowe obiekty, które zawierają dane, ale nie pochodzą od MonoBehaviour. Dodanie tych obiektów jako pola w klasie, która jest MonoBehaviour, nie będzie miało żadnego efektu wizualnego, chyba że napiszesz własną szufladę właściwości niestandardowych dla typu obiektu.
Poniżej znajduje się prosty przykład obiektu niestandardowego dodanego do MonoBehaviour oraz szuflada właściwości niestandardowej dla obiektu niestandardowego.
public enum Gender {
Male,
Female,
Other
}
// Needs the Serializable attribute otherwise the CustomPropertyDrawer wont be used
[Serializable]
public class UserInfo {
public string Name;
public int Age;
public Gender Gender;
}
// The class that you can attach to a GameObject
public class PropertyDrawerExample : MonoBehaviour {
public UserInfo UInfo;
}
[CustomPropertyDrawer( typeof( UserInfo ) )]
public class UserInfoDrawer : PropertyDrawer {
public override float GetPropertyHeight( SerializedProperty property, GUIContent label ) {
// The 6 comes from extra spacing between the fields (2px each)
return EditorGUIUtility.singleLineHeight * 4 + 6;
}
public override void OnGUI( Rect position, SerializedProperty property, GUIContent label ) {
EditorGUI.BeginProperty( position, label, property );
EditorGUI.LabelField( position, label );
var nameRect = new Rect( position.x, position.y + 18, position.width, 16 );
var ageRect = new Rect( position.x, position.y + 36, position.width, 16 );
var genderRect = new Rect( position.x, position.y + 54, position.width, 16 );
EditorGUI.indentLevel++;
EditorGUI.PropertyField( nameRect, property.FindPropertyRelative( "Name" ) );
EditorGUI.PropertyField( ageRect, property.FindPropertyRelative( "Age" ) );
EditorGUI.PropertyField( genderRect, property.FindPropertyRelative( "Gender" ) );
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
}
Najpierw definiujemy obiekt niestandardowy ze wszystkimi jego wymaganiami. Po prostu prosta klasa opisująca użytkownika. Ta klasa jest używana w naszej klasie PropertyDrawerExample, którą możemy dodać do GameObject.
public enum Gender {
Male,
Female,
Other
}
[Serializable]
public class UserInfo {
public string Name;
public int Age;
public Gender Gender;
}
public class PropertyDrawerExample : MonoBehaviour {
public UserInfo UInfo;
}
Klasa niestandardowa wymaga atrybutu Serializable, w przeciwnym razie CustomPropertyDrawer nie będzie używany
Następny jest CustomPropertyDrawer
Najpierw musimy zdefiniować klasę wywodzącą się z PropertyDrawer. Definicja klasy wymaga również atrybutu CustomPropertyDrawer. Przekazywany parametr to typ obiektu, dla którego ma być używana ta szuflada.
[CustomPropertyDrawer( typeof( UserInfo ) )]
public class UserInfoDrawer : PropertyDrawer {
Następnie zastępujemy funkcję GetPropertyHeight. To pozwala nam zdefiniować niestandardową wysokość dla naszej nieruchomości. W tym przypadku wiemy, że nasza nieruchomość będzie składać się z czterech części: etykiety, nazwiska, wieku i płci. Dlatego używamy EditorGUIUtility.singleLineHeight * 4 , dodajemy kolejne 6 pikseli, ponieważ chcemy rozmieścić każde pole z dwoma pikselami pomiędzy nimi.
public override float GetPropertyHeight( SerializedProperty property, GUIContent label ) {
return EditorGUIUtility.singleLineHeight * 4 + 6;
}
Dalej jest faktyczna metoda OnGUI. Zaczynamy od EditorGUI.BeginProperty ([...]), a kończymy funkcję EditorGUI.EndProperty () . Robimy to, aby jeśli ta właściwość była częścią prefabrykatu, rzeczywista logika zastępująca prefabrykację działałaby dla wszystkiego pomiędzy tymi dwiema metodami.
public override void OnGUI( Rect position, SerializedProperty property, GUIContent label ) {
EditorGUI.BeginProperty( position, label, property );
Następnie wyświetlamy etykietę zawierającą nazwę pola i już definiujemy prostokąty dla naszych pól.
EditorGUI.LabelField( position, label );
var nameRect = new Rect( position.x, position.y + 18, position.width, 16 );
var ageRect = new Rect( position.x, position.y + 36, position.width, 16 );
var genderRect = new Rect( position.x, position.y + 54, position.width, 16 );
Każde pole jest rozmieszczone w odstępach 16 + 2 pikseli, a wysokość wynosi 16 (czyli tyle samo, co EditorGUIUtility.singleLineHeight)
Następnie wciskamy interfejs użytkownika za pomocą jednej karty, aby uzyskać nieco ładniejszy układ, wyświetlać właściwości, cofać wcięcie GUI i kończyć na EditorGUI.EndProperty .
EditorGUI.indentLevel++;
EditorGUI.PropertyField( nameRect, property.FindPropertyRelative( "Name" ) );
EditorGUI.PropertyField( ageRect, property.FindPropertyRelative( "Age" ) );
EditorGUI.PropertyField( genderRect, property.FindPropertyRelative( "Gender" ) );
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
Wyświetlamy pola za pomocą EditorGUI.PropertyField, który wymaga prostokąta dla pozycji i SerializedProperty do wyświetlenia właściwości. Pozyskujemy tę właściwość, wywołując FindPropertyRelative („...”) na właściwości przekazanej w funkcji OnGUI . Pamiętaj, że w przypadku rozróżnianych wielkości liter nie można znaleźć właściwości niepublicznych!
W tym przykładzie nie zapisuję zwrotu właściwości z właściwości.FindPropertyRelative („...”). Powinieneś zapisać je w prywatnych polach w klasie, aby zapobiec niepotrzebnym połączeniom
Wynik
Przed
Po
Elementy menu
Pozycje menu to świetny sposób na dodanie niestandardowych akcji do edytora. Możesz dodać elementy menu do paska menu, ustawić je jako kliknięcia kontekstowe określonych składników lub nawet kliknięcia kontekstowe pól w skryptach.
Poniżej znajduje się przykład zastosowania elementów menu.
public class MenuItemsExample : MonoBehaviour {
[MenuItem( "Example/DoSomething %#&d" )]
private static void DoSomething() {
// Execute some code
}
[MenuItem( "Example/DoAnotherThing", true )]
private static bool DoAnotherThingValidator() {
return Selection.gameObjects.Length > 0;
}
[MenuItem( "Example/DoAnotherThing _PGUP", false )]
private static void DoAnotherThing() {
// Execute some code
}
[MenuItem( "Example/DoOne %a", false, 1 )]
private static void DoOne() {
// Execute some code
}
[MenuItem( "Example/DoTwo #b", false, 2 )]
private static void DoTwo() {
// Execute some code
}
[MenuItem( "Example/DoFurther &c", false, 13 )]
private static void DoFurther() {
// Execute some code
}
[MenuItem( "CONTEXT/Camera/DoCameraThing" )]
private static void DoCameraThing( MenuCommand cmd ) {
// Execute some code
}
[ContextMenu( "ContextSomething" )]
private void ContentSomething() {
// Execute some code
}
[ContextMenuItem( "Reset", "ResetDate" )]
[ContextMenuItem( "Set to Now", "SetDateToNow" )]
public string Date = "";
public void ResetDate() {
Date = "";
}
public void SetDateToNow() {
Date = DateTime.Now.ToString();
}
}
Który wygląda tak
Przejdźmy do podstawowego elementu menu. Jak widać poniżej, musisz zdefiniować funkcję statyczną z atrybutem MenuItem , który przekazujesz ciąg jako tytuł pozycji menu. Możesz umieścić swój element menu na wielu poziomach głęboko, dodając / do nazwy.
[MenuItem( "Example/DoSomething %#&d" )]
private static void DoSomething() {
// Execute some code
}
Nie możesz mieć pozycji menu na najwyższym poziomie. Twoje pozycje menu muszą znajdować się w podmenu!
Znaki specjalne na końcu nazwy MenuItem są skrótami klawiszowymi, nie są wymagane.
Istnieją klawisze specjalne, których można użyć do klawiszy skrótu, są to:
- % - Ctrl w Windows, Cmd w OS X
- # - Zmiana
- & - Alt
Oznacza to, że skrót % # & d oznacza ctrl + shift + alt + D w systemie Windows, a cmd + shift + alt + D w systemie OS X.
Jeśli chcesz użyć skrótu bez specjalnych klawiszy, a więc na przykład tylko klawisz „D”, możesz wstawić znak _ (podkreślenie) do klawisza skrótu, którego chcesz użyć.
Obsługiwane są inne specjalne klucze, które są:
- LEWO, PRAWO, GÓRA, DÓŁ - dla klawiszy strzałek
- F1..F12 - dla klawiszy funkcyjnych
- HOME, END, PGUP, PGDN - dla klawiszy nawigacyjnych
Klawisze skrótów należy oddzielić od dowolnego innego tekstu spacją
Następnie znajdują się elementy menu walidatora. Elementy menu walidatora pozwalają na wyłączenie elementów menu (wyszarzone, niemożliwe do kliknięcia), gdy warunek nie jest spełniony. Przykładem może być to, że element menu działa na bieżący wybór GameObjects, co można sprawdzić w elemencie menu walidatora.
[MenuItem( "Example/DoAnotherThing", true )]
private static bool DoAnotherThingValidator() {
return Selection.gameObjects.Length > 0;
}
[MenuItem( "Example/DoAnotherThing _PGUP", false )]
private static void DoAnotherThing() {
// Execute some code
}
Aby element menu walidatora zadziałał, musisz utworzyć dwie funkcje statyczne, obie z atrybutem MenuItem i o tej samej nazwie (klawisz skrótu nie ma znaczenia). Różnica między nimi polega na tym, że oznacza się je jako funkcję walidatora lub nie, przekazując parametr boolowski.
Możesz także zdefiniować kolejność pozycji menu, dodając priorytet. Priorytet jest definiowany przez liczbę całkowitą przekazywaną jako trzeci parametr. Im mniejsza liczba, tym wyżej na liście, tym większa liczba, tym niższa na liście. Możesz dodać separator pomiędzy dwiema pozycjami menu, upewniając się, że między priorytetem pozycji menu jest co najmniej 10 cyfr.
[MenuItem( "Example/DoOne %a", false, 1 )]
private static void DoOne() {
// Execute some code
}
[MenuItem( "Example/DoTwo #b", false, 2 )]
private static void DoTwo() {
// Execute some code
}
[MenuItem( "Example/DoFurther &c", false, 13 )]
private static void DoFurther() {
// Execute some code
}
Jeśli masz listę menu, która zawiera kombinację elementów z priorytetami i bez priorytetów, elementy bez priorytetów zostaną oddzielone od elementów z priorytetami.
Następnie dodaje się pozycję menu do menu kontekstowego już istniejącego komponentu. Musisz zacząć nazwę MenuItem od CONTEXT (rozróżnia małe i wielkie litery), a twoja funkcja przyjmuje parametr MenuCommand.
Poniższy fragment doda element menu kontekstowego do komponentu Camera.
[MenuItem( "CONTEXT/Camera/DoCameraThing" )]
private static void DoCameraThing( MenuCommand cmd ) {
// Execute some code
}
Który wygląda tak
Parametr MenuCommand daje dostęp do wartości składnika i wszelkich danych użytkownika, które zostaną z nim wysłane.
Możesz także dodać pozycję menu kontekstowego do własnych komponentów, używając atrybutu ContextMenu. Ten atrybut ma tylko nazwę, brak walidacji lub priorytetu i musi być częścią metody niestatycznej.
[ContextMenu( "ContextSomething" )]
private void ContentSomething() {
// Execute some code
}
Który wygląda tak
Możesz także dodawać elementy menu kontekstowego do pól we własnym komponencie. Te elementy menu pojawią się po kliknięciu kontekstowego pola, do którego należą i mogą wykonywać metody zdefiniowane w tym komponencie. W ten sposób możesz na przykład dodać wartości domyślne lub bieżącą datę, jak pokazano poniżej.
[ContextMenuItem( "Reset", "ResetDate" )]
[ContextMenuItem( "Set to Now", "SetDateToNow" )]
public string Date = "";
public void ResetDate() {
Date = "";
}
public void SetDateToNow() {
Date = DateTime.Now.ToString();
}
Który wygląda tak
Gizmos
Gizmo służą do rysowania kształtów w widoku sceny. Możesz użyć tych kształtów, aby narysować dodatkowe informacje o swoich obiektach GameObject, na przykład o ich skorupce lub zasięgu wykrywania.
Poniżej znajdują się dwa przykłady, jak to zrobić
Przykład pierwszy
W tym przykładzie zastosowano metody OnDrawGizmos i OnDrawGizmosSelected (magia).
public class GizmoExample : MonoBehaviour {
public float GetDetectionRadius() {
return 12.5f;
}
public float GetFOV() {
return 25f;
}
public float GetMaxRange() {
return 6.5f;
}
public float GetMinRange() {
return 0;
}
public float GetAspect() {
return 2.5f;
}
public void OnDrawGizmos() {
var gizmoMatrix = Gizmos.matrix;
var gizmoColor = Gizmos.color;
Gizmos.matrix = Matrix4x4.TRS( transform.position, transform.rotation, transform.lossyScale );
Gizmos.color = Color.red;
Gizmos.DrawFrustum( Vector3.zero, GetFOV(), GetMaxRange(), GetMinRange(), GetAspect() );
Gizmos.matrix = gizmoMatrix;
Gizmos.color = gizmoColor;
}
public void OnDrawGizmosSelected() {
Handles.DrawWireDisc( transform.position, Vector3.up, GetDetectionRadius() );
}
}
W tym przykładzie mamy dwie metody rysowania gadżetów, jedną, która rysuje, gdy obiekt jest aktywny (OnDrawGizmos), i drugą, gdy obiekt jest wybrany w hierarchii (OnDrawGizmosSelected).
public void OnDrawGizmos() {
var gizmoMatrix = Gizmos.matrix;
var gizmoColor = Gizmos.color;
Gizmos.matrix = Matrix4x4.TRS( transform.position, transform.rotation, transform.lossyScale );
Gizmos.color = Color.red;
Gizmos.DrawFrustum( Vector3.zero, GetFOV(), GetMaxRange(), GetMinRange(), GetAspect() );
Gizmos.matrix = gizmoMatrix;
Gizmos.color = gizmoColor;
}
Najpierw zapisujemy matrycę i kolor Gizmo, ponieważ zamierzamy go zmienić i chcemy przywrócić go z powrotem, gdy skończymy, aby nie wpływać na żaden inny rysunek Gizmo.
Następnie chcemy narysować fragment, który ma nasz obiekt, musimy jednak zmienić matrycę Gizmosa, aby dopasowała się do pozycji, obrotu i skali. Ustawiliśmy także kolor Gizmos na czerwony, aby podkreślić frustum. Po wykonaniu tej czynności możemy wywołać Gizmos.DrawFrustum, aby narysować fragment w widoku sceny.
Kiedy skończymy rysować to, co chcemy narysować, resetujemy matrycę i kolor Gizmosów.
public void OnDrawGizmosSelected() {
Handles.DrawWireDisc( transform.position, Vector3.up, GetDetectionRadius() );
}
Chcemy również narysować zasięg wykrywania, gdy wybieramy nasz GameObject. Odbywa się to za pośrednictwem klasy Handles, ponieważ klasa Gizmos nie ma żadnych metod dla dysków.
Korzystanie z tej formy rysowania wyników Gizmo w wynikach pokazanych poniżej.
Przykład drugi
W tym przykładzie użyto atrybutu DrawGizmo .
public class GizmoDrawerExample {
[DrawGizmo( GizmoType.Selected | GizmoType.NonSelected, typeof( GizmoExample ) )]
public static void DrawGizmo( GizmoExample obj, GizmoType type ) {
var gizmoMatrix = Gizmos.matrix;
var gizmoColor = Gizmos.color;
Gizmos.matrix = Matrix4x4.TRS( obj.transform.position, obj.transform.rotation, obj.transform.lossyScale );
Gizmos.color = Color.red;
Gizmos.DrawFrustum( Vector3.zero, obj.GetFOV(), obj.GetMaxRange(), obj.GetMinRange(), obj.GetAspect() );
Gizmos.matrix = gizmoMatrix;
Gizmos.color = gizmoColor;
if ( ( type & GizmoType.Selected ) == GizmoType.Selected ) {
Handles.DrawWireDisc( obj.transform.position, Vector3.up, obj.GetDetectionRadius() );
}
}
}
W ten sposób możesz oddzielić wywołania Gizmo od skryptu. Większość z nich używa tego samego kodu, co w innym przykładzie, z wyjątkiem dwóch rzeczy.
[DrawGizmo( GizmoType.Selected | GizmoType.NonSelected, typeof( GizmoExample ) )]
public static void DrawGizmo( GizmoExample obj, GizmoType type ) {
Musisz użyć atrybutu DrawGizmo, który przyjmuje enum GizmoType jako pierwszy parametr, a Typ jako drugi parametr. Typ powinien być typem, którego chcesz użyć do rysowania Gizmo.
Metoda rysowania Gizmo musi być statyczna, publiczna lub niepubliczna i może mieć dowolną nazwę. Pierwszy parametr jest typem, który powinien pasować do typu przekazanego jako drugi parametr w atrybucie, a drugim parametrem jest enum GizmoType, który opisuje bieżący stan obiektu.
if ( ( type & GizmoType.Selected ) == GizmoType.Selected ) {
Handles.DrawWireDisc( obj.transform.position, Vector3.up, obj.GetDetectionRadius() );
}
Inną różnicą jest to, że aby sprawdzić, jaki jest GizmoType obiektu, musisz wykonać operację ORAZ parametru i typu, który chcesz.
Wynik
Nie zaznaczone
Wybrany
Okno edytora
Dlaczego okno edytora?
Jak zapewne zauważyłeś, możesz zrobić wiele rzeczy w niestandardowym inspektorze (jeśli nie wiesz, czym jest niestandardowy inspektor, sprawdź przykład tutaj: http://www.riptutorial.com/unity3d/topic/2506 / extending-the-editor . Ale w pewnym momencie możesz zaimplementować panel konfiguracji lub dostosowaną paletę zasobów. W takich przypadkach będziesz używać programu EditorWindow . Sam interfejs Unity składa się z systemu Windows edytora; możesz je otworzyć (zwykle przez górny pasek), tabuluj je itp.
Utwórz podstawowe okno edytora
Prosty przykład
Tworzenie niestandardowego okna edytora jest dość proste. Wszystko, co musisz zrobić, to rozszerzyć klasę EditorWindow i skorzystać z metod Init () i OnGUI (). Oto prosty przykład:
using UnityEngine;
using UnityEditor;
public class CustomWindow : EditorWindow
{
// Add menu named "Custom Window" to the Window menu
[MenuItem("Window/Custom Window")]
static void Init()
{
// Get existing open window or if none, make a new one:
CustomWindow window = (CustomWindow) EditorWindow.GetWindow(typeof(CustomWindow));
window.Show();
}
void OnGUI()
{
GUILayout.Label("This is a custom Editor Window", EditorStyles.boldLabel);
}
}
3 ważne punkty to:
- Nie zapomnij rozszerzyć EditorWindow
- Użyj Init () jak podano w przykładzie. EditorWindow.GetWindow sprawdza, czy CustomWindow jest już utworzony. Jeśli nie, utworzy nową instancję. Korzystając z tego, upewniasz się, że nie masz jednocześnie kilku wystąpień swojego okna
- Użyj OnGUI () jak zwykle, aby wyświetlić informacje w swoim oknie
Wynik końcowy będzie wyglądał następująco:
Zejść głębiej
Oczywiście prawdopodobnie będziesz chciał zarządzać lub modyfikować niektóre zasoby za pomocą tego edytora Windows. Oto przykład użycia klasy Selection (w celu uzyskania aktywnego Selection) i modyfikacji właściwości wybranych zasobów za pomocą SerializedObject i SerializedProperty .
using System.Linq;
using UnityEngine;
using UnityEditor;
public class CustomWindow : EditorWindow
{
private AnimationClip _animationClip;
private SerializedObject _serializedClip;
private SerializedProperty _events;
private string _text = "Hello World";
// Add menu named "Custom Window" to the Window menu
[MenuItem("Window/Custom Window")]
static void Init()
{
// Get existing open window or if none, make a new one:
CustomWindow window = (CustomWindow) EditorWindow.GetWindow(typeof(CustomWindow));
window.Show();
}
void OnGUI()
{
GUILayout.Label("This is a custom Editor Window", EditorStyles.boldLabel);
// You can use EditorGUI, EditorGUILayout and GUILayout classes to display anything you want
// A TextField example
_text = EditorGUILayout.TextField("Text Field", _text);
// Note that you can modify an asset or a gameobject using an EditorWindow. Here is a quick example with an AnimationClip asset
// The _animationClip, _serializedClip and _events are set in OnSelectionChange()
if (_animationClip == null || _serializedClip == null || _events == null) return;
// We can modify our serializedClip like we would do in a Custom Inspector. For example we can grab its events and display their information
GUILayout.Label(_animationClip.name, EditorStyles.boldLabel);
for (var i = 0; i < _events.arraySize; i++)
{
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField(
"Event : " + _events.GetArrayElementAtIndex(i).FindPropertyRelative("functionName").stringValue,
EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_events.GetArrayElementAtIndex(i).FindPropertyRelative("time"), true,
GUILayout.ExpandWidth(true));
EditorGUILayout.PropertyField(_events.GetArrayElementAtIndex(i).FindPropertyRelative("functionName"),
true, GUILayout.ExpandWidth(true));
EditorGUILayout.PropertyField(_events.GetArrayElementAtIndex(i).FindPropertyRelative("floatParameter"),
true, GUILayout.ExpandWidth(true));
EditorGUILayout.PropertyField(_events.GetArrayElementAtIndex(i).FindPropertyRelative("intParameter"),
true, GUILayout.ExpandWidth(true));
EditorGUILayout.PropertyField(
_events.GetArrayElementAtIndex(i).FindPropertyRelative("objectReferenceParameter"), true,
GUILayout.ExpandWidth(true));
EditorGUILayout.Separator();
EditorGUILayout.EndVertical();
}
// Of course we need to Apply the modified properties. We don't our changes won't be saved
_serializedClip.ApplyModifiedProperties();
}
/// This Message is triggered when the user selection in the editor changes. That's when we should tell our Window to Repaint() if the user selected another AnimationClip
private void OnSelectionChange()
{
_animationClip =
Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Assets).FirstOrDefault() as AnimationClip;
if (_animationClip == null) return;
_serializedClip = new SerializedObject(_animationClip);
_events = _serializedClip.FindProperty("m_Events");
Repaint();
}
}
Zaawansowane tematy
W edytorze możesz robić naprawdę zaawansowane rzeczy, a klasa EditorWindow jest idealna do wyświetlania dużej ilości informacji. Najbardziej zaawansowane zasoby w Unity Asset Store (takie jak NodeCanvas lub PlayMaker) używają EditorWindow do wyświetlania niestandardowych widoków.
Rysowanie w SceneView
Jedną z interesujących rzeczy związanych z EditorWindow jest wyświetlanie informacji bezpośrednio w SceneView. W ten sposób możesz stworzyć w pełni spersonalizowany edytor mapy / świata, na przykład używając niestandardowego EditorWindow jako palety zasobów i słuchając kliknięć w SceneView w celu utworzenia nowych obiektów. Oto przykład :
using UnityEngine;
using System;
using UnityEditor;
public class CustomWindow : EditorWindow {
private enum Mode {
View = 0,
Paint = 1,
Erase = 2
}
private Mode CurrentMode = Mode.View;
[MenuItem ("Window/Custom Window")]
static void Init () {
// Get existing open window or if none, make a new one:
CustomWindow window = (CustomWindow)EditorWindow.GetWindow (typeof (CustomWindow));
window.Show();
}
void OnGUI () {
GUILayout.Label ("This is a custom Editor Window", EditorStyles.boldLabel);
}
void OnEnable() {
SceneView.onSceneGUIDelegate = SceneViewGUI;
if (SceneView.lastActiveSceneView) SceneView.lastActiveSceneView.Repaint();
}
void SceneViewGUI(SceneView sceneView) {
Handles.BeginGUI();
// We define the toolbars' rects here
var ToolBarRect = new Rect((SceneView.lastActiveSceneView.camera.pixelRect.width / 6), 10, (SceneView.lastActiveSceneView.camera.pixelRect.width * 4 / 6) , SceneView.lastActiveSceneView.camera.pixelRect.height / 5);
GUILayout.BeginArea(ToolBarRect);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
CurrentMode = (Mode) GUILayout.Toolbar(
(int) CurrentMode,
Enum.GetNames(typeof(Mode)),
GUILayout.Height(ToolBarRect.height));
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.EndArea();
Handles.EndGUI();
}
}
Spowoduje to wyświetlenie paska narzędzi bezpośrednio w SceneView
Oto krótkie spojrzenie na to, jak daleko możesz się posunąć: