chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
182
Assets/Scripts/Camera/AutoAddCinemachineCamera.cs
Normal file
182
Assets/Scripts/Camera/AutoAddCinemachineCamera.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using UnityEngine;
|
||||
using Cinemachine;
|
||||
using System.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class AutoAddCinemachineCamera : MonoBehaviour
|
||||
{
|
||||
[TagField]
|
||||
public string Tag = string.Empty;
|
||||
|
||||
void Start()
|
||||
{
|
||||
var vcam = GetComponent<CinemachineFreeLook>();
|
||||
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<TeamMember>();
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Follow to character ROOT (orbit center) and LookAt to HEAD (aim point).
|
||||
/// This is the correct configuration for CinemachineFreeLook.
|
||||
/// </summary>
|
||||
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<CinemachineComposer>();
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a CinemachineCollider extension so the camera stays above terrain
|
||||
/// and doesn't clip through the environment after changing levels.
|
||||
/// </summary>
|
||||
private void EnsureCinemachineCollider(CinemachineFreeLook vcam)
|
||||
{
|
||||
var colliderExt = vcam.GetComponent<CinemachineCollider>();
|
||||
if (colliderExt == null)
|
||||
{
|
||||
colliderExt = vcam.gameObject.AddComponent<CinemachineCollider>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user