using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using Unity.Cinemachine;
/// <summary>
/// GAME 1303 Tier 8 — Apep's Trial's core mechanic: the camera switches from first-person
/// 3D to a 2D side-scroller or top-down view, and the controls change to match.
///
/// HOW IT WORKS
/// The player object carries BOTH control schemes at once (FirstPersonController for 3D,
/// PlayerController2D for side-scrolling, TopDownMover for top-down) plus one Cinemachine
/// camera per mode. Switching = enable one controller, disable the others, raise the
/// priority of that mode's camera, and let Cinemachine blend between them.
///
/// SETUP
/// 1. Player: CharacterController + FirstPersonController, AND Rigidbody2D +
/// PlayerController2D, AND TopDownMover. Disable two of the three in the Inspector.
/// (Yes, both a CharacterController and a Rigidbody2D on one object — they operate in
/// different axes and don't fight as long as only one controller is enabled.)
/// 2. Three Cinemachine cameras in the scene, one per mode. Drag each into its slot.
/// 3. Set the CinemachineBrain's Default Blend to Ease In Out, ~0.6s.
/// 4. Switch modes from a trigger: put a ModeSwitchTrigger (below) at the doorway where
/// the perspective changes, or call SetMode() from any UnityEvent.
/// 5. Lock Input During Blend is on by default — letting the player move mid-blend is
/// how you get people walking off ledges they couldn't see.
///
/// A WORD OF ADVICE FOR LAZARO: prototype this switch on a flat grey box level FIRST,
/// before any art or level design. The mechanic either feels good or it doesn't, and you
/// want to know which in week two rather than week ten.
/// </summary>
public class CameraModeSwitcher : MonoBehaviour
{
public enum Mode { FirstPerson3D, SideScroll2D, TopDown2D }
[Header("Cameras")]
public CinemachineCamera camera3D;
public CinemachineCamera cameraSide;
public CinemachineCamera cameraTopDown;
public int activePriority = 20;
public int inactivePriority = 5;
[Header("Controllers (on the player)")]
public FirstPersonController controller3D;
public PlayerController2D controllerSide;
public TopDownMover controllerTopDown;
public CharacterController characterController;
public Rigidbody2D body2D;
[Header("Switching")]
public Mode startingMode = Mode.FirstPerson3D;
public float blendTime = 0.6f;
public bool lockInputDuringBlend = true;
[Tooltip("Freeze the 2D rigidbody while in 3D mode (and vice versa).")]
public bool manageBodies = true;
[Header("Feedback")]
public string switchSfx = "mode_switch";
public UnityEvent<Mode> OnModeChanged;
public UnityEvent OnSwitchStarted;
public UnityEvent OnSwitchFinished;
public Mode CurrentMode { get; private set; }
public bool IsSwitching { get; private set; }
void Start() => ApplyMode(startingMode, true);
/// <summary>Wire a trigger or UnityEvent here. 0 = 3D, 1 = side, 2 = top-down.</summary>
public void SetModeByIndex(int index) => SetMode((Mode)Mathf.Clamp(index, 0, 2));
public void SetMode(Mode mode)
{
if (mode == CurrentMode || IsSwitching) return;
StartCoroutine(SwitchRoutine(mode));
}
IEnumerator SwitchRoutine(Mode mode)
{
IsSwitching = true;
OnSwitchStarted?.Invoke();
if (!string.IsNullOrEmpty(switchSfx)) AudioManager.Instance?.PlaySFX(switchSfx);
if (lockInputDuringBlend) SetAllInput(false);
ApplyMode(mode, false);
yield return new WaitForSeconds(blendTime);
if (lockInputDuringBlend) SetAllInput(true);
IsSwitching = false;
OnSwitchFinished?.Invoke();
}
void ApplyMode(Mode mode, bool instant)
{
CurrentMode = mode;
// Cameras: priority decides which one Cinemachine blends to
if (camera3D != null) camera3D.Priority = mode == Mode.FirstPerson3D ? activePriority : inactivePriority;
if (cameraSide != null) cameraSide.Priority = mode == Mode.SideScroll2D ? activePriority : inactivePriority;
if (cameraTopDown != null) cameraTopDown.Priority = mode == Mode.TopDown2D ? activePriority : inactivePriority;
// Controllers: exactly one is enabled
bool is3D = mode == Mode.FirstPerson3D;
bool isSide = mode == Mode.SideScroll2D;
bool isTop = mode == Mode.TopDown2D;
if (controller3D != null) controller3D.enabled = is3D;
if (controllerSide != null) controllerSide.enabled = isSide;
if (controllerTopDown != null) controllerTopDown.enabled = isTop;
if (manageBodies)
{
if (characterController != null) characterController.enabled = is3D;
if (body2D != null)
{
body2D.simulated = !is3D;
if (!is3D) body2D.gravityScale = isSide ? 3f : 0f;
body2D.linearVelocity = Vector2.zero;
}
}
// Cursor: locked in first person, free otherwise
if (controller3D != null)
{
if (is3D) controller3D.LockCursor();
else controller3D.UnlockCursor();
}
OnModeChanged?.Invoke(mode);
}
void SetAllInput(bool enabled)
{
if (controller3D != null) controller3D.InputEnabled = enabled;
if (controllerSide != null) controllerSide.InputEnabled = enabled;
if (controllerTopDown != null) controllerTopDown.InputEnabled = enabled;
}
}
/* ---- Cinemachine 2.x (Unity 2022) ----
using Cinemachine; and CinemachineVirtualCamera instead of CinemachineCamera.
Priority is an int on both.
*/