using UnityEngine; using UnityEditor; using UnityEditor.Playables; using UnityEngine.Playables; namespace Interactions { [CustomEditor(typeof(InteractionTimelineAction))] public class InteractionTimelineActionEditor : UnityEditor.Editor { private SerializedProperty respondToEventsProp; private SerializedProperty pauseInteractionFlowProp; private SerializedProperty playableDirectorProp; private SerializedProperty timelineMappingsProp; private void OnEnable() { respondToEventsProp = serializedObject.FindProperty("respondToEvents"); pauseInteractionFlowProp = serializedObject.FindProperty("pauseInteractionFlow"); playableDirectorProp = serializedObject.FindProperty("playableDirector"); timelineMappingsProp = serializedObject.FindProperty("timelineMappings"); } public override void OnInspectorGUI() { serializedObject.Update(); // Basic properties from the base class EditorGUILayout.LabelField("Basic Settings", EditorStyles.boldLabel); // Show the pause interaction flow property EditorGUILayout.PropertyField(pauseInteractionFlowProp, new GUIContent("Pause Interaction Flow", "If true, the interaction will wait for the timeline to complete before proceeding")); // Show the respondToEvents list EditorGUILayout.PropertyField(respondToEventsProp, new GUIContent("Respond To Events", "Select which interaction events this timeline action should respond to"), true); // Show the playable director reference EditorGUILayout.PropertyField(playableDirectorProp, new GUIContent("Playable Director", "The director component that will play the timeline. Auto-assigned if not specified.")); // Show the timeline mappings EditorGUILayout.Space(10); EditorGUILayout.LabelField("Timeline Mappings", EditorStyles.boldLabel); if (timelineMappingsProp.arraySize == 0) { EditorGUILayout.HelpBox("No timeline mappings added yet. Add one to assign timeline assets to specific interaction events.", MessageType.Info); } EditorGUILayout.PropertyField(timelineMappingsProp, new GUIContent("Timeline Mappings"), true); // Add buttons for quickly adding timeline mappings for common events EditorGUILayout.Space(10); EditorGUILayout.LabelField("Quick Add Mappings", EditorStyles.boldLabel); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Add Player Arrival")) { AddTimelineMapping(InteractionEventType.PlayerArrived); } if (GUILayout.Button("Add Interacting Character")) { AddTimelineMapping(InteractionEventType.InteractingCharacterArrived); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Add Interaction Started")) { AddTimelineMapping(InteractionEventType.InteractionStarted); } if (GUILayout.Button("Add Interaction Complete")) { AddTimelineMapping(InteractionEventType.InteractionComplete); } EditorGUILayout.EndHorizontal(); // Check for configuration issues InteractionTimelineAction timelineAction = (InteractionTimelineAction)target; ValidateConfiguration(timelineAction); serializedObject.ApplyModifiedProperties(); } private void AddTimelineMapping(InteractionEventType eventType) { int index = timelineMappingsProp.arraySize; timelineMappingsProp.InsertArrayElementAtIndex(index); SerializedProperty newElement = timelineMappingsProp.GetArrayElementAtIndex(index); // Set default values newElement.FindPropertyRelative("eventType").enumValueIndex = (int)eventType; // Create a default array with one empty slot for the timeline SerializedProperty timelinesArray = newElement.FindPropertyRelative("timelines"); timelinesArray.ClearArray(); timelinesArray.InsertArrayElementAtIndex(0); timelinesArray.GetArrayElementAtIndex(0).objectReferenceValue = null; // Set default binding values newElement.FindPropertyRelative("bindPlayerCharacter").boolValue = false; newElement.FindPropertyRelative("bindPulverCharacter").boolValue = false; newElement.FindPropertyRelative("playerTrackName").stringValue = "Player"; newElement.FindPropertyRelative("pulverTrackName").stringValue = "Pulver"; newElement.FindPropertyRelative("timeoutSeconds").floatValue = 30f; newElement.FindPropertyRelative("loopLast").boolValue = false; newElement.FindPropertyRelative("loopAll").boolValue = false; // Also add the event to the respondToEvents list if it's not already there bool found = false; for (int i = 0; i < respondToEventsProp.arraySize; i++) { if (respondToEventsProp.GetArrayElementAtIndex(i).enumValueIndex == (int)eventType) { found = true; break; } } if (!found) { int responseIndex = respondToEventsProp.arraySize; respondToEventsProp.InsertArrayElementAtIndex(responseIndex); respondToEventsProp.GetArrayElementAtIndex(responseIndex).enumValueIndex = (int)eventType; } } private void ValidateConfiguration(InteractionTimelineAction timelineAction) { // Check if we have a PlayableDirector component PlayableDirector director = timelineAction.GetComponent(); if (director == null) { EditorGUILayout.HelpBox("This GameObject is missing a PlayableDirector component, which is required for timeline playback.", MessageType.Error); } // Check if we have mappings but no events to respond to if (timelineMappingsProp.arraySize > 0 && respondToEventsProp.arraySize == 0) { EditorGUILayout.HelpBox("You have timeline mappings but no events to respond to. Add events to the 'Respond To Events' list.", MessageType.Warning); } // Check if we have events to respond to but no mappings for them bool hasUnmappedEvents = false; for (int i = 0; i < respondToEventsProp.arraySize; i++) { InteractionEventType eventType = (InteractionEventType)respondToEventsProp.GetArrayElementAtIndex(i).enumValueIndex; bool found = false; for (int j = 0; j < timelineMappingsProp.arraySize; j++) { SerializedProperty mappingProp = timelineMappingsProp.GetArrayElementAtIndex(j); InteractionEventType mappingEventType = (InteractionEventType)mappingProp.FindPropertyRelative("eventType").enumValueIndex; if (mappingEventType == eventType) { found = true; // Check if the mapping has timelines assigned SerializedProperty timelinesArray = mappingProp.FindPropertyRelative("timelines"); if (timelinesArray.arraySize == 0 || timelinesArray.GetArrayElementAtIndex(0).objectReferenceValue == null) { EditorGUILayout.HelpBox($"The mapping for {eventType} has no timeline assets assigned.", MessageType.Warning); } break; } } if (!found) { hasUnmappedEvents = true; break; } } if (hasUnmappedEvents) { EditorGUILayout.HelpBox("Some events in 'Respond To Events' have no timeline mapping.", MessageType.Warning); } } } }