Compare commits
4 Commits
3e1fd3fb95
...
unity_time
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
092e2a6b32 | ||
|
|
e1ab0ea937 | ||
|
|
bdec77d36f | ||
|
|
fd611ac27f |
|
After Width: | Height: | Size: 9.7 KiB |
@@ -0,0 +1,195 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44a64b7a80921694790236bab7765357
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: -8897872742393391051
|
||||
second: TennisBall_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WindowsStoreApps
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: TennisBall_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 21
|
||||
y: 29
|
||||
width: 219
|
||||
height: 190
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 538d20d32e7648480800000000000000
|
||||
internalID: -8897872742393391051
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
TennisBall_0: -8897872742393391051
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
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
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2011e96b1b84886825d0509dd0a5cee
|
||||
timeCreated: 1759744192
|
||||
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
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ab56ee48f1445529a62d30c1985f059
|
||||
timeCreated: 1759746824
|
||||
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
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5a227e33ab14a0f86bf391016c57b00
|
||||
timeCreated: 1759756346
|
||||
394
Assets/Playables/PulverCucumberSmack1.playable
Normal file
@@ -0,0 +1,394 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-7584736085941489071
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d21dcc2386d650c4597f3633c75a1f98, type: 3}
|
||||
m_Name: Animation Track
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.AnimationTrack
|
||||
m_Version: 3
|
||||
m_AnimClip: {fileID: 0}
|
||||
m_Locked: 0
|
||||
m_Muted: 0
|
||||
m_CustomPlayableFullTypename:
|
||||
m_Curves: {fileID: 0}
|
||||
m_Parent: {fileID: 11400000}
|
||||
m_Children: []
|
||||
m_Clips:
|
||||
- m_Version: 1
|
||||
m_Start: 0.06666666666666667
|
||||
m_ClipIn: 0
|
||||
m_Asset: {fileID: -2070454477998879764}
|
||||
m_Duration: 1.0333333333333334
|
||||
m_TimeScale: 1
|
||||
m_ParentTrack: {fileID: -7584736085941489071}
|
||||
m_EaseInDuration: 0
|
||||
m_EaseOutDuration: 0
|
||||
m_BlendInDuration: -1
|
||||
m_BlendOutDuration: -1
|
||||
m_MixInCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_MixOutCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_BlendInCurveMode: 0
|
||||
m_BlendOutCurveMode: 0
|
||||
m_ExposedParameterNames: []
|
||||
m_AnimationCurves: {fileID: 0}
|
||||
m_Recordable: 0
|
||||
m_PostExtrapolationMode: 1
|
||||
m_PreExtrapolationMode: 1
|
||||
m_PostExtrapolationTime: Infinity
|
||||
m_PreExtrapolationTime: 0.06666666666666667
|
||||
m_DisplayName: BalltreeHit
|
||||
m_Markers:
|
||||
m_Objects: []
|
||||
m_InfiniteClipPreExtrapolation: 0
|
||||
m_InfiniteClipPostExtrapolation: 0
|
||||
m_InfiniteClipOffsetPosition: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipOffsetEulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipTimeOffset: 0
|
||||
m_InfiniteClipRemoveOffset: 0
|
||||
m_InfiniteClipApplyFootIK: 1
|
||||
mInfiniteClipLoop: 0
|
||||
m_MatchTargetFields: 63
|
||||
m_Position: {x: 0, y: 0, z: 0}
|
||||
m_EulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_AvatarMask: {fileID: 0}
|
||||
m_ApplyAvatarMask: 1
|
||||
m_TrackOffset: 0
|
||||
m_InfiniteClip: {fileID: 0}
|
||||
m_OpenClipOffsetRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_ApplyOffsets: 0
|
||||
--- !u!114 &-4664548104421960294
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fde0d25a170598d46a0b9dc16b4527a5, type: 3}
|
||||
m_Name: ActivationPlayableAsset
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.ActivationPlayableAsset
|
||||
--- !u!114 &-2395336864975438248
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d21dcc2386d650c4597f3633c75a1f98, type: 3}
|
||||
m_Name: Animation Track (1)
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.AnimationTrack
|
||||
m_Version: 3
|
||||
m_AnimClip: {fileID: 0}
|
||||
m_Locked: 0
|
||||
m_Muted: 0
|
||||
m_CustomPlayableFullTypename:
|
||||
m_Curves: {fileID: 0}
|
||||
m_Parent: {fileID: 11400000}
|
||||
m_Children: []
|
||||
m_Clips:
|
||||
- m_Version: 1
|
||||
m_Start: 0
|
||||
m_ClipIn: 0
|
||||
m_Asset: {fileID: 3115908604919352715}
|
||||
m_Duration: 0.8333333333333334
|
||||
m_TimeScale: 1
|
||||
m_ParentTrack: {fileID: -2395336864975438248}
|
||||
m_EaseInDuration: 0
|
||||
m_EaseOutDuration: 0
|
||||
m_BlendInDuration: -1
|
||||
m_BlendOutDuration: -1
|
||||
m_MixInCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_MixOutCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_BlendInCurveMode: 0
|
||||
m_BlendOutCurveMode: 0
|
||||
m_ExposedParameterNames: []
|
||||
m_AnimationCurves: {fileID: 0}
|
||||
m_Recordable: 0
|
||||
m_PostExtrapolationMode: 1
|
||||
m_PreExtrapolationMode: 1
|
||||
m_PostExtrapolationTime: Infinity
|
||||
m_PreExtrapolationTime: 0
|
||||
m_DisplayName: Pulver_Cucumbatacc
|
||||
m_Markers:
|
||||
m_Objects: []
|
||||
m_InfiniteClipPreExtrapolation: 0
|
||||
m_InfiniteClipPostExtrapolation: 0
|
||||
m_InfiniteClipOffsetPosition: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipOffsetEulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipTimeOffset: 0
|
||||
m_InfiniteClipRemoveOffset: 0
|
||||
m_InfiniteClipApplyFootIK: 1
|
||||
mInfiniteClipLoop: 0
|
||||
m_MatchTargetFields: 63
|
||||
m_Position: {x: 0, y: 0, z: 0}
|
||||
m_EulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_AvatarMask: {fileID: 0}
|
||||
m_ApplyAvatarMask: 1
|
||||
m_TrackOffset: 0
|
||||
m_InfiniteClip: {fileID: 0}
|
||||
m_OpenClipOffsetRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_ApplyOffsets: 0
|
||||
--- !u!114 &-2070454477998879764
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 030f85c3f73729f4f976f66ffb23b875, type: 3}
|
||||
m_Name: AnimationPlayableAsset
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.AnimationPlayableAsset
|
||||
m_Clip: {fileID: 7400000, guid: c77ba8b4bbb8013478339a542995d25b, type: 2}
|
||||
m_Position: {x: 0, y: 0, z: 0}
|
||||
m_EulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_UseTrackMatchFields: 1
|
||||
m_MatchTargetFields: 63
|
||||
m_RemoveStartOffset: 1
|
||||
m_ApplyFootIK: 1
|
||||
m_Loop: 0
|
||||
m_Version: 1
|
||||
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bfda56da833e2384a9677cd3c976a436, type: 3}
|
||||
m_Name: PulverCucumberSmack1
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.TimelineAsset
|
||||
m_Version: 0
|
||||
m_Tracks:
|
||||
- {fileID: -7584736085941489071}
|
||||
- {fileID: -2395336864975438248}
|
||||
- {fileID: 3942302933360259000}
|
||||
m_FixedDuration: 0
|
||||
m_EditorSettings:
|
||||
m_Framerate: 60
|
||||
m_ScenePreview: 1
|
||||
m_DurationMode: 0
|
||||
m_MarkerTrack: {fileID: 0}
|
||||
--- !u!114 &3115908604919352715
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 030f85c3f73729f4f976f66ffb23b875, type: 3}
|
||||
m_Name: AnimationPlayableAsset
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.AnimationPlayableAsset
|
||||
m_Clip: {fileID: 7400000, guid: 09d7dd4e84cbed54bb4ca4e63ad0c6fa, type: 2}
|
||||
m_Position: {x: 0, y: 0, z: 0}
|
||||
m_EulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_UseTrackMatchFields: 1
|
||||
m_MatchTargetFields: 63
|
||||
m_RemoveStartOffset: 1
|
||||
m_ApplyFootIK: 1
|
||||
m_Loop: 0
|
||||
m_Version: 1
|
||||
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
--- !u!114 &3942302933360259000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 21bf7f712d84d26478ebe6a299f21738, type: 3}
|
||||
m_Name: Activation Track
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.ActivationTrack
|
||||
m_Version: 3
|
||||
m_AnimClip: {fileID: 0}
|
||||
m_Locked: 0
|
||||
m_Muted: 0
|
||||
m_CustomPlayableFullTypename:
|
||||
m_Curves: {fileID: 0}
|
||||
m_Parent: {fileID: 11400000}
|
||||
m_Children: []
|
||||
m_Clips:
|
||||
- m_Version: 1
|
||||
m_Start: 1.0166666666666666
|
||||
m_ClipIn: 0
|
||||
m_Asset: {fileID: -4664548104421960294}
|
||||
m_Duration: 0.8999999999999999
|
||||
m_TimeScale: 1
|
||||
m_ParentTrack: {fileID: 3942302933360259000}
|
||||
m_EaseInDuration: 0
|
||||
m_EaseOutDuration: 0
|
||||
m_BlendInDuration: 0
|
||||
m_BlendOutDuration: 0
|
||||
m_MixInCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_MixOutCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_BlendInCurveMode: 0
|
||||
m_BlendOutCurveMode: 0
|
||||
m_ExposedParameterNames: []
|
||||
m_AnimationCurves: {fileID: 0}
|
||||
m_Recordable: 0
|
||||
m_PostExtrapolationMode: 0
|
||||
m_PreExtrapolationMode: 0
|
||||
m_PostExtrapolationTime: 0
|
||||
m_PreExtrapolationTime: 0
|
||||
m_DisplayName: Active
|
||||
m_Markers:
|
||||
m_Objects: []
|
||||
m_PostPlaybackState: 3
|
||||
8
Assets/Playables/PulverCucumberSmack1.playable.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1791fd5a24a3142418ed441a2a25b374
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
197
Assets/Playables/PulverCucumberSmack_empty.playable
Normal file
@@ -0,0 +1,197 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-7584736085941489071
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d21dcc2386d650c4597f3633c75a1f98, type: 3}
|
||||
m_Name: Animation Track
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.AnimationTrack
|
||||
m_Version: 3
|
||||
m_AnimClip: {fileID: 0}
|
||||
m_Locked: 0
|
||||
m_Muted: 0
|
||||
m_CustomPlayableFullTypename:
|
||||
m_Curves: {fileID: 0}
|
||||
m_Parent: {fileID: 11400000}
|
||||
m_Children: []
|
||||
m_Clips: []
|
||||
m_Markers:
|
||||
m_Objects: []
|
||||
m_InfiniteClipPreExtrapolation: 0
|
||||
m_InfiniteClipPostExtrapolation: 0
|
||||
m_InfiniteClipOffsetPosition: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipOffsetEulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipTimeOffset: 0
|
||||
m_InfiniteClipRemoveOffset: 0
|
||||
m_InfiniteClipApplyFootIK: 1
|
||||
mInfiniteClipLoop: 0
|
||||
m_MatchTargetFields: 63
|
||||
m_Position: {x: 0, y: 0, z: 0}
|
||||
m_EulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_AvatarMask: {fileID: 0}
|
||||
m_ApplyAvatarMask: 1
|
||||
m_TrackOffset: 0
|
||||
m_InfiniteClip: {fileID: 0}
|
||||
m_OpenClipOffsetRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_ApplyOffsets: 0
|
||||
--- !u!114 &-2395336864975438248
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d21dcc2386d650c4597f3633c75a1f98, type: 3}
|
||||
m_Name: Animation Track (1)
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.AnimationTrack
|
||||
m_Version: 3
|
||||
m_AnimClip: {fileID: 0}
|
||||
m_Locked: 0
|
||||
m_Muted: 0
|
||||
m_CustomPlayableFullTypename:
|
||||
m_Curves: {fileID: 0}
|
||||
m_Parent: {fileID: 11400000}
|
||||
m_Children: []
|
||||
m_Clips:
|
||||
- m_Version: 1
|
||||
m_Start: 0
|
||||
m_ClipIn: 0
|
||||
m_Asset: {fileID: 3115908604919352715}
|
||||
m_Duration: 0.8333333333333334
|
||||
m_TimeScale: 1
|
||||
m_ParentTrack: {fileID: -2395336864975438248}
|
||||
m_EaseInDuration: 0
|
||||
m_EaseOutDuration: 0
|
||||
m_BlendInDuration: -1
|
||||
m_BlendOutDuration: -1
|
||||
m_MixInCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_MixOutCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_BlendInCurveMode: 0
|
||||
m_BlendOutCurveMode: 0
|
||||
m_ExposedParameterNames: []
|
||||
m_AnimationCurves: {fileID: 0}
|
||||
m_Recordable: 0
|
||||
m_PostExtrapolationMode: 1
|
||||
m_PreExtrapolationMode: 1
|
||||
m_PostExtrapolationTime: Infinity
|
||||
m_PreExtrapolationTime: 0
|
||||
m_DisplayName: Pulver_Cucumbatacc
|
||||
m_Markers:
|
||||
m_Objects: []
|
||||
m_InfiniteClipPreExtrapolation: 0
|
||||
m_InfiniteClipPostExtrapolation: 0
|
||||
m_InfiniteClipOffsetPosition: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipOffsetEulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_InfiniteClipTimeOffset: 0
|
||||
m_InfiniteClipRemoveOffset: 0
|
||||
m_InfiniteClipApplyFootIK: 1
|
||||
mInfiniteClipLoop: 0
|
||||
m_MatchTargetFields: 63
|
||||
m_Position: {x: 0, y: 0, z: 0}
|
||||
m_EulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_AvatarMask: {fileID: 0}
|
||||
m_ApplyAvatarMask: 1
|
||||
m_TrackOffset: 0
|
||||
m_InfiniteClip: {fileID: 0}
|
||||
m_OpenClipOffsetRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_ApplyOffsets: 0
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bfda56da833e2384a9677cd3c976a436, type: 3}
|
||||
m_Name: PulverCucumberSmack_empty
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.TimelineAsset
|
||||
m_Version: 0
|
||||
m_Tracks:
|
||||
- {fileID: -7584736085941489071}
|
||||
- {fileID: -2395336864975438248}
|
||||
m_FixedDuration: 0
|
||||
m_EditorSettings:
|
||||
m_Framerate: 60
|
||||
m_ScenePreview: 1
|
||||
m_DurationMode: 0
|
||||
m_MarkerTrack: {fileID: 0}
|
||||
--- !u!114 &3115908604919352715
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 030f85c3f73729f4f976f66ffb23b875, type: 3}
|
||||
m_Name: AnimationPlayableAsset
|
||||
m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.AnimationPlayableAsset
|
||||
m_Clip: {fileID: 7400000, guid: 09d7dd4e84cbed54bb4ca4e63ad0c6fa, type: 2}
|
||||
m_Position: {x: 0, y: 0, z: 0}
|
||||
m_EulerAngles: {x: 0, y: 0, z: 0}
|
||||
m_UseTrackMatchFields: 1
|
||||
m_MatchTargetFields: 63
|
||||
m_RemoveStartOffset: 1
|
||||
m_ApplyFootIK: 1
|
||||
m_Loop: 0
|
||||
m_Version: 1
|
||||
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
8
Assets/Playables/PulverCucumberSmack_empty.playable.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee609df51f47bd541a23d5425e289e30
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -313,6 +313,14 @@ PrefabInstance:
|
||||
propertyPath: m_Layer
|
||||
value: 7
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6109476811019011833, guid: 361ccc9ef82acef4784b24b72013d971, type: 3}
|
||||
propertyPath: m_Size.x
|
||||
value: 3.8700001
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6109476811019011833, guid: 361ccc9ef82acef4784b24b72013d971, type: 3}
|
||||
propertyPath: m_Size.y
|
||||
value: 7.55122
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6109476811019011833, guid: 361ccc9ef82acef4784b24b72013d971, type: 3}
|
||||
propertyPath: m_SortingOrder
|
||||
value: 1
|
||||
|
||||
@@ -136,7 +136,7 @@ MonoBehaviour:
|
||||
radius: 2
|
||||
height: 2
|
||||
canMove: 1
|
||||
maxSpeed: 15
|
||||
maxSpeed: 30
|
||||
gravity: {x: 0, y: 0, z: 0}
|
||||
groundMask:
|
||||
serializedVersion: 2
|
||||
@@ -236,6 +236,8 @@ SpriteRenderer:
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
@@ -257,6 +259,7 @@ SpriteRenderer:
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 2
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1646387898454772943
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7371967679236352629}
|
||||
- component: {fileID: 2280359367130513804}
|
||||
m_Layer: 0
|
||||
m_Name: PulverMoveTarget
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7371967679236352629
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1646387898454772943}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: -4.4615383, y: 0.61538696, z: 0}
|
||||
m_LocalScale: {x: 0.7692308, y: 0.7692308, z: 0.7692308}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4937390562043858043}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &2280359367130513804
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1646387898454772943}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2bcf343b3ef74f0fb3c64be6fd2893b6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: AppleHillsScripts::Interactions.CharacterMoveToTarget
|
||||
characterType: 2
|
||||
positionOffset: {x: 0, y: 0, z: 1}
|
||||
--- !u!1 &3591802784221671576
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -94,6 +140,11 @@ GameObject:
|
||||
- component: {fileID: 4937390562043858043}
|
||||
- component: {fileID: 2720557426779044373}
|
||||
- component: {fileID: 8897661028274890141}
|
||||
- component: {fileID: 8437452310832126615}
|
||||
- component: {fileID: 492578671844741631}
|
||||
- component: {fileID: 8984729148657672365}
|
||||
- component: {fileID: 1569498917964935965}
|
||||
- component: {fileID: 3871210969445384207}
|
||||
m_Layer: 10
|
||||
m_Name: BallTree
|
||||
m_TagString: Untagged
|
||||
@@ -115,6 +166,7 @@ Transform:
|
||||
m_ConstrainProportionsScale: 1
|
||||
m_Children:
|
||||
- {fileID: 6631072601870453588}
|
||||
- {fileID: 7371967679236352629}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!212 &2720557426779044373
|
||||
@@ -137,6 +189,8 @@ SpriteRenderer:
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
@@ -158,6 +212,7 @@ SpriteRenderer:
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 1
|
||||
@@ -218,3 +273,115 @@ BoxCollider2D:
|
||||
m_AutoTiling: 0
|
||||
m_Size: {x: 3.437712, y: 7.532383}
|
||||
m_EdgeRadius: 0
|
||||
--- !u!114 &8437452310832126615
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7379304988657006554}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 73d6494a73174ffabc6a7d3089d51e73, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
isOneTime: 0
|
||||
cooldown: -1
|
||||
characterToInteract: 2
|
||||
interactionStarted:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
interactionInterrupted:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
characterArrived:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
interactionComplete:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &492578671844741631
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7379304988657006554}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1101f6c4eb04423b89dc78dc7c9f1aae, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
stepData: {fileID: 11400000, guid: 8ac614a698631554ab8ac39aed04a189, type: 2}
|
||||
puzzleIndicator: {fileID: 0}
|
||||
drawPromptRangeGizmo: 1
|
||||
--- !u!114 &8984729148657672365
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7379304988657006554}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 42e77a0c97604b6eb7674e58726c831a, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: AppleHillsScripts::Interactions.InteractionTimelineAction
|
||||
respondToEvents: 04000000
|
||||
pauseInteractionFlow: 1
|
||||
playableDirector: {fileID: 0}
|
||||
timelineMappings:
|
||||
- eventType: 4
|
||||
timelines: []
|
||||
bindPlayerCharacter: 0
|
||||
bindPulverCharacter: 0
|
||||
playerTrackName:
|
||||
pulverTrackName:
|
||||
timeoutSeconds: 30
|
||||
loopLast: 0
|
||||
loopAll: 0
|
||||
--- !u!320 &1569498917964935965
|
||||
PlayableDirector:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7379304988657006554}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_PlayableAsset: {fileID: 11400000, guid: dd9566026364e814a8dad109e6c365ca, type: 2}
|
||||
m_InitialState: 0
|
||||
m_WrapMode: 2
|
||||
m_DirectorUpdateMode: 1
|
||||
m_InitialTime: 0
|
||||
m_SceneBindings:
|
||||
- key: {fileID: -7584736085941489071, guid: dd9566026364e814a8dad109e6c365ca, type: 2}
|
||||
value: {fileID: 3871210969445384207}
|
||||
- key: {fileID: -2395336864975438248, guid: dd9566026364e814a8dad109e6c365ca, type: 2}
|
||||
value: {fileID: 0}
|
||||
- key: {fileID: -7231857257271738743, guid: dd9566026364e814a8dad109e6c365ca, type: 2}
|
||||
value: {fileID: 0}
|
||||
m_ExposedReferences:
|
||||
m_References: []
|
||||
--- !u!95 &3871210969445384207
|
||||
Animator:
|
||||
serializedVersion: 7
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7379304988657006554}
|
||||
m_Enabled: 1
|
||||
m_Avatar: {fileID: 0}
|
||||
m_Controller: {fileID: 0}
|
||||
m_CullingMode: 0
|
||||
m_UpdateMode: 0
|
||||
m_ApplyRootMotion: 0
|
||||
m_LinearVelocityBlending: 0
|
||||
m_StabilizeFeet: 0
|
||||
m_AnimatePhysics: 0
|
||||
m_WarningMessage:
|
||||
m_HasTransformHierarchy: 1
|
||||
m_AllowConstantClipSamplingOptimization: 1
|
||||
m_KeepAnimatorStateOnDisable: 0
|
||||
m_WriteDefaultValuesOnDisable: 0
|
||||
|
||||
@@ -447648,18 +447648,6 @@ MonoBehaviour:
|
||||
stepData: {fileID: 11400000, guid: 8ac614a698631554ab8ac39aed04a189, type: 2}
|
||||
puzzleIndicator: {fileID: 0}
|
||||
drawPromptRangeGizmo: 1
|
||||
--- !u!114 &1182494941
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1182494929}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 833a4ccef651449e973e623d9107bef5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!95 &1182494942
|
||||
Animator:
|
||||
serializedVersion: 7
|
||||
@@ -447670,7 +447658,7 @@ Animator:
|
||||
m_GameObject: {fileID: 1182494929}
|
||||
m_Enabled: 1
|
||||
m_Avatar: {fileID: 0}
|
||||
m_Controller: {fileID: 9100000, guid: 4587ce13b65b5154c853fe4bddbd6247, type: 2}
|
||||
m_Controller: {fileID: 0}
|
||||
m_CullingMode: 0
|
||||
m_UpdateMode: 0
|
||||
m_ApplyRootMotion: 0
|
||||
@@ -449505,6 +449493,14 @@ PrefabInstance:
|
||||
propertyPath: m_Name
|
||||
value: FootballBall
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3606875748053192296, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
propertyPath: m_IsActive
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4386785291364665203, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
propertyPath: characterToInteract
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4419731015739629793, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
propertyPath: m_Sprite
|
||||
value:
|
||||
@@ -449515,7 +449511,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5572894649736340512, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 92.757
|
||||
value: 92.79
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5572894649736340512, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
@@ -449560,8 +449556,38 @@ PrefabInstance:
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 3606875748053192296, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1295249128}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
--- !u!1 &1295249127 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 3606875748053192296, guid: 30285f2632211504484661965ed61c57, type: 3}
|
||||
m_PrefabInstance: {fileID: 1295249126}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!95 &1295249128
|
||||
Animator:
|
||||
serializedVersion: 7
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1295249127}
|
||||
m_Enabled: 1
|
||||
m_Avatar: {fileID: 0}
|
||||
m_Controller: {fileID: 0}
|
||||
m_CullingMode: 0
|
||||
m_UpdateMode: 0
|
||||
m_ApplyRootMotion: 0
|
||||
m_LinearVelocityBlending: 0
|
||||
m_StabilizeFeet: 0
|
||||
m_AnimatePhysics: 0
|
||||
m_WarningMessage:
|
||||
m_HasTransformHierarchy: 1
|
||||
m_AllowConstantClipSamplingOptimization: 1
|
||||
m_KeepAnimatorStateOnDisable: 0
|
||||
m_WriteDefaultValuesOnDisable: 0
|
||||
--- !u!1001 &1313372821
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -450508,11 +450534,11 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2264394306674147778, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: -40.250267
|
||||
value: -36.1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2264394306674147778, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: -44.838055
|
||||
value: -41.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2264394306674147778, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
@@ -450555,6 +450581,11 @@ PrefabInstance:
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
|
||||
--- !u!95 &1363194739 stripped
|
||||
Animator:
|
||||
m_CorrespondingSourceObject: {fileID: 1798693240065965692, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
|
||||
m_PrefabInstance: {fileID: 1363194738}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!4 &1370055784 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 7815739457642955130, guid: f645a67c7970b124cacb6450fefdebad, type: 3}
|
||||
@@ -461610,6 +461641,10 @@ PrefabInstance:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 1682591185}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1597866798502552092, guid: 0b255c6ea64a74240a8db4d9e8f820be, type: 3}
|
||||
propertyPath: characterToInteract
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1784002662241348359, guid: 0b255c6ea64a74240a8db4d9e8f820be, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: Nails
|
||||
@@ -463989,6 +464024,10 @@ PrefabInstance:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2491802807835645092, guid: 3346526f3046f424196615241a307104, type: 3}
|
||||
propertyPath: characterToInteract
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2531688711670838070, guid: 3346526f3046f424196615241a307104, type: 3}
|
||||
propertyPath: m_Sprite
|
||||
value:
|
||||
@@ -464041,6 +464080,10 @@ PrefabInstance:
|
||||
propertyPath: isOneTime
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4289780218821574471, guid: 3346526f3046f424196615241a307104, type: 3}
|
||||
propertyPath: characterToInteract
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5236930998804014616, guid: 3346526f3046f424196615241a307104, type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 10
|
||||
@@ -464188,6 +464231,10 @@ PrefabInstance:
|
||||
propertyPath: isOneTime
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 519585874127847016, guid: df01157608cce6447b7ccde0bfa290e1, type: 3}
|
||||
propertyPath: characterToInteract
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1638886621542193701, guid: df01157608cce6447b7ccde0bfa290e1, type: 3}
|
||||
propertyPath: characterArrived.m_PersistentCalls.m_Calls.Array.data[0].m_Mode
|
||||
value: 2
|
||||
@@ -464574,6 +464621,10 @@ PrefabInstance:
|
||||
propertyPath: isOneTime
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2767794910448825193, guid: 1fda7fccaa5fbd04695f4c98d29bcbe0, type: 3}
|
||||
propertyPath: characterToInteract
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2767794910448825193, guid: 1fda7fccaa5fbd04695f4c98d29bcbe0, type: 3}
|
||||
propertyPath: characterArrived.m_PersistentCalls.m_Calls.Array.size
|
||||
value: 2
|
||||
@@ -465069,6 +465120,58 @@ PrefabInstance:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_PlayableAsset
|
||||
value:
|
||||
objectReference: {fileID: 11400000, guid: ee609df51f47bd541a23d5425e289e30, type: 2}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.size
|
||||
value: 8
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[3].key
|
||||
value:
|
||||
objectReference: {fileID: -2395336864975438248, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[4].key
|
||||
value:
|
||||
objectReference: {fileID: -7584736085941489071, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[5].key
|
||||
value:
|
||||
objectReference: {fileID: 3942302933360259000, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[6].key
|
||||
value:
|
||||
objectReference: {fileID: -7584736085941489071, guid: ee609df51f47bd541a23d5425e289e30, type: 2}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[7].key
|
||||
value:
|
||||
objectReference: {fileID: -2395336864975438248, guid: ee609df51f47bd541a23d5425e289e30, type: 2}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[0].value
|
||||
value:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[3].value
|
||||
value:
|
||||
objectReference: {fileID: 1363194739}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[4].value
|
||||
value:
|
||||
objectReference: {fileID: 1182494942}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[5].value
|
||||
value:
|
||||
objectReference: {fileID: 1295249127}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[6].value
|
||||
value:
|
||||
objectReference: {fileID: 1182494942}
|
||||
- target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_SceneBindings.Array.data[7].value
|
||||
value:
|
||||
objectReference: {fileID: 1363194739}
|
||||
- target: {fileID: 2720557426779044373, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_Sprite
|
||||
value:
|
||||
@@ -465133,6 +465236,14 @@ PrefabInstance:
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: -0.16
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7371967679236352629, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: -1.606
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7371967679236352629, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7379304988657006554, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: BallTree
|
||||
@@ -465201,6 +465312,82 @@ PrefabInstance:
|
||||
propertyPath: m_SpriteTilingProperty.oldSize.y
|
||||
value: 9.060193
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: playableDirector
|
||||
value:
|
||||
objectReference: {fileID: 7530821580781571561}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.size
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: 'respondToEvents.Array.data[0]'
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[0].loopLast
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[0].eventType
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[1].eventType
|
||||
value: 4
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[1].timeoutSeconds
|
||||
value: 30
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[0].playerTrackName
|
||||
value: Player
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[0].pulverTrackName
|
||||
value: Pulver
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[1].playerTrackName
|
||||
value: Player
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[1].pulverTrackName
|
||||
value: Pulver
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[0].bindPlayerCharacter
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[0].bindPulverCharacter
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[1].bindPlayerCharacter
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[1].bindPulverCharacter
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[0].timelines.Array.size
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: timelineMappings.Array.data[1].timelines.Array.size
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: 'timelineMappings.Array.data[0].timelines.Array.data[0]'
|
||||
value:
|
||||
objectReference: {fileID: 11400000, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2}
|
||||
- target: {fileID: 8984729148657672365, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
propertyPath: 'timelineMappings.Array.data[0].timelines.Array.data[1]'
|
||||
value:
|
||||
objectReference: {fileID: 11400000, guid: ee609df51f47bd541a23d5425e289e30, type: 2}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
@@ -465213,11 +465400,28 @@ PrefabInstance:
|
||||
addedObject: {fileID: 1182494937}
|
||||
- targetCorrespondingSourceObject: {fileID: 7379304988657006554, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1182494941}
|
||||
addedObject: {fileID: 1182494942}
|
||||
- targetCorrespondingSourceObject: {fileID: 7379304988657006554, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1182494942}
|
||||
addedObject: {fileID: 7530821580781571568}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
--- !u!320 &7530821580781571561 stripped
|
||||
PlayableDirector:
|
||||
m_CorrespondingSourceObject: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3}
|
||||
m_PrefabInstance: {fileID: 7530821580781571560}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &7530821580781571568
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1182494929}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 833a4ccef651449e973e623d9107bef5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: AppleHillsScripts::OneClickInteraction
|
||||
--- !u!1001 &7535757761066548300
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -465535,6 +465739,10 @@ PrefabInstance:
|
||||
propertyPath: isOneTime
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8818689886719637838, guid: 3144c6bbac26fbd49a1608152821cc5f, type: 3}
|
||||
propertyPath: characterToInteract
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
|
||||
58
Assets/Scripts/Interactions/CharacterMoveToTarget.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a target position for character movement during interaction.
|
||||
/// Attach this to an interactable object's child to specify where
|
||||
/// characters should move during interaction rather than using the default calculations.
|
||||
/// </summary>
|
||||
public class CharacterMoveToTarget : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Which character this target position is for")]
|
||||
public CharacterToInteract characterType = CharacterToInteract.Pulver;
|
||||
|
||||
[Tooltip("Optional offset from this transform's position")]
|
||||
public Vector3 positionOffset = Vector3.zero;
|
||||
|
||||
/// <summary>
|
||||
/// Get the target position for this character to move to
|
||||
/// </summary>
|
||||
public Vector3 GetTargetPosition()
|
||||
{
|
||||
return transform.position + positionOffset;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// Draw a different colored sphere based on which character this target is for
|
||||
switch (characterType)
|
||||
{
|
||||
case CharacterToInteract.Trafalgar:
|
||||
Gizmos.color = new Color(0f, 0.5f, 1f, 0.8f); // Blue for player
|
||||
break;
|
||||
case CharacterToInteract.Pulver:
|
||||
Gizmos.color = new Color(1f, 0.5f, 0f, 0.8f); // Orange for follower
|
||||
break;
|
||||
case CharacterToInteract.Both:
|
||||
Gizmos.color = new Color(0.7f, 0f, 0.7f, 0.8f); // Purple for both
|
||||
break;
|
||||
default:
|
||||
Gizmos.color = new Color(0.5f, 0.5f, 0.5f, 0.8f); // Gray for none
|
||||
break;
|
||||
}
|
||||
|
||||
Vector3 targetPos = GetTargetPosition();
|
||||
Gizmos.DrawSphere(targetPos, 0.2f);
|
||||
|
||||
// Draw a line from the parent interactable to this target
|
||||
Interactable parentInteractable = GetComponentInParent<Interactable>();
|
||||
if (parentInteractable != null)
|
||||
{
|
||||
Gizmos.DrawLine(parentInteractable.transform.position, targetPos);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bcf343b3ef74f0fb3c64be6fd2893b6
|
||||
timeCreated: 1759744130
|
||||
@@ -1,22 +1,27 @@
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Events;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Interactions
|
||||
{
|
||||
public enum CharacterToInteract
|
||||
{
|
||||
None,
|
||||
Trafalgar,
|
||||
Pulver
|
||||
Pulver,
|
||||
Both
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an interactable object that can respond to tap input events.
|
||||
/// </summary>
|
||||
public class Interactable : MonoBehaviour, ITouchInputConsumer
|
||||
{
|
||||
[Header("Interaction Settings")]
|
||||
public bool isOneTime = false;
|
||||
public bool isOneTime;
|
||||
public float cooldown = -1f;
|
||||
public CharacterToInteract characterToInteract = CharacterToInteract.Pulver;
|
||||
|
||||
@@ -30,15 +35,64 @@ namespace Interactions
|
||||
private bool _interactionInProgress;
|
||||
private PlayerTouchController _playerRef;
|
||||
private FollowerController _followerController;
|
||||
|
||||
private bool _isActive = true;
|
||||
private InteractionEventType _currentEventType;
|
||||
|
||||
// Action component system
|
||||
private List<InteractionActionBase> _registeredActions = new List<InteractionActionBase>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Subscribe to interactionComplete event
|
||||
interactionComplete.AddListener(OnInteractionComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an action component with this interactable
|
||||
/// </summary>
|
||||
public void RegisterAction(InteractionActionBase action)
|
||||
{
|
||||
if (!_registeredActions.Contains(action))
|
||||
{
|
||||
_registeredActions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister an action component from this interactable
|
||||
/// </summary>
|
||||
public void UnregisterAction(InteractionActionBase action)
|
||||
{
|
||||
_registeredActions.Remove(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch an interaction event to all registered actions and await their completion
|
||||
/// </summary>
|
||||
private async Task DispatchEventAsync(InteractionEventType eventType)
|
||||
{
|
||||
_currentEventType = eventType;
|
||||
|
||||
// Collect all tasks from actions that want to respond
|
||||
List<Task<bool>> tasks = new List<Task<bool>>();
|
||||
|
||||
foreach (var action in _registeredActions)
|
||||
{
|
||||
Task<bool> task = action.OnInteractionEvent(eventType, _playerRef, _followerController);
|
||||
if (task != null)
|
||||
{
|
||||
tasks.Add(task);
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.Count > 0)
|
||||
{
|
||||
// Wait for all tasks to complete
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
// If no tasks were added, the method will complete immediately (no need for await)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles tap input. Triggers interaction logic.
|
||||
/// </summary>
|
||||
@@ -50,11 +104,12 @@ namespace Interactions
|
||||
return;
|
||||
}
|
||||
Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}");
|
||||
// Broadcast interaction started event
|
||||
TryInteract();
|
||||
|
||||
// Start the interaction process asynchronously
|
||||
_ = TryInteractAsync();
|
||||
}
|
||||
|
||||
public void TryInteract()
|
||||
private async Task TryInteractAsync()
|
||||
{
|
||||
_interactionInProgress = true;
|
||||
|
||||
@@ -63,68 +118,302 @@ namespace Interactions
|
||||
|
||||
interactionStarted?.Invoke(_playerRef, _followerController);
|
||||
|
||||
// Dispatch the InteractionStarted event to action components
|
||||
await DispatchEventAsync(InteractionEventType.InteractionStarted);
|
||||
|
||||
// After all InteractionStarted actions complete, proceed to player movement
|
||||
await StartPlayerMovementAsync();
|
||||
}
|
||||
|
||||
private async Task StartPlayerMovementAsync()
|
||||
{
|
||||
if (_playerRef == null)
|
||||
{
|
||||
Debug.Log($"[Interactable] Player character could not be found. Aborting interaction.");
|
||||
interactionInterrupted.Invoke();
|
||||
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
|
||||
return;
|
||||
}
|
||||
|
||||
// If characterToInteract is None, immediately trigger the characterArrived event
|
||||
if (characterToInteract == CharacterToInteract.None)
|
||||
{
|
||||
await BroadcastCharacterArrivedAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute closest point on the interaction radius
|
||||
Vector3 interactablePos = transform.position;
|
||||
Vector3 playerPos = _playerRef.transform.position;
|
||||
float stopDistance = characterToInteract == CharacterToInteract.Pulver
|
||||
? GameManager.Instance.PlayerStopDistance
|
||||
: GameManager.Instance.PlayerStopDistanceDirectInteraction;
|
||||
Vector3 toPlayer = (playerPos - interactablePos).normalized;
|
||||
Vector3 stopPoint = interactablePos + toPlayer * stopDistance;
|
||||
// Check for a CharacterMoveToTarget component for Trafalgar (player) or Both
|
||||
Vector3 stopPoint;
|
||||
bool customTargetFound = false;
|
||||
|
||||
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
||||
foreach (var target in moveTargets)
|
||||
{
|
||||
// Target is valid if it matches Trafalgar specifically or is set to Both
|
||||
if (target.characterType == CharacterToInteract.Trafalgar || target.characterType == CharacterToInteract.Both)
|
||||
{
|
||||
stopPoint = target.GetTargetPosition();
|
||||
customTargetFound = true;
|
||||
|
||||
// We need to wait for the player to arrive, so use a TaskCompletionSource
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
// Use local functions instead of circular lambda references
|
||||
void OnPlayerArrivedLocal()
|
||||
{
|
||||
// First remove both event handlers to prevent memory leaks
|
||||
if (_playerRef != null)
|
||||
{
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
||||
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
||||
}
|
||||
|
||||
// Then continue with the interaction flow
|
||||
OnPlayerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
||||
}
|
||||
|
||||
void OnPlayerMoveCancelledLocal()
|
||||
{
|
||||
// First remove both event handlers to prevent memory leaks
|
||||
if (_playerRef != null)
|
||||
{
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
||||
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
||||
}
|
||||
|
||||
// Then handle the cancellation
|
||||
OnPlayerMoveCancelledAsync().ContinueWith(_ => tcs.TrySetResult(false));
|
||||
}
|
||||
|
||||
// Unsubscribe previous handlers (if any)
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
|
||||
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled;
|
||||
|
||||
// Subscribe our new handlers
|
||||
_playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
|
||||
_playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
|
||||
|
||||
// Start the player movement
|
||||
_playerRef.MoveToAndNotify(stopPoint);
|
||||
|
||||
// Await player arrival
|
||||
await tcs.Task;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If no custom target was found, use the default behavior
|
||||
if (!customTargetFound)
|
||||
{
|
||||
// Compute closest point on the interaction radius
|
||||
Vector3 interactablePos = transform.position;
|
||||
Vector3 playerPos = _playerRef.transform.position;
|
||||
float stopDistance = characterToInteract == CharacterToInteract.Pulver
|
||||
? GameManager.Instance.PlayerStopDistance
|
||||
: GameManager.Instance.PlayerStopDistanceDirectInteraction;
|
||||
Vector3 toPlayer = (playerPos - interactablePos).normalized;
|
||||
stopPoint = interactablePos + toPlayer * stopDistance;
|
||||
|
||||
// Unsubscribe previous to avoid duplicate calls
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
|
||||
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled;
|
||||
_playerRef.OnArrivedAtTarget += OnPlayerArrived;
|
||||
_playerRef.OnMoveToCancelled += OnPlayerMoveCancelled;
|
||||
_playerRef.MoveToAndNotify(stopPoint);
|
||||
// We need to wait for the player to arrive, so use a TaskCompletionSource
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
// Use local functions instead of circular lambda references
|
||||
void OnPlayerArrivedLocal()
|
||||
{
|
||||
// First remove both event handlers to prevent memory leaks
|
||||
if (_playerRef != null)
|
||||
{
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
||||
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
||||
}
|
||||
|
||||
// Then continue with the interaction flow
|
||||
OnPlayerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
||||
}
|
||||
|
||||
void OnPlayerMoveCancelledLocal()
|
||||
{
|
||||
// First remove both event handlers to prevent memory leaks
|
||||
if (_playerRef != null)
|
||||
{
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
||||
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
||||
}
|
||||
|
||||
// Then handle the cancellation
|
||||
OnPlayerMoveCancelledAsync().ContinueWith(_ => tcs.TrySetResult(false));
|
||||
}
|
||||
|
||||
// Unsubscribe previous handlers (if any)
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
|
||||
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled;
|
||||
|
||||
// Subscribe our new handlers
|
||||
_playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
|
||||
_playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
|
||||
|
||||
// Start the player movement
|
||||
_playerRef.MoveToAndNotify(stopPoint);
|
||||
|
||||
// Await player arrival
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerMoveCancelled()
|
||||
private async Task OnPlayerMoveCancelledAsync()
|
||||
{
|
||||
_interactionInProgress = false;
|
||||
interactionInterrupted?.Invoke();
|
||||
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
|
||||
}
|
||||
|
||||
private void OnPlayerArrived()
|
||||
private async Task OnPlayerArrivedAsync()
|
||||
{
|
||||
if (!_interactionInProgress)
|
||||
return;
|
||||
|
||||
// Unsubscribe to avoid memory leaks
|
||||
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
|
||||
// Dispatch PlayerArrived event
|
||||
await DispatchEventAsync(InteractionEventType.PlayerArrived);
|
||||
|
||||
// After all PlayerArrived actions complete, proceed to character interaction
|
||||
await HandleCharacterInteractionAsync();
|
||||
}
|
||||
|
||||
private async Task HandleCharacterInteractionAsync()
|
||||
{
|
||||
if (characterToInteract == CharacterToInteract.Pulver)
|
||||
{
|
||||
_followerController.OnPickupArrived -= OnFollowerArrived;
|
||||
_followerController.OnPickupArrived += OnFollowerArrived;
|
||||
_followerController.GoToPointAndReturn(transform.position, _playerRef.transform);
|
||||
// We need to wait for the follower to arrive, so use a TaskCompletionSource
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
// Create a proper local function for the event handler
|
||||
void OnFollowerArrivedLocal()
|
||||
{
|
||||
// First remove the event handler to prevent memory leaks
|
||||
if (_followerController != null)
|
||||
{
|
||||
_followerController.OnPickupArrived -= OnFollowerArrivedLocal;
|
||||
}
|
||||
|
||||
// Then continue with the interaction flow
|
||||
OnFollowerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
||||
}
|
||||
|
||||
// Register our new local function handler
|
||||
_followerController.OnPickupArrived += OnFollowerArrivedLocal;
|
||||
|
||||
// Check for a CharacterMoveToTarget component for Pulver or Both
|
||||
Vector3 targetPosition = transform.position;
|
||||
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
||||
foreach (var target in moveTargets)
|
||||
{
|
||||
if (target.characterType == CharacterToInteract.Pulver || target.characterType == CharacterToInteract.Both)
|
||||
{
|
||||
targetPosition = target.GetTargetPosition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the new GoToPoint method instead of GoToPointAndReturn
|
||||
_followerController.GoToPoint(targetPosition);
|
||||
|
||||
// Await follower arrival
|
||||
await tcs.Task;
|
||||
}
|
||||
else if (characterToInteract == CharacterToInteract.Trafalgar)
|
||||
{
|
||||
BroadcastCharacterArrived();
|
||||
await BroadcastCharacterArrivedAsync();
|
||||
}
|
||||
else if (characterToInteract == CharacterToInteract.Both)
|
||||
{
|
||||
// We need to wait for the follower to arrive, so use a TaskCompletionSource
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
// Create a proper local function for the event handler
|
||||
void OnFollowerArrivedLocal()
|
||||
{
|
||||
// First remove the event handler to prevent memory leaks
|
||||
if (_followerController != null)
|
||||
{
|
||||
_followerController.OnPickupArrived -= OnFollowerArrivedLocal;
|
||||
}
|
||||
|
||||
// Then continue with the interaction flow
|
||||
OnFollowerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
||||
}
|
||||
|
||||
// Register our new local function handler
|
||||
_followerController.OnPickupArrived += OnFollowerArrivedLocal;
|
||||
|
||||
// Check for a CharacterMoveToTarget component for Pulver or Both
|
||||
Vector3 targetPosition = transform.position;
|
||||
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
||||
foreach (var target in moveTargets)
|
||||
{
|
||||
if (target.characterType == CharacterToInteract.Pulver || target.characterType == CharacterToInteract.Both)
|
||||
{
|
||||
targetPosition = target.GetTargetPosition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the new GoToPoint method instead of GoToPointAndReturn
|
||||
_followerController.GoToPoint(targetPosition);
|
||||
|
||||
// Await follower arrival
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFollowerArrived()
|
||||
private async Task OnFollowerArrivedAsync()
|
||||
{
|
||||
if (!_interactionInProgress)
|
||||
return;
|
||||
|
||||
// Unsubscribe to avoid memory leaks
|
||||
_followerController.OnPickupArrived -= OnFollowerArrived;
|
||||
// Dispatch InteractingCharacterArrived event and WAIT for all actions to complete
|
||||
// This ensures we wait for any timeline animations to finish before proceeding
|
||||
Debug.Log("[Interactable] Follower arrived, dispatching InteractingCharacterArrived event and waiting for completion");
|
||||
await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
|
||||
Debug.Log("[Interactable] All InteractingCharacterArrived actions completed, proceeding with interaction");
|
||||
|
||||
BroadcastCharacterArrived();
|
||||
// Check if we have any components that might have paused the interaction flow
|
||||
bool hasTimelineActions = false;
|
||||
foreach (var action in _registeredActions)
|
||||
{
|
||||
if (action is InteractionTimelineAction timelineAction &&
|
||||
timelineAction.respondToEvents.Contains(InteractionEventType.InteractingCharacterArrived) &&
|
||||
timelineAction.pauseInteractionFlow)
|
||||
{
|
||||
hasTimelineActions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the follower to return to the player
|
||||
if (_followerController != null && _playerRef != null)
|
||||
{
|
||||
_followerController.ReturnToPlayer(_playerRef.transform);
|
||||
}
|
||||
|
||||
// After all InteractingCharacterArrived actions complete, proceed to character arrived
|
||||
await BroadcastCharacterArrivedAsync();
|
||||
}
|
||||
|
||||
private void BroadcastCharacterArrived()
|
||||
// Legacy non-async method to maintain compatibility with existing code
|
||||
private void OnPlayerArrived()
|
||||
{
|
||||
// This is now just a wrapper for the async version
|
||||
_ = OnPlayerArrivedAsync();
|
||||
}
|
||||
|
||||
// Legacy non-async method to maintain compatibility with existing code
|
||||
private void OnPlayerMoveCancelled()
|
||||
{
|
||||
// This is now just a wrapper for the async version
|
||||
_ = OnPlayerMoveCancelledAsync();
|
||||
}
|
||||
|
||||
private async Task BroadcastCharacterArrivedAsync()
|
||||
{
|
||||
// Check for ObjectiveStepBehaviour and lock state
|
||||
var step = GetComponent<PuzzleS.ObjectiveStepBehaviour>();
|
||||
@@ -138,16 +427,24 @@ namespace Interactions
|
||||
_followerController = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch CharacterArrived event
|
||||
// await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
|
||||
|
||||
// Broadcast appropriate event
|
||||
characterArrived?.Invoke();
|
||||
|
||||
// Reset variables for next time
|
||||
_interactionInProgress = false;
|
||||
_playerRef = null;
|
||||
_followerController = null;
|
||||
}
|
||||
|
||||
private void OnInteractionComplete(bool success)
|
||||
private async void OnInteractionComplete(bool success)
|
||||
{
|
||||
// Dispatch InteractionComplete event
|
||||
await DispatchEventAsync(InteractionEventType.InteractionComplete);
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (isOneTime)
|
||||
|
||||
91
Assets/Scripts/Interactions/InteractionActionBase.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Input;
|
||||
|
||||
namespace Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all interaction action components
|
||||
/// These components respond to interaction events and can control the interaction flow
|
||||
/// </summary>
|
||||
public abstract class InteractionActionBase : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Which interaction events this action should respond to")]
|
||||
public List<InteractionEventType> respondToEvents = new List<InteractionEventType>();
|
||||
|
||||
[Tooltip("Whether the interaction flow should wait for this action to complete")]
|
||||
public bool pauseInteractionFlow = true;
|
||||
|
||||
protected Interactable parentInteractable;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
// Get the parent interactable component
|
||||
parentInteractable = GetComponentInParent<Interactable>();
|
||||
|
||||
if (parentInteractable == null)
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}] Cannot find parent Interactable component!");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (parentInteractable != null)
|
||||
{
|
||||
parentInteractable.RegisterAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (parentInteractable != null)
|
||||
{
|
||||
parentInteractable.UnregisterAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an interaction event occurs that this action is registered for
|
||||
/// </summary>
|
||||
/// <param name="eventType">The type of event that occurred</param>
|
||||
/// <returns>A task that completes when the action is finished, or null if action won't execute</returns>
|
||||
public Task<bool> OnInteractionEvent(InteractionEventType eventType, PlayerTouchController player, FollowerController follower)
|
||||
{
|
||||
if (respondToEvents.Contains(eventType) && ShouldExecute(eventType, player, follower))
|
||||
{
|
||||
if (pauseInteractionFlow)
|
||||
{
|
||||
return ExecuteAsync(eventType, player, follower);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't need to pause the flow, execute in the background
|
||||
// and return a completed task
|
||||
_ = ExecuteAsync(eventType, player, follower);
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the action for the given event asynchronously
|
||||
/// </summary>
|
||||
protected abstract Task<bool> ExecuteAsync(InteractionEventType eventType, PlayerTouchController player, FollowerController follower);
|
||||
|
||||
/// <summary>
|
||||
/// Called to determine if this action should execute for the given event
|
||||
/// Override this to add additional conditions for execution
|
||||
/// </summary>
|
||||
protected virtual bool ShouldExecute(InteractionEventType eventType, PlayerTouchController player, FollowerController follower)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cf351d32dac4169a9db20609727a70f
|
||||
timeCreated: 1759746705
|
||||
16
Assets/Scripts/Interactions/InteractionEventType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the different types of events that can occur during an interaction
|
||||
/// </summary>
|
||||
public enum InteractionEventType
|
||||
{
|
||||
InteractionStarted,
|
||||
PlayerArrived,
|
||||
InteractingCharacterArrived,
|
||||
InteractionComplete,
|
||||
InteractionInterrupted
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Interactions/InteractionEventType.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78684d31bd4d4636834a494c7cb74f48
|
||||
timeCreated: 1759746690
|
||||
258
Assets/Scripts/Interactions/InteractionTimelineAction.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Input;
|
||||
|
||||
namespace Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that plays timeline animations in response to interaction events
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(PlayableDirector))]
|
||||
public class InteractionTimelineAction : InteractionActionBase
|
||||
{
|
||||
[System.Serializable]
|
||||
public class TimelineEventMapping
|
||||
{
|
||||
public InteractionEventType eventType;
|
||||
public PlayableAsset[] timelines;
|
||||
|
||||
[Tooltip("Whether to bind the player character to the track named 'Player'")]
|
||||
public bool bindPlayerCharacter = false;
|
||||
|
||||
[Tooltip("Whether to bind the follower character to the track named 'Pulver'")]
|
||||
public bool bindPulverCharacter = false;
|
||||
|
||||
[Tooltip("Custom track name for player character binding")]
|
||||
public string playerTrackName = "Player";
|
||||
|
||||
[Tooltip("Custom track name for follower character binding")]
|
||||
public string pulverTrackName = "Pulver";
|
||||
|
||||
[Tooltip("Time in seconds before the timeline is automatically completed (safety feature)")]
|
||||
public float timeoutSeconds = 30f;
|
||||
|
||||
[Tooltip("Whether to loop the last timeline in the sequence")]
|
||||
public bool loopLast = false;
|
||||
|
||||
[Tooltip("Whether to loop through all timelines in the sequence")]
|
||||
public bool loopAll = false;
|
||||
|
||||
// Helper property to check if we have valid timelines
|
||||
public bool HasValidTimelines => timelines != null && timelines.Length > 0 && timelines[0] != null;
|
||||
}
|
||||
|
||||
[Header("Timeline Configuration")] [SerializeField]
|
||||
private PlayableDirector playableDirector;
|
||||
|
||||
[SerializeField] private TimelineEventMapping[] timelineMappings;
|
||||
|
||||
private TaskCompletionSource<bool> _currentPlaybackTCS;
|
||||
private int _currentTimelineIndex = 0;
|
||||
private TimelineEventMapping _currentMapping = null;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (playableDirector == null)
|
||||
{
|
||||
playableDirector = GetComponent<PlayableDirector>();
|
||||
}
|
||||
|
||||
if (playableDirector == null)
|
||||
{
|
||||
Debug.LogError("[InteractionTimelineAction] PlayableDirector component is missing!");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscribe to the director's stopped event
|
||||
playableDirector.stopped += OnPlayableDirectorStopped;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (playableDirector != null)
|
||||
{
|
||||
playableDirector.stopped -= OnPlayableDirectorStopped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<bool> ExecuteAsync(InteractionEventType eventType, PlayerTouchController player,
|
||||
FollowerController follower)
|
||||
{
|
||||
// Find the timeline for this event type
|
||||
TimelineEventMapping mapping = Array.Find(timelineMappings, m => m.eventType == eventType);
|
||||
|
||||
if (mapping == null || !mapping.HasValidTimelines)
|
||||
{
|
||||
// No timeline configured for this event
|
||||
return true;
|
||||
}
|
||||
|
||||
_currentMapping = mapping;
|
||||
// _currentTimelineIndex = 0;
|
||||
|
||||
return await PlayTimelineSequence(player, follower);
|
||||
}
|
||||
|
||||
private async Task<bool> PlayTimelineSequence(PlayerTouchController player, FollowerController follower)
|
||||
{
|
||||
if (_currentMapping == null || !_currentMapping.HasValidTimelines)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
follower.DropHeldItemAt(follower.transform.position);
|
||||
|
||||
// Play the current timeline in the sequence
|
||||
bool result = await PlaySingleTimeline(_currentMapping.timelines[_currentTimelineIndex], _currentMapping, player, follower);
|
||||
|
||||
// Return false if the playback failed
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Increment the timeline index for next playback
|
||||
_currentTimelineIndex++;
|
||||
|
||||
// Check if we've reached the end of the sequence
|
||||
if (_currentTimelineIndex >= _currentMapping.timelines.Length)
|
||||
{
|
||||
// If loop all is enabled, start over
|
||||
if (_currentMapping.loopAll)
|
||||
{
|
||||
_currentTimelineIndex = 0;
|
||||
// Don't continue automatically, wait for next interaction
|
||||
return true;
|
||||
}
|
||||
// If loop last is enabled, replay the last timeline
|
||||
else if (_currentMapping.loopLast)
|
||||
{
|
||||
_currentTimelineIndex = _currentMapping.timelines.Length - 1;
|
||||
// Don't continue automatically, wait for next interaction
|
||||
return true;
|
||||
}
|
||||
// Otherwise, we're done with the sequence
|
||||
else
|
||||
{
|
||||
_currentTimelineIndex = 0;
|
||||
_currentMapping = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have more timelines in the sequence, we're done for now
|
||||
// Next interaction will pick up where we left off
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> PlaySingleTimeline(PlayableAsset timelineAsset, TimelineEventMapping mapping,
|
||||
PlayerTouchController player, FollowerController follower)
|
||||
{
|
||||
if (timelineAsset == null)
|
||||
{
|
||||
Debug.LogWarning("[InteractionTimelineAction] Timeline asset is null");
|
||||
return true; // Return true to continue the interaction flow
|
||||
}
|
||||
|
||||
// Set the timeline asset
|
||||
playableDirector.playableAsset = timelineAsset;
|
||||
|
||||
// Bind characters if needed
|
||||
if (mapping.bindPlayerCharacter && player != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var trackOutput = playableDirector.playableAsset.outputs.FirstOrDefault(o => o.streamName == mapping.playerTrackName);
|
||||
if (trackOutput.sourceObject != null)
|
||||
{
|
||||
playableDirector.SetGenericBinding(trackOutput.sourceObject, player.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[InteractionTimelineAction] Could not find track named '{mapping.playerTrackName}' for player binding");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[InteractionTimelineAction] Error binding player to timeline: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.bindPulverCharacter && follower != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var trackOutput = playableDirector.playableAsset.outputs.FirstOrDefault(o => o.streamName == mapping.pulverTrackName);
|
||||
if (trackOutput.sourceObject != null)
|
||||
{
|
||||
playableDirector.SetGenericBinding(trackOutput.sourceObject, follower.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[InteractionTimelineAction] Could not find track named '{mapping.pulverTrackName}' for follower binding");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[InteractionTimelineAction] Error binding follower to timeline: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Create a task completion source to await the timeline completion
|
||||
_currentPlaybackTCS = new TaskCompletionSource<bool>();
|
||||
|
||||
// Register for the stopped event if not already registered
|
||||
playableDirector.stopped -= OnPlayableDirectorStopped;
|
||||
playableDirector.stopped += OnPlayableDirectorStopped;
|
||||
|
||||
// Log the timeline playback
|
||||
Debug.Log($"[InteractionTimelineAction] Playing timeline {timelineAsset.name} for event {mapping.eventType}");
|
||||
|
||||
// Play the timeline
|
||||
playableDirector.Play();
|
||||
|
||||
// Start a timeout coroutine for safety using the mapping's timeout
|
||||
StartCoroutine(TimeoutCoroutine(mapping.timeoutSeconds));
|
||||
|
||||
// Await the timeline completion (will be signaled by the OnPlayableDirectorStopped callback)
|
||||
bool result = await _currentPlaybackTCS.Task;
|
||||
|
||||
// Log completion
|
||||
Debug.Log($"[InteractionTimelineAction] Timeline {timelineAsset.name} playback completed with result: {result}");
|
||||
|
||||
// Clear the task completion source
|
||||
_currentPlaybackTCS = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnPlayableDirectorStopped(PlayableDirector director)
|
||||
{
|
||||
if (director != playableDirector || _currentPlaybackTCS == null)
|
||||
return;
|
||||
|
||||
Debug.Log($"[InteractionTimelineAction] PlayableDirector stopped. Signaling completion.");
|
||||
|
||||
// Signal completion when the director stops
|
||||
_currentPlaybackTCS.TrySetResult(true);
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator TimeoutCoroutine(float timeoutDuration)
|
||||
{
|
||||
yield return new WaitForSeconds(timeoutDuration);
|
||||
|
||||
// If the TCS still exists after timeout, complete it with failure
|
||||
if (_currentPlaybackTCS != null)
|
||||
{
|
||||
Debug.LogWarning($"[InteractionTimelineAction] Timeline playback timed out after {timeoutDuration} seconds");
|
||||
_currentPlaybackTCS.TrySetResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42e77a0c97604b6eb7674e58726c831a
|
||||
timeCreated: 1759746720
|
||||
@@ -268,25 +268,22 @@ public class FollowerController: MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// Command follower to go to a specific point (pathfinding mode)
|
||||
/// <summary>
|
||||
/// Command follower to go to a specific point (pathfinding mode).
|
||||
/// Make the follower move to a specific point only. Will not automatically return.
|
||||
/// </summary>
|
||||
/// <param name="worldPosition">The world position to move to.</param>
|
||||
public void GoToPoint(Vector2 worldPosition)
|
||||
/// <param name="targetPosition">The position to move to.</param>
|
||||
public void GoToPoint(Vector2 targetPosition)
|
||||
{
|
||||
_isManualFollowing = false;
|
||||
if (_pickupCoroutine != null)
|
||||
StopCoroutine(_pickupCoroutine);
|
||||
if (_aiPath != null)
|
||||
{
|
||||
_aiPath.enabled = true;
|
||||
_aiPath.maxSpeed = _followerMaxSpeed;
|
||||
_aiPath.destination = new Vector3(worldPosition.x, worldPosition.y, 0);
|
||||
}
|
||||
_pickupCoroutine = StartCoroutine(GoToPointSequence(targetPosition));
|
||||
}
|
||||
|
||||
// Command follower to go to a specific point and return to player
|
||||
/// <summary>
|
||||
/// Command follower to go to a specific point and return to player.
|
||||
/// Command follower to go to a specific point and return to player after a brief delay.
|
||||
/// Legacy method that combines GoToPoint and ReturnToPlayer for backward compatibility.
|
||||
/// </summary>
|
||||
/// <param name="itemPosition">The position of the item to pick up.</param>
|
||||
/// <param name="playerTransform">The transform of the player.</param>
|
||||
@@ -299,6 +296,19 @@ public class FollowerController: MonoBehaviour
|
||||
_pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make the follower return to the player after it has reached a point.
|
||||
/// </summary>
|
||||
/// <param name="playerTransform">The transform of the player to return to.</param>
|
||||
public void ReturnToPlayer(Transform playerTransform)
|
||||
{
|
||||
if (_pickupCoroutine != null)
|
||||
StopCoroutine(_pickupCoroutine);
|
||||
if (_aiPath != null)
|
||||
_aiPath.maxSpeed = _followerMaxSpeed;
|
||||
_pickupCoroutine = StartCoroutine(ReturnToPlayerSequence(playerTransform));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform)
|
||||
{
|
||||
_isManualFollowing = false;
|
||||
@@ -340,6 +350,63 @@ public class FollowerController: MonoBehaviour
|
||||
_aiPath.enabled = false;
|
||||
_pickupCoroutine = null;
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator GoToPointSequence(Vector2 targetPosition)
|
||||
{
|
||||
_isManualFollowing = false;
|
||||
_isReturningToPlayer = false;
|
||||
|
||||
if (_aiPath != null)
|
||||
{
|
||||
_aiPath.enabled = true;
|
||||
_aiPath.maxSpeed = _followerMaxSpeed;
|
||||
_aiPath.destination = new Vector3(targetPosition.x, targetPosition.y, 0);
|
||||
}
|
||||
|
||||
// Wait until follower reaches target
|
||||
while (Vector2.Distance(new Vector2(transform.position.x, transform.position.y),
|
||||
new Vector2(targetPosition.x, targetPosition.y)) > GameManager.Instance.StopThreshold)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Signal arrival
|
||||
OnPickupArrived?.Invoke();
|
||||
|
||||
_pickupCoroutine = null;
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ReturnToPlayerSequence(Transform playerTransform)
|
||||
{
|
||||
if (_aiPath != null && playerTransform != null)
|
||||
{
|
||||
_aiPath.maxSpeed = _followerMaxSpeed;
|
||||
_aiPath.destination = playerTransform.position;
|
||||
}
|
||||
|
||||
_isReturningToPlayer = true;
|
||||
|
||||
// Wait until follower returns to player
|
||||
while (playerTransform != null &&
|
||||
Vector2.Distance(new Vector2(transform.position.x, transform.position.y),
|
||||
new Vector2(playerTransform.position.x, playerTransform.position.y)) > GameManager.Instance.StopThreshold)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_isReturningToPlayer = false;
|
||||
OnPickupReturned?.Invoke();
|
||||
|
||||
// Reset follower speed to normal after pickup
|
||||
_followerMaxSpeed = _defaultFollowerMaxSpeed;
|
||||
if (_aiPath != null)
|
||||
_aiPath.maxSpeed = _followerMaxSpeed;
|
||||
_isManualFollowing = true;
|
||||
if (_aiPath != null)
|
||||
_aiPath.enabled = false;
|
||||
|
||||
_pickupCoroutine = null;
|
||||
}
|
||||
#endregion Movement
|
||||
|
||||
#region ItemInteractions
|
||||
|
||||
@@ -198,16 +198,36 @@ namespace PuzzleS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks all initial steps (those with no dependencies).
|
||||
/// Unlocks all initial steps (those with no dependencies) and any steps whose dependencies are already met.
|
||||
/// </summary>
|
||||
private void UnlockInitialSteps()
|
||||
{
|
||||
// First, unlock all steps with no dependencies (initial steps)
|
||||
var initialSteps = PuzzleGraphUtility.FindInitialSteps(_runtimeDependencies);
|
||||
foreach (var step in initialSteps)
|
||||
{
|
||||
Debug.Log($"[Puzzles] Initial step unlocked: {step.stepId}");
|
||||
UnlockStep(step);
|
||||
}
|
||||
|
||||
// Keep trying to unlock steps as long as we're making progress
|
||||
bool madeProgress;
|
||||
do
|
||||
{
|
||||
madeProgress = false;
|
||||
|
||||
// Check all steps that haven't been unlocked yet
|
||||
foreach (var step in _runtimeDependencies.Keys.Where(s => !_unlockedSteps.Contains(s)))
|
||||
{
|
||||
// Check if all dependencies have been completed
|
||||
if (AreRuntimeDependenciesMet(step))
|
||||
{
|
||||
Debug.Log($"[Puzzles] Chain step unlocked: {step.stepId}");
|
||||
UnlockStep(step);
|
||||
madeProgress = true;
|
||||
}
|
||||
}
|
||||
} while (madeProgress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
257
README.md
@@ -1,211 +1,92 @@
|
||||
# Apple Hills Dialogue System
|
||||
# Apple Hills
|
||||
|
||||
This document provides an overview of the dialogue system used in Apple Hills, intended primarily for designers working with the dialogue creation tools.
|
||||
Apple Hills is a Unity-based adventure game featuring interactive puzzle mechanics, dialogue systems, and item interactions.
|
||||
|
||||
## Overview
|
||||
## Project Overview
|
||||
|
||||
The Apple Hills dialogue system is a node-based dialogue management system that allows for interactive conversations with NPCs. The system currently supports linear, condition-guarded dialogue paths that can respond to various game conditions such as puzzle completion, item pickups, and item combinations. While the architecture was designed to facilitate branching dialogue in future extensions, the current implementation follows a linear progression through nodes.
|
||||
Apple Hills provides a unique gaming experience with:
|
||||
|
||||
## Dialogue Structure
|
||||
- Node-based dialogue system for interactive NPC conversations
|
||||
- Puzzle mechanics with condition-based progression
|
||||
- Item pickup, combination, and interaction systems
|
||||
- Custom Universal Render Pipeline setup
|
||||
|
||||
The dialogue system uses a graph-based structure with different types of nodes that control the flow of conversation. Each dialogue is represented as a graph containing multiple nodes connected through defined paths.
|
||||
## Repository Structure
|
||||
|
||||
### Core Components
|
||||
```
|
||||
AppleHills/
|
||||
├── Assets/ # Unity asset files
|
||||
│ ├── Art/ # Visual assets, models, textures
|
||||
│ ├── Dialogue/ # Dialogue system and dialogue data
|
||||
│ ├── Scripts/ # C# code for game systems
|
||||
│ │ ├── Core/ # Core managers and services
|
||||
│ │ ├── Dialogue/ # Dialogue system implementation
|
||||
│ │ ├── Input/ # Input handling systems
|
||||
│ │ ├── Interactions/ # Object interaction systems
|
||||
│ │ ├── Movement/ # Character movement controllers
|
||||
│ │ ├── PuzzleS/ # Puzzle mechanics and systems
|
||||
│ │ └── UI/ # User interface components
|
||||
│ ├── Scenes/ # Game scenes and levels
|
||||
│ ├── Prefabs/ # Reusable game objects
|
||||
│ └── ... # Other asset folders
|
||||
├── Packages/ # Unity package dependencies
|
||||
├── ProjectSettings/ # Unity project settings
|
||||
└── docs/ # Project documentation
|
||||
├── media/ # Images and other media for documentation
|
||||
└── dialogue_readme.md # Detailed documentation about the dialogue system
|
||||
```
|
||||
|
||||
1. **RuntimeDialogueGraph**: The container for all dialogue nodes that make up a conversation
|
||||
2. **DialogueComponent**: Attached to game objects to manage dialogue state and progression
|
||||
3. **SpeechBubble**: Handles the visual presentation of dialogue text
|
||||
## Code Structure
|
||||
|
||||
## QuickStart Guide
|
||||
### Scripts Organization
|
||||
|
||||
Setting up a dialogue interaction in your scene is straightforward:
|
||||
The game's codebase is organized into several key modules:
|
||||
|
||||
### 1. Component Setup
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| Animation | Animation controllers and state machines |
|
||||
| Bootstrap | Game initialization and scene loading |
|
||||
| Core | Core game managers and services |
|
||||
| Dialogue | Dialogue tree implementation and text handling |
|
||||
| Input | Player input processing and mapping |
|
||||
| Interactions | Interactive object behaviors and triggers |
|
||||
| Movement | Character controllers and navigation |
|
||||
| Pooling | Object pooling for performance optimization |
|
||||
| PuzzleS | Puzzle mechanics, conditions, and validators |
|
||||
| Settings | Configurable game parameters and constants |
|
||||
| UI | User interface elements and controllers |
|
||||
| Utilities | Helper classes and extension methods |
|
||||
|
||||
1. **Place the Dialogue Component**:
|
||||
- Add the `DialogueComponent` to any game object that has an `Interactable` component
|
||||
- The `Interactable` component handles player proximity and interaction triggers
|
||||
- Make sure the interactable is properly configured with appropriate interaction radius
|
||||
### Core Systems Highlight
|
||||
|
||||
2. **Add DialogueCanvas**:
|
||||
- Add the "DialogueCanvas" prefab from the project assets as a child of your object
|
||||
- Position the speech bubble appropriately above or near the interactable object
|
||||
- The speech bubble should be clearly visible but not obstruct important scene elements
|
||||
- You can adjust the scale and position to fit your specific character or object
|
||||
The `Assets/Scripts/Core` directory contains critical managers that orchestrate the game's systems:
|
||||
|
||||
3. **Assign Dialogue Graph**:
|
||||
- Create a `RuntimeDialogueGraph` scriptable object (Right Click > Create > Dialogue Graph)
|
||||
- Set up your dialogue nodes in the graph (see Node Types for details)
|
||||
- Assign the created graph to the `DialogueComponent` on your object
|
||||
- Make sure to set the entry node ID in the dialogue graph
|
||||
#### Key Managers
|
||||
|
||||
### 2. Testing Your Dialogue
|
||||
- **GameManager**: Central hub managing game state, scene transitions, and system coordination
|
||||
- **ItemManager**: Handles inventory system, item pickup, combination, and usage logic
|
||||
- **SceneManagerService**: Controls scene loading, unloading, and transitions
|
||||
|
||||
1. Enter play mode and approach the interactable object
|
||||
2. When the component has any lines to serve, the speech bubble should display the prompt ("...")
|
||||
3. Interact with the object to advance through dialogue lines
|
||||
4. Test any conditional nodes by completing their requirements
|
||||
5. Verify that the dialogue progresses as expected
|
||||
#### Settings Framework
|
||||
|
||||
### 3. Common Issues
|
||||
The `Core/Settings` system provides a robust configuration framework:
|
||||
|
||||
- **No speech bubble appears**: Check that the DialogueCanvas is properly added as a child and is active
|
||||
- **Dialogue doesn't advance**: Ensure the node connections (in/out) are properly set in the dialogue graph
|
||||
- **Condition not triggering**: Verify that the condition IDs (puzzle step, item, etc.) match exactly with your game systems
|
||||
- **ServiceLocator**: Dependency injection system for accessing game services
|
||||
- **SettingsProvider**: Central access point for game configuration values
|
||||
- **BaseSettings/BaseDeveloperSettings**: Foundation for creating configurable parameters
|
||||
- **InteractionSettings**: Configuration for player-object interactions
|
||||
- **MovementModeTypes**: Movement parameters for different locomotion modes
|
||||
|
||||
## Node Types
|
||||
## Documentation
|
||||
|
||||
The dialogue system supports several node types, each serving a specific purpose in the conversation flow:
|
||||
Detailed documentation about specific systems can be found in the `docs` folder:
|
||||
|
||||
### 1. Dialogue Node
|
||||
- [Dialogue System Documentation](docs/dialogue_readme.md)
|
||||
|
||||
Simple dialogue nodes display text to the player. They can contain multiple lines that are shown sequentially when the player interacts with the NPC.
|
||||
## Development
|
||||
|
||||
**Key features:**
|
||||
- Multiple dialogue lines displayed in sequence
|
||||
- Optional looping through lines
|
||||
- Automatic progression to the next node when all lines are exhausted
|
||||
The project is structured using standard Unity practices. Key components:
|
||||
|
||||
### 2. WaitOnPuzzleStep Node
|
||||
|
||||
This node pauses dialogue progression until a specific puzzle step has been completed by the player.
|
||||
|
||||
**Key features:**
|
||||
- Automatically advances when the specified puzzle step is completed
|
||||
- Displays dialogue while waiting for the condition to be met
|
||||
- Visual prompt appears when the condition is met, indicating available dialogue
|
||||
|
||||
### 3. WaitOnPickup Node
|
||||
|
||||
This node waits until the player has picked up a specific item before advancing the dialogue.
|
||||
|
||||
**Key features:**
|
||||
- Automatically advances when the player picks up the specified item
|
||||
- Shows dialogue while waiting for the item pickup
|
||||
- Visual prompt appears when the item is picked up, indicating available dialogue
|
||||
|
||||
### 4. WaitOnSlot Node
|
||||
|
||||
This node requires the player to place a specific item in a designated slot before the dialogue can progress.
|
||||
|
||||
**Key features:**
|
||||
- Supports different dialogue lines for different slot states:
|
||||
- Default lines when no item is slotted
|
||||
- Incorrect item lines when the wrong item is placed
|
||||
- Forbidden item lines when a specifically disallowed item is placed
|
||||
- Visual prompt appears when the correct item is slotted, indicating available dialogue
|
||||
|
||||
### 5. WaitOnCombination Node
|
||||
|
||||
This node waits for the player to create a specific item through the combination system.
|
||||
|
||||
**Key features:**
|
||||
- Automatically advances when the player creates the specified item through combination
|
||||
- Shows dialogue while waiting for the item combination
|
||||
- Visual prompt appears when the item is created, indicating available dialogue
|
||||
|
||||
### 6. End Node
|
||||
|
||||
Terminates the dialogue sequence.
|
||||
|
||||
**Key features:**
|
||||
- Marks the dialogue as completed
|
||||
- No further interaction available until the dialogue is restarted
|
||||
|
||||
## Dialogue Flow
|
||||
|
||||
1. **Dialogue Initialization**
|
||||
- When a dialogue is started (often through character interaction), the system begins at the entry node
|
||||
- Each node's dialogue lines are displayed in sequence as the player interacts
|
||||
|
||||
2. **Interaction Mechanism**
|
||||
- Dialogue advances when the player interacts with the NPC
|
||||
- Each interaction displays the next line of dialogue
|
||||
- When all lines in a node are displayed, the system moves to the next node (unless waiting on a condition)
|
||||
|
||||
3. **Conditional Progress**
|
||||
- When the dialogue reaches a conditional node (like WaitOnPuzzleStep), it waits for the condition to be met
|
||||
- Once the condition is satisfied, the speech bubble shows a prompt
|
||||
- The next interaction after the condition is met advances to the next node
|
||||
|
||||
4. **Visual Indicators**
|
||||
- Speech bubbles show ellipses ("...") as a prompt when dialogue is available
|
||||
- The dialogue text can be displayed instantly or with a typewriter effect
|
||||
- The speech bubble hides when no dialogue is available
|
||||
|
||||
## Designer Tips
|
||||
|
||||
1. **Node Organization**
|
||||
- Start every dialogue graph with a standard Dialogue node as the entry point
|
||||
- End every dialogue path with an End node to properly terminate the conversation
|
||||
- Use conditional nodes strategically to create gameplay-driven dialogue experiences
|
||||
|
||||
2. **Dialogue Writing**
|
||||
- Keep individual dialogue lines concise for better readability
|
||||
- Consider using the looping option for nodes when you want to repeat information
|
||||
- For WaitOnSlot nodes, write unique dialogue for incorrect/forbidden items to provide clear feedback
|
||||
|
||||
3. **Flow Control**
|
||||
- Ensure all nodes (except End nodes) have a valid next node specified
|
||||
- Test dialogue paths to verify all conditions can be met during gameplay
|
||||
- Consider using multiple dialogue lines within a single node rather than creating separate nodes for sequential lines
|
||||
|
||||
4. **Best Practices**
|
||||
- Name your nodes descriptively in the editor for easier management
|
||||
- Group related dialogue sequences into separate dialogue graphs
|
||||
- Use the speaker name field to clearly identify who is speaking
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Public Events and APIs
|
||||
|
||||
The dialogue system exposes several events that can be used by other systems:
|
||||
|
||||
#### DialogueComponent
|
||||
|
||||
- **Events**:
|
||||
- `OnDialogueChanged`: Triggered when the dialogue text changes
|
||||
|
||||
- **Properties**:
|
||||
- `IsActive`: Indicates whether the dialogue is currently active
|
||||
- `IsCompleted`: Indicates whether the dialogue has reached an End node
|
||||
- `CurrentSpeakerName`: Returns the name of the current speaker
|
||||
|
||||
- **Public Methods**:
|
||||
- `StartDialogue()`: Initiates the dialogue from the beginning
|
||||
- `GetCurrentDialogueLine()`: Retrieves the current dialogue line text
|
||||
- `HasAnyLines()`: Checks if the dialogue component has any lines available
|
||||
- `SetDialogueGraph(RuntimeDialogueGraph)`: Sets the dialogue graph for the component
|
||||
|
||||
#### SpeechBubble
|
||||
|
||||
- **Public Methods**:
|
||||
- `Show()`: Makes the speech bubble visible
|
||||
- `Hide()`: Hides the speech bubble
|
||||
- `Toggle()`: Toggles the visibility of the speech bubble
|
||||
- `SetText(string)`: Sets the text displayed in the speech bubble
|
||||
- `DisplayDialogueLine(string, bool)`: Displays a dialogue line and handles prompt visibility
|
||||
- `UpdatePromptVisibility(bool)`: Updates the speech bubble to show a prompt or hide based on dialogue availability
|
||||
- `SetDisplayMode(TextDisplayMode)`: Changes how text is displayed (instant or typewriter)
|
||||
- `SkipTypewriter()`: Immediately displays the full text, skipping the typewriter effect
|
||||
- `SetTypewriterSpeed(float)`: Sets the speed of the typewriter effect
|
||||
|
||||
### Integration with Other Systems
|
||||
|
||||
The dialogue system integrates with several other game systems:
|
||||
|
||||
1. **Puzzle System**: Monitors puzzle completion events to advance WaitOnPuzzleStep nodes
|
||||
2. **Item System**: Tracks item pickups, combinations, and slot interactions to advance respective node types
|
||||
3. **Interaction System**: Responds to player interaction with the NPC to progress through dialogue lines
|
||||
|
||||
### Technical Workflow
|
||||
|
||||
1. Create a RuntimeDialogueGraph asset in the Unity editor
|
||||
2. Add nodes and connections using the dialogue editor
|
||||
3. Assign the graph to a DialogueComponent on an NPC GameObject
|
||||
4. Ensure a SpeechBubble component is available (as a child object or referenced)
|
||||
5. Set up any necessary puzzle steps, items, or slots that the dialogue will reference
|
||||
|
||||
## Summary
|
||||
|
||||
The Apple Hills dialogue system provides a powerful and flexible way to create interactive conversations that respond to player actions and game state. By using different node types and conditions, designers can craft engaging dialogues that feel natural and responsive within the game world.
|
||||
- Universal Render Pipeline for consistent visuals
|
||||
- Input System for configurable controls
|
||||
- Addressable Assets for asset management
|
||||
|
||||
277
docs/dialogue_readme.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Apple Hills Dialogue System
|
||||
|
||||
This document provides an overview of the dialogue system used in Apple Hills, intended primarily for designers (Damian) working with the dialogue creation tools.
|
||||
|
||||
## Overview
|
||||
|
||||
The Apple Hills dialogue system is a node-based dialogue management system that allows for interactive conversations with NPCs. The system currently supports linear, condition-guarded dialogue paths that can respond to various game conditions such as puzzle completion, item pickups, and item combinations. While the architecture was designed to facilitate branching dialogue in future extensions, the current implementation follows a linear progression through nodes.
|
||||
|
||||
## Dialogue Structure
|
||||
|
||||
The dialogue system uses a graph-based structure with different types of nodes that control the flow of conversation. Each dialogue is represented as a graph containing multiple nodes connected through defined paths.
|
||||
|
||||

|
||||
|
||||
### Core Components
|
||||
|
||||
1. **RuntimeDialogueGraph**: The container for all dialogue nodes that make up a conversation
|
||||
- Defined in `Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs`
|
||||
- Contains the entry point node ID and a list of all dialogue nodes
|
||||
- Holds the speaker name that appears in dialogue UI
|
||||
|
||||
2. **DialogueComponent**: Attached to game objects to manage dialogue state and progression
|
||||
- Defined in `Assets/Scripts/Dialogue/DialogueComponent.cs`
|
||||
- Manages the current state of dialogue (active node, line index)
|
||||
- Responds to game events like puzzle completion, item pickup, etc.
|
||||
- Controls dialogue advancement through player interaction
|
||||
|
||||
3. **SpeechBubble**: Handles the visual presentation of dialogue text
|
||||
- Defined in `Assets/Scripts/Dialogue/SpeechBubble.cs`
|
||||
- Manages the dialogue UI elements and text display
|
||||
- Implements typewriter effects and visual prompts
|
||||
|
||||
## QuickStart Guide
|
||||
|
||||
Setting up a dialogue interaction in your scene is straightforward:
|
||||
|
||||
### 1. Component Setup
|
||||
|
||||
1. **Place the Dialogue Component**:
|
||||
- Add the `DialogueComponent` to any game object that has an `Interactable` component
|
||||
- The `Interactable` component handles player proximity and interaction triggers
|
||||
- Make sure the interactable is properly configured with appropriate interaction radius
|
||||
|
||||

|
||||
|
||||
2. **Add DialogueCanvas**:
|
||||
- Add the "DialogueCanvas" prefab from the project assets as a child of your object
|
||||
- Position the speech bubble appropriately above or near the interactable object
|
||||
- The speech bubble should be clearly visible but not obstruct important scene elements
|
||||
- You can adjust the scale and position to fit your specific character or object
|
||||
|
||||

|
||||
|
||||
3. **Assign Dialogue Graph**:
|
||||
- Create a `RuntimeDialogueGraph` scriptable object (Right Click > Create > Dialogue Graph)
|
||||
- Set up your dialogue nodes in the graph (see Node Types for details)
|
||||
- Assign the created graph to the `DialogueComponent` on your object
|
||||
- Make sure to set the entry node ID in the dialogue graph
|
||||
|
||||

|
||||
|
||||
### 2. Testing Your Dialogue
|
||||
|
||||
1. Enter play mode and approach the interactable object
|
||||
2. When the component has any lines to serve, the speech bubble should display the prompt ("...")
|
||||
3. Interact with the object to advance through dialogue lines
|
||||
4. Test any conditional nodes by completing their requirements
|
||||
5. Verify that the dialogue progresses as expected
|
||||
|
||||

|
||||
|
||||
### 3. Common Issues
|
||||
|
||||
- **No speech bubble appears**: Check that the DialogueCanvas is properly added as a child and is active
|
||||
- **Dialogue doesn't advance**: Ensure the node connections (in/out) are properly set in the dialogue graph
|
||||
- **Condition not triggering**: Verify that the condition IDs (puzzle step, item, etc.) match exactly with your game systems
|
||||
|
||||
## Node Types
|
||||
|
||||
The dialogue system supports several node types, each serving a specific purpose in the conversation flow:
|
||||
|
||||
### 1. Dialogue Node
|
||||
|
||||
Simple dialogue nodes display text to the player. They can contain multiple lines that are shown sequentially when the player interacts with the NPC.
|
||||
|
||||
**Key features:**
|
||||
- Multiple dialogue lines displayed in sequence
|
||||
- Optional looping through lines
|
||||
- Automatic progression to the next node when all lines are exhausted
|
||||
|
||||

|
||||
|
||||
**Implementation details:**
|
||||
- Defined as `RuntimeDialogueNodeType.Dialogue` in `RuntimeDialogueGraph.cs`
|
||||
- Uses `dialogueLines` list to store sequential lines of text
|
||||
- The `loopThroughLines` boolean controls whether the dialogue returns to the first line after reaching the end
|
||||
|
||||
### 2. WaitOnPuzzleStep Node
|
||||
|
||||
This node pauses dialogue progression until a specific puzzle step has been completed by the player.
|
||||
|
||||
**Key features:**
|
||||
- Automatically advances when the specified puzzle step is completed
|
||||
- Displays dialogue while waiting for the condition to be met
|
||||
- Visual prompt appears when the condition is met, indicating available dialogue
|
||||
|
||||

|
||||
|
||||
**Implementation details:**
|
||||
- Defined as `RuntimeDialogueNodeType.WaitOnPuzzleStep` in `RuntimeDialogueGraph.cs`
|
||||
- Links to `PuzzleManager.OnStepCompleted` event through the `puzzleStepID` property
|
||||
- The `DialogueComponent` listens for puzzle completion events through `OnAnyPuzzleStepCompleted` method
|
||||
|
||||
### 3. WaitOnPickup Node
|
||||
|
||||
This node waits until the player has picked up a specific item before advancing the dialogue.
|
||||
|
||||
**Key features:**
|
||||
- Automatically advances when the player picks up the specified item
|
||||
- Shows dialogue while waiting for the item pickup
|
||||
- Visual prompt appears when the item is picked up, indicating available dialogue
|
||||
|
||||

|
||||
|
||||
**Implementation details:**
|
||||
- Defined as `RuntimeDialogueNodeType.WaitOnPickup` in `RuntimeDialogueGraph.cs`
|
||||
- Links to `ItemManager.OnItemPickedUp` event through the `pickupItemID` property
|
||||
- The `DialogueComponent` listens for item pickup events through `OnAnyItemPickedUp` method
|
||||
|
||||
### 4. WaitOnSlot Node
|
||||
|
||||
This node requires the player to place a specific item in a designated slot before the dialogue can progress.
|
||||
|
||||
**Key features:**
|
||||
- Supports different dialogue lines for different slot states:
|
||||
- Default lines when no item is slotted
|
||||
- Incorrect item lines when the wrong item is placed
|
||||
- Forbidden item lines when a specifically disallowed item is placed
|
||||
- Visual prompt appears when the correct item is slotted, indicating available dialogue
|
||||
|
||||

|
||||
|
||||
**Implementation details:**
|
||||
- Defined as `RuntimeDialogueNodeType.WaitOnSlot` in `RuntimeDialogueGraph.cs`
|
||||
- Uses multiple events from `ItemManager` including:
|
||||
- `OnCorrectItemSlotted` - Triggered when the matching `slotItemID` is placed
|
||||
- `OnIncorrectItemSlotted` - For displaying incorrect item dialogue
|
||||
- `OnForbiddenItemSlotted` - For displaying forbidden item dialogue
|
||||
- `OnItemSlotCleared` - For resetting to default dialogue
|
||||
|
||||
### 5. WaitOnCombination Node
|
||||
|
||||
This node waits for the player to create a specific item through the combination system.
|
||||
|
||||
**Key features:**
|
||||
- Automatically advances when the player creates the specified item through combination
|
||||
- Shows dialogue while waiting for the item combination
|
||||
- Visual prompt appears when the item is created, indicating available dialogue
|
||||
|
||||

|
||||
|
||||
**Implementation details:**
|
||||
- Defined as `RuntimeDialogueNodeType.WaitOnCombination` in `RuntimeDialogueGraph.cs`
|
||||
- Links to `ItemManager.OnItemsCombined` event through the `combinationResultItemID` property
|
||||
- The `DialogueComponent` listens for item combination events through `OnAnyItemsCombined` method
|
||||
|
||||
### 6. End Node
|
||||
|
||||
Terminates the dialogue sequence.
|
||||
|
||||
**Key features:**
|
||||
- Marks the dialogue as completed
|
||||
- No further interaction available until the dialogue is restarted
|
||||
|
||||

|
||||
|
||||
**Implementation details:**
|
||||
- Defined as `RuntimeDialogueNodeType.End` in `RuntimeDialogueGraph.cs`
|
||||
- When reached, sets the `IsCompleted` flag on the `DialogueComponent`
|
||||
- No next node connection is required for this node type
|
||||
|
||||
## Dialogue Editor
|
||||
|
||||
The dialogue editor is a custom Unity tool that allows for visual creation and editing of dialogue graphs.
|
||||
|
||||

|
||||
|
||||
### Key Editor Features
|
||||
|
||||
- **Visual Node Editing**: Add and connect nodes in a visual graph
|
||||
- **Node Type Selection**: Choose from the six supported node types
|
||||
- **Dialogue Text Entry**: Add multiple lines of dialogue for each node
|
||||
- **Condition Setup**: Specify condition IDs for conditional nodes
|
||||
- **Node Connections**: Create the flow between dialogue nodes
|
||||
|
||||
### Editor Workflow
|
||||
|
||||
1. **Create New Graph**: Right-click in Project view and select Create > Dialogue Graph
|
||||
2. **Open Editor**: Double-click the created asset to open the dialogue editor
|
||||
3. **Add Nodes**: Right-click in the editor and select Add Node > [Node Type]
|
||||
4. **Configure Nodes**: Enter dialogue text and set conditions as needed
|
||||
5. **Connect Nodes**: Drag from output ports to input ports to create connections
|
||||
6. **Set Entry Node**: Mark one node as the entry point for the dialogue
|
||||
7. **Save**: Save your dialogue graph when finished
|
||||
|
||||
## Designer Tips
|
||||
|
||||
1. **Node Organization**
|
||||
- Start every dialogue graph with a standard Dialogue node as the entry point
|
||||
- End every dialogue path with an End node to properly terminate the conversation
|
||||
- Use conditional nodes strategically to create gameplay-driven dialogue experiences
|
||||
|
||||
2. **Dialogue Writing**
|
||||
- Keep individual dialogue lines concise for better readability
|
||||
- Consider using the looping option for nodes when you want to repeat information
|
||||
- For WaitOnSlot nodes, write unique dialogue for incorrect/forbidden items to provide clear feedback
|
||||
|
||||
3. **Flow Control**
|
||||
- Ensure all nodes (except End nodes) have a valid next node specified
|
||||
- Test dialogue paths to verify all conditions can be met during gameplay
|
||||
- Consider using multiple dialogue lines within a single node rather than creating separate nodes for sequential lines
|
||||
|
||||
4. **Best Practices**
|
||||
- Name your nodes descriptively in the editor for easier management
|
||||
- Group related dialogue sequences into separate dialogue graphs
|
||||
- Use the speaker name field to clearly identify who is speaking
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Public Events and APIs
|
||||
|
||||
The dialogue system exposes several events that can be used by other systems:
|
||||
|
||||
#### DialogueComponent
|
||||
|
||||
- **Events**:
|
||||
- `OnDialogueChanged`: Triggered when the dialogue text changes
|
||||
|
||||
- **Properties**:
|
||||
- `IsActive`: Indicates whether the dialogue is currently active
|
||||
- `IsCompleted`: Indicates whether the dialogue has reached an End node
|
||||
- `CurrentSpeakerName`: Returns the name of the current speaker
|
||||
|
||||
- **Public Methods**:
|
||||
- `StartDialogue()`: Initiates the dialogue from the beginning
|
||||
- `GetCurrentDialogueLine()`: Retrieves the current dialogue line text
|
||||
- `HasAnyLines()`: Checks if the dialogue component has any lines available
|
||||
- `SetDialogueGraph(RuntimeDialogueGraph)`: Sets the dialogue graph for the component
|
||||
|
||||
#### SpeechBubble
|
||||
|
||||
- **Public Methods**:
|
||||
- `Show()`: Makes the speech bubble visible
|
||||
- `Hide()`: Hides the speech bubble
|
||||
- `Toggle()`: Toggles the visibility of the speech bubble
|
||||
- `SetText(string)`: Sets the text displayed in the speech bubble
|
||||
- `DisplayDialogueLine(string, bool)`: Displays a dialogue line and handles prompt visibility
|
||||
- `UpdatePromptVisibility(bool)`: Updates the speech bubble to show a prompt or hide based on dialogue availability
|
||||
- `SetDisplayMode(TextDisplayMode)`: Changes how text is displayed (instant or typewriter)
|
||||
- `SkipTypewriter()`: Immediately displays the full text, skipping the typewriter effect
|
||||
- `SetTypewriterSpeed(float)`: Sets the speed of the typewriter effect
|
||||
|
||||
### Integration with Other Systems
|
||||
|
||||
The dialogue system integrates with several other game systems:
|
||||
|
||||
1. **Puzzle System**: Monitors puzzle completion events to advance WaitOnPuzzleStep nodes
|
||||
2. **Item System**: Tracks item pickups, combinations, and slot interactions to advance respective node types
|
||||
3. **Interaction System**: Responds to player interaction with the NPC to progress through dialogue lines
|
||||
|
||||
### Technical Workflow
|
||||
|
||||
1. Create a RuntimeDialogueGraph asset in the Unity editor
|
||||
2. Add nodes and connections using the dialogue editor
|
||||
3. Assign the graph to a DialogueComponent on an NPC GameObject
|
||||
4. Ensure a SpeechBubble component is available (as a child object or referenced)
|
||||
5. Set up any necessary puzzle steps, items, or slots that the dialogue will reference
|
||||
275
docs/interactables_readme.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Apple Hills Interaction System
|
||||
|
||||
This document provides a comprehensive overview of the interaction system in Apple Hills, detailing how interactions are structured, configured, and extended with custom actions.
|
||||
|
||||
## Overview
|
||||
|
||||
The Apple Hills interaction system allows players to interact with objects in the game world. It supports character movement to interaction points, timed and conditional interactions, and complex behaviors through a component-based architecture. The system is particularly powerful when combined with the Timeline feature for creating cinematic sequences during interactions.
|
||||
|
||||
## Core Components
|
||||
|
||||
The interaction system consists of several key components that work together:
|
||||
|
||||
### Interactable
|
||||
|
||||
The `Interactable` component is the foundation of the interaction system. It:
|
||||
- Handles player input (tapping/clicking)
|
||||
- Manages which character(s) should interact (Trafalgar, Pulver, or both)
|
||||
- Coordinates character movement to interaction points
|
||||
- Dispatches events during the interaction lifecycle
|
||||
- Manages one-time interactions and cooldowns
|
||||
|
||||

|
||||
|
||||
### CharacterMoveToTarget
|
||||
|
||||
The `CharacterMoveToTarget` component defines positions where characters should move when interacting:
|
||||
- Can be configured for specific characters (Trafalgar, Pulver, or both)
|
||||
- Supports position offsets for fine-tuning
|
||||
- Provides visual gizmos in the editor for easy positioning
|
||||
- Multiple targets can be set up for complex interactions
|
||||
|
||||

|
||||
|
||||
### Interaction Actions
|
||||
|
||||
Actions are components that respond to interaction events and execute custom behavior:
|
||||
- Derive from the abstract `InteractionActionBase` class
|
||||
- Can be attached to interactable objects
|
||||
- Multiple actions can be added to a single interactable
|
||||
- Actions can optionally block the interaction flow until completion
|
||||
|
||||
The inspector for all interaction action components shows the key parameters from InteractionActionBase, with custom configuration options for specific action types:
|
||||
|
||||

|
||||
|
||||
### Interaction Requirements
|
||||
|
||||
Requirements are components that determine whether an interaction can occur:
|
||||
- Derive from the abstract `InteractionRequirementBase` class
|
||||
- Can prevent interactions based on custom conditions
|
||||
- Multiple requirements can be added to a single interactable
|
||||
- Used for creating conditional interactions (e.g., requiring an item)
|
||||
|
||||
## Interaction Event Flow
|
||||
|
||||
Interactions follow a defined event flow:
|
||||
|
||||
1. **InteractionStarted**: Triggered when the player initiates an interaction
|
||||
2. **PlayerArrived**: Triggered when the player character reaches the interaction point
|
||||
3. **InteractingCharacterArrived**: Triggered when the interacting character (often Pulver) reaches the interaction point
|
||||
4. **InteractionComplete**: Triggered when the interaction is completed
|
||||
5. **InteractionInterrupted**: Triggered if the interaction is interrupted before completion
|
||||
|
||||
## InteractionActionBase
|
||||
|
||||
The `InteractionActionBase` is the abstract base class for all interaction actions. It provides the framework for creating custom behaviors that respond to interaction events.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Event Filtering**: Actions can choose which interaction events to respond to
|
||||
- **Flow Control**: Actions can optionally pause the interaction flow until completion
|
||||
- **Asynchronous Execution**: Actions use `async/await` pattern for time-consuming operations
|
||||
- **Character References**: Actions receive references to both player and follower characters
|
||||
|
||||
### Implementation
|
||||
|
||||
```csharp
|
||||
public abstract class InteractionActionBase : MonoBehaviour
|
||||
{
|
||||
// Which events this action should respond to
|
||||
public List<InteractionEventType> respondToEvents;
|
||||
|
||||
// Whether to pause the interaction flow during execution
|
||||
public bool pauseInteractionFlow;
|
||||
|
||||
// The main execution method that must be implemented by derived classes
|
||||
protected abstract Task<bool> ExecuteAsync(
|
||||
InteractionEventType eventType,
|
||||
PlayerTouchController player,
|
||||
FollowerController follower);
|
||||
|
||||
// Optional method for adding execution conditions
|
||||
protected virtual bool ShouldExecute(
|
||||
InteractionEventType eventType,
|
||||
PlayerTouchController player,
|
||||
FollowerController follower)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## InteractionTimelineAction
|
||||
|
||||
The `InteractionTimelineAction` is a powerful action that plays Unity Timeline sequences during interactions. It enables cinematic sequences, character animations, camera movements, and more.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Multiple Timeline Support**: Can play different timelines for different interaction events
|
||||
- **Timeline Sequences**: Can play multiple timelines in sequence for a single event
|
||||
- **Character Binding**: Automatically binds player and follower characters to timeline tracks
|
||||
- **Flow Control**: Waits for timeline completion before continuing interaction flow
|
||||
- **Timeout Safety**: Includes a configurable timeout to prevent interactions from getting stuck
|
||||
- **Looping Options**: Supports looping all timelines or just the last timeline in a sequence
|
||||
|
||||
### Timeline Event Mapping
|
||||
|
||||
Each mapping connects an interaction event to one or more timeline assets:
|
||||
|
||||
```csharp
|
||||
public class TimelineEventMapping
|
||||
{
|
||||
// The event that triggers this timeline
|
||||
public InteractionEventType eventType;
|
||||
|
||||
// The timeline assets to play (in sequence)
|
||||
public PlayableAsset[] timelines;
|
||||
|
||||
// Character binding options
|
||||
public bool bindPlayerCharacter;
|
||||
public bool bindPulverCharacter;
|
||||
public string playerTrackName = "Player";
|
||||
public string pulverTrackName = "Pulver";
|
||||
|
||||
// Playback options
|
||||
public float timeoutSeconds = 30f;
|
||||
public bool loopLast = false;
|
||||
public bool loopAll = false;
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Editor
|
||||
|
||||
The `InteractionTimelineAction` includes a custom editor that makes it easy to configure:
|
||||
- Quick buttons to add mappings for common events
|
||||
- Character binding options
|
||||
- Timeline sequence configuration
|
||||
- Validation warnings for misconfigured timelines
|
||||
|
||||

|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
For a cinematic interaction with timelines:
|
||||
|
||||
1. Add an `Interactable` component to your object
|
||||
2. Add an `InteractionTimelineAction` component to the same object
|
||||
3. Set up character move targets if needed
|
||||
4. Create timeline assets for each interaction phase
|
||||
5. Configure the timeline mappings in the inspector
|
||||
6. Test the interaction in play mode
|
||||
|
||||
### Timeline Configuration
|
||||
|
||||
When setting up a timeline for interaction, you'll need to create a Timeline asset and configure it in Unity's Timeline editor:
|
||||
|
||||

|
||||
|
||||
## Working with the Interactable Editor
|
||||
|
||||
The `Interactable` component includes a custom editor that enhances the workflow:
|
||||
|
||||
### Character Move Target Creation
|
||||
|
||||
The editor provides buttons to easily create character move targets:
|
||||
- "Add Trafalgar Target" - Creates a move target for the player character
|
||||
- "Add Pulver Target" - Creates a move target for the follower character
|
||||
- "Add Both Characters Target" - Creates a move target for both characters
|
||||
|
||||

|
||||
|
||||
### Target Visualization
|
||||
|
||||
The editor displays the number of targets for each character type and warns about potential conflicts:
|
||||
```
|
||||
Trafalgar Targets: 1, Pulver Targets: 1, Both Targets: 0
|
||||
```
|
||||
|
||||
If multiple conflicting targets are detected, a warning is displayed to help prevent unexpected behavior.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Target Positioning
|
||||
|
||||
- Place character targets with appropriate spacing to prevent characters from overlapping
|
||||
- Consider the character's facing direction (targets automatically make characters face the interactable)
|
||||
- Use the position offset for fine-tuning without moving the actual target GameObject
|
||||
|
||||

|
||||
|
||||
### Timeline Design
|
||||
|
||||
- Keep timelines modular and focused on specific actions
|
||||
- Use signals to trigger game events from timelines
|
||||
- Consider using director notification tracks for advanced timeline integration
|
||||
- Test timelines with actual characters to ensure animations blend correctly
|
||||
|
||||
### Action Combinations
|
||||
|
||||
- Combine multiple actions for complex behaviors (e.g., dialogue + timeline)
|
||||
- Order actions in the Inspector to control execution priority
|
||||
- Use the `pauseInteractionFlow` option strategically to control sequence flow
|
||||
|
||||
## Technical Reference
|
||||
|
||||
### InteractionEventType
|
||||
|
||||
```csharp
|
||||
public enum InteractionEventType
|
||||
{
|
||||
InteractionStarted, // When interaction is first triggered
|
||||
PlayerArrived, // When player arrives at interaction point
|
||||
InteractingCharacterArrived, // When interacting character arrives
|
||||
InteractionComplete, // When interaction is successfully completed
|
||||
InteractionInterrupted // When interaction is interrupted
|
||||
}
|
||||
```
|
||||
|
||||
### CharacterToInteract
|
||||
|
||||
```csharp
|
||||
public enum CharacterToInteract
|
||||
{
|
||||
None, // No character interactions
|
||||
Trafalgar, // Player character only
|
||||
Pulver, // Follower character only
|
||||
Both // Both characters
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Characters not moving to targets**: Ensure targets have the correct CharacterToInteract type set
|
||||
- **Timeline not playing**: Check that the PlayableDirector has a reference to the timeline asset
|
||||
- **Characters not appearing in timeline**: Verify the track names match the binding configuration
|
||||
- **Interaction getting stuck**: Make sure timelines have a reasonable timeout value set
|
||||
- **Multiple timelines playing simultaneously**: Check that the event mappings are correctly configured
|
||||
|
||||
|
||||
## Setup Example
|
||||
|
||||
Here's how a typical interaction setup might look in the Inspector:
|
||||
|
||||
1. Interactable component with appropriate settings
|
||||
2. Character move targets positioned in the scene
|
||||
3. InteractionTimelineAction component configured with timeline mappings
|
||||
4. PlayableDirector component referencing timeline assets
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Timeline Integration with Dialogue
|
||||
|
||||
Timeline actions can be synchronized with dialogue using:
|
||||
- Animation tracks to trigger dialogue displays
|
||||
- Signal tracks to advance dialogue
|
||||
- Custom markers to synchronize dialogue with character animations
|
||||
|
||||
### Interaction Sequences
|
||||
|
||||
Complex interaction sequences can be created by:
|
||||
- Using multiple interactables in sequence
|
||||
- Enabling/disabling interactables based on game state
|
||||
- Using timelines to guide the player through sequential interactions
|
||||
BIN
docs/media/character_move_target_inspector.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/media/create_dialogue_graph.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/media/dialogue_component_inspector.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/media/dialogue_editor_interface.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
docs/media/dialogue_graph_example.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
docs/media/dialogue_node_example.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/media/dialogue_testing_flow.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/media/end_node.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/media/interactable_inspector.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/media/interaction_timeline_action_inspector.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/media/speech_bubble_setup.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/media/target_positioning_scene.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
docs/media/timeline_editor.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
docs/media/timeline_mapping_editor.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/media/wait_on_combination_node.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/media/wait_on_pickup_node.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/media/wait_on_puzzle_node.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/media/wait_on_slot_node.png
Normal file
|
After Width: | Height: | Size: 47 KiB |