using UnityEngine; using UnityEditor; using UnityEngine.UIElements; using UnityEditor.UIElements; [CustomEditor(typeof(EventController))] public class EventControllerEditor : Editor { private const string UxmlPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/EventControllerEditor.uxml"; private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss"; private VisualElement eventsContainer; private Label eventsTitleLabel; private EventController controller; public override VisualElement CreateInspectorGUI() { controller = (EventController)target; var root = new VisualElement(); var commonUss = AssetDatabase.LoadAssetAtPath(CommonUssPath); if (commonUss != null) root.styleSheets.Add(commonUss); var uxml = AssetDatabase.LoadAssetAtPath(UxmlPath); if (uxml != null) uxml.CloneTree(root); BuildEventGroupsSection(root); Undo.undoRedoPerformed += OnUndoRedo; root.RegisterCallback(_ => Undo.undoRedoPerformed -= OnUndoRedo); return root; } private void OnUndoRedo() { if (controller == null) return; serializedObject.Update(); RebuildEventList(); } #region Event Groups List private void BuildEventGroupsSection(VisualElement root) { var section = root.Q("eventGroupsSection"); if (section == null) return; var header = new VisualElement(); header.AddToClassList("list-header"); eventsTitleLabel = new Label($"이벤트 그룹 ({controller.eventGroups.Count})"); eventsTitleLabel.AddToClassList("list-title"); header.Add(eventsTitleLabel); var addBtn = new Button(AddEventGroup) { text = "+ 이벤트 추가" }; addBtn.AddToClassList("list-add-btn"); header.Add(addBtn); section.Add(header); eventsContainer = new VisualElement(); section.Add(eventsContainer); RebuildEventList(); } private void RebuildEventList() { if (eventsContainer == null || controller == null) return; eventsContainer.Clear(); if (eventsTitleLabel != null) eventsTitleLabel.text = $"이벤트 그룹 ({controller.eventGroups.Count})"; if (controller.eventGroups.Count == 0) { var empty = new Label("이벤트 그룹이 없습니다. '+ 이벤트 추가' 버튼을 눌러 추가하세요."); empty.AddToClassList("list-empty"); eventsContainer.Add(empty); return; } for (int i = 0; i < controller.eventGroups.Count; i++) { eventsContainer.Add(CreateEventGroupElement(i)); } eventsContainer.Bind(serializedObject); } private VisualElement CreateEventGroupElement(int index) { var group = controller.eventGroups[index]; var item = new VisualElement(); item.AddToClassList("list-item"); // Header row var headerRow = new VisualElement(); headerRow.AddToClassList("list-item-header"); var indexLabel = new Label($"{index + 1}"); indexLabel.AddToClassList("list-index"); headerRow.Add(indexLabel); var nameField = new TextField(); nameField.value = group.groupName; nameField.AddToClassList("list-name-field"); int idx = index; nameField.RegisterValueChangedCallback(evt => { Undo.RecordObject(target, "Rename Event Group"); controller.eventGroups[idx].groupName = evt.newValue; EditorUtility.SetDirty(target); }); headerRow.Add(nameField); // Reorder buttons var upBtn = new Button(() => SwapEvents(idx, idx - 1)) { text = "\u25B2" }; upBtn.AddToClassList("list-reorder-btn"); upBtn.SetEnabled(index > 0); headerRow.Add(upBtn); var downBtn = new Button(() => SwapEvents(idx, idx + 1)) { text = "\u25BC" }; downBtn.AddToClassList("list-reorder-btn"); downBtn.SetEnabled(index < controller.eventGroups.Count - 1); headerRow.Add(downBtn); var duplicateBtn = new Button(() => DuplicateEvent(idx)) { text = "\u2398" }; duplicateBtn.tooltip = "이벤트 복제"; duplicateBtn.AddToClassList("list-reorder-btn"); headerRow.Add(duplicateBtn); var deleteBtn = new Button(() => DeleteEvent(idx)) { text = "X" }; deleteBtn.AddToClassList("list-delete-btn"); headerRow.Add(deleteBtn); item.Add(headerRow); // Fields - UnityEvent uses PropertyField for the built-in event drawer var fields = new VisualElement(); fields.AddToClassList("list-fields"); var listProp = serializedObject.FindProperty("eventGroups"); var elementProp = listProp.GetArrayElementAtIndex(index); var eventField = new PropertyField(elementProp.FindPropertyRelative("unityEvent"), "이벤트"); fields.Add(eventField); item.Add(fields); return item; } private void AddEventGroup() { Undo.RecordObject(target, "Add Event Group"); controller.eventGroups.Add(new EventController.EventGroup("새 이벤트")); EditorUtility.SetDirty(target); serializedObject.Update(); RebuildEventList(); } private void DeleteEvent(int index) { Undo.RecordObject(target, "Delete Event Group"); controller.eventGroups.RemoveAt(index); EditorUtility.SetDirty(target); serializedObject.Update(); RebuildEventList(); } private void DuplicateEvent(int index) { serializedObject.Update(); var listProp = serializedObject.FindProperty("eventGroups"); // 배열 끝에 새 요소 추가 후, 원본의 SerializedProperty 값을 복사 int newIndex = listProp.arraySize; listProp.InsertArrayElementAtIndex(newIndex); var srcElement = listProp.GetArrayElementAtIndex(index); var dstElement = listProp.GetArrayElementAtIndex(newIndex); // groupName 복사 (접미사 추가) var srcName = srcElement.FindPropertyRelative("groupName"); var dstName = dstElement.FindPropertyRelative("groupName"); dstName.stringValue = srcName.stringValue + " (복사)"; // UnityEvent persistent calls 복사 var srcEvent = srcElement.FindPropertyRelative("unityEvent"); var dstEvent = dstElement.FindPropertyRelative("unityEvent"); CopySerializedProperty(srcEvent, dstEvent); serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(target); RebuildEventList(); } private void CopySerializedProperty(SerializedProperty src, SerializedProperty dst) { var srcCalls = src.FindPropertyRelative("m_PersistentCalls.m_Calls"); var dstCalls = dst.FindPropertyRelative("m_PersistentCalls.m_Calls"); if (srcCalls == null || dstCalls == null) return; dstCalls.ClearArray(); for (int i = 0; i < srcCalls.arraySize; i++) { dstCalls.InsertArrayElementAtIndex(i); var srcCall = srcCalls.GetArrayElementAtIndex(i); var dstCall = dstCalls.GetArrayElementAtIndex(i); dstCall.FindPropertyRelative("m_Target").objectReferenceValue = srcCall.FindPropertyRelative("m_Target").objectReferenceValue; dstCall.FindPropertyRelative("m_MethodName").stringValue = srcCall.FindPropertyRelative("m_MethodName").stringValue; dstCall.FindPropertyRelative("m_Mode").intValue = srcCall.FindPropertyRelative("m_Mode").intValue; dstCall.FindPropertyRelative("m_CallState").intValue = srcCall.FindPropertyRelative("m_CallState").intValue; // Arguments var srcArgs = srcCall.FindPropertyRelative("m_Arguments"); var dstArgs = dstCall.FindPropertyRelative("m_Arguments"); if (srcArgs != null && dstArgs != null) { dstArgs.FindPropertyRelative("m_ObjectArgument").objectReferenceValue = srcArgs.FindPropertyRelative("m_ObjectArgument").objectReferenceValue; dstArgs.FindPropertyRelative("m_ObjectArgumentAssemblyTypeName").stringValue = srcArgs.FindPropertyRelative("m_ObjectArgumentAssemblyTypeName").stringValue; dstArgs.FindPropertyRelative("m_IntArgument").intValue = srcArgs.FindPropertyRelative("m_IntArgument").intValue; dstArgs.FindPropertyRelative("m_FloatArgument").floatValue = srcArgs.FindPropertyRelative("m_FloatArgument").floatValue; dstArgs.FindPropertyRelative("m_StringArgument").stringValue = srcArgs.FindPropertyRelative("m_StringArgument").stringValue; dstArgs.FindPropertyRelative("m_BoolArgument").boolValue = srcArgs.FindPropertyRelative("m_BoolArgument").boolValue; } } } private void SwapEvents(int a, int b) { Undo.RecordObject(target, "Reorder Event Groups"); (controller.eventGroups[a], controller.eventGroups[b]) = (controller.eventGroups[b], controller.eventGroups[a]); EditorUtility.SetDirty(target); serializedObject.Update(); RebuildEventList(); } #endregion }