using UnityEngine; using Cinemachine; using System.Collections; /// /// Automatically configures Cinemachine FreeLook camera targets on scene start. /// Correctly separates Follow (character root) from LookAt (head bone) /// to prevent orbit collapse and camera looking at the sky. /// public class AutoAddCinemachineCamera : MonoBehaviour { [TagField] public string Tag = string.Empty; void Start() { var vcam = GetComponent(); if (vcam != null && Tag.Length > 0) { bool isCashSystemMode = SelectionOptions.Instance != null && SelectionOptions.Instance.isCashSystemSelected; if (isCashSystemMode) { // In Cash System mode, characters spawn after a delay StartCoroutine(FindPlayerControlledTargetDelayed(vcam, 1.0f)); } else { // Normal mode - find immediately var targets = GameObject.FindGameObjectsWithTag(Tag); if (targets.Length > 0) { SetCameraTargets(vcam, targets[0]); } } } } private IEnumerator FindPlayerControlledTargetDelayed(CinemachineFreeLook vcam, float delay) { yield return new WaitForSeconds(delay); // If CameraManager already set targets, don't override if (CameraManager.Instance != null && CameraManager.Instance.GetCurrentTarget() != null) { Debug.Log("[AutoAddCinemachineCamera] CameraManager already handling camera — skipping."); yield break; } var targets = GameObject.FindGameObjectsWithTag(Tag); // Find the character with IsPlayerControlled = true foreach (var target in targets) { TeamMember teamMember = target.GetComponent(); if (teamMember != null && teamMember.IsPlayerControlled) { // Route through CameraManager if available if (CameraManager.Instance != null) { CameraManager.Instance.SetTarget(teamMember); Debug.Log($"[AutoAddCinemachineCamera] Routed to CameraManager for {target.name}"); } else { SetCameraTargets(vcam, target); Debug.Log($"[AutoAddCinemachineCamera] Set targets directly for {target.name}"); } yield break; } } // Fallback if no TeamMember found if (targets.Length > 0) { Debug.LogWarning("[AutoAddCinemachineCamera] No player-controlled character found, using first target"); SetCameraTargets(vcam, targets[0]); } } /// /// Set Follow to character ROOT (orbit center) and LookAt to HEAD (aim point). /// This is the correct configuration for CinemachineFreeLook. /// private void SetCameraTargets(CinemachineFreeLook vcam, GameObject character) { // Follow = character root (stable orbit center at ground level) Transform followTarget = character.transform; // LookAt = head bone (camera aims at upper body) Transform lookAtTarget = FindHeadTransform(character) ?? character.transform; vcam.Follow = followTarget; vcam.LookAt = lookAtTarget; // CRITICAL: Start at middle rig (0.5), not bottom (0) // Without this, camera defaults to looking UP at the character from below vcam.m_YAxis.Value = 0.5f; // Force Cinemachine to recalculate from scratch (forget cached underground position) vcam.PreviousStateIsValid = false; // Clear input axis names — CinemachineCameraInput handles all input vcam.m_XAxis.m_InputAxisName = ""; vcam.m_YAxis.m_InputAxisName = ""; // Set professional orbit heights (consistent with CameraManager) vcam.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 }; // Configure Composer aim on each rig for (int i = 0; i < 3; i++) { var rig = vcam.GetRig(i); if (rig == null) continue; var composer = rig.GetCinemachineComponent(); if (composer != null) { composer.m_ScreenX = 0.5f; composer.m_ScreenY = 0.4f; composer.m_DeadZoneWidth = 0.1f; composer.m_DeadZoneHeight = 0.1f; composer.m_SoftZoneWidth = 0.8f; composer.m_SoftZoneHeight = 0.8f; } } // Add CinemachineCollider to prevent camera clipping below terrain EnsureCinemachineCollider(vcam); Debug.Log($"[AutoAddCinemachineCamera] Follow={followTarget.name}, LookAt={lookAtTarget.name}, YAxis=0.5"); } /// /// Adds a CinemachineCollider extension so the camera stays above terrain /// and doesn't clip through the environment after changing levels. /// private void EnsureCinemachineCollider(CinemachineFreeLook vcam) { var colliderExt = vcam.GetComponent(); if (colliderExt == null) { colliderExt = vcam.gameObject.AddComponent(); } colliderExt.m_Strategy = CinemachineCollider.ResolutionStrategy.PullCameraForward; colliderExt.m_CollideAgainst = LayerMask.GetMask("Default", "Ground"); colliderExt.m_IgnoreTag = "Player"; colliderExt.m_CameraRadius = 0.2f; colliderExt.m_Damping = 0f; colliderExt.m_MinimumDistanceFromTarget = 1f; Debug.Log("[AutoAddCinemachineCamera] CinemachineCollider extension configured for terrain avoidance"); } private Transform FindHeadTransform(GameObject character) { string[] headNames = { "Head", "head", "Bip001 Head", "mixamorig:Head", "Bip01 Head" }; foreach (string headName in headNames) { Transform head = FindChildRecursive(character.transform, headName); if (head != null) return head; } return null; } private Transform FindChildRecursive(Transform parent, string name) { foreach (Transform child in parent) { if (child.name == name) return child; Transform found = FindChildRecursive(child, name); if (found != null) return found; } return null; } }