Pulver trash maze sequence
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
GameObject currentGameObject = (currentController as MonoBehaviour)?.gameObject;
|
||||
GameObject targetGameObject = (targetController as MonoBehaviour)?.gameObject;
|
||||
|
||||
Logging.Debug($"[ControllerSwitchItem] Switching from {currentGameObject?.name} to {targetGameObject?.name}");
|
||||
|
||||
Logging.Debug("[ControllerSwitchItem] Character has arrived, beginning switch");
|
||||
// Step 2: Deactivate current controller
|
||||
DeactivateCurrentController(currentController, currentGameObject);
|
||||
|
||||
// Step 2: Disable current player controller
|
||||
_currentPlayerController.enabled = false;
|
||||
Logging.Debug("[ControllerSwitchItem] Disabled current player controller");
|
||||
// Step 3: Activate target controller
|
||||
ActivateTargetController(targetController, targetGameObject);
|
||||
|
||||
// Step 3: Blend to target camera based on mode
|
||||
yield return SwitchCamera();
|
||||
|
||||
// Step 4: Switch to target controller
|
||||
ITouchInputConsumer targetController = InputManager.Instance.GetController(targetControllerName);
|
||||
if (targetController != null)
|
||||
// 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;
|
||||
|
||||
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;
|
||||
currentPlayerController.DeactivateController();
|
||||
}
|
||||
|
||||
// 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>();
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
Logging.Debug("[ControllerSwitchItem] Camera blend completed");
|
||||
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)
|
||||
{
|
||||
|
||||
136
Assets/Scripts/Interactions/TriggerAnimationOnInteraction.cs
Normal file
136
Assets/Scripts/Interactions/TriggerAnimationOnInteraction.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7da1b8703214c4e813e672ba8fc3b8e
|
||||
timeCreated: 1766153752
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
92
Assets/Scripts/Minigames/TrashMaze/Objects/MazeExit.cs
Normal file
92
Assets/Scripts/Minigames/TrashMaze/Objects/MazeExit.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4543937f547b49ce8e506aac52442f73
|
||||
timeCreated: 1766151078
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cf42e4c3a1e3c345aaef352bf84c762
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe2d6200d0d54638adb61befd932228f
|
||||
timeCreated: 1766149622
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60a323cce5144fe9bae5dd3b313315a1
|
||||
timeCreated: 1766149639
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user