Semi-working timelines integration
This commit is contained in:
127
Assets/Editor/InteractableEditor.cs
Normal file
127
Assets/Editor/InteractableEditor.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Interactions
|
||||
{
|
||||
[CustomEditor(typeof(Interactable))]
|
||||
public class InteractableEditor : UnityEditor.Editor
|
||||
{
|
||||
SerializedProperty isOneTimeProp;
|
||||
SerializedProperty cooldownProp;
|
||||
SerializedProperty characterToInteractProp;
|
||||
SerializedProperty interactionStartedProp;
|
||||
SerializedProperty interactionInterruptedProp;
|
||||
SerializedProperty characterArrivedProp;
|
||||
SerializedProperty interactionCompleteProp;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
isOneTimeProp = serializedObject.FindProperty("isOneTime");
|
||||
cooldownProp = serializedObject.FindProperty("cooldown");
|
||||
characterToInteractProp = serializedObject.FindProperty("characterToInteract");
|
||||
interactionStartedProp = serializedObject.FindProperty("interactionStarted");
|
||||
interactionInterruptedProp = serializedObject.FindProperty("interactionInterrupted");
|
||||
characterArrivedProp = serializedObject.FindProperty("characterArrived");
|
||||
interactionCompleteProp = serializedObject.FindProperty("interactionComplete");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.LabelField("Interaction Settings", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(isOneTimeProp);
|
||||
EditorGUILayout.PropertyField(cooldownProp);
|
||||
EditorGUILayout.PropertyField(characterToInteractProp);
|
||||
|
||||
// Add the buttons for creating move targets
|
||||
EditorGUILayout.Space(10);
|
||||
EditorGUILayout.LabelField("Character Move Targets", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Add Trafalgar Target"))
|
||||
{
|
||||
CreateMoveTarget(CharacterToInteract.Trafalgar);
|
||||
}
|
||||
if (GUILayout.Button("Add Pulver Target"))
|
||||
{
|
||||
CreateMoveTarget(CharacterToInteract.Pulver);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// Add a button for creating a "Both" target
|
||||
if (GUILayout.Button("Add Both Characters Target"))
|
||||
{
|
||||
CreateMoveTarget(CharacterToInteract.Both);
|
||||
}
|
||||
|
||||
// Display character target counts
|
||||
Interactable interactable = (Interactable)target;
|
||||
CharacterMoveToTarget[] moveTargets = interactable.GetComponentsInChildren<CharacterMoveToTarget>();
|
||||
int trafalgarTargets = 0;
|
||||
int pulverTargets = 0;
|
||||
int bothTargets = 0;
|
||||
|
||||
foreach (var target in moveTargets)
|
||||
{
|
||||
if (target.characterType == CharacterToInteract.Trafalgar)
|
||||
trafalgarTargets++;
|
||||
else if (target.characterType == CharacterToInteract.Pulver)
|
||||
pulverTargets++;
|
||||
else if (target.characterType == CharacterToInteract.Both)
|
||||
bothTargets++;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField($"Trafalgar Targets: {trafalgarTargets}, Pulver Targets: {pulverTargets}, Both Targets: {bothTargets}");
|
||||
|
||||
if (trafalgarTargets > 1 || pulverTargets > 1 || bothTargets > 1 ||
|
||||
(bothTargets > 0 && (trafalgarTargets > 0 || pulverTargets > 0)))
|
||||
{
|
||||
EditorGUILayout.HelpBox("Warning: Multiple move targets found that may conflict. Priority order: Both > Character-specific targets.", MessageType.Warning);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
EditorGUILayout.LabelField("Interaction Events", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(interactionStartedProp);
|
||||
EditorGUILayout.PropertyField(interactionInterruptedProp);
|
||||
EditorGUILayout.PropertyField(characterArrivedProp);
|
||||
EditorGUILayout.PropertyField(interactionCompleteProp);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void CreateMoveTarget(CharacterToInteract characterType)
|
||||
{
|
||||
Interactable interactable = (Interactable)target;
|
||||
|
||||
// Create a new GameObject
|
||||
GameObject targetObj = new GameObject($"{characterType}MoveTarget");
|
||||
|
||||
// Set parent
|
||||
targetObj.transform.SetParent(interactable.transform);
|
||||
targetObj.transform.localPosition = Vector3.zero; // Start at the same position as the interactable
|
||||
|
||||
// Add CharacterMoveToTarget component
|
||||
CharacterMoveToTarget moveTarget = targetObj.AddComponent<CharacterMoveToTarget>();
|
||||
moveTarget.characterType = characterType;
|
||||
|
||||
// Position it based on character type (offset for better visibility)
|
||||
switch (characterType)
|
||||
{
|
||||
case CharacterToInteract.Trafalgar:
|
||||
moveTarget.positionOffset = new Vector3(1.0f, 0, 0);
|
||||
break;
|
||||
case CharacterToInteract.Pulver:
|
||||
moveTarget.positionOffset = new Vector3(0, 0, 1.0f);
|
||||
break;
|
||||
case CharacterToInteract.Both:
|
||||
moveTarget.positionOffset = new Vector3(0.7f, 0, 0.7f);
|
||||
break;
|
||||
}
|
||||
|
||||
// Select the newly created object
|
||||
Selection.activeGameObject = targetObj;
|
||||
Undo.RegisterCreatedObjectUndo(targetObj, $"Create {characterType} Move Target");
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Editor/InteractableEditor.cs.meta
Normal file
3
Assets/Editor/InteractableEditor.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2011e96b1b84886825d0509dd0a5cee
|
||||
timeCreated: 1759744192
|
||||
184
Assets/Editor/InteractionTimelineActionEditor.cs
Normal file
184
Assets/Editor/InteractionTimelineActionEditor.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
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<PlayableDirector>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Editor/InteractionTimelineActionEditor.cs.meta
Normal file
3
Assets/Editor/InteractionTimelineActionEditor.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ab56ee48f1445529a62d30c1985f059
|
||||
timeCreated: 1759746824
|
||||
48
Assets/Editor/TimelineEventMappingDrawer.cs
Normal file
48
Assets/Editor/TimelineEventMappingDrawer.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using Interactions;
|
||||
using System;
|
||||
|
||||
[CustomPropertyDrawer(typeof(InteractionTimelineAction.TimelineEventMapping))]
|
||||
public class TimelineEventMappingDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Use default property drawer, but initialize values if needed
|
||||
InitializeDefaultValues(property);
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUI.GetPropertyHeight(property, label, true);
|
||||
}
|
||||
|
||||
// Called when property is created to initialize default values
|
||||
private void InitializeDefaultValues(SerializedProperty property)
|
||||
{
|
||||
// Check if this is a new/empty property that needs initialization
|
||||
SerializedProperty timelinesArray = property.FindPropertyRelative("timelines");
|
||||
if (timelinesArray != null && timelinesArray.arraySize == 0)
|
||||
{
|
||||
// This appears to be a new property, so initialize default values
|
||||
|
||||
// Initialize timelines array with one empty element
|
||||
timelinesArray.ClearArray();
|
||||
timelinesArray.InsertArrayElementAtIndex(0);
|
||||
timelinesArray.GetArrayElementAtIndex(0).objectReferenceValue = null;
|
||||
|
||||
// Set default binding values
|
||||
property.FindPropertyRelative("bindPlayerCharacter").boolValue = true;
|
||||
property.FindPropertyRelative("bindPulverCharacter").boolValue = true;
|
||||
property.FindPropertyRelative("playerTrackName").stringValue = "Player";
|
||||
property.FindPropertyRelative("pulverTrackName").stringValue = "Pulver";
|
||||
property.FindPropertyRelative("timeoutSeconds").floatValue = 30f;
|
||||
property.FindPropertyRelative("loopLast").boolValue = false;
|
||||
property.FindPropertyRelative("loopAll").boolValue = false;
|
||||
|
||||
// Make sure to apply modifications
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Editor/TimelineEventMappingDrawer.cs.meta
Normal file
3
Assets/Editor/TimelineEventMappingDrawer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5a227e33ab14a0f86bf391016c57b00
|
||||
timeCreated: 1759756346
|
||||
Reference in New Issue
Block a user