2026-02-10 21:27:46 +00:00
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
[AddComponentMenu("Camera/Top Down Follow Camera")]
|
|
|
|
|
public class TopDownCameraController : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
[Header("Target")]
|
|
|
|
|
public Transform target;
|
|
|
|
|
|
|
|
|
|
[Header("Positioning")]
|
|
|
|
|
public float distance = 8f; // horizontal distance from target
|
|
|
|
|
public float height = 6f; // vertical offset above target
|
|
|
|
|
[Range(0f, 89f)]
|
|
|
|
|
public float pitch = 80f; // X rotation in degrees (80 by default)
|
|
|
|
|
|
|
|
|
|
[Header("Smoothing")]
|
|
|
|
|
public bool smoothPosition = true;
|
|
|
|
|
public float positionSmoothTime = 0.12f;
|
|
|
|
|
public bool smoothRotation = true;
|
|
|
|
|
public float rotationSmoothTime = 0.08f;
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
Vector3 positionVelocity = Vector3.zero;
|
|
|
|
|
float rotationInterpolation = 0f;
|
|
|
|
|
|
|
|
|
|
// Cached values for optimization
|
|
|
|
|
private Quaternion cachedPitchRotation;
|
|
|
|
|
private Vector3 cachedLocalOffset;
|
|
|
|
|
private bool cachedPitchNeedsUpdate = true;
|
2026-02-10 21:27:46 +00:00
|
|
|
|
|
|
|
|
void Reset()
|
|
|
|
|
{
|
|
|
|
|
pitch = 80f;
|
|
|
|
|
distance = 8f;
|
|
|
|
|
height = 6f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
void OnValidate()
|
|
|
|
|
{
|
|
|
|
|
// Invalidate cache when values change in editor
|
|
|
|
|
cachedPitchNeedsUpdate = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-10 21:27:46 +00:00
|
|
|
void LateUpdate()
|
|
|
|
|
{
|
|
|
|
|
if (target == null) return;
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
// Update cached rotation if pitch changed
|
|
|
|
|
if (cachedPitchNeedsUpdate)
|
|
|
|
|
{
|
|
|
|
|
cachedPitchRotation = Quaternion.Euler(pitch, 0f, 0f);
|
|
|
|
|
cachedLocalOffset = cachedPitchRotation * new Vector3(0f, height, -distance);
|
|
|
|
|
cachedPitchNeedsUpdate = false;
|
|
|
|
|
}
|
2026-02-10 21:27:46 +00:00
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
Vector3 desiredPosition = target.position + cachedLocalOffset;
|
2026-02-10 21:27:46 +00:00
|
|
|
|
|
|
|
|
// Smooth position
|
|
|
|
|
if (smoothPosition)
|
|
|
|
|
{
|
|
|
|
|
transform.position = Vector3.SmoothDamp(transform.position, desiredPosition, ref positionVelocity, positionSmoothTime);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
transform.position = desiredPosition;
|
2026-02-11 17:30:49 +00:00
|
|
|
positionVelocity = Vector3.zero;
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Desired rotation: look at target but keep the pitch
|
|
|
|
|
Vector3 lookDirection = (target.position - transform.position).normalized;
|
|
|
|
|
Quaternion desiredRotation = Quaternion.LookRotation(lookDirection, Vector3.up);
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
// Lock X rotation to the pitch value
|
2026-02-10 21:27:46 +00:00
|
|
|
Vector3 euler = desiredRotation.eulerAngles;
|
|
|
|
|
euler.x = pitch;
|
|
|
|
|
desiredRotation = Quaternion.Euler(euler);
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
// Smooth rotation using exponential decay
|
2026-02-10 21:27:46 +00:00
|
|
|
if (smoothRotation)
|
|
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
float slerp = 1f - Mathf.Exp(-rotationSmoothTime * 60f * Time.deltaTime);
|
|
|
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, desiredRotation, slerp);
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
transform.rotation = desiredRotation;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|