Last active
March 4, 2019 03:51
-
-
Save TsubameUnity/4fb8d97628f2cd03290284c71aa546d9 to your computer and use it in GitHub Desktop.
Transformのインスペクタ表示に、一括入力ボタンを表示させるエディタ拡張。一括入力ウィンドウを開いているときにEnterで入力を確定、ESCで入力をキャンセルが可能です。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using System; | |
namespace TransformEx | |
{ | |
/// <summary> | |
/// 一括表示を行う場合の設定項目 | |
/// </summary> | |
internal interface ISetAllMode | |
{ | |
/// <summary> | |
/// パラメータのプロパティ一括変更方法を定義する | |
/// </summary> | |
Action<FieldProperty, float> SetAllProp { get; set; } | |
/// <summary> | |
/// 一括設定用ボタンの表示文 | |
/// </summary> | |
string AllInputLabel { get; set; } | |
/// <summary> | |
/// パラメータのプロパティ一括変更方法が実行されているか | |
/// </summary> | |
bool Inputting { get; set; } | |
} | |
/// <summary> | |
/// TransFormパラメータのエディタ描画設定 | |
/// </summary> | |
internal class FieldProperty : ISetAllMode | |
{ | |
/// <summary> | |
/// パラメータのSerializedProperty | |
/// </summary> | |
internal SerializedProperty Prop { get; private set; } | |
/// <summary> | |
/// パラメータのエディタ表示スタイル | |
/// </summary> | |
internal GUIContent Content { get; private set; } | |
/// <summary> | |
/// 一括設定用ボタンの表示文 | |
/// </summary> | |
public string AllInputLabel { get; set; } | |
/// <summary> | |
/// パラメータのプロパティ描写方法を定義する | |
/// </summary> | |
internal Action<FieldProperty> ViewProp { get; set; } | |
/// <summary> | |
/// パラメータのプロパティ一括変更方法を定義する | |
/// </summary> | |
public Action<FieldProperty, float> SetAllProp { get; set; } | |
/// <summary> | |
/// パラメータのプロパティ一括変更方法が実行されているか | |
/// </summary> | |
public bool Inputting { get; set; } | |
internal FieldProperty(SerializedProperty prop, GUIContent content, string allInputLabel) | |
{ | |
Prop = prop; | |
Content = content; | |
AllInputLabel = allInputLabel; | |
Inputting = false; | |
} | |
} | |
/// <summary> | |
/// Transform表示拡張の実体 | |
/// </summary> | |
[CustomEditor(typeof(Transform)), CanEditMultipleObjects] | |
internal class TransformInspectorEx : Editor | |
{ | |
static readonly GUIContent positionGUIContent = new GUIContent( | |
"Position", "The local position of this Game Object relative to the parent."); | |
static readonly GUIContent rotationGUIContent = new GUIContent( | |
"Rotation", "The local rotation of this Game Object relative to the parent."); | |
static readonly GUIContent scaleGUIContent = new GUIContent( | |
"Scale", "The local scaling of this Game Object relative to the parent."); | |
FieldProperty positionField; | |
FieldProperty scaleField; | |
TransformRotationGUI rotationGUI; | |
void OnEnable() | |
{ | |
// コンパイル時に走るのでエラーが出ないように排除 | |
if (!targets[0]) | |
{ | |
return; | |
} | |
// Positionの表示設定 | |
positionField = new FieldProperty(serializedObject.FindProperty("m_LocalPosition"), positionGUIContent, "A"); | |
positionField.ViewProp = (field) => EditorGUILayout.PropertyField(field.Prop, field.Content, GUILayout.Height(16)); | |
positionField.SetAllProp = (field, allSetValue) => field.Prop.vector3Value = Vector3.one * allSetValue; | |
// Scaleの表示設定 | |
scaleField = new FieldProperty(serializedObject.FindProperty("m_LocalScale"), scaleGUIContent, "A"); | |
scaleField.ViewProp = (field) => EditorGUILayout.PropertyField(field.Prop, field.Content, GUILayout.Height(16)); | |
scaleField.SetAllProp = (field, allSetValue) => field.Prop.vector3Value = Vector3.one * allSetValue; | |
// Rotationの表示設定 | |
var rotationField = new FieldProperty(serializedObject.FindProperty("m_LocalRotation"), rotationGUIContent, "A"); | |
rotationGUI = new TransformRotationGUI(rotationField); | |
rotationGUI.FieldProp.ViewProp = (field) => rotationGUI.RotationField(field); | |
rotationGUI.FieldProp.SetAllProp = (field, allSetValue) => | |
{ | |
// Rotationのみ表示と実体が異なるため、手動でUndoの情報を設定する | |
Undo.RecordObjects(targets, "SetAllInspector"); | |
foreach (Transform trans in targets) | |
{ | |
TransformUtils.SetInspectorRotation(trans, Vector3.one * allSetValue); | |
} | |
// 選択が複数の場合でも正常に適用されるように設定する | |
field.Prop.serializedObject.SetIsDifferentCacheDirty(); | |
}; | |
} | |
public override void OnInspectorGUI() | |
{ | |
if (!EditorGUIUtility.wideMode) | |
{ | |
// ウィンドウ幅が狭い場合に、二行になるのを防ぐ | |
EditorGUIUtility.wideMode = true; | |
EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth - 212; | |
} | |
serializedObject.Update(); | |
// Transformの各パラメータ描写の開始 | |
DrawPropField(positionField); | |
DrawPropField(rotationGUI.FieldProp); | |
DrawPropField(scaleField); | |
serializedObject.ApplyModifiedProperties(); | |
} | |
/// <summary> | |
/// パラメータの描写実行 | |
/// </summary> | |
/// <param name="allInputLabel">一括設定用ボタンの表示文</param> | |
/// <param name="fieldProp">表示するパラメータ</param> | |
void DrawPropField(FieldProperty fieldProp) | |
{ | |
// 一括入力設定を行うか | |
var allSetMode = fieldProp as ISetAllMode; | |
if (allSetMode != null) | |
{ | |
using (new EditorGUILayout.HorizontalScope()) | |
using (new EditorGUI.DisabledGroupScope(allSetMode.Inputting)) | |
{ | |
// 一括設定フォームのウィンドウ作成 | |
if (GUILayout.Button(allSetMode.AllInputLabel, GUILayout.Width(20), GUILayout.Height(16))) | |
{ | |
allSetMode.Inputting = true; | |
OpenInputConfirmWindow(fieldProp, (allSetValue) => | |
{ | |
allSetMode.Inputting = false; | |
serializedObject.Update(); | |
allSetMode.SetAllProp(fieldProp, allSetValue); | |
serializedObject.ApplyModifiedProperties(); | |
}, | |
() => | |
{ | |
allSetMode.Inputting = false; | |
}); | |
} | |
if (fieldProp.ViewProp != null) | |
{ | |
fieldProp.ViewProp(fieldProp); | |
} | |
} | |
} | |
else | |
{ | |
if (fieldProp.ViewProp != null) | |
{ | |
fieldProp.ViewProp(fieldProp); | |
} | |
} | |
} | |
InputConfirmWindow inputConfirmWindow; | |
/// <summary> | |
/// 一括入力用のウィンドウを開く | |
/// </summary> | |
/// <param name="fieldProp">一括入力するプロパティ</param> | |
/// <param name="onComplete">入力完了時に呼び出される</param> | |
/// <param name="onCancel">入力未完了で終了された場合に呼び出される</param> | |
void OpenInputConfirmWindow(FieldProperty fieldProp, Action<float> onComplete, Action onCancel) | |
{ | |
if (inputConfirmWindow) | |
{ | |
inputConfirmWindow.Close(); | |
} | |
Vector2 inputConfirmWindowSize = new Vector2(170.0f, 90.0f); | |
inputConfirmWindow = EditorWindow.GetWindow<InputConfirmWindow>(true, "一括設定"); | |
// サイズ変更不可に設定 | |
inputConfirmWindow.minSize = inputConfirmWindowSize; | |
inputConfirmWindow.maxSize = inputConfirmWindowSize; | |
// 入力のコールバックを設定 | |
inputConfirmWindow.Setup(fieldProp, onComplete, onCancel); | |
// 一括入力ウィンドウの表示位置をいい感じに調整 | |
Vector2 mousePos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); | |
mousePos.x += 30; | |
mousePos.y += 50; | |
inputConfirmWindow.position = new Rect(mousePos, inputConfirmWindowSize); | |
// フォーカスされなくなると即時閉じるウィンドウを作成 | |
inputConfirmWindow.ShowAuxWindow(); | |
} | |
} | |
/// <summary> | |
/// 一括入力の設定を行うウィンドウ | |
/// </summary> | |
internal class InputConfirmWindow : EditorWindow | |
{ | |
FieldProperty fieldProp; | |
Action<float> onComplete; | |
Action onCancel; | |
/// <summary> | |
/// 入力のコールバックを設定 | |
/// </summary> | |
/// <param name="fieldProp">一括入力したいパラメータ</param> | |
/// <param name="onComplete">入力完了時に呼び出される</param> | |
/// <param name="onCancel">入力未完了で終了された場合に呼び出される</param> | |
internal void Setup(FieldProperty fieldProp, Action<float> onComplete, Action onCancel) | |
{ | |
this.fieldProp = fieldProp; | |
this.onComplete = onComplete; | |
this.onCancel = onCancel; | |
} | |
// GUIにフォーカスを設定するための文字列 | |
const string ForcusField = "Focus"; | |
bool initFocus; | |
/// <summary> | |
/// 一括入力の値として引き渡されるフィールド | |
/// </summary> | |
float setAllValue = 0.0f; | |
void OnGUI() | |
{ | |
// Escキーが押される、コンパイル中、編集データが失われている場合は入力を終了 | |
if (Event.current.keyCode == KeyCode.Escape || EditorApplication.isCompiling || fieldProp == null) | |
{ | |
if (onCancel != null) | |
{ | |
onCancel(); | |
} | |
Close(); | |
return; | |
} | |
// 入力確定ショートカットキーの指定 | |
if (Event.current.keyCode == KeyCode.Return) | |
{ | |
onComplete(setAllValue); | |
Close(); | |
return; | |
} | |
GUILayout.Space(5); | |
// m_localから始まるので、不要な先頭の7文字を削る | |
string propName = fieldProp.Prop.name.Substring(7); | |
GUILayout.Label(string.Format("[{0}] を入力してください", propName)); | |
GUILayout.Space(5); | |
// 一括入力欄の描写 | |
using (new EditorGUILayout.HorizontalScope()) | |
{ | |
EditorGUIUtility.labelWidth = 60; | |
GUI.SetNextControlName(ForcusField); | |
setAllValue = EditorGUILayout.FloatField(propName, setAllValue, GUILayout.Width(160)); | |
GUILayout.FlexibleSpace(); | |
} | |
GUILayout.Space(5); | |
using (new GUILayout.HorizontalScope()) | |
{ | |
if (GUILayout.Button("OK", GUILayout.Width(80.0f), GUILayout.Height(30.0f))) | |
{ | |
onComplete(setAllValue); | |
Close(); | |
} | |
if (GUILayout.Button("CANCEL", GUILayout.Width(80.0f), GUILayout.Height(30.0f))) | |
{ | |
onCancel(); | |
Close(); | |
} | |
} | |
// 最初のフォーカスを一括入力欄に設定しておく | |
if (!initFocus) | |
{ | |
EditorGUI.FocusTextInControl(ForcusField); | |
initFocus = true; | |
} | |
} | |
/// <summary> | |
/// ゲームの実行状態が変化した際に呼ばれる | |
/// </summary> | |
/// <param name="state">現在のゲームの実行状態</param> | |
void OnChangedPlayMode(PlayModeStateChange state) | |
{ | |
// 実行した瞬間、停止した瞬間に入力を終了させる | |
if (state == PlayModeStateChange.ExitingEditMode || state == PlayModeStateChange.ExitingPlayMode) | |
{ | |
if (onCancel != null) | |
{ | |
onCancel(); | |
} | |
Close(); | |
} | |
} | |
void OnEnable() | |
{ | |
initFocus = false; | |
// ゲームの実行状態が変化した際に呼ばれる | |
EditorApplication.playModeStateChanged += OnChangedPlayMode; | |
} | |
void OnDisable() | |
{ | |
if (onCancel != null) | |
{ | |
onCancel(); | |
} | |
EditorApplication.playModeStateChanged -= OnChangedPlayMode; | |
} | |
void OnSelectionChange() | |
{ | |
if (onCancel != null) | |
{ | |
onCancel(); | |
} | |
Close(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
namespace TransformEx | |
{ | |
/// <summary> | |
/// Rotationのエディタ表示用処理 | |
/// </summary> | |
[System.Serializable] | |
internal class TransformRotationGUI | |
{ | |
internal FieldProperty FieldProp { get; private set; } | |
Object[] selectObjects; | |
internal TransformRotationGUI(FieldProperty fieldProp) | |
{ | |
FieldProp = fieldProp; | |
selectObjects = fieldProp.Prop.serializedObject.targetObjects; | |
} | |
/// <summary> | |
/// Rotationの表示処理 | |
/// </summary> | |
/// <param name="fieldProp">表示するパラメータ</param> | |
internal void RotationField(FieldProperty fieldProp) | |
{ | |
var baseTransform = (Transform)selectObjects[0]; | |
var localEulerAngles = TransformUtils.GetInspectorRotation(baseTransform); | |
foreach (Transform transform in selectObjects) | |
{ | |
// インスペクタ側の値で比較を行う | |
if (localEulerAngles != TransformUtils.GetInspectorRotation(transform)) | |
{ | |
// 不一致扱いとして、入力欄にハイフンを表示させる | |
EditorGUI.showMixedValue = true; | |
break; | |
} | |
} | |
using (var changeCheck = new EditorGUI.ChangeCheckScope()) | |
{ | |
Vector3 inputEulerAngles = EditorGUILayout.Vector3Field(fieldProp.Content, localEulerAngles); | |
if (changeCheck.changed) | |
{ | |
// ローテーションのみ表示と実体が異なるため、手動でUndoの情報を設定する | |
Undo.RecordObjects(selectObjects, "ChangeInspector"); | |
foreach (Transform transform in selectObjects) | |
{ | |
TransformUtils.SetInspectorRotation(transform, inputEulerAngles); | |
} | |
// 選択が複数の場合でも正常に更新適用がされるように設定する | |
fieldProp.Prop.serializedObject.SetIsDifferentCacheDirty(); | |
} | |
} | |
// 入力欄にハイフンを表示させないように解除 | |
EditorGUI.showMixedValue = false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment