Pulver trash maze sequence

This commit is contained in:
Michal Pikulski
2025-12-19 15:26:13 +01:00
parent 15c9ba0127
commit f0905f92d3
36 changed files with 2372 additions and 842 deletions

View File

@@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: LeverIdle
serializedVersion: 7
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves:
- serializedVersion: 2
curve:
- time: 0
value: {fileID: 1983369653, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
attribute: m_Sprite
path:
classID: 212
script: {fileID: 0}
flags: 2
m_SampleRate: 30
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings:
- serializedVersion: 2
path: 0
attribute: 0
script: {fileID: 0}
typeID: 212
customType: 23
isPPtrCurve: 1
isIntCurve: 0
isSerializeReferenceCurve: 0
pptrCurveMapping:
- {fileID: 1983369653, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 0.033333335
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9fa6031302d433f408438e70a14fffc8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: LeverOver
serializedVersion: 7
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves:
- serializedVersion: 2
curve:
- time: 0
value: {fileID: 1983369653, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- time: 0.033333335
value: {fileID: 182198627, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- time: 0.06666667
value: {fileID: 2099342698, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- time: 0.1
value: {fileID: 1141896416, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- time: 0.13333334
value: {fileID: 1211958902, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- time: 0.16666667
value: {fileID: 1892208514, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- time: 0.2
value: {fileID: 1469259267, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- time: 0.23333333
value: {fileID: -1077132752, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
attribute: m_Sprite
path:
classID: 212
script: {fileID: 0}
flags: 2
m_SampleRate: 30
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings:
- serializedVersion: 2
path: 0
attribute: 0
script: {fileID: 0}
typeID: 212
customType: 23
isPPtrCurve: 1
isIntCurve: 0
isSerializeReferenceCurve: 0
pptrCurveMapping:
- {fileID: 1983369653, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- {fileID: 182198627, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- {fileID: 2099342698, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- {fileID: 1141896416, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- {fileID: 1211958902, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- {fileID: 1892208514, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- {fileID: 1469259267, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
- {fileID: -1077132752, guid: aa293e7c44a20ca40aa2cdd1c9989e53, type: 3}
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 0.26666668
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

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

View File

@@ -1,5 +1,27 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1101 &-9052709553426898635
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions: []
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 6005905035337375392}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 1
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1107 &-4471614901610054980
AnimatorStateMachine:
serializedVersion: 6
@@ -11,17 +33,23 @@ AnimatorStateMachine:
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: -4348510085696419942}
m_Position: {x: 200, y: 0, z: 0}
m_Position: {x: 260, y: 110, z: 0}
- serializedVersion: 1
m_State: {fileID: -314990716293606577}
m_Position: {x: 110, y: 220, z: 0}
- serializedVersion: 1
m_State: {fileID: 6005905035337375392}
m_Position: {x: 460, y: 230, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_AnyStatePosition: {x: 100, y: -30, z: 0}
m_EntryPosition: {x: -20, y: 110, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: -4348510085696419942}
m_DefaultState: {fileID: -314990716293606577}
--- !u!1102 &-4348510085696419942
AnimatorState:
serializedVersion: 6
@@ -32,7 +60,8 @@ AnimatorState:
m_Name: Lever
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_Transitions:
- {fileID: -9052709553426898635}
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
@@ -48,6 +77,58 @@ AnimatorState:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1101 &-2774546409871568984
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 1
m_ConditionEvent: PushLever
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: -4348510085696419942}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1102 &-314990716293606577
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Idle
m_Speed: 1
m_CycleOffset: 0
m_Transitions:
- {fileID: -2774546409871568984}
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 9fa6031302d433f408438e70a14fffc8, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
@@ -56,7 +137,13 @@ AnimatorController:
m_PrefabAsset: {fileID: 0}
m_Name: gate_lever0001
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorParameters:
- m_Name: PushLever
m_Type: 9
m_DefaultFloat: 0
m_DefaultInt: 0
m_DefaultBool: 0
m_Controller: {fileID: 9100000}
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
@@ -70,3 +157,29 @@ AnimatorController:
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &6005905035337375392
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: LeverOver
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 0e15051dcb3df814fab52e617496fc40, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:

View File

@@ -7,33 +7,39 @@ namespace Editor
/// <summary>
/// Custom editor for ControllerSwitchItem that shows only relevant fields based on camera switch mode.
/// </summary>
[CustomEditor(typeof(ControllerSwitchItem))]
[CustomEditor(typeof(ControllerSwitchItem), true)]
public class ControllerSwitchItemEditor : UnityEditor.Editor
{
private SerializedProperty _targetControllerName;
private SerializedProperty _cameraSwitchMode;
private SerializedProperty _targetVirtualCamera;
private SerializedProperty _targetCameraState;
private SerializedProperty _visualRepresentation;
protected SerializedProperty _targetControllerName;
protected SerializedProperty _visualRepresentation;
// Base class properties
private SerializedProperty _isOneTime;
private SerializedProperty _cooldown;
private SerializedProperty _characterToInteract;
protected SerializedProperty _isOneTime;
protected SerializedProperty _cooldown;
protected SerializedProperty _characterToInteract;
private void OnEnable()
// Base interactable events
protected SerializedProperty _interactionStarted;
protected SerializedProperty _interactionInterrupted;
protected SerializedProperty _characterArrived;
protected SerializedProperty _interactionComplete;
protected virtual void OnEnable()
{
// Controller Switch Settings
_targetControllerName = serializedObject.FindProperty("targetControllerName");
_cameraSwitchMode = serializedObject.FindProperty("cameraSwitchMode");
_targetVirtualCamera = serializedObject.FindProperty("targetVirtualCamera");
_targetCameraState = serializedObject.FindProperty("targetCameraState");
_visualRepresentation = serializedObject.FindProperty("visualRepresentation");
// Base class properties
_isOneTime = serializedObject.FindProperty("isOneTime");
_cooldown = serializedObject.FindProperty("cooldown");
_characterToInteract = serializedObject.FindProperty("characterToInteract");
// Base interactable events
_interactionStarted = serializedObject.FindProperty("interactionStarted");
_interactionInterrupted = serializedObject.FindProperty("interactionInterrupted");
_characterArrived = serializedObject.FindProperty("characterArrived");
_interactionComplete = serializedObject.FindProperty("interactionComplete");
}
public override void OnInspectorGUI()
@@ -59,33 +65,9 @@ namespace Editor
EditorGUILayout.LabelField("Controller Switch Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_targetControllerName);
EditorGUILayout.Space();
// Camera Settings
EditorGUILayout.LabelField("Camera Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_cameraSwitchMode);
// Show relevant camera fields based on mode
CameraSwitchMode mode = (CameraSwitchMode)_cameraSwitchMode.enumValueIndex;
switch (mode)
if (string.IsNullOrEmpty(_targetControllerName.stringValue))
{
case CameraSwitchMode.None:
EditorGUILayout.HelpBox("No camera switching will occur. Only the controller will be switched.", MessageType.Info);
break;
case CameraSwitchMode.DirectReference:
EditorGUILayout.PropertyField(_targetVirtualCamera);
if (_targetVirtualCamera.objectReferenceValue == null)
{
EditorGUILayout.HelpBox("Assign a Cinemachine camera to blend to when switching controllers.", MessageType.Warning);
}
break;
case CameraSwitchMode.TrashMazeCameraState:
EditorGUILayout.PropertyField(_targetCameraState);
EditorGUILayout.HelpBox("Uses TrashMazeCameraController to switch camera state. Make sure TrashMazeCameraController is present in the scene.", MessageType.Info);
break;
EditorGUILayout.HelpBox("Assign a target controller name (must match registration name in InputManager).", MessageType.Warning);
}
EditorGUILayout.Space();
@@ -94,8 +76,27 @@ namespace Editor
EditorGUILayout.LabelField("Visual Feedback", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_visualRepresentation);
EditorGUILayout.Space();
// Base Interactable Events
EditorGUILayout.LabelField("Interaction Events", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_interactionStarted);
EditorGUILayout.PropertyField(_interactionInterrupted);
EditorGUILayout.PropertyField(_characterArrived);
EditorGUILayout.PropertyField(_interactionComplete);
DrawCustomFields();
serializedObject.ApplyModifiedProperties();
}
/// <summary>
/// Override this in derived editors to add custom fields specific to derived classes
/// </summary>
protected virtual void DrawCustomFields()
{
// Base class has no additional custom fields
}
}
}

View File

@@ -0,0 +1,89 @@
using UnityEditor;
using UnityEngine;
using Minigames.TrashMaze.Objects;
namespace Editor
{
/// <summary>
/// Custom editor for MazeExit showing teleportation and camera settings
/// </summary>
[CustomEditor(typeof(MazeExit))]
public class MazeExitEditor : UnityEditor.Editor
{
private SerializedProperty _teleportTarget;
private SerializedProperty _targetCameraState;
// Base class properties
private SerializedProperty _isOneTime;
private SerializedProperty _cooldown;
private SerializedProperty _characterToInteract;
private void OnEnable()
{
_teleportTarget = serializedObject.FindProperty("teleportTarget");
_targetCameraState = serializedObject.FindProperty("targetCameraState");
// Base class properties
_isOneTime = serializedObject.FindProperty("isOneTime");
_cooldown = serializedObject.FindProperty("cooldown");
_characterToInteract = serializedObject.FindProperty("characterToInteract");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Draw script field (read-only)
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((MonoBehaviour)target), GetType(), false);
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
// Interaction Settings (from base class)
EditorGUILayout.LabelField("Interaction Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_isOneTime);
EditorGUILayout.PropertyField(_cooldown);
EditorGUILayout.PropertyField(_characterToInteract);
EditorGUILayout.Space();
// Maze Exit Settings
EditorGUILayout.LabelField("Maze Exit Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_teleportTarget);
if (_teleportTarget.objectReferenceValue == null)
{
EditorGUILayout.HelpBox("Assign a Transform where Pulver should be teleported to (maze exit position). Teleportation happens midway through camera blend.", MessageType.Warning);
}
else
{
EditorGUILayout.HelpBox("✓ Pulver will be teleported to this position halfway through the camera blend. Tracking target is set immediately.", MessageType.Info);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(_targetCameraState);
EditorGUILayout.HelpBox("Camera state to blend to. Typically 'PulverGameplay' when exiting maze. Requires TrashMazeCameraController in scene.", MessageType.Info);
EditorGUILayout.Space();
// Info box about the behavior
EditorGUILayout.HelpBox(
"Maze Exit Behavior:\n" +
"1. Find Pulver (player-controlled in maze)\n" +
"2. Start camera blend to target state\n" +
"3. ★ Teleport Pulver midway through blend ★\n" +
"4. Set Pulver as tracking target immediately\n" +
"5. Wait for blend to complete\n" +
"6. Stop Pulver movement (clear cached input)\n" +
"\n" +
"Note: Does NOT switch controllers - Pulver remains player-controlled!",
MessageType.None
);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9bcc6ff619f74a88b899223936a66845
timeCreated: 1766151279

View File

@@ -0,0 +1,63 @@
using UnityEditor;
using Minigames.TrashMaze.Objects;
namespace Editor
{
/// <summary>
/// Custom editor for TrashMazeSwitchToPulver showing maze-specific settings
/// </summary>
[CustomEditor(typeof(TrashMazeSwitchToPulver))]
public class TrashMazeSwitchToPulverEditor : ControllerSwitchItemEditor
{
private SerializedProperty _teleportTarget;
private SerializedProperty _targetCameraState;
protected override void OnEnable()
{
base.OnEnable();
_teleportTarget = serializedObject.FindProperty("teleportTarget");
_targetCameraState = serializedObject.FindProperty("targetCameraState");
}
protected override void DrawCustomFields()
{
EditorGUILayout.Space();
// Trash Maze Specific Settings
EditorGUILayout.LabelField("Trash Maze - To Pulver Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_teleportTarget);
if (_teleportTarget.objectReferenceValue == null)
{
EditorGUILayout.HelpBox("Assign a Transform where Pulver should be teleported to (maze entrance position). Teleportation happens midway through camera blend.", MessageType.Warning);
}
else
{
EditorGUILayout.HelpBox("✓ Pulver will be teleported to this position halfway through the camera blend, creating a seamless 'entering maze' effect.", MessageType.Info);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(_targetCameraState);
EditorGUILayout.HelpBox("Camera state to blend to. Typically 'Maze' when entering the maze. Requires TrashMazeCameraController in scene.", MessageType.Info);
EditorGUILayout.Space();
// Info box about the behavior
EditorGUILayout.HelpBox(
"Switch Behavior:\n" +
"1. Deactivate current controller (Trafalgar)\n" +
"2. Deactivate Pulver's follower mode\n" +
"3. Start camera blend to Maze view\n" +
"4. ★ Teleport Pulver midway through blend ★\n" +
"5. Set Pulver as tracking target immediately\n" +
"6. Wait for blend to complete\n" +
"7. Activate PulverController for player control\n" +
"8. Switch input to 'pulver'",
MessageType.None
);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ded1ca7102b144a99d9e36a67d9054c0
timeCreated: 1766149825

View File

@@ -0,0 +1,50 @@
using UnityEditor;
using UnityEngine;
using Minigames.TrashMaze.Objects;
namespace Editor
{
/// <summary>
/// Custom editor for TrashMazeSwitchToTrafalgar showing maze-specific settings
/// </summary>
[CustomEditor(typeof(TrashMazeSwitchToTrafalgar))]
public class TrashMazeSwitchToTrafalgarEditor : ControllerSwitchItemEditor
{
private SerializedProperty _targetCameraState;
protected override void OnEnable()
{
base.OnEnable();
_targetCameraState = serializedObject.FindProperty("targetCameraState");
}
protected override void DrawCustomFields()
{
EditorGUILayout.Space();
// Trash Maze Specific Settings
EditorGUILayout.LabelField("Trash Maze - To Trafalgar Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_targetCameraState);
EditorGUILayout.HelpBox("Camera state to blend to. Typically 'Gameplay' when exiting the maze. Requires TrashMazeCameraController in scene.", MessageType.Info);
EditorGUILayout.Space();
// Info box about the behavior
EditorGUILayout.HelpBox(
"Switch Behavior:\n" +
"1. Deactivate PulverController (stops input)\n" +
"2. Start camera blend to Gameplay view\n" +
"3. Wait for blend to complete\n" +
"4. Clear Pulver from maze camera tracking\n" +
"5. Set Trafalgar as tracking target for gameplay camera\n" +
"6. Activate Pulver's follower mode (follows Trafalgar)\n" +
"7. Activate PlayerTouchController (Trafalgar)\n" +
"8. Switch input to 'trafalgar'",
MessageType.None
);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b58486fd20fd458ea14a508070a0f277
timeCreated: 1766149834

View File

@@ -0,0 +1,121 @@
using UnityEditor;
using UnityEngine;
using Interactions;
namespace Editor
{
/// <summary>
/// Custom editor for TriggerAnimationOnInteraction with helpful validation and feedback
/// </summary>
[CustomEditor(typeof(TriggerAnimationOnInteraction))]
public class TriggerAnimationOnInteractionEditor : UnityEditor.Editor
{
private SerializedProperty _interactable;
private SerializedProperty _animator;
private SerializedProperty _triggerName;
private void OnEnable()
{
_interactable = serializedObject.FindProperty("interactable");
_animator = serializedObject.FindProperty("animator");
_triggerName = serializedObject.FindProperty("triggerName");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Draw script field (read-only)
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((MonoBehaviour)target), GetType(), false);
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
// Component References
EditorGUILayout.LabelField("Component References (Optional)", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_interactable);
EditorGUILayout.PropertyField(_animator);
if (_interactable.objectReferenceValue == null || _animator.objectReferenceValue == null)
{
EditorGUILayout.HelpBox("Leave fields empty to auto-discover components on this GameObject or children.", MessageType.Info);
}
EditorGUILayout.Space();
// Animation Settings
EditorGUILayout.LabelField("Animation Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_triggerName);
if (string.IsNullOrEmpty(_triggerName.stringValue))
{
EditorGUILayout.HelpBox("Assign a trigger name that matches a trigger parameter in the Animator.", MessageType.Warning);
}
EditorGUILayout.Space();
// Component Discovery Info
EditorGUILayout.LabelField("Component Discovery Status", EditorStyles.boldLabel);
var component = (TriggerAnimationOnInteraction)target;
// Check what will be used at runtime
InteractableBase resolvedInteractable = _interactable.objectReferenceValue as InteractableBase;
if (resolvedInteractable == null)
{
resolvedInteractable = component.GetComponent<InteractableBase>();
}
if (resolvedInteractable == null)
{
resolvedInteractable = component.GetComponentInChildren<InteractableBase>();
}
Animator resolvedAnimator = _animator.objectReferenceValue as Animator;
if (resolvedAnimator == null)
{
resolvedAnimator = component.GetComponent<Animator>();
}
if (resolvedAnimator == null)
{
resolvedAnimator = component.GetComponentInChildren<Animator>();
}
// Show discovered components
if (resolvedInteractable != null)
{
string source = _interactable.objectReferenceValue != null ? "Assigned" : "Auto-discovered";
EditorGUILayout.HelpBox($"✓ Interactable: {resolvedInteractable.gameObject.name} ({source})", MessageType.Info);
}
else
{
EditorGUILayout.HelpBox("✗ No Interactable found! Assign one or ensure an Interactable exists on this GameObject or children.", MessageType.Error);
}
if (resolvedAnimator != null)
{
string source = _animator.objectReferenceValue != null ? "Assigned" : "Auto-discovered";
EditorGUILayout.HelpBox($"✓ Animator: {resolvedAnimator.gameObject.name} ({source})", MessageType.Info);
}
else
{
EditorGUILayout.HelpBox("✗ No Animator found! Assign one or ensure an Animator exists on this GameObject or children.", MessageType.Error);
}
EditorGUILayout.Space();
// Behavior explanation
EditorGUILayout.HelpBox(
"Behavior:\n" +
"• Components can be manually assigned or auto-discovered\n" +
"• Auto-discovery searches: this GameObject → children\n" +
"• Listens to interactionComplete event from Interactable\n" +
"• Triggers animation ONLY when interaction succeeds (success = true)\n" +
"• Uses SetTrigger() with the specified trigger name",
MessageType.None
);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cb5befe45d9c4f9ab10e20ee4c946cf4
timeCreated: 1766153779

View File

@@ -19,6 +19,7 @@ GameObject:
- component: {fileID: 4467608046243604209}
- component: {fileID: 887004370483616855}
- component: {fileID: 3342764969520326238}
- component: {fileID: 3719568838531840149}
m_Layer: 8
m_Name: PulverCharacter
m_TagString: Pulver
@@ -379,6 +380,23 @@ MonoBehaviour:
m_EditorClassIdentifier: AppleHillsScripts::UI.Tracking.TrackableTarget
icon: {fileID: -4695408507704126972, guid: 31cfa2eea2b70af4b97a7a588fad0758, type: 3}
trackDistance: 0
--- !u!114 &3719568838531840149
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1102400833121127473}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.TrashMaze.Core.PulverController
moveSpeed: 5
obstacleMask:
serializedVersion: 2
m_Bits: 64
colliderRadius: 0.5
--- !u!1 &5934518940303293264
GameObject:
m_ObjectHideFlags: 0

View File

@@ -1,6 +1,6 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1190045438452732069
--- !u!1 &4288680281016853984
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -8,75 +8,40 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1136290793271151494}
- component: {fileID: 5320225058563658919}
- component: {fileID: 7813271480623895155}
- component: {fileID: 6196606079257550}
- component: {fileID: 7174400527820218549}
- component: {fileID: 2385482746242042445}
- component: {fileID: 6654368395698888506}
- component: {fileID: 6351228451172460299}
m_Layer: 0
m_Name: ControllerSwitchItem_To_pulver
m_Name: TrashMazeSwitch_ToPulver
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1136290793271151494
--- !u!4 &7174400527820218549
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1190045438452732069}
m_GameObject: {fileID: 4288680281016853984}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -56, y: -38.8, z: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 10, y: 10, z: 10}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5320225058563658919
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1190045438452732069}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 915abd653d714ea3ae11bbf14feafb1e, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Items.ControllerSwitchItem
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: []
targetControllerName: pulver
cameraSwitchMode: 2
targetVirtualCamera: {fileID: 0}
targetCameraState: 1
visualRepresentation: {fileID: 0}
OnCharacterSwitch:
m_PersistentCalls:
m_Calls: []
--- !u!212 &7813271480623895155
--- !u!212 &2385482746242042445
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1190045438452732069}
m_GameObject: {fileID: 4288680281016853984}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
@@ -128,13 +93,13 @@ SpriteRenderer:
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!61 &6196606079257550
--- !u!61 &6654368395698888506
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1190045438452732069}
m_GameObject: {fileID: 4288680281016853984}
m_Enabled: 1
serializedVersion: 3
m_Density: 1
@@ -174,3 +139,37 @@ BoxCollider2D:
m_AutoTiling: 0
m_Size: {x: 0.44087836, y: 0.440879}
m_EdgeRadius: 0
--- !u!114 &6351228451172460299
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4288680281016853984}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe2d6200d0d54638adb61befd932228f, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.TrashMaze.Objects.TrashMazeSwitchToPulver
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: []
targetControllerName: pulver
visualRepresentation: {fileID: 0}
OnCharacterSwitch:
m_PersistentCalls:
m_Calls: []
teleportTarget: {fileID: 0}
targetCameraState: 1

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 67a60833f9f205940a2308bd74a2863e
guid: 842cf175dd9fcfa438021df8b9868fed
PrefabImporter:
externalObjects: {}
userData:

File diff suppressed because it is too large Load Diff

View File

@@ -406,6 +406,13 @@ namespace Input
while (!_interruptMoveTo)
{
// Use AIPath's built-in destination check if available
if (_aiPath != null && _aiPath.reachedDestination)
{
break;
}
// Fallback to distance check
Vector2 current2D = new Vector2(transform.position.x, transform.position.y);
Vector2 target2D = new Vector2(target.x, target.y);
float dist = Vector2.Distance(current2D, target2D);
@@ -425,6 +432,74 @@ namespace Input
}
#endregion
#region Controller Lifecycle
/// <summary>
/// Called when this controller is given input control.
/// Default implementation cleans up any active movement state.
/// Override to add controller-specific activation logic.
/// </summary>
public virtual void ActivateController()
{
// Stop any in-progress movement coroutines
if (_moveToCoroutine != null)
{
StopCoroutine(_moveToCoroutine);
_moveToCoroutine = null;
}
if (_pathfindingDragCoroutine != null)
{
StopCoroutine(_pathfindingDragCoroutine);
_pathfindingDragCoroutine = null;
}
// Reset movement state
_isHolding = false;
_directMoveVelocity = Vector3.zero;
_interruptMoveTo = false;
Logging.Debug($"[{GetType().Name}] Controller activated");
}
/// <summary>
/// Called when this controller loses input control.
/// Default implementation stops all movement and cleans up state.
/// Override to add controller-specific deactivation logic.
/// </summary>
public virtual void DeactivateController()
{
// Stop all movement coroutines
if (_moveToCoroutine != null)
{
StopCoroutine(_moveToCoroutine);
_moveToCoroutine = null;
}
if (_pathfindingDragCoroutine != null)
{
StopCoroutine(_pathfindingDragCoroutine);
_pathfindingDragCoroutine = null;
}
// Reset all movement state
_isHolding = false;
_directMoveVelocity = Vector3.zero;
_interruptMoveTo = false;
_isMoving = false;
// Stop AIPath movement
if (_aiPath != null)
{
_aiPath.enabled = false;
_aiPath.isStopped = true;
}
Logging.Debug($"[{GetType().Name}] Controller deactivated");
}
#endregion
}
}

View File

@@ -1,5 +1,4 @@
using UnityEngine;
using AppleHills.Core.Settings;
using Core;
using Core.Settings;
@@ -45,6 +44,10 @@ namespace Input
InputManager.Instance.RegisterController("trafalgar", this, setAsDefaultConsumer: true);
Logging.Debug($"[PlayerTouchController] Registered controller '{gameObject.name}' as default consumer");
}
// Auto-activate as the default player controller
ActivateController();
Logging.Debug("[PlayerTouchController] Auto-activated as default player controller");
}
#region IInteractingCharacter Override

View File

@@ -3,9 +3,6 @@ using System.Collections;
using Core;
using Input;
using Interactions;
using Minigames.TrashMaze.Core;
using Minigames.TrashMaze.Data;
using Unity.Cinemachine;
using UnityEngine;
using UnityEngine.Events;
@@ -20,61 +17,27 @@ namespace Items
public bool hasBeenUsed;
}
/// <summary>
/// Camera switching mode for controller switch items
/// </summary>
public enum CameraSwitchMode
{
/// <summary>
/// No camera switching - controller switch only
/// </summary>
None,
/// <summary>
/// Use a direct reference to a Cinemachine camera
/// </summary>
DirectReference,
/// <summary>
/// Use TrashMazeCameraController state manager API
/// </summary>
TrashMazeCameraState
}
/// <summary>
/// An interactable item that switches control from one character controller to another.
/// When clicked:
/// 1. The selected character moves to this item's position
/// 2. Upon arrival, the current controller is disabled
/// 3. Camera blends to the target camera (based on camera mode)
/// 4. Once the blend completes, control switches to the target controller
/// Base interactable item that switches control from one character controller to another.
/// Level-agnostic - handles only controller switching logic.
/// Derive from this class for level-specific behavior (camera switching, teleportation, etc.)
/// </summary>
public class ControllerSwitchItem : SaveableInteractable
{
[Header("Controller Switch Settings")]
[Tooltip("Name of the controller to switch to (must match GameObject name of the controller)")]
[SerializeField] private string targetControllerName;
[Header("Camera Settings")]
[Tooltip("How to switch the camera when changing controllers")]
[SerializeField] private CameraSwitchMode cameraSwitchMode = CameraSwitchMode.None;
[Tooltip("Direct camera reference (only used if Camera Switch Mode is DirectReference)")]
[SerializeField] private CinemachineCamera targetVirtualCamera;
[Tooltip("Target camera state (only used if Camera Switch Mode is TrashMazeCameraState)")]
[SerializeField] private TrashMazeCameraState targetCameraState;
[Tooltip("Name of the controller to switch to (must match registration name in InputManager)")]
[SerializeField] protected string targetControllerName;
[Header("Visual Feedback")]
[Tooltip("Visual representation to hide after use (optional)")]
[SerializeField] private GameObject visualRepresentation;
[SerializeField] protected GameObject visualRepresentation;
public UnityEvent OnCharacterSwitch;
// State
private bool _hasBeenUsed;
private PlayerTouchController _currentPlayerController;
private bool _isSwitching;
protected bool _hasBeenUsed;
protected bool _isSwitching;
public override string SaveId => $"{gameObject.scene.name}/ControllerSwitchItem/{gameObject.name}";
@@ -123,68 +86,50 @@ namespace Items
if (_isSwitching)
return false;
// By the time this is called, the interacting character has already arrived at this item
// We just need to perform the controller/camera switch
Logging.Debug("[ControllerSwitchItem] Starting controller switch sequence");
// Start the async switch sequence (camera blend + controller switch)
StartCoroutine(SwitchControllerSequence());
// Return true immediately - interaction is considered successful
// The coroutine will handle the actual switching asynchronously
return true;
}
private IEnumerator SwitchControllerSequence()
protected virtual IEnumerator SwitchControllerSequence()
{
_isSwitching = true;
// Step 1: Get current player controller (the one we're switching FROM)
_currentPlayerController = FindFirstObjectByType<PlayerTouchController>();
if (_currentPlayerController == null)
// Step 1: Get controllers
var currentController = InputManager.Instance.GetActiveController();
var targetController = InputManager.Instance.GetController(targetControllerName);
if (currentController == null || targetController == null)
{
Debug.LogError("[ControllerSwitchItem] Could not find PlayerTouchController in scene!");
Debug.LogError($"[ControllerSwitchItem] Failed to get controllers! Current: {currentController}, Target: {targetController} (name: {targetControllerName})");
_isSwitching = false;
yield break;
}
Logging.Debug("[ControllerSwitchItem] Character has arrived, beginning switch");
GameObject currentGameObject = (currentController as MonoBehaviour)?.gameObject;
GameObject targetGameObject = (targetController as MonoBehaviour)?.gameObject;
// Step 2: Disable current player controller
_currentPlayerController.enabled = false;
Logging.Debug("[ControllerSwitchItem] Disabled current player controller");
Logging.Debug($"[ControllerSwitchItem] Switching from {currentGameObject?.name} to {targetGameObject?.name}");
// Step 3: Blend to target camera based on mode
yield return SwitchCamera();
// Step 2: Deactivate current controller
DeactivateCurrentController(currentController, currentGameObject);
// Step 4: Switch to target controller
ITouchInputConsumer targetController = InputManager.Instance.GetController(targetControllerName);
if (targetController != null)
// Step 3: Activate target controller
ActivateTargetController(targetController, targetGameObject);
// Step 4: Switch InputManager to target controller
bool switchSuccess = InputManager.Instance.SwitchToController(targetControllerName);
if (switchSuccess)
{
// Enable the target controller if it's a MonoBehaviour
if (targetController is MonoBehaviour targetMono)
{
targetMono.enabled = true;
Logging.Debug($"[ControllerSwitchItem] Enabled target controller: {targetControllerName}");
}
// Switch input control to the target controller
bool switchSuccess = InputManager.Instance.SwitchToController(targetControllerName);
if (switchSuccess)
{
Logging.Debug($"[ControllerSwitchItem] Successfully switched input to controller: {targetControllerName}");
OnCharacterSwitch.Invoke();
}
else
{
Debug.LogError($"[ControllerSwitchItem] Failed to switch to controller: {targetControllerName}");
}
Logging.Debug($"[ControllerSwitchItem] Successfully switched input to controller: {targetControllerName}");
OnCharacterSwitch.Invoke();
}
else
{
Debug.LogError($"[ControllerSwitchItem] Target controller '{targetControllerName}' not found!");
Debug.LogError($"[ControllerSwitchItem] Failed to switch to controller: {targetControllerName}");
}
// Step 5: Mark as used if one-time use
@@ -197,79 +142,65 @@ namespace Items
_isSwitching = false;
}
private IEnumerator SwitchCamera()
protected virtual void DeactivateCurrentController(ITouchInputConsumer currentController, GameObject currentGameObject)
{
switch (cameraSwitchMode)
// If current is a player controller, deactivate it
if (currentController is BasePlayerMovementController currentPlayerController)
{
case CameraSwitchMode.None:
// No camera switching
Logging.Debug("[ControllerSwitchItem] No camera switching configured");
break;
currentPlayerController.DeactivateController();
}
case CameraSwitchMode.DirectReference:
if (targetVirtualCamera != null)
{
Logging.Debug($"[ControllerSwitchItem] Blending to camera: {targetVirtualCamera.name}");
// Set the target camera as highest priority
targetVirtualCamera.Priority = 100;
// Wait for camera blend to complete
yield return WaitForCameraBlend();
}
else
{
Debug.LogWarning("[ControllerSwitchItem] DirectReference mode selected but no camera assigned!");
}
break;
case CameraSwitchMode.TrashMazeCameraState:
if (TrashMazeCameraController.Instance != null)
{
Logging.Debug($"[ControllerSwitchItem] Switching to camera state: {targetCameraState}");
// Use the state manager API
if (targetCameraState == TrashMazeCameraState.Gameplay)
{
TrashMazeCameraController.Instance.SwitchToGameplay();
}
else if (targetCameraState == TrashMazeCameraState.Maze)
{
TrashMazeCameraController.Instance.SwitchToMaze();
}
// Wait for camera blend to complete
yield return WaitForCameraBlend();
}
else
{
Debug.LogError("[ControllerSwitchItem] TrashMazeCameraController instance not found in scene!");
}
break;
// If switching FROM follower mode, deactivate follower
if (currentGameObject != null)
{
var currentFollower = currentGameObject.GetComponent<FollowerController>();
if (currentFollower != null && currentFollower.IsFollowerActive)
{
currentFollower.DeactivateFollower();
}
}
}
private IEnumerator WaitForCameraBlend()
protected virtual void ActivateTargetController(ITouchInputConsumer targetController, GameObject targetGameObject)
{
CinemachineBrain brain = Camera.main?.GetComponent<CinemachineBrain>();
if (brain != null)
// Check if target GameObject has FollowerController component
FollowerController targetFollower = null;
if (targetGameObject != null)
{
// Wait until blend is not active
while (brain.IsBlending)
{
yield return null;
}
targetFollower = targetGameObject.GetComponent<FollowerController>();
}
Logging.Debug("[ControllerSwitchItem] Camera blend completed");
// If switching TO a GameObject with FollowerController, we need special handling
if (targetFollower != null)
{
// Switching TO Pulver player control (Pulver has both FollowerController and PulverController)
// Deactivate follower mode, activate player control
targetFollower.DeactivateFollower();
if (targetController is BasePlayerMovementController targetPlayerController)
{
targetPlayerController.ActivateController();
}
}
else
{
// If no brain, just wait a brief moment
yield return new WaitForSeconds(0.5f);
// Switching TO Trafalgar (no FollowerController on Trafalgar)
// If there's a Pulver in the scene, activate its follower mode
var pulverFollower = FindFirstObjectByType<FollowerController>();
if (pulverFollower != null)
{
pulverFollower.ActivateFollower();
}
// Activate the target player controller
if (targetController is BasePlayerMovementController targetPlayerController)
{
targetPlayerController.ActivateController();
}
}
}
private void DisableVisual()
protected void DisableVisual()
{
if (visualRepresentation != null)
{

View File

@@ -0,0 +1,136 @@
using Core;
using UnityEngine;
namespace Interactions
{
/// <summary>
/// Triggers an animation when an interactable interaction completes successfully.
/// Auto-discovers Interactable and Animator components on the same GameObject or children.
/// </summary>
public class TriggerAnimationOnInteraction : MonoBehaviour
{
[Header("Component References (Optional)")]
[Tooltip("Interactable to listen to. Leave empty to auto-discover on this GameObject or children.")]
[SerializeField] private InteractableBase interactable;
[Tooltip("Animator to trigger. Leave empty to auto-discover on this GameObject or children.")]
[SerializeField] private Animator animator;
[Header("Animation Settings")]
[Tooltip("Name of the animation trigger to fire when interaction completes")]
[SerializeField] private string triggerName;
private InteractableBase _interactable;
private Animator _animator;
private bool _isInitialized;
private void Awake()
{
DiscoverComponents();
}
private void OnEnable()
{
if (_isInitialized && _interactable != null)
{
_interactable.interactionComplete.AddListener(OnInteractionComplete);
}
}
private void OnDisable()
{
if (_interactable != null)
{
_interactable.interactionComplete.RemoveListener(OnInteractionComplete);
}
}
/// <summary>
/// Auto-discover Interactable and Animator components if not manually assigned
/// </summary>
private void DiscoverComponents()
{
// Use assigned Interactable or try to find it
_interactable = interactable;
if (_interactable == null)
{
_interactable = GetComponent<InteractableBase>();
}
if (_interactable == null)
{
_interactable = GetComponentInChildren<InteractableBase>();
}
if (_interactable == null)
{
Debug.LogError($"[TriggerAnimationOnInteraction] No InteractableBase found on {gameObject.name} or its children!");
return;
}
// Use assigned Animator or try to find it
_animator = animator;
if (_animator == null)
{
_animator = GetComponent<Animator>();
}
if (_animator == null)
{
_animator = GetComponentInChildren<Animator>();
}
if (_animator == null)
{
Debug.LogError($"[TriggerAnimationOnInteraction] No Animator found on {gameObject.name} or its children!");
return;
}
if (string.IsNullOrEmpty(triggerName))
{
Debug.LogWarning($"[TriggerAnimationOnInteraction] Trigger name is empty on {gameObject.name}!");
}
_isInitialized = true;
string interactableSource = interactable != null ? "assigned" : "auto-discovered";
string animatorSource = animator != null ? "assigned" : "auto-discovered";
Logging.Debug($"[TriggerAnimationOnInteraction] Initialized on {gameObject.name} - Interactable: {_interactable.gameObject.name} ({interactableSource}), Animator: {_animator.gameObject.name} ({animatorSource})");
}
/// <summary>
/// Called when interaction completes
/// </summary>
/// <param name="success">Whether the interaction was successful</param>
private void OnInteractionComplete(bool success)
{
if (!success)
{
Logging.Debug($"[TriggerAnimationOnInteraction] Interaction failed, not triggering animation");
return;
}
if (_animator == null || string.IsNullOrEmpty(triggerName))
{
return;
}
_animator.SetTrigger(triggerName);
Logging.Debug($"[TriggerAnimationOnInteraction] Triggered animation: {triggerName}");
}
#if UNITY_EDITOR
private void OnValidate()
{
// Validate in editor
if (string.IsNullOrEmpty(triggerName))
{
name = "TriggerAnimationOnInteraction (NO TRIGGER)";
}
else
{
name = $"TriggerAnimationOnInteraction ({triggerName})";
}
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f7da1b8703214c4e813e672ba8fc3b8e
timeCreated: 1766153752

View File

@@ -23,6 +23,9 @@ namespace Minigames.TrashMaze.Core
// Vision radius loaded from settings
private float _visionRadius;
// Controller active state
private bool _isControllerActive = false;
// Public accessors for other systems
public static Vector2 PlayerPosition => Instance != null ? Instance.transform.position : Vector2.zero;
public static float VisionRadius => Instance != null ? Instance._visionRadius : 8f;
@@ -64,10 +67,18 @@ namespace Minigames.TrashMaze.Core
InputManager.Instance.RegisterController("pulver", this, setAsDefaultConsumer: false);
Logging.Debug($"[PulverController] Registered controller '{gameObject.name}'");
}
// Start deactivated - PulverController only activates when explicitly switched to
// This allows FollowerController to be the default mode for Pulver
DeactivateController();
Logging.Debug("[PulverController] Auto-deactivated - waiting for controller switch");
}
protected override void Update()
{
// Only process if this controller is active
if (!_isControllerActive) return;
base.Update(); // Call base for movement and animation
// Update global shader properties for vision system
@@ -127,6 +138,37 @@ namespace Minigames.TrashMaze.Core
}
#endregion
#region Controller Lifecycle Overrides
public override void ActivateController()
{
base.ActivateController();
_isControllerActive = true;
// Enable AIPath for player control
if (_aiPath != null)
{
_aiPath.enabled = true;
_aiPath.maxSpeed = Settings.MoveSpeed;
}
Logging.Debug("[PulverController] Controller activated - shader updates enabled");
}
public override void DeactivateController()
{
// DO NOT call base.DeactivateController() - it disables the shared AIPath
// that FollowerController needs for pickup dispatch!
// Just manage our own internal state instead
_isControllerActive = false;
Logging.Debug("[PulverController] Controller deactivated - shader updates paused (AIPath untouched for FollowerController)");
}
#endregion
}
}

View File

@@ -84,6 +84,17 @@ namespace Minigames.TrashMaze.Core
Logging.Debug("[TrashMazeCameraController] Switched to Maze camera");
}
/// <summary>
/// Switch to Pulver gameplay camera (Pulver in main level, player-controlled)
/// </summary>
public void SwitchToPulverGameplay()
{
SwitchToState(TrashMazeCameraState.PulverGameplay);
if (showDebugLogs)
Logging.Debug("[TrashMazeCameraController] Switched to PulverGameplay camera");
}
/// <summary>
/// Get the gameplay camera
/// </summary>
@@ -100,6 +111,14 @@ namespace Minigames.TrashMaze.Core
return GetCamera(TrashMazeCameraState.Maze);
}
/// <summary>
/// Get the Pulver gameplay camera
/// </summary>
public CinemachineCamera GetPulverGameplayCamera()
{
return GetCamera(TrashMazeCameraState.PulverGameplay);
}
#endregion
}
}

View File

@@ -15,9 +15,6 @@ namespace Minigames.TrashMaze.Core
{
public static TrashMazeController Instance { get; private set; }
[Header("Player")]
[SerializeField] private PulverController pulverController;
[Header("Background")]
[Tooltip("Background sprite renderer - world size and center are inferred from its bounds")]
[SerializeField] private SpriteRenderer backgroundRenderer;
@@ -118,15 +115,6 @@ namespace Minigames.TrashMaze.Core
private void InitializePulver()
{
if (pulverController == null)
{
Logging.Error("[TrashMazeController] PulverController reference not assigned! Please assign it in the Inspector.");
return;
}
Logging.Debug($"[TrashMazeController] Pulver controller initialized at {pulverController.transform.position}");
// TODO: Implement proper events for maze start and finish
pulverControllerSwitch.OnCharacterSwitch.AddListener(SwitchedToPulver);
trafalgarControllerSwitch.OnCharacterSwitch.AddListener(SwitchedToTrafalgar);
}

View File

@@ -13,7 +13,12 @@
/// <summary>
/// Maze camera following Pulver when exploring the maze alone
/// </summary>
Maze
Maze,
/// <summary>
/// Gameplay camera following Pulver (after exiting maze while still player-controlled)
/// </summary>
PulverGameplay
}
}

View File

@@ -0,0 +1,92 @@
using System.Collections;
using Core;
using Interactions;
using Minigames.TrashMaze.Core;
using Minigames.TrashMaze.Data;
using UnityEngine;
namespace Minigames.TrashMaze.Objects
{
/// <summary>
/// Maze exit interactable that teleports Pulver out of the maze.
/// Uses camera blend with midway teleportation to create seamless transition.
/// Does NOT switch controllers - Pulver remains player-controlled.
/// </summary>
public class MazeExit : SaveableInteractable
{
[Header("Maze Exit Settings")]
[Tooltip("Transform where Pulver should be teleported to (maze exit position)")]
[SerializeField] private Transform teleportTarget;
[Tooltip("Camera state to blend to (typically PulverGameplay)")]
[SerializeField] private TrashMazeCameraState targetCameraState = TrashMazeCameraState.PulverGameplay;
public override string SaveId => $"{gameObject.scene.name}/MazeExit/{gameObject.name}";
protected override object GetSerializableState()
{
// MazeExit doesn't need to save state - it's a simple trigger
return null;
}
protected override void ApplySerializableState(string state)
{
// MazeExit doesn't need to restore state
}
protected override bool DoInteraction()
{
Logging.Debug("[MazeExit] Starting maze exit sequence");
StartCoroutine(ExitMazeSequence());
return true;
}
private IEnumerator ExitMazeSequence()
{
// Step 1: Find Pulver (should be the active player-controlled character)
var pulverController = FindFirstObjectByType<PulverController>();
if (pulverController == null)
{
Debug.LogError("[MazeExit] PulverController not found in scene!");
yield break;
}
GameObject pulverGameObject = pulverController.gameObject;
Logging.Debug($"[MazeExit] Found Pulver at {pulverGameObject.transform.position}");
// Step 2: Start camera blend to target state (PulverGameplay)
TeleportationHelper.StartCameraBlend(targetCameraState);
// Step 3: Wait for halfway through blend, teleport Pulver, and set tracking target
yield return TeleportationHelper.TeleportMidBlendAndSetTracking(pulverGameObject, teleportTarget, targetCameraState);
// Step 4: Wait for camera blend to complete
yield return TeleportationHelper.WaitForCameraBlend();
// Step 5: Stop Pulver movement to prevent it from continuing with cached input
if (pulverController != null)
{
pulverController.InterruptMoveTo();
Logging.Debug("[MazeExit] Stopped Pulver movement after teleportation");
}
Logging.Debug("[MazeExit] Maze exit sequence completed");
}
#if UNITY_EDITOR
private void OnValidate()
{
name = "MazeExit";
// Default to PulverGameplay camera
if (targetCameraState == TrashMazeCameraState.Gameplay)
{
targetCameraState = TrashMazeCameraState.PulverGameplay;
}
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4543937f547b49ce8e506aac52442f73
timeCreated: 1766151078

View File

@@ -0,0 +1,158 @@
using System.Collections;
using Core;
using Minigames.TrashMaze.Core;
using Minigames.TrashMaze.Data;
using Unity.Cinemachine;
using UnityEngine;
namespace Minigames.TrashMaze.Objects
{
/// <summary>
/// Static helper class for trash maze teleportation logic.
/// Provides reusable methods for:
/// - Starting camera blend to a target state
/// - Teleporting a character midway through blend
/// - Repositioning camera to teleported character
/// - Setting up camera tracking after blend completes
/// </summary>
public static class TeleportationHelper
{
/// <summary>
/// Get the camera for the target camera state
/// </summary>
private static CinemachineCamera GetTargetCamera(TrashMazeCameraState cameraState)
{
if (TrashMazeCameraController.Instance == null)
return null;
return cameraState switch
{
TrashMazeCameraState.Gameplay => TrashMazeCameraController.Instance.GetGameplayCamera(),
TrashMazeCameraState.Maze => TrashMazeCameraController.Instance.GetMazeCamera(),
TrashMazeCameraState.PulverGameplay => TrashMazeCameraController.Instance.GetPulverGameplayCamera(),
_ => null
};
}
/// <summary>
/// Start the camera blend to the target state
/// </summary>
public static void StartCameraBlend(TrashMazeCameraState targetState)
{
if (TrashMazeCameraController.Instance != null)
{
Logging.Debug($"[TeleportationHelper] Starting camera blend to {targetState}");
switch (targetState)
{
case TrashMazeCameraState.Gameplay:
TrashMazeCameraController.Instance.SwitchToGameplay();
break;
case TrashMazeCameraState.Maze:
TrashMazeCameraController.Instance.SwitchToMaze();
break;
case TrashMazeCameraState.PulverGameplay:
TrashMazeCameraController.Instance.SwitchToPulverGameplay();
break;
}
}
else
{
Debug.LogError($"[TeleportationHelper] TrashMazeCameraController instance not found!");
}
}
/// <summary>
/// Coroutine that waits for halfway through camera blend, then teleports the character
/// and immediately sets it as the tracking target for the camera
/// </summary>
public static IEnumerator TeleportMidBlendAndSetTracking(GameObject character, Transform teleportTarget, TrashMazeCameraState targetCameraState)
{
CinemachineBrain brain = Camera.main?.GetComponent<CinemachineBrain>();
if (brain != null && brain.IsBlending)
{
// Get blend duration from brain
float blendDuration = brain.ActiveBlend != null ? brain.ActiveBlend.Duration : 1f;
float halfBlendTime = blendDuration / 2f;
Logging.Debug($"[TeleportationHelper] Waiting {halfBlendTime:F2}s (half of {blendDuration:F2}s blend) before teleport");
// Wait for halfway through the blend
yield return new WaitForSeconds(halfBlendTime);
}
else
{
// Fallback: wait a short moment if no blend is detected
yield return new WaitForSeconds(0.25f);
}
// Teleport character
if (character != null && teleportTarget != null)
{
character.transform.position = teleportTarget.position;
character.transform.rotation = teleportTarget.rotation;
Logging.Debug($"[TeleportationHelper] Teleported {character.name} to {teleportTarget.position}");
// Immediately set as tracking target - let the blend finish naturally
SetCameraTrackingTarget(character, targetCameraState);
}
else
{
if (character == null)
Debug.LogError($"[TeleportationHelper] Character GameObject is null!");
if (teleportTarget == null)
Debug.LogError($"[TeleportationHelper] Teleport target not assigned!");
}
}
/// <summary>
/// Wait for camera blend to complete
/// </summary>
public static IEnumerator WaitForCameraBlend()
{
CinemachineBrain brain = Camera.main?.GetComponent<CinemachineBrain>();
if (brain != null)
{
// Wait until blend is complete
while (brain.IsBlending)
{
yield return null;
}
Logging.Debug($"[TeleportationHelper] Camera blend completed");
}
else
{
// Fallback: wait a brief moment
yield return new WaitForSeconds(0.5f);
}
}
/// <summary>
/// Set a character as the tracking target for the target camera
/// </summary>
public static void SetCameraTrackingTarget(GameObject character, TrashMazeCameraState targetCameraState)
{
if (character == null)
{
Debug.LogError($"[TeleportationHelper] Cannot set tracking target - character is null");
return;
}
var targetCamera = GetTargetCamera(targetCameraState);
if (targetCamera != null)
{
targetCamera.Follow = character.transform;
targetCamera.LookAt = character.transform;
Logging.Debug($"[TeleportationHelper] Set {character.name} as tracking target for {targetCameraState} camera");
}
else
{
Debug.LogError($"[TeleportationHelper] Target camera for state {targetCameraState} not found!");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1cf42e4c3a1e3c345aaef352bf84c762

View File

@@ -0,0 +1,109 @@
using System.Collections;
using Core;
using Input;
using Items;
using Minigames.TrashMaze.Core;
using Minigames.TrashMaze.Data;
using Unity.Cinemachine;
using UnityEngine;
namespace Minigames.TrashMaze.Objects
{
/// <summary>
/// Trash Maze specific controller switch that transitions TO Pulver (maze entrance).
/// Handles camera blend with midway teleportation to create the illusion of entering the maze.
/// </summary>
public class TrashMazeSwitchToPulver : ControllerSwitchItem
{
[Header("Trash Maze - To Pulver Settings")]
[Tooltip("Transform where Pulver should be teleported to (maze entrance)")]
[SerializeField] private Transform teleportTarget;
[Tooltip("Camera state to blend to (Maze camera)")]
[SerializeField] private TrashMazeCameraState targetCameraState = TrashMazeCameraState.Maze;
protected override IEnumerator SwitchControllerSequence()
{
_isSwitching = true;
// Step 1: Get controllers
var currentController = InputManager.Instance.GetActiveController();
var targetController = InputManager.Instance.GetController("pulver");
if (currentController == null || targetController == null)
{
Debug.LogError($"[TrashMazeSwitchToPulver] Failed to get controllers!");
_isSwitching = false;
yield break;
}
GameObject currentGameObject = (currentController as MonoBehaviour)?.gameObject;
GameObject targetGameObject = (targetController as MonoBehaviour)?.gameObject;
Logging.Debug($"[TrashMazeSwitchToPulver] Switching from {currentGameObject?.name} to Pulver");
// Step 2: Deactivate current controller (Trafalgar)
DeactivateCurrentController(currentController, currentGameObject);
// Step 3: Deactivate Pulver's follower controller (will be teleported)
if (targetGameObject != null)
{
var pulverFollower = targetGameObject.GetComponent<FollowerController>();
if (pulverFollower != null)
{
pulverFollower.DeactivateFollower();
}
}
// Step 4: Start camera blend to maze
TeleportationHelper.StartCameraBlend(targetCameraState);
// Step 5: Wait for halfway through the blend, teleport Pulver, and set tracking target
yield return TeleportationHelper.TeleportMidBlendAndSetTracking(targetGameObject, teleportTarget, targetCameraState);
// Step 6: Wait for camera blend to complete
yield return TeleportationHelper.WaitForCameraBlend();
// Step 7: Activate Pulver controller
if (targetController is BasePlayerMovementController targetPlayerController)
{
targetPlayerController.ActivateController();
}
// Step 8: Switch InputManager to Pulver controller
bool switchSuccess = InputManager.Instance.SwitchToController("pulver");
if (switchSuccess)
{
Logging.Debug($"[TrashMazeSwitchToPulver] Successfully switched to Pulver controller");
OnCharacterSwitch.Invoke();
}
else
{
Debug.LogError($"[TrashMazeSwitchToPulver] Failed to switch to Pulver controller");
}
// Step 9: Mark as used if one-time use
if (isOneTime)
{
DisableVisual();
}
_isSwitching = false;
}
#if UNITY_EDITOR
private void OnValidate()
{
name = "TrashMazeSwitch_ToPulver";
// Default to Maze camera
if (targetCameraState != TrashMazeCameraState.Maze)
{
targetCameraState = TrashMazeCameraState.Maze;
}
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fe2d6200d0d54638adb61befd932228f
timeCreated: 1766149622

View File

@@ -0,0 +1,180 @@
using System.Collections;
using Core;
using Input;
using Items;
using Minigames.TrashMaze.Core;
using Minigames.TrashMaze.Data;
using Unity.Cinemachine;
using UnityEngine;
namespace Minigames.TrashMaze.Objects
{
/// <summary>
/// Trash Maze specific controller switch that transitions FROM Pulver back TO Trafalgar (maze exit).
/// Handles camera blend back to gameplay view and re-enables follower mode.
/// </summary>
public class TrashMazeSwitchToTrafalgar : ControllerSwitchItem
{
[Header("Trash Maze - To Trafalgar Settings")]
[Tooltip("Camera state to blend to (Gameplay camera)")]
[SerializeField] private TrashMazeCameraState targetCameraState = TrashMazeCameraState.Gameplay;
protected override IEnumerator SwitchControllerSequence()
{
_isSwitching = true;
// Step 1: Get controllers
var currentController = InputManager.Instance.GetActiveController();
var targetController = InputManager.Instance.GetController("trafalgar");
if (currentController == null || targetController == null)
{
Debug.LogError($"[TrashMazeSwitchToTrafalgar] Failed to get controllers!");
_isSwitching = false;
yield break;
}
GameObject currentGameObject = (currentController as MonoBehaviour)?.gameObject;
GameObject targetGameObject = (targetController as MonoBehaviour)?.gameObject;
Logging.Debug($"[TrashMazeSwitchToTrafalgar] Switching from Pulver to {targetGameObject?.name}");
// Step 2: Deactivate current controller (Pulver)
DeactivateCurrentController(currentController, currentGameObject);
// Explicitly deactivate PulverController to ensure it stops receiving input
if (currentGameObject != null)
{
var pulverController = currentGameObject.GetComponent<PulverController>();
if (pulverController != null)
{
pulverController.DeactivateController();
Logging.Debug("[TrashMazeSwitchToTrafalgar] Explicitly deactivated PulverController");
}
}
// Step 3: Start camera blend back to gameplay
StartCameraBlend();
// Step 4: Wait for camera blend to complete
yield return WaitForCameraBlend();
// Step 5: Unset Pulver as tracking target and set Trafalgar for gameplay camera
SetTrafalgarAsTrackingTarget(targetGameObject);
// Step 6: Activate Pulver's follower mode (so it follows Trafalgar)
if (currentGameObject != null)
{
var pulverFollower = currentGameObject.GetComponent<FollowerController>();
if (pulverFollower != null)
{
pulverFollower.ActivateFollower();
}
}
// Step 7: Activate Trafalgar controller
if (targetController is BasePlayerMovementController targetPlayerController)
{
targetPlayerController.ActivateController();
}
// Step 8: Switch InputManager to Trafalgar controller
bool switchSuccess = InputManager.Instance.SwitchToController("trafalgar");
if (switchSuccess)
{
Logging.Debug($"[TrashMazeSwitchToTrafalgar] Successfully switched to Trafalgar controller");
OnCharacterSwitch.Invoke();
}
else
{
Debug.LogError($"[TrashMazeSwitchToTrafalgar] Failed to switch to Trafalgar controller");
}
// Step 8: Mark as used if one-time use
if (isOneTime)
{
DisableVisual();
}
_isSwitching = false;
}
private void StartCameraBlend()
{
if (TrashMazeCameraController.Instance != null)
{
Logging.Debug($"[TrashMazeSwitchToTrafalgar] Starting camera blend to {targetCameraState}");
if (targetCameraState == TrashMazeCameraState.Gameplay)
{
TrashMazeCameraController.Instance.SwitchToGameplay();
}
else
{
Logging.Warning($"[TrashMazeSwitchToTrafalgar] Unexpected camera state: {targetCameraState}");
}
}
else
{
Debug.LogError("[TrashMazeSwitchToTrafalgar] TrashMazeCameraController instance not found!");
}
}
private IEnumerator WaitForCameraBlend()
{
CinemachineBrain brain = Camera.main?.GetComponent<CinemachineBrain>();
if (brain != null)
{
// Wait until blend is complete
while (brain.IsBlending)
{
yield return null;
}
Logging.Debug("[TrashMazeSwitchToTrafalgar] Camera blend completed");
}
else
{
// Fallback: wait a brief moment
yield return new WaitForSeconds(0.5f);
}
}
private void SetTrafalgarAsTrackingTarget(GameObject trafalgarGameObject)
{
if (TrashMazeCameraController.Instance != null)
{
// Clear maze camera tracking target
var mazeCamera = TrashMazeCameraController.Instance.GetMazeCamera();
if (mazeCamera != null)
{
mazeCamera.Follow = null;
mazeCamera.LookAt = null;
Logging.Debug($"[TrashMazeSwitchToTrafalgar] Cleared Pulver as tracking target from maze camera");
}
// Set Trafalgar as tracking target for gameplay camera
if (trafalgarGameObject != null)
{
var gameplayCamera = TrashMazeCameraController.Instance.GetGameplayCamera();
if (gameplayCamera != null)
{
gameplayCamera.Follow = trafalgarGameObject.transform;
gameplayCamera.LookAt = trafalgarGameObject.transform;
Logging.Debug($"[TrashMazeSwitchToTrafalgar] Set Trafalgar as tracking target for gameplay camera");
}
}
}
}
#if UNITY_EDITOR
private void OnValidate()
{
name = "TrashMazeSwitch_ToTrafalgar";
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 60a323cce5144fe9bae5dd3b313315a1
timeCreated: 1766149639

View File

@@ -42,6 +42,9 @@ public class FollowerController : ManagedBehaviour
private IFollowerSettings _settings;
private IInteractionSettings _interactionSettings;
// Follower active state
private bool _isFollowerActive = false;
private GameObject _playerRef;
private Transform _playerTransform;
private AIPath _playerAIPath;
@@ -133,10 +136,18 @@ public class FollowerController : ManagedBehaviour
{
// Find player reference when scene is ready (called for every scene load)
FindPlayerReference();
// Auto-activate follower mode when scene is ready
// This ensures Pulver automatically follows Trafalgar in any scene by default
ActivateFollower();
Logging.Debug("[FollowerController] Auto-activated follower mode on scene ready");
}
void Update()
{
// Only process if follower is active
if (!_isFollowerActive) return;
if (_playerTransform == null)
{
return;
@@ -274,6 +285,118 @@ public class FollowerController : ManagedBehaviour
}
}
#region Follower Lifecycle
/// <summary>
/// Activate follower behavior - starts following the player.
/// </summary>
public void ActivateFollower()
{
_isFollowerActive = true;
// Find/refresh player reference
FindPlayerReference();
// Check if a pickup is currently in progress
bool pickupInProgress = _pickupCoroutine != null;
if (!pickupInProgress)
{
// Only reset to manual following mode if no pickup is active
_isManualFollowing = true;
_isReturningToPlayer = false;
_isPlayingStationaryAnimation = false;
_currentSpeed = 0f;
_timer = 0f;
// Stop stationary animation coroutine if active
if (_stationaryAnimationCoroutine != null)
{
StopCoroutine(_stationaryAnimationCoroutine);
_stationaryAnimationCoroutine = null;
}
// Disable AIPath for manual following
if (_aiPath != null)
{
_aiPath.enabled = false;
}
// Initialize follow target position
UpdateFollowTarget();
}
else
{
// Pickup in progress - don't interfere, just mark as active
Logging.Debug("[FollowerController] Follower activated but pickup in progress - not resetting state");
}
// Always enable TrackableTarget when follower is active
var trackableTarget = GetComponent<UI.Tracking.TrackableTarget>();
if (trackableTarget != null)
{
trackableTarget.enabled = true;
}
Logging.Debug("[FollowerController] Follower activated");
}
/// <summary>
/// Deactivate follower behavior - stops all following and movement.
/// </summary>
public void DeactivateFollower()
{
_isFollowerActive = false;
// Stop all coroutines
if (_pickupCoroutine != null)
{
StopCoroutine(_pickupCoroutine);
_pickupCoroutine = null;
}
if (_stationaryAnimationCoroutine != null)
{
StopCoroutine(_stationaryAnimationCoroutine);
_stationaryAnimationCoroutine = null;
}
// Reset movement state
_isManualFollowing = false;
_isReturningToPlayer = false;
_isPlayingStationaryAnimation = false;
_currentSpeed = 0f;
// Disable AIPath
if (_aiPath != null)
{
_aiPath.enabled = false;
_aiPath.isStopped = true;
}
// Disable TrackableTarget component if present
var trackableTarget = GetComponent<UI.Tracking.TrackableTarget>();
if (trackableTarget != null)
{
trackableTarget.enabled = false;
}
// Set animator to idle
if (_animator != null)
{
_animator.SetFloat("Speed", 0f);
}
Logging.Debug("[FollowerController] Follower deactivated");
}
/// <summary>
/// Check if follower is currently active.
/// </summary>
public bool IsFollowerActive => _isFollowerActive;
#endregion
#region Movement
/// <summary>
/// Updates the follower's target point to follow the player at a specified distance,