Pull from Michal Stuff

This commit is contained in:
2025-10-07 13:14:45 +02:00
54 changed files with 2948 additions and 324 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -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:

View 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");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e2011e96b1b84886825d0509dd0a5cee
timeCreated: 1759744192

View 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);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6ab56ee48f1445529a62d30c1985f059
timeCreated: 1759746824

View 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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c5a227e33ab14a0f86bf391016c57b00
timeCreated: 1759756346

View 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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1791fd5a24a3142418ed441a2a25b374
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View 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}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ee609df51f47bd541a23d5425e289e30
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -159,72 +159,10 @@ public class GameManager : MonoBehaviour
{
return DeveloperSettingsProvider.Instance?.GetSettings<T>();
}
// PLAYER & FOLLOWER SETTINGS
// Player settings
public float MoveSpeed => GetSettings<IPlayerFollowerSettings>()?.MoveSpeed ?? 5f;
public float StopDistance => GetSettings<IPlayerFollowerSettings>()?.StopDistance ?? 0.1f;
public bool UseRigidbody => GetSettings<IPlayerFollowerSettings>()?.UseRigidbody ?? true;
public HoldMovementMode DefaultHoldMovementMode =>
GetSettings<IPlayerFollowerSettings>()?.DefaultHoldMovementMode ?? HoldMovementMode.Pathfinding;
// Follower settings
public float FollowDistance => GetSettings<IPlayerFollowerSettings>()?.FollowDistance ?? 1.5f;
public float ManualMoveSmooth => GetSettings<IPlayerFollowerSettings>()?.ManualMoveSmooth ?? 8f;
public float ThresholdFar => GetSettings<IPlayerFollowerSettings>()?.ThresholdFar ?? 2.5f;
public float ThresholdNear => GetSettings<IPlayerFollowerSettings>()?.ThresholdNear ?? 0.5f;
public float StopThreshold => GetSettings<IPlayerFollowerSettings>()?.StopThreshold ?? 0.1f;
public float FollowUpdateInterval => GetSettings<IPlayerFollowerSettings>()?.FollowUpdateInterval ?? 0.1f;
public float FollowerSpeedMultiplier => GetSettings<IPlayerFollowerSettings>()?.FollowerSpeedMultiplier ?? 1.2f;
public float HeldIconDisplayHeight => GetSettings<IPlayerFollowerSettings>()?.HeldIconDisplayHeight ?? 2.0f;
// INTERACTION SETTINGS
// LEFTOVER LEGACY SETTINGS
public float PlayerStopDistance => GetSettings<IInteractionSettings>()?.PlayerStopDistance ?? 6.0f;
public float PlayerStopDistanceDirectInteraction => GetSettings<IInteractionSettings>()?.PlayerStopDistanceDirectInteraction ?? 2.0f;
public float FollowerPickupDelay => GetSettings<IInteractionSettings>()?.FollowerPickupDelay ?? 0.2f;
public LayerMask InteractableLayerMask => GetSettings<IInteractionSettings>()?.InteractableLayerMask ?? -1;
public GameObject BasePickupPrefab => GetSettings<IInteractionSettings>()?.BasePickupPrefab;
public GameObject LevelSwitchMenuPrefab => GetSettings<IInteractionSettings>()?.LevelSwitchMenuPrefab;
// PUZZLE SETTINGS
public float DefaultPuzzlePromptRange => GetSettings<IInteractionSettings>()?.DefaultPuzzlePromptRange ?? 3.0f;
public GameObject DefaultPuzzleIndicatorPrefab => GetSettings<IInteractionSettings>()?.DefaultPuzzleIndicatorPrefab;
/// <summary>
/// Returns the combination rule for two items, if any.
/// </summary>
public CombinationRule GetCombinationRule(PickupItemData item1, PickupItemData item2)
{
var settings = GetSettings<IInteractionSettings>();
if (settings == null || settings.CombinationRules == null) return null;
foreach (var rule in settings.CombinationRules)
{
if ((PickupItemData.AreEquivalent(rule.itemA, item1) && PickupItemData.AreEquivalent(rule.itemB, item2)) ||
(PickupItemData.AreEquivalent(rule.itemA, item2) && PickupItemData.AreEquivalent(rule.itemB, item1)))
{
return rule;
}
}
return null;
}
/// <summary>
/// Returns the slot item config for a given slot item.
/// </summary>
public SlotItemConfig GetSlotItemConfig(PickupItemData slotItem)
{
var settings = GetSettings<IInteractionSettings>();
if (settings == null || settings.SlotItemConfigs == null || slotItem == null) return null;
foreach (var config in settings.SlotItemConfigs)
{
if (PickupItemData.AreEquivalent(slotItem, config.slotItem))
return config;
}
return null;
}
}

View File

@@ -53,5 +53,38 @@ namespace AppleHills.Core.Settings
followerPickupDelay = Mathf.Max(0f, followerPickupDelay);
defaultPuzzlePromptRange = Mathf.Max(0.1f, defaultPuzzlePromptRange);
}
/// <summary>
/// Returns the combination rule for two items, if any.
/// </summary>
public CombinationRule GetCombinationRule(PickupItemData item1, PickupItemData item2)
{
if (combinationRules == null) return null;
foreach (var rule in combinationRules)
{
if ((PickupItemData.AreEquivalent(rule.itemA, item1) && PickupItemData.AreEquivalent(rule.itemB, item2)) ||
(PickupItemData.AreEquivalent(rule.itemA, item2) && PickupItemData.AreEquivalent(rule.itemB, item1)))
{
return rule;
}
}
return null;
}
/// <summary>
/// Returns the slot item config for a given slot item.
/// </summary>
public SlotItemConfig GetSlotItemConfig(PickupItemData slotItem)
{
if (slotItemConfigs == null || slotItem == null) return null;
foreach (var config in slotItemConfigs)
{
if (PickupItemData.AreEquivalent(slotItem, config.slotItem))
return config;
}
return null;
}
}
}

View File

@@ -43,6 +43,10 @@ namespace AppleHills.Core.Settings
// Puzzle settings
GameObject DefaultPuzzleIndicatorPrefab { get; }
float DefaultPuzzlePromptRange { get; }
// Methods to query item configurations
CombinationRule GetCombinationRule(PickupItemData item1, PickupItemData item2);
SlotItemConfig GetSlotItemConfig(PickupItemData slotItem);
}
/// <summary>

View File

@@ -3,6 +3,7 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using AppleHills.Core.Settings; // Added for IInteractionSettings
namespace Input
{
@@ -44,6 +45,9 @@ namespace Input
}
}
// Settings reference
private IInteractionSettings _interactionSettings;
private PlayerInput playerInput;
private InputAction tapMoveAction;
private InputAction holdMoveAction;
@@ -55,6 +59,10 @@ namespace Input
{
_instance = this;
// DontDestroyOnLoad(gameObject);
// Initialize settings reference
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
playerInput = GetComponent<PlayerInput>();
if (playerInput == null)
{
@@ -217,7 +225,7 @@ namespace Input
/// </summary>
private bool TryDelegateToInteractable(Vector2 worldPos)
{
LayerMask mask = GameManager.Instance != null ? GameManager.Instance.InteractableLayerMask : -1;
LayerMask mask = _interactionSettings != null ? _interactionSettings.InteractableLayerMask : -1;
Collider2D hit = Physics2D.OverlapPoint(worldPos, mask);
if (hit != null)
{

View 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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2bcf343b3ef74f0fb3c64be6fd2893b6
timeCreated: 1759744130

View File

@@ -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)

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5cf351d32dac4169a9db20609727a70f
timeCreated: 1759746705

View 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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 78684d31bd4d4636834a494c7cb74f48
timeCreated: 1759746690

View 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);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 42e77a0c97604b6eb7674e58726c831a
timeCreated: 1759746720

View File

@@ -3,6 +3,7 @@ using UnityEngine;
using UnityEngine.Events;
using System; // for Action<T>
using Core; // register with ItemManager
using AppleHills.Core.Settings; // Added for IInteractionSettings
namespace Interactions
{
@@ -24,6 +25,10 @@ namespace Interactions
// Tracks the current state of the slotted item
private ItemSlotState _currentState = ItemSlotState.None;
// Settings reference
private IInteractionSettings _interactionSettings;
private IPlayerFollowerSettings _playerFollowerSettings;
/// <summary>
/// Read-only access to the current slotted item state.
/// </summary>
@@ -64,13 +69,22 @@ namespace Interactions
}
}
public override void Awake()
{
base.Awake();
// Initialize settings references
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
_playerFollowerSettings = GameManager.GetSettingsObject<IPlayerFollowerSettings>();
}
protected override void OnCharacterArrived()
{
Debug.Log("[ItemSlot] OnCharacterArrived");
var heldItemData = FollowerController.CurrentlyHeldItemData;
var heldItemObj = FollowerController.GetHeldPickupObject();
var config = GameManager.Instance.GetSlotItemConfig(itemData);
var config = _interactionSettings?.GetSlotItemConfig(itemData);
var forbidden = config?.forbiddenItems ?? new List<PickupItemData>();
// Held item, slot empty -> try to slot item
@@ -120,7 +134,7 @@ namespace Interactions
{
slottedItemRenderer.sprite = _currentlySlottedItemData.mapSprite;
// Scale sprite to desired height, preserve aspect ratio, compensate for parent scale
float desiredHeight = GameManager.Instance.HeldIconDisplayHeight;
float desiredHeight = _playerFollowerSettings?.HeldIconDisplayHeight ?? 2.0f;
var sprite = _currentlySlottedItemData.mapSprite;
float spriteHeight = sprite.bounds.size.y;
float spriteWidth = sprite.bounds.size.x;
@@ -175,7 +189,7 @@ namespace Interactions
// Once an item is slotted, we know it is not forbidden, so we can skip that check, but now check if it was
// the correct item we're looking for
var config = GameManager.Instance.GetSlotItemConfig(itemData);
var config = _interactionSettings?.GetSlotItemConfig(itemData);
var allowed = config?.allowedItems ?? new List<PickupItemData>();
if (itemToSlotData != null && PickupItemData.ListContainsEquivalent(allowed, itemToSlotData))
{

View File

@@ -26,7 +26,7 @@ namespace Interactions
/// <summary>
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
/// </summary>
void Awake()
public virtual void Awake()
{
if (iconRenderer == null)
iconRenderer = GetComponent<SpriteRenderer>();

View File

@@ -2,6 +2,7 @@
using Input;
using Interactions;
using UnityEngine;
using AppleHills.Core.Settings; // Added for IInteractionSettings
/// <summary>
/// Handles level switching when interacted with. Applies switch data and triggers scene transitions.
@@ -14,6 +15,9 @@ public class LevelSwitch : MonoBehaviour
public LevelSwitchData switchData;
private SpriteRenderer _iconRenderer;
private Interactable _interactable;
// Settings reference
private IInteractionSettings _interactionSettings;
private bool _isActive = true;
@@ -30,6 +34,10 @@ public class LevelSwitch : MonoBehaviour
{
_interactable.characterArrived.AddListener(OnCharacterArrived);
}
// Initialize settings reference
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
ApplySwitchData();
}
@@ -78,10 +86,10 @@ public class LevelSwitch : MonoBehaviour
if (switchData == null || string.IsNullOrEmpty(switchData.targetLevelSceneName) || !_isActive)
return;
var menuPrefab = GameManager.Instance.LevelSwitchMenuPrefab;
var menuPrefab = _interactionSettings?.LevelSwitchMenuPrefab;
if (menuPrefab == null)
{
Debug.LogError("LevelSwitchMenu prefab not assigned in GameSettings!");
Debug.LogError("LevelSwitchMenu prefab not assigned in InteractionSettings!");
return;
}
// Spawn the menu overlay (assume Canvas parent is handled in prefab setup)

View File

@@ -3,6 +3,7 @@ using UnityEngine;
using Pathfinding;
using UnityEngine.SceneManagement;
using Utils;
using AppleHills.Core.Settings;
/// <summary>
/// Controls the follower character, including following the player, handling pickups, and managing held items.
@@ -20,6 +21,10 @@ public class FollowerController: MonoBehaviour
/// </summary>
public float manualMoveSmooth = 8f;
// Settings reference
private IPlayerFollowerSettings _settings;
private IInteractionSettings _interactionSettings;
private GameObject _playerRef;
private Transform _playerTransform;
private AIPath _playerAIPath;
@@ -80,6 +85,10 @@ public class FollowerController: MonoBehaviour
_animator = GetComponentInChildren<Animator>(); // fallback
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
}
// Initialize settings references
_settings = GameManager.GetSettingsObject<IPlayerFollowerSettings>();
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
}
void OnEnable()
@@ -108,7 +117,7 @@ public class FollowerController: MonoBehaviour
}
_timer += Time.deltaTime;
if (_timer >= GameManager.Instance.FollowUpdateInterval)
if (_timer >= _settings.FollowUpdateInterval)
{
_timer = 0f;
UpdateFollowTarget();
@@ -120,24 +129,24 @@ public class FollowerController: MonoBehaviour
Vector2 target2D = new Vector2(_targetPoint.x, _targetPoint.y);
float dist = Vector2.Distance(current2D, target2D);
float minSpeed = _followerMaxSpeed * 0.3f;
float lerpFactor = GameManager.Instance.ManualMoveSmooth * Time.deltaTime;
float lerpFactor = _settings.ManualMoveSmooth * Time.deltaTime;
float targetSpeed = 0f;
if (dist > GameManager.Instance.StopThreshold)
if (dist > _settings.StopThreshold)
{
if (dist > GameManager.Instance.ThresholdFar)
if (dist > _settings.ThresholdFar)
{
targetSpeed = _followerMaxSpeed;
}
else if (dist > GameManager.Instance.ThresholdNear && dist <= GameManager.Instance.ThresholdFar)
else if (dist > _settings.ThresholdNear && dist <= _settings.ThresholdFar)
{
targetSpeed = _followerMaxSpeed;
}
else if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
else if (dist > _settings.StopThreshold && dist <= _settings.ThresholdNear)
{
targetSpeed = minSpeed;
}
_currentSpeed = Mathf.Lerp(_currentSpeed, targetSpeed, lerpFactor);
if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear)
if (dist > _settings.StopThreshold && dist <= _settings.ThresholdNear)
{
_currentSpeed = Mathf.Max(_currentSpeed, minSpeed);
}
@@ -215,7 +224,7 @@ public class FollowerController: MonoBehaviour
{
_playerMaxSpeed = _playerAIPath.maxSpeed;
_defaultFollowerMaxSpeed = _playerMaxSpeed;
_followerMaxSpeed = _playerMaxSpeed * GameManager.Instance.FollowerSpeedMultiplier;
_followerMaxSpeed = _playerMaxSpeed * _settings.FollowerSpeedMultiplier;
}
}
else
@@ -258,8 +267,8 @@ public class FollowerController: MonoBehaviour
{
moveDir = _lastMoveDir;
}
// Use GameSettings for followDistance
_targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance;
// Use settings for followDistance
_targetPoint = playerPos - moveDir * _settings.FollowDistance;
_targetPoint.z = 0;
if (_aiPath != null)
{
@@ -268,25 +277,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 +305,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;
@@ -310,14 +329,14 @@ public class FollowerController: MonoBehaviour
_aiPath.destination = new Vector3(itemPosition.x, itemPosition.y, 0);
}
// Wait until follower reaches item (2D distance)
while (Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(itemPosition.x, itemPosition.y)) > GameManager.Instance.StopThreshold)
while (Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(itemPosition.x, itemPosition.y)) > _settings.StopThreshold)
{
yield return null;
}
OnPickupArrived?.Invoke();
// Wait briefly, then return to player
yield return new WaitForSeconds(0.2f);
yield return new WaitForSeconds(_interactionSettings.FollowerPickupDelay);
if (_aiPath != null && playerTransform != null)
{
_aiPath.maxSpeed = _followerMaxSpeed;
@@ -325,7 +344,7 @@ public class FollowerController: MonoBehaviour
}
_isReturningToPlayer = true;
// Wait until follower returns to player (2D distance)
while (playerTransform != null && Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(playerTransform.position.x, playerTransform.position.y)) > GameManager.Instance.StopThreshold)
while (playerTransform != null && Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(playerTransform.position.x, playerTransform.position.y)) > _settings.StopThreshold)
{
yield return null;
}
@@ -340,6 +359,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)) > _settings.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)) > _settings.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
@@ -375,15 +451,18 @@ public class FollowerController: MonoBehaviour
{
return CombinationResult.NotApplicable;
}
var rule = GameManager.Instance.GetCombinationRule(pickupA.itemData, pickupB.itemData);
// Use the InteractionSettings directly instead of GameManager
CombinationRule matchingRule = _interactionSettings.GetCombinationRule(pickupA.itemData, pickupB.itemData);
Vector3 spawnPos = pickupA.gameObject.transform.position;
if (rule != null && rule.resultPrefab != null)
if (matchingRule != null && matchingRule.resultPrefab != null)
{
newItem = Instantiate(rule.resultPrefab, spawnPos, Quaternion.identity);
newItem = Instantiate(matchingRule.resultPrefab, spawnPos, Quaternion.identity);
PickupItemData itemData = newItem.GetComponent<Pickup>().itemData;
Destroy(pickupA.gameObject);
Destroy(pickupB.gameObject);
TryPickupItem(newItem,itemData);
TryPickupItem(newItem, itemData);
return CombinationResult.Successful;
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using AppleHills.Core.Settings; // Added for IInteractionSettings
namespace PuzzleS
{
@@ -21,6 +22,9 @@ namespace PuzzleS
private Transform _playerTransform;
private Coroutine _proximityCheckCoroutine;
// Settings reference
private IInteractionSettings _interactionSettings;
/// <summary>
/// Singleton instance of the PuzzleManager.
/// </summary>
@@ -58,6 +62,9 @@ namespace PuzzleS
_instance = this;
// DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
// Initialize settings reference
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
}
void Start()
@@ -123,8 +130,8 @@ namespace PuzzleS
{
if (_playerTransform != null)
{
// Get the proximity threshold from settings (half of the prompt range)
float proximityThreshold = GameManager.Instance.DefaultPuzzlePromptRange;
// Get the proximity threshold from settings directly using our settings reference
float proximityThreshold = _interactionSettings?.DefaultPuzzlePromptRange ?? 3.0f;
// Check distance to each step behavior
foreach (var kvp in _stepBehaviours)
@@ -198,16 +205,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>