Files
DeviantMobile-Rohan/Assets/Scripts/Managers/CameraManager.cs

450 lines
16 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;
public enum SinglePlayerCameraMode
{
Stationary,
FollowAndOrbit
}
public class CameraManager : Singleton<CameraManager>
{
[Header("Component References")]
public GameObject gameplayCameraObject;
public GameObject uiOverlayCameraObject;
[Header("Virtual Cameras")]
public GameObject VCamStationaryObject;
public GameObject VCamSinglePlayerOrbitObject;
[Header("Cinemachine References")]
[SerializeField] private CinemachineFreeLook mainFreeLookCamera;
[SerializeField] private CinemachineVirtualCamera mainVirtualCamera;
[Header("Current Target")]
[SerializeField] private TeamMember currentTarget;
[SerializeField] private bool debugLogging = true;
public void SetupManager()
{
SetCameraObjectNewState(gameplayCameraObject, true);
SetCameraObjectNewState(uiOverlayCameraObject, false);
// Find camera references if not set
FindCameraReferences();
}
private void Start()
{
FindCameraReferences();
SubscribeToAllTeamMembers();
}
private void FindCameraReferences()
{
if (mainFreeLookCamera == null)
{
GameObject camObj = GameObject.Find("CinemachineCamera");
if (camObj != null)
{
mainFreeLookCamera = camObj.GetComponent<CinemachineFreeLook>();
}
}
if (mainVirtualCamera == null)
{
mainVirtualCamera = FindObjectOfType<CinemachineVirtualCamera>();
}
}
public void SetupSinglePlayerCamera(SinglePlayerCameraMode currentCameraMode)
{
switch (currentCameraMode)
{
case SinglePlayerCameraMode.Stationary:
SetCameraObjectNewState(VCamStationaryObject, true);
SetCameraObjectNewState(VCamSinglePlayerOrbitObject, false);
break;
case SinglePlayerCameraMode.FollowAndOrbit:
SetCameraObjectNewState(VCamStationaryObject, false);
SetCameraObjectNewState(VCamSinglePlayerOrbitObject, true);
break;
}
}
void SetCameraObjectNewState(GameObject cameraObject, bool newState)
{
if (cameraObject != null)
cameraObject.SetActive(newState);
}
//This is called by UIBillboardBehaviour so they can orient to wherever the gameplay camera is.
public Transform GetGameplayCameraTransform()
{
return gameplayCameraObject?.transform;
}
public Camera GetGameplayCamera()
{
return gameplayCameraObject?.GetComponent<Camera>();
}
// ==================== NEW: ControlType-aware Camera Targeting ====================
/// <summary>
/// Subscribe to OnControlTypeChanged for all TeamMembers in the scene
/// </summary>
private void SubscribeToAllTeamMembers()
{
TeamMember[] allMembers = FindObjectsOfType<TeamMember>();
foreach (var member in allMembers)
{
member.OnControlTypeChanged += OnMemberControlTypeChanged;
}
}
/// <summary>
/// Register a newly spawned TeamMember
/// </summary>
public void RegisterTeamMember(TeamMember member)
{
if (member != null)
{
member.OnControlTypeChanged += OnMemberControlTypeChanged;
// If this member is HumanControlled, set as target
if (member.CurrentControlType == TeamMember.ControlType.HumanControlled)
{
SetTarget(member);
}
}
}
/// <summary>
/// Unregister a TeamMember (e.g., on death)
/// </summary>
public void UnregisterTeamMember(TeamMember member)
{
if (member != null)
{
member.OnControlTypeChanged -= OnMemberControlTypeChanged;
}
}
/// <summary>
/// Called when any TeamMember's control type changes
/// </summary>
private void OnMemberControlTypeChanged(TeamMember.ControlType newType)
{
// Find the new human-controlled character
if (newType == TeamMember.ControlType.HumanControlled)
{
FindAndSetHumanControlledTarget();
}
}
/// <summary>
/// Find the HumanControlled character and set as camera target
/// </summary>
public void FindAndSetHumanControlledTarget()
{
TeamMember[] allMembers = FindObjectsOfType<TeamMember>();
foreach (var member in allMembers)
{
if (member.CurrentControlType == TeamMember.ControlType.HumanControlled)
{
SetTarget(member);
return;
}
}
if (debugLogging)
{
Debug.LogWarning("[CameraManager] No HumanControlled character found in scene!");
}
}
/// <summary>
/// Set the camera target to a specific TeamMember
/// </summary>
public void SetTarget(TeamMember target)
{
if (target == null)
{
Debug.LogWarning("[CameraManager] Attempted to set null target");
return;
}
currentTarget = target;
// Use root for Follow (stable orbit center)
Transform followTransform = target.transform;
// Use head for LookAt (camera points at upper body)
Transform lookAtTransform = FindCharacterHead(target.gameObject);
// Set FreeLook camera target
if (mainFreeLookCamera != null)
{
mainFreeLookCamera.Follow = followTransform;
mainFreeLookCamera.LookAt = lookAtTransform;
// CRITICAL: Tell Cinemachine to forget its cached (underground) position
// and recalculate from scratch based on the new Follow target position
mainFreeLookCamera.PreviousStateIsValid = false;
// Configure camera to automatically stay behind player
ConfigureCameraRecentering();
if (debugLogging)
{
Debug.Log($"[CameraManager] FreeLook camera - Follow: {followTransform.name}, LookAt: {lookAtTransform.name} (state reset)");
}
}
// Set VirtualCamera target
if (mainVirtualCamera != null)
{
mainVirtualCamera.Follow = followTransform;
mainVirtualCamera.LookAt = lookAtTransform;
}
// Also update CameraFollow if present
CameraFollow cameraFollow = FindObjectOfType<CameraFollow>();
if (cameraFollow != null)
{
cameraFollow.target = lookAtTransform;
}
}
/// <summary>
/// Configure camera to automatically recenter behind the player.
/// Also initializes orbit heights, Y axis position, and Composer aim settings
/// to ensure the camera starts BEHIND and ABOVE the character, never below.
/// </summary>
private void ConfigureCameraRecentering()
{
if (mainFreeLookCamera == null) return;
// ====== FIX #1: Start at MIDDLE rig (0.5), not bottom rig (0) ======
// Y Axis: 0 = bottom rig, 0.5 = middle rig, 1 = top rig
// Without this, camera defaults to bottom rig and looks UP at the character
mainFreeLookCamera.m_YAxis.Value = 0.5f;
// CRITICAL: Clear default input axis names — CinemachineCameraInput handles all input.
// Without this, Cinemachine also reads Input.GetAxis("Mouse X") which returns 0
// and snaps the Y axis to the bottom rig (= camera underground).
mainFreeLookCamera.m_XAxis.m_InputAxisName = "";
mainFreeLookCamera.m_YAxis.m_InputAxisName = "";
// ====== FIX #2: Set proper orbit heights and radii ======
// These define the 3 orbital rings the camera blends between.
// Height = vertical offset from Follow target (character root)
// Radius = horizontal distance from character
mainFreeLookCamera.m_Orbits = new CinemachineFreeLook.Orbit[] {
new CinemachineFreeLook.Orbit(4.5f, 5f), // TopRig: bird's eye, medium distance
new CinemachineFreeLook.Orbit(2.5f, 6f), // MiddleRig: shoulder height, wider orbit
new CinemachineFreeLook.Orbit(0.5f, 5f), // BottomRig: just above ground level
};
// Set binding mode to follow player's heading (critical for auto-follow)
// "SimpleFollowWithWorldUp" makes the camera orbit relative to where the player is facing
mainFreeLookCamera.m_BindingMode = CinemachineTransposer.BindingMode.SimpleFollowWithWorldUp;
// Configure Heading - this is how the camera knows which direction is "behind" the player
// Velocity-based heading follows the direction the player is moving
mainFreeLookCamera.m_Heading.m_Definition = CinemachineOrbitalTransposer.Heading.HeadingDefinition.Velocity;
mainFreeLookCamera.m_Heading.m_VelocityFilterStrength = 5; // Smooth out velocity changes
// Try to enable X Axis recentering (works in Cinemachine 2.x)
try
{
mainFreeLookCamera.m_XAxis.m_Recentering.m_enabled = true;
mainFreeLookCamera.m_XAxis.m_Recentering.m_WaitTime = 0.3f;
mainFreeLookCamera.m_XAxis.m_Recentering.m_RecenteringTime = 0.8f;
}
catch (System.Exception)
{
Debug.Log("[CameraManager] X Axis recentering not available via m_XAxis.m_Recentering");
}
// Configure Y Axis (vertical tilt) recentering
mainFreeLookCamera.m_YAxis.m_Recentering.m_enabled = true;
mainFreeLookCamera.m_YAxis.m_Recentering.m_WaitTime = 0.3f;
mainFreeLookCamera.m_YAxis.m_Recentering.m_RecenteringTime = 0.8f;
// ====== FIX #3: Configure Composer aim on each rig ======
// This controls WHERE on screen the character appears.
// ScreenY = 0.4 means character is at 40% from bottom (slightly above center)
// This prevents the camera from tilting upward to look at the character
ConfigureRigComposers();
// Configure heading settings on each rig's orbital transposer
ConfigureRigHeadings();
if (debugLogging)
{
Debug.Log("[CameraManager] Camera fully initialized:");
Debug.Log($" Y Axis = {mainFreeLookCamera.m_YAxis.Value} (0.5 = middle rig)");
Debug.Log($" Orbits: Top({mainFreeLookCamera.m_Orbits[0].m_Height}h, {mainFreeLookCamera.m_Orbits[0].m_Radius}r) " +
$"Mid({mainFreeLookCamera.m_Orbits[1].m_Height}h, {mainFreeLookCamera.m_Orbits[1].m_Radius}r) " +
$"Bot({mainFreeLookCamera.m_Orbits[2].m_Height}h, {mainFreeLookCamera.m_Orbits[2].m_Radius}r)");
Debug.Log($" Heading: {mainFreeLookCamera.m_Heading.m_Definition}");
}
}
/// <summary>
/// Configure the Composer aim component on each rig.
/// This sets WHERE on screen the character is framed.
/// Without this, ScreenY defaults to 0.27 which makes the camera look UP.
/// Pattern from WaitingSceneManager which correctly sets these values.
/// </summary>
private void ConfigureRigComposers()
{
for (int i = 0; i < 3; i++)
{
var rig = mainFreeLookCamera.GetRig(i);
if (rig == null) continue;
var composer = rig.GetCinemachineComponent<CinemachineComposer>();
if (composer != null)
{
composer.m_ScreenX = 0.5f; // Character centered horizontally
composer.m_ScreenY = 0.4f; // Character at 40% — standard third person
composer.m_DeadZoneWidth = 0.1f; // Small dead zone for responsive aiming
composer.m_DeadZoneHeight = 0.1f;
composer.m_SoftZoneWidth = 0.8f; // Wide soft zone for smooth transitions
composer.m_SoftZoneHeight = 0.8f;
composer.m_LookaheadTime = 0f; // No lookahead for combat (responsive)
composer.m_LookaheadSmoothing = 0f;
if (debugLogging)
{
string rigName = i == 0 ? "Top" : (i == 1 ? "Middle" : "Bottom");
Debug.Log($"[CameraManager] {rigName} rig Composer: ScreenY={composer.m_ScreenY}, ScreenX={composer.m_ScreenX}");
}
}
}
}
/// <summary>
/// Configure heading settings on each rig's orbital transposer
/// </summary>
private void ConfigureRigHeadings()
{
for (int i = 0; i < 3; i++)
{
var rig = mainFreeLookCamera.GetRig(i);
if (rig != null)
{
var transposer = rig.GetCinemachineComponent<CinemachineOrbitalTransposer>();
if (transposer != null)
{
// Enable recenter to target heading on each rig
transposer.m_RecenterToTargetHeading.m_enabled = true;
transposer.m_RecenterToTargetHeading.m_WaitTime = 0.3f;
transposer.m_RecenterToTargetHeading.m_RecenteringTime = 0.8f;
}
}
}
}
/// <summary>
/// Set target by GameObject (convenience method)
/// </summary>
public void SetTarget(GameObject target)
{
if (target == null) return;
TeamMember member = target.GetComponent<TeamMember>();
if (member != null)
{
SetTarget(member);
}
else
{
// Fallback: set camera directly with correct separation
Transform followTransform = target.transform; // Root = orbit center
Transform lookAtTransform = FindCharacterHead(target); // Head = aim point
if (mainFreeLookCamera != null)
{
mainFreeLookCamera.Follow = followTransform;
mainFreeLookCamera.LookAt = lookAtTransform;
}
}
}
/// <summary>
/// Get the currently followed character
/// </summary>
public TeamMember GetCurrentTarget()
{
return currentTarget;
}
/// <summary>
/// Find the character's head transform for camera targeting
/// </summary>
private Transform FindCharacterHead(GameObject character)
{
if (character == null) return null;
// Try to find common head bone names
string[] headNames = { "mixamorig:Head", "Head", "head", "Bip001 Head", "Bone_Head", "head_bone" };
foreach (string headName in headNames)
{
Transform head = FindChildRecursive(character.transform, headName);
if (head != null)
{
if (debugLogging)
{
Debug.Log($"[CameraManager] Found head bone '{headName}' for {character.name}");
}
return head;
}
}
// Try character-specific paths
string characterName = character.name;
try
{
Transform headTransform = null;
if (characterName.Contains("Cheetah"))
headTransform = character.transform.GetChild(9)?.GetChild(6)?.GetChild(1);
else if (characterName.Contains("Rabbit") || characterName.Contains("Eagle") ||
characterName.Contains("Hyena") || characterName.Contains("HM") ||
characterName.Contains("Cat"))
headTransform = character.transform.GetChild(7)?.GetChild(6)?.GetChild(1);
if (headTransform != null)
{
if (debugLogging)
{
Debug.Log($"[CameraManager] Found head via hardcoded path for {characterName}: {headTransform.name}");
}
return headTransform;
}
}
catch { }
// Final fallback - log warning
Debug.LogWarning($"[CameraManager] Could not find head bone for {characterName}, using root transform. This will cause camera to show legs!");
return character.transform;
}
private Transform FindChildRecursive(Transform parent, string name)
{
if (parent.name == name) return parent;
foreach (Transform child in parent)
{
Transform result = FindChildRecursive(child, name);
if (result != null) return result;
}
return null;
}
}