- Обучение
- Interface & Essentials
- Property Drawers & Custom Inspectors
Property Drawers & Custom Inspectors
Проверено с версией:: 4.6
-
Сложность: Средняя
As an introduction to editor scripting, in the session we will look at how to customize and change the way the Inspector looks in our project by using Property Drawers and Custom Inspectors. Property Drawers allow us to customize the way certain controls or a custom class look in the inspector. Custom Inspectors allow us to change the look of specific components in the inspector. We will show you how to do this. Tutor - Adam Buckner


Property Drawers & Custom Inspectors
Средняя Interface & Essentials
Additional Links being added asap...
This script, TransformInspector, is no longer needed as a custom inspector, as noted in the lesson, but it still serves as a solid lesson in Editor Scripting.
Transform Inspector
Code snippet
using UnityEngine;
using UnityEditor;
using System.Collections;
[CustomEditor(typeof(Transform))]
public class TransformInspector : Editor {
public bool showTools;
public bool copyPosition;
public bool copyRotation;
public bool copyScale;
public bool pastePosition;
public bool pasteRotation;
public bool pasteScale;
public bool selectionNullError;
public override void OnInspectorGUI() {
Transform t = (Transform)target;
// Replicate the standard transform inspector gui
EditorGUIUtility.LookLikeControls();
EditorGUI.indentLevel = 0;
Vector3 position = EditorGUILayout.Vector3Field("Position", t.localPosition);
Vector3 eulerAngles = EditorGUILayout.Vector3Field("Rotation", t.localEulerAngles);
Vector3 scale = EditorGUILayout.Vector3Field("Scale", t.localScale);
EditorGUIUtility.LookLikeInspector();
//
if (GUILayout.Button ((showTools) ? "Hide Transform Tools" : "Show Transform Tools")) {
showTools = !showTools;
EditorPrefs.SetBool ("ShowTools", showTools);
}
// START TRANSFORM TOOLS FOLD DOWN //
if (showTools) {
if (!copyPosition && !copyRotation && !copyScale) {
selectionNullError = true;
} else {
selectionNullError = false;
}
EditorGUILayout.BeginHorizontal ();
if (GUILayout.Button (selectionNullError ? "Nothing Selected" : "Copy Transform")) {
if (copyPosition) {
EditorPrefs.SetFloat("LocalPosX", t.localPosition.x);
EditorPrefs.SetFloat("LocalPosY", t.localPosition.y);
EditorPrefs.SetFloat("LocalPosZ", t.localPosition.z);
}
if (copyRotation) {
EditorPrefs.SetFloat("LocalRotX", t.localEulerAngles.x);
EditorPrefs.SetFloat("LocalRotY", t.localEulerAngles.y);
EditorPrefs.SetFloat("LocalRotZ", t.localEulerAngles.z);
}
if (copyScale) {
EditorPrefs.SetFloat("LocalScaleX", t.localScale.x);
EditorPrefs.SetFloat("LocalScaleY", t.localScale.y);
EditorPrefs.SetFloat("LocalScaleZ", t.localScale.z);
}
Debug.Log("LP: " + t.localPosition + " LT: (" + t.localEulerAngles.x + ", " + t.localEulerAngles.y + ", " + t.localEulerAngles.z + ") LS: " + t.localScale);
}
if (GUILayout.Button ("Paste Transform")) {
Vector3 tV3 = new Vector3();
if (pastePosition) {
tV3.x = EditorPrefs.GetFloat("LocalPosX", 0.0f);
tV3.y = EditorPrefs.GetFloat("LocalPosY", 0.0f);
tV3.z = EditorPrefs.GetFloat("LocalPosZ", 0.0f);
t.localPosition = tV3;
}
if (pasteRotation) {
tV3.x = EditorPrefs.GetFloat("LocalRotX", 0.0f);
tV3.y = EditorPrefs.GetFloat("LocalRotY", 0.0f);
tV3.z = EditorPrefs.GetFloat("LocalRotZ", 0.0f);
t.localEulerAngles = tV3;
}
if (pasteScale) {
tV3.x = EditorPrefs.GetFloat("LocalScaleX", 1.0f);
tV3.y = EditorPrefs.GetFloat("LocalScaleY", 1.0f);
tV3.z = EditorPrefs.GetFloat("LocalScaleZ", 1.0f);
t.localScale = tV3;
}
Debug.Log("LP: " + t.localPosition + " LT: " + t.localEulerAngles + " LS: " + t.localScale);
}
EditorGUILayout.EndHorizontal ();
EditorGUIUtility.LookLikeControls();
EditorGUILayout.BeginHorizontal();
GUILayout.Label ("Position", GUILayout.Width (75));
GUILayout.Label ("Rotation", GUILayout.Width (75));
GUILayout.Label ("Scale", GUILayout.Width (50));
if (GUILayout.Button ("All", GUILayout.MaxWidth (40))) TransformCopyAll ();
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Space(20);
copyPosition = EditorGUILayout.Toggle (copyPosition, GUILayout.Width (75));
copyRotation = EditorGUILayout.Toggle (copyRotation, GUILayout.Width (65));
copyScale = EditorGUILayout.Toggle (copyScale, GUILayout.Width (45));
if (GUILayout.Button ("None", GUILayout.MaxWidth (40))) TransformCopyNone ();
EditorGUILayout.EndHorizontal();
EditorGUIUtility.LookLikeInspector();
}
// END TRANSFORM TOOLS FOLD DOWN //
if (GUI.changed) {
SetCopyPasteBools ();
Undo.RegisterUndo(t, "Transform Change");
t.localPosition = FixIfNaN(position);
t.localEulerAngles = FixIfNaN(eulerAngles);
t.localScale = FixIfNaN(scale);
}
}
private Vector3 FixIfNaN(Vector3 v) {
if (float.IsNaN(v.x)) {
v.x = 0;
}
if (float.IsNaN(v.y)) {
v.y = 0;
}
if (float.IsNaN(v.z)) {
v.z = 0;
}
return v;
}
void OnEnable () {
showTools = EditorPrefs.GetBool ("ShowTools", false);
copyPosition = EditorPrefs.GetBool ("Copy Position", true);
copyRotation = EditorPrefs.GetBool ("Copy Rotation", true);
copyScale = EditorPrefs.GetBool ("Copy Scale", true);
pastePosition = EditorPrefs.GetBool ("Paste Position", true);
pasteRotation = EditorPrefs.GetBool ("Paste Rotation", true);
pasteScale = EditorPrefs.GetBool ("Paste Scale", true);
}
void TransformCopyAll () {
copyPosition = true;
copyRotation = true;
copyScale = true;
GUI.changed = true;
}
void TransformCopyNone () {
copyPosition = false;
copyRotation = false;
copyScale = false;
GUI.changed = true;
}
void SetCopyPasteBools () {
pastePosition = copyPosition;
pasteRotation = copyRotation;
pasteScale = copyScale;
EditorPrefs.SetBool ("Copy Position", copyPosition);
EditorPrefs.SetBool ("Copy Rotation", copyRotation);
EditorPrefs.SetBool ("Copy Scale", copyScale);
EditorPrefs.SetBool ("Paste Position", pastePosition);
EditorPrefs.SetBool ("Paste Rotation", pasteRotation);
EditorPrefs.SetBool ("Paste Scale", pasteScale);
}
}
Align With Ground
Code snippet
using UnityEngine;
using UnityEditor;
using System.Collections;
public class AlignWithGround : MonoBehaviour {
[MenuItem ("Tools/Transform Tools/Align with ground %t")]
static void Align() {
Transform [] transforms = Selection.transforms;
foreach (Transform myTransform in transforms) {
RaycastHit hit;
if (Physics.Raycast (myTransform.position, -Vector3.up, out hit)) {
Vector3 targetPosition = hit.point;
if (myTransform.gameObject.GetComponent<MeshFilter>() != null) {
Bounds bounds = myTransform.gameObject.GetComponent<MeshFilter>().sharedMesh.bounds;
targetPosition.y += bounds.extents.y;
}
myTransform.position = targetPosition;
Vector3 targetRotation = new Vector3 (hit.normal.x, myTransform.eulerAngles.y, hit.normal.z);
myTransform.eulerAngles = targetRotation;
}
}
}
}
My Player
Code snippet
using UnityEngine;
using System.Collections;
public class MyPlayer : MonoBehaviour {
public int armor;
public int damage;
public GameObject gun;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
My Player Editor
Code snippet
using UnityEngine;
using UnityEditor;
using System.Collections;
[CustomEditor (typeof (MyPlayer))]
[CanEditMultipleObjects]
public class MyPlayerEditor : Editor {
SerializedProperty damageProp;
SerializedProperty armorProp;
SerializedProperty gunProp;
void OnEnable () {
// Setup the SerializedProperties
damageProp = serializedObject.FindProperty ("damage");
armorProp = serializedObject.FindProperty ("armor");
gunProp = serializedObject.FindProperty ("gun");
}
public override void OnInspectorGUI() {
// Update the serializedProperty - always do this in the beginning of OnInspectorGUI.
serializedObject.Update ();
// Show the custom GUI controls
EditorGUILayout.IntSlider (damageProp, 0, 100, new GUIContent ("Damage"));
// Only show the damage progress bar if all the objects have the same damage value:
if (!damageProp.hasMultipleDifferentValues)
ProgressBar (damageProp.intValue / 100.0f, "Damage");
EditorGUILayout.IntSlider (armorProp, 0, 100, new GUIContent ("Armor"));
// Only show the armor progress bar if all the objects have the same armor value:
if (!armorProp.hasMultipleDifferentValues)
ProgressBar (armorProp.intValue / 100.0f, "Armor");
EditorGUILayout.PropertyField (gunProp, new GUIContent ("Gun Object"));
// Apply changes to the serializedProperty - always do this in the end of OnInspectorGUI.
serializedObject.ApplyModifiedProperties ();
}
// Custom GUILayout progress bar.
void ProgressBar (float value, string label) {
// Get a rect for the progress bar using the same margins as a textfield:
Rect rect = GUILayoutUtility.GetRect (18, 18, "TextField");
EditorGUI.ProgressBar (rect, value, label);
EditorGUILayout.Space ();
}
}
Make UI Image
Code snippet
using UnityEngine;
using UnityEditor;
class MakeUIImage : AssetPostprocessor {
void OnPreprocessTexture () {
// Automatically convert any texture file with "GUIImages" in its file name into an uncompressed unchanged GUI Image.
if (assetPath.Contains("UI_Images") || assetPath.Contains("SpriteFonts") || assetPath.Contains("SpriteAtlases")) {
Debug.Log ("Importing new GUI Image!");
TextureImporter myTextureImporter = (TextureImporter)assetImporter;
myTextureImporter.textureType = TextureImporterType.Advanced;
myTextureImporter.textureFormat = TextureImporterFormat.ARGB32;
myTextureImporter.convertToNormalmap = false;
myTextureImporter.maxTextureSize = 2048;
myTextureImporter.grayscaleToAlpha = false;
myTextureImporter.generateCubemap = TextureImporterGenerateCubemap.None;
myTextureImporter.npotScale = TextureImporterNPOTScale.None;
myTextureImporter.isReadable = true;
myTextureImporter.mipmapEnabled = false;
// myTextureImporter.borderMipmap = false;
// myTextureImporter.correctGamma = false;
myTextureImporter.mipmapFilter = TextureImporterMipFilter.BoxFilter;
myTextureImporter.fadeout = false;
// myTextureImporter.mipmapFadeDistanceStart;
// myTextureImporter.mipmapFadeDistanceEnd;
myTextureImporter.convertToNormalmap = false;
// myTextureImporter.normalmap;
// myTextureImporter.normalmapFilter;
// myTextureImporter.heightmapScale;
myTextureImporter.lightmap = false;
myTextureImporter.ClearPlatformTextureSettings("Web");
myTextureImporter.ClearPlatformTextureSettings("Standalone");
myTextureImporter.ClearPlatformTextureSettings("iPhone");
}
}
}
Scaled Curve
Code snippet
using UnityEngine;
using System.Collections;
// Custom serializable class
[System.Serializable]
public class ScaledCurve {
public float scale = 1;
public AnimationCurve curve = AnimationCurve.Linear (0, 0, 1, 1);
}
Testing Curves
Code snippet
using UnityEngine;
using System.Collections;
public class TestingCurves : MonoBehaviour {
public ScaledCurve scaledCurve01;
public ScaledCurve scaledCurve02;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
Another Testing Script
Code snippet
using UnityEngine;
using System.Collections;
public class AnotherTestingScript : MonoBehaviour {
public ScaledCurve myScaledCurve;
public int myInt;
public Vector3 myV3;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
Scaled Curve Drawer
Code snippet
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer (typeof (ScaledCurve))]
public class ScaledCurveDrawer : PropertyDrawer {
const int curveWidth = 50;
const float min = 0;
const float max = 1;
public override void OnGUI (Rect pos, SerializedProperty prop, GUIContent label) {
SerializedProperty scale = prop.FindPropertyRelative ("scale");
SerializedProperty curve = prop.FindPropertyRelative ("curve");
// Draw scale
EditorGUI.Slider (
new Rect (pos.x, pos.y, pos.width - curveWidth, pos.height),
scale, min, max, label);
// Draw curve
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField (
new Rect (pos.width - curveWidth, pos.y, curveWidth, pos.height),
curve, GUIContent.none);
EditorGUI.indentLevel = indent;
}
}
Regex Attribute
Code snippet
using UnityEngine;
public class RegexAttribute : PropertyAttribute {
public readonly string pattern;
public readonly string helpMessage;
public RegexAttribute (string pattern, string helpMessage) {
this.pattern = pattern;
this.helpMessage = helpMessage;
}
}
Regex Example
Code snippet
using UnityEngine;
public class RegexExample : MonoBehaviour {
public string name;
public GameObject tankModel;
[Regex (@"^(?:\d{1,3}\.){3}\d{1,3}$", "Invalid IP address!\nExample: '127.0.0.1'")]
public string serverAddress = "192.168.0.1";
}
Regex Drawer
Code snippet
using UnityEditor;
using UnityEngine;
using System.Text.RegularExpressions;
[CustomPropertyDrawer (typeof (RegexAttribute))]
public class RegexDrawer : PropertyDrawer {
// These constants describe the height of the help box and the text field.
const int helpHeight = 30;
const int textHeight = 16;
// Provide easy access to the RegexAttribute for reading information from it.
RegexAttribute regexAttribute { get { return ((RegexAttribute)attribute); } }
// Here you must define the height of your property drawer. Called by Unity.
public override float GetPropertyHeight (SerializedProperty prop,
GUIContent label) {
if (IsValid (prop))
return base.GetPropertyHeight (prop, label);
else
return base.GetPropertyHeight (prop, label) + helpHeight;
}
// Here you can define the GUI for your property drawer. Called by Unity.
public override void OnGUI (Rect position, SerializedProperty prop, GUIContent label) {
// Adjust height of the text field
Rect textFieldPosition = position;
textFieldPosition.height = textHeight;
DrawTextField (textFieldPosition, prop, label);
// Adjust the help box position to appear indented underneath the text field.
Rect helpPosition = EditorGUI.IndentedRect (position);
helpPosition.y += textHeight;
helpPosition.height = helpHeight;
DrawHelpBox (helpPosition, prop);
}
void DrawTextField (Rect position, SerializedProperty prop, GUIContent label) {
// Draw the text field control GUI.
EditorGUI.BeginChangeCheck ();
string value = EditorGUI.TextField (position, label, prop.stringValue);
if (EditorGUI.EndChangeCheck ())
prop.stringValue = value;
}
void DrawHelpBox (Rect position, SerializedProperty prop) {
// No need for a help box if the pattern is valid.
if (IsValid (prop))
return;
EditorGUI.HelpBox (position, regexAttribute.helpMessage, MessageType.Error);
}
// Test if the propertys string value matches the regex pattern.
bool IsValid (SerializedProperty prop) {
return Regex.IsMatch (prop.stringValue, regexAttribute.pattern);
}
}
Ingredient Testing
Code snippet
using UnityEngine;
public enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class
[System.Serializable]
public class Ingredient{
public string name;
public int amount = 1;
public IngredientUnit unit;
}
//This is not an editor script
class IngredientTesting : MonoBehaviour{
[Range (1,10)]
public int myInt;
public Ingredient potionResult;
public Ingredient[] potionIngredients;
void Update () {
// Update logic here...
}
}
Ingredient Drawer
Code snippet
using UnityEngine;
using UnityEditor;
using System.Collections;
[CustomPropertyDrawer (typeof (Ingredient))]
class IngredientDrawer : PropertyDrawer {
// Draw the property inside the given rect
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
// Using BeginProperty / EndProperty on the parent property means that
// prefab override logic works on the entire property.
EditorGUI.BeginProperty (position, label, property);
// Draw label
position = EditorGUI.PrefixLabel (position, GUIUtility.GetControlID (FocusType.Passive), label);
// Don't make child fields be indented
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
// Calculate rects
Rect amountRect = new Rect (position.x, position.y, 30, position.height);
Rect unitRect = new Rect (position.x + 35, position.y, 50, position.height);
Rect nameRect = new Rect (position.x + 90, position.y, position.width - 90, position.height);
// Draw fields - passs GUIContent.none to each so they are drawn without labels
EditorGUI.PropertyField (amountRect, property.FindPropertyRelative ("amount"), GUIContent.none);
EditorGUI.PropertyField (unitRect, property.FindPropertyRelative ("unit"), GUIContent.none);
EditorGUI.PropertyField (nameRect, property.FindPropertyRelative ("name"), GUIContent.none);
// Set indent back to what it was
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty ();
}
}
Связанные обучающие материалы
- Editor Scripting Intro (Урок)
- Unity Editor Extensions – Menu Items (Урок)