using UnityEngine;
using Cinemachine;
using System.Collections;
///
/// Resets the Main Camera to a known-good position BEFORE CinemachineBrain takes over,
/// then snaps the camera to the correct position AFTER the character spawns.
///
/// This solves the camera-underground problem with a two-phase approach:
/// Phase 1 (Awake): Force camera above ground, set CinemachineBrain blend to Cut
/// Phase 2 (Coroutine): Wait for player character, then force camera to correct orbit position
///
/// Attach this to the MainCamera GameObject.
///
[DefaultExecutionOrder(-100)] // Run before everything else
public class CameraInitializer : MonoBehaviour
{
[Header("Safe Starting Position")]
[Tooltip("Camera starts here before Cinemachine takes control")]
public Vector3 resetPosition = new Vector3(0f, 10f, -10f);
public Vector3 resetRotation = new Vector3(20f, 0f, 0f);
[Header("Spawn Wait")]
[Tooltip("Maximum time to wait for the player character to spawn")]
[SerializeField] private float maxSpawnWaitTime = 5f;
[Tooltip("How often to check for the player character")]
[SerializeField] private float spawnCheckInterval = 0.1f;
[Header("Debug")]
[SerializeField] private bool debugLogging = true;
private void Awake()
{
// ===== PHASE 1: Force camera to a known-good position =====
transform.position = resetPosition;
transform.rotation = Quaternion.Euler(resetRotation);
if (debugLogging)
Debug.Log($"[CameraInitializer] Phase 1: Camera reset to position={resetPosition}");
// Set CinemachineBrain to Cut (instant snap, no blending from underground)
CinemachineBrain brain = GetComponent();
if (brain != null)
{
brain.m_DefaultBlend = new CinemachineBlendDefinition(
CinemachineBlendDefinition.Style.Cut, 0f
);
if (debugLogging)
Debug.Log("[CameraInitializer] CinemachineBrain set to CUT blend");
}
// Invalidate all FreeLook camera states
CinemachineFreeLook[] freeLooks = FindObjectsOfType();
foreach (var fl in freeLooks)
{
fl.m_YAxis.Value = 0.5f;
fl.PreviousStateIsValid = false;
// Set orbit heights
fl.m_Orbits = new CinemachineFreeLook.Orbit[] {
new CinemachineFreeLook.Orbit(4.5f, 5f), // TopRig
new CinemachineFreeLook.Orbit(2.5f, 6f), // MiddleRig
new CinemachineFreeLook.Orbit(0.5f, 5f), // BottomRig
};
// CRITICAL: Clear default input axis names so Cinemachine doesn't fight
// with CinemachineCameraInput. Without this, Cinemachine reads Input.GetAxis("Mouse X")
// which returns 0 and snaps Y axis to bottom rig.
fl.m_XAxis.m_InputAxisName = "";
fl.m_YAxis.m_InputAxisName = "";
if (debugLogging)
Debug.Log($"[CameraInitializer] FreeLook '{fl.name}': YAxis=0.5, axes cleared, state invalidated");
}
}
private IEnumerator Start()
{
// ===== PHASE 2: Wait for player character, then snap camera =====
float elapsed = 0f;
Transform playerTransform = null;
while (elapsed < maxSpawnWaitTime)
{
// Try to find the player character
playerTransform = FindPlayerCharacter();
if (playerTransform != null)
{
if (debugLogging)
Debug.Log($"[CameraInitializer] Phase 2: Found player '{playerTransform.name}' at {playerTransform.position} after {elapsed:F1}s");
break;
}
yield return new WaitForSeconds(spawnCheckInterval);
elapsed += spawnCheckInterval;
}
if (playerTransform == null)
{
Debug.LogWarning("[CameraInitializer] Phase 2: No player found within timeout — camera may be incorrect");
yield break;
}
// Force camera to the correct position relative to the character
SnapCameraToCharacter(playerTransform);
// Wait a frame for Cinemachine to process the snap
yield return null;
yield return null;
// Restore smooth blending for character switching
CinemachineBrain brain = GetComponent();
if (brain != null)
{
brain.m_DefaultBlend = new CinemachineBlendDefinition(
CinemachineBlendDefinition.Style.EaseInOut, 1f
);
if (debugLogging)
Debug.Log("[CameraInitializer] Phase 2: Blend restored to EaseInOut");
}
}
///
/// Force-snap the camera to the correct orbit position relative to the character.
///
private void SnapCameraToCharacter(Transform character)
{
CinemachineFreeLook[] freeLooks = FindObjectsOfType();
foreach (var fl in freeLooks)
{
if (!fl.isActiveAndEnabled) continue;
// Ensure target is set
if (fl.Follow == null)
{
fl.Follow = character;
fl.LookAt = character;
}
// Reset to middle rig
fl.m_YAxis.Value = 0.5f;
fl.PreviousStateIsValid = false;
// Force camera transform to the correct position
// MiddleRig: Height 2.5, Radius 6 — camera should be behind and above
Vector3 targetPos = character.position;
Vector3 cameraPos = targetPos + new Vector3(0f, 2.5f, -6f);
// Set the Main Camera transform directly
transform.position = cameraPos;
transform.LookAt(targetPos + Vector3.up * 1.5f);
if (debugLogging)
{
Debug.Log($"[CameraInitializer] Camera snapped to {cameraPos} (character at {targetPos})");
Debug.Log($"[CameraInitializer] FreeLook Follow={fl.Follow?.name}, LookAt={fl.LookAt?.name}");
}
}
}
///
/// Find the player-controlled character in the scene.
///
private Transform FindPlayerCharacter()
{
// Try CameraManager first
if (CameraManager.Instance != null)
{
var target = CameraManager.Instance.GetCurrentTarget();
if (target != null) return target.transform;
}
// Try finding by tag
GameObject player = GameObject.FindGameObjectWithTag("Player");
if (player != null) return player.transform;
// Try TeamMember with IsPlayerControlled
TeamMember[] teamMembers = FindObjectsOfType();
foreach (var tm in teamMembers)
{
if (tm.IsPlayerControlled) return tm.transform;
}
return null;
}
}