319 lines
11 KiB
C#
319 lines
11 KiB
C#
using System;
|
|
using System.Collections;
|
|
using Core;
|
|
using Input;
|
|
using Interactions;
|
|
using Minigames.TrashMaze.Core;
|
|
using Minigames.TrashMaze.Data;
|
|
using Unity.Cinemachine;
|
|
using UnityEngine;
|
|
|
|
namespace Items
|
|
{
|
|
/// <summary>
|
|
/// Saveable data for ControllerSwitchItem state
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ControllerSwitchItemSaveData
|
|
{
|
|
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
|
|
/// </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;
|
|
|
|
[Header("Visual Feedback")]
|
|
[Tooltip("Visual representation to hide after use (optional)")]
|
|
[SerializeField] private GameObject visualRepresentation;
|
|
|
|
// State
|
|
private bool _hasBeenUsed;
|
|
private PlayerTouchController _currentPlayerController;
|
|
private bool _isSwitching;
|
|
|
|
public override string SaveId => $"{gameObject.scene.name}/ControllerSwitchItem/{gameObject.name}";
|
|
|
|
internal override void OnManagedAwake()
|
|
{
|
|
base.OnManagedAwake();
|
|
|
|
if (string.IsNullOrEmpty(targetControllerName))
|
|
{
|
|
Debug.LogError($"[ControllerSwitchItem] {gameObject.name} has no target controller name specified!");
|
|
}
|
|
}
|
|
|
|
internal override void OnManagedStart()
|
|
{
|
|
base.OnManagedStart();
|
|
|
|
// Apply state after restoration
|
|
if (_hasBeenUsed && isOneTime)
|
|
{
|
|
DisableVisual();
|
|
}
|
|
}
|
|
|
|
protected override bool CanBeClicked()
|
|
{
|
|
// Cannot be clicked if already used (one-time) or if currently switching
|
|
if (_isSwitching)
|
|
return false;
|
|
|
|
if (isOneTime && _hasBeenUsed)
|
|
return false;
|
|
|
|
// Check if target controller is registered
|
|
if (!InputManager.Instance.IsControllerRegistered(targetControllerName))
|
|
{
|
|
Debug.LogWarning($"[ControllerSwitchItem] Target controller '{targetControllerName}' is not registered with InputManager.");
|
|
return false;
|
|
}
|
|
|
|
return base.CanBeClicked();
|
|
}
|
|
|
|
protected override bool DoInteraction()
|
|
{
|
|
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()
|
|
{
|
|
_isSwitching = true;
|
|
|
|
// Step 1: Get current player controller (the one we're switching FROM)
|
|
_currentPlayerController = FindFirstObjectByType<PlayerTouchController>();
|
|
if (_currentPlayerController == null)
|
|
{
|
|
Debug.LogError("[ControllerSwitchItem] Could not find PlayerTouchController in scene!");
|
|
_isSwitching = false;
|
|
yield break;
|
|
}
|
|
|
|
Logging.Debug("[ControllerSwitchItem] Character has arrived, beginning switch");
|
|
|
|
// Step 2: Disable current player controller
|
|
_currentPlayerController.enabled = false;
|
|
Logging.Debug("[ControllerSwitchItem] Disabled current player controller");
|
|
|
|
// 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)
|
|
{
|
|
// 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}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[ControllerSwitchItem] Failed to switch to controller: {targetControllerName}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[ControllerSwitchItem] Target controller '{targetControllerName}' not found!");
|
|
}
|
|
|
|
// Step 5: Mark as used if one-time use
|
|
if (isOneTime)
|
|
{
|
|
_hasBeenUsed = true;
|
|
DisableVisual();
|
|
}
|
|
|
|
_isSwitching = false;
|
|
}
|
|
|
|
private IEnumerator SwitchCamera()
|
|
{
|
|
switch (cameraSwitchMode)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
private IEnumerator WaitForCameraBlend()
|
|
{
|
|
CinemachineBrain brain = Camera.main?.GetComponent<CinemachineBrain>();
|
|
if (brain != null)
|
|
{
|
|
// Wait until blend is not active
|
|
while (brain.IsBlending)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
Logging.Debug("[ControllerSwitchItem] Camera blend completed");
|
|
}
|
|
else
|
|
{
|
|
// If no brain, just wait a brief moment
|
|
yield return new WaitForSeconds(0.5f);
|
|
}
|
|
}
|
|
|
|
private void DisableVisual()
|
|
{
|
|
if (visualRepresentation != null)
|
|
{
|
|
visualRepresentation.SetActive(false);
|
|
}
|
|
}
|
|
|
|
#region Save/Load
|
|
|
|
protected override object GetSerializableState()
|
|
{
|
|
return new ControllerSwitchItemSaveData
|
|
{
|
|
hasBeenUsed = _hasBeenUsed
|
|
};
|
|
}
|
|
|
|
protected override void ApplySerializableState(string serializedData)
|
|
{
|
|
try
|
|
{
|
|
var data = JsonUtility.FromJson<ControllerSwitchItemSaveData>(serializedData);
|
|
_hasBeenUsed = data.hasBeenUsed;
|
|
Logging.Debug($"[ControllerSwitchItem] Restored state: hasBeenUsed={_hasBeenUsed}");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[ControllerSwitchItem] Failed to deserialize save data: {e.Message}");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#if UNITY_EDITOR
|
|
private void OnValidate()
|
|
{
|
|
// Visual feedback in editor
|
|
if (string.IsNullOrEmpty(targetControllerName))
|
|
{
|
|
name = "ControllerSwitchItem (UNCONFIGURED)";
|
|
}
|
|
else
|
|
{
|
|
name = $"ControllerSwitchItem_To_{targetControllerName}";
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|