using System; using System.Collections.Generic; using TMPro; using UnityEditor; using UnityEngine; using UnityEngine.InputSystem; [CustomEditor(typeof(InputGlyph))] [CanEditMultipleObjects] public sealed class InputGlyphEditor : Editor { private SerializedProperty _actionSourceMode; private SerializedProperty _actionReference; private SerializedProperty _hotkeyTrigger; private SerializedProperty _actionName; private SerializedProperty _compositePartName; private SerializedProperty _outputMode; private SerializedProperty _targetImage; private SerializedProperty _targetText; private SerializedProperty _categoryEvents; private GUIStyle _titleStyle; private GUIStyle _sectionStyle; private GUIStyle _hintStyle; private void OnEnable() { _actionSourceMode = serializedObject.FindProperty("actionSourceMode"); _actionReference = serializedObject.FindProperty("actionReference"); _hotkeyTrigger = serializedObject.FindProperty("hotkeyTrigger"); _actionName = serializedObject.FindProperty("actionName"); _compositePartName = serializedObject.FindProperty("compositePartName"); _outputMode = serializedObject.FindProperty("outputMode"); _targetImage = serializedObject.FindProperty("targetImage"); _targetText = serializedObject.FindProperty("targetText"); _categoryEvents = serializedObject.FindProperty("categoryEvents"); BuildStyles(); } public override void OnInspectorGUI() { serializedObject.Update(); DrawSourceSection(); DrawOutputSection(); DrawEventsSection(); serializedObject.ApplyModifiedProperties(); } private void DrawSourceSection() { InputAction resolvedAction = ResolveSelectedAction(); EditorGUILayout.BeginVertical(_sectionStyle); EditorGUILayout.PropertyField(_actionSourceMode, new GUIContent("Reference Mode")); DrawSourceFields(); DrawResolvedActionInfo(resolvedAction); DrawCompositePartField(resolvedAction); EditorGUILayout.EndVertical(); EditorGUILayout.Space(6f); } private void DrawSourceFields() { InputGlyph.ActionSourceMode mode = (InputGlyph.ActionSourceMode)_actionSourceMode.enumValueIndex; switch (mode) { case InputGlyph.ActionSourceMode.ActionReference: EditorGUILayout.PropertyField(_actionReference, new GUIContent("Action Reference")); EditorGUILayout.LabelField("Use a direct InputActionReference.", _hintStyle); break; case InputGlyph.ActionSourceMode.HotkeyTrigger: EditorGUILayout.PropertyField(_hotkeyTrigger, new GUIContent("Hotkey Trigger")); Component component = _hotkeyTrigger.objectReferenceValue as Component; if (component != null && !(component is UnityEngine.UI.IHotkeyTrigger)) { EditorGUILayout.HelpBox("Hotkey Trigger must implement IHotkeyTrigger.", MessageType.Warning); } else { EditorGUILayout.LabelField("Reads the action from an external IHotkeyTrigger component.", _hintStyle); } break; case InputGlyph.ActionSourceMode.ActionName: EditorGUILayout.PropertyField(_actionName, new GUIContent("Action Name")); EditorGUILayout.LabelField("Supports ActionName or MapName/ActionName.", _hintStyle); break; } } private void DrawOutputSection() { EditorGUILayout.BeginVertical(_sectionStyle); EditorGUILayout.PropertyField(_outputMode, new GUIContent("Render Mode")); DrawOutputFields(); EditorGUILayout.EndVertical(); EditorGUILayout.Space(6f); } private void DrawOutputFields() { InputGlyph.OutputMode mode = (InputGlyph.OutputMode)_outputMode.enumValueIndex; switch (mode) { case InputGlyph.OutputMode.Image: EditorGUILayout.PropertyField(_targetImage, new GUIContent("Target Image")); EditorGUILayout.LabelField("Shows the resolved sprite on a Unity UI Image.", _hintStyle); break; case InputGlyph.OutputMode.Text: EditorGUILayout.PropertyField(_targetText, new GUIContent("Target TMP Text")); EditorGUILayout.LabelField("Uses the current TMP text as a template and replaces {0}.", _hintStyle); TMP_Text text = _targetText.objectReferenceValue as TMP_Text; if (text == null) { EditorGUILayout.HelpBox("If TMP_Text is empty, the component tries GetComponent().", MessageType.None); } break; } } private void DrawEventsSection() { EditorGUILayout.BeginVertical(_sectionStyle); EditorGUILayout.PropertyField(_categoryEvents, new GUIContent("Category Events"), true); EditorGUILayout.EndVertical(); } private void DrawResolvedActionInfo(InputAction action) { if (action == null) { return; } string mapName = action.actionMap != null ? action.actionMap.name : ""; EditorGUILayout.LabelField($"Resolved Action: {mapName}/{action.name}", _hintStyle); } private void DrawCompositePartField(InputAction action) { List compositeParts = CollectCompositePartNames(action); if (compositeParts.Count == 0) { if (!string.IsNullOrEmpty(_compositePartName.stringValue)) { _compositePartName.stringValue = string.Empty; } return; } string[] options = new string[compositeParts.Count + 1]; options[0] = ""; for (int i = 0; i < compositeParts.Count; i++) { options[i + 1] = compositeParts[i]; } int selectedIndex = 0; for (int i = 0; i < compositeParts.Count; i++) { if (string.Equals(compositeParts[i], _compositePartName.stringValue, StringComparison.OrdinalIgnoreCase)) { selectedIndex = i + 1; break; } } int newIndex = EditorGUILayout.Popup(new GUIContent("Composite Part"), selectedIndex, options); _compositePartName.stringValue = newIndex <= 0 ? string.Empty : compositeParts[newIndex - 1]; EditorGUILayout.LabelField("Shown only when the resolved action contains composite bindings.", _hintStyle); } private InputAction ResolveSelectedAction() { InputGlyph.ActionSourceMode mode = (InputGlyph.ActionSourceMode)_actionSourceMode.enumValueIndex; switch (mode) { case InputGlyph.ActionSourceMode.ActionReference: InputActionReference actionReference = _actionReference.objectReferenceValue as InputActionReference; return actionReference != null ? actionReference.action : null; case InputGlyph.ActionSourceMode.HotkeyTrigger: Component component = _hotkeyTrigger.objectReferenceValue as Component; if (component is UnityEngine.UI.IHotkeyTrigger trigger && trigger.HotkeyAction != null) { return trigger.HotkeyAction.action; } return null; case InputGlyph.ActionSourceMode.ActionName: return ResolveActionByName(_actionName.stringValue); default: return null; } } private InputAction ResolveActionByName(string actionName) { if (string.IsNullOrWhiteSpace(actionName)) { return null; } foreach (InputActionAsset asset in EnumerateInputActionAssets()) { if (asset == null) { continue; } InputAction action = asset.FindAction(actionName, false); if (action != null) { return action; } } return null; } private IEnumerable EnumerateInputActionAssets() { HashSet visited = new HashSet(); InputBindingManager[] managers = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < managers.Length; i++) { InputActionAsset asset = managers[i] != null ? managers[i].actions : null; if (asset != null && visited.Add(asset)) { yield return asset; } } string[] guids = AssetDatabase.FindAssets("t:InputActionAsset"); for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); InputActionAsset asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null && visited.Add(asset)) { yield return asset; } } } private static List CollectCompositePartNames(InputAction action) { List parts = new List(); if (action == null) { return parts; } HashSet uniqueParts = new HashSet(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < action.bindings.Count; i++) { InputBinding binding = action.bindings[i]; if (!binding.isPartOfComposite || string.IsNullOrWhiteSpace(binding.name)) { continue; } if (uniqueParts.Add(binding.name)) { parts.Add(binding.name); } } return parts; } private void BuildStyles() { if (_titleStyle == null) { _titleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 }; } if (_sectionStyle == null) { _sectionStyle = new GUIStyle(EditorStyles.helpBox) { padding = new RectOffset(12, 12, 10, 10) }; } if (_hintStyle == null) { _hintStyle = new GUIStyle(EditorStyles.miniLabel) { wordWrap = true }; } } }