Untitled

mail@pastecode.io avatar
unknown
csharp
8 months ago
6.5 kB
1
Indexable
Never
public class CharacterCamera : MonoBehaviour
{
    [Header("Input")]
    public bool handleInput = true;
    [ShowIf(nameof(handleInput))]
    public InputActionReference lookAction;
    [ShowIf(nameof(handleInput))]
    public InputActionReference zoomAction;

    [Header("Framing")]
    public Transform followTransform;
    public Vector2 followPointFraming = new Vector2(0f, 0f);
    public float followSharpness = 100f;

    [Header("Distance")]
    public float defaultDistance = 2f;
    public float minDistance = 1f;
    public float maxDistance = 4f;
    public float distanceSpeed = 0.01f;
    public float distanceSharpness = 10f;

    [Header("Rotation")]
    public bool invertX = false;
    public bool invertY = false;
    [Range(-90f, 90f)]
    public float defaultVerticalAngle = 20f;
    [Range(-90f, 90f)]
    public float minVerticalAngle = -70f;
    [Range(-90f, 90f)]
    public float maxVerticalAngle = 90f;
    public float xSensitivity = 30f;
    public float ySensitivity = 20f;
    public float rotationSharpness = 100f;

    [Header("Collision")]
    public float collisionRadius = 0.2f;
    public LayerMask collisionLayers = -1;
    public float collisionSharpness = 10000f;
    public List<Collider> ignoredColliders = new List<Collider>();

    private bool distanceIsObstructed;
    private float currentDistance;
    private float targetDistance;

    private float targetVerticalAngle;
    private int collisionCount;
    private RaycastHit[] collisions = new RaycastHit[maxObstructions];
    private Vector3 currentFollowPosition;
    private Vector3 planarDirection;

    private const int maxObstructions = 32;

    public Vector3 InputRotation { get; set; }
    public float ZoomInput { get; set; }

    public void SetTarget(Transform target)
    {
        followTransform = target;
        var coll = followTransform.GetComponentInParent<Collider>();

        if (!ignoredColliders.Contains(coll))
            ignoredColliders.Add(coll);

        currentDistance = defaultDistance;
        targetDistance = currentDistance;
        targetVerticalAngle = 0f;

        planarDirection = followTransform.forward;
        currentFollowPosition = followTransform.position;
    }

    private void Update()
    {
        if (!handleInput)
            return;

        var lookInput = lookAction.action.ReadValue<Vector2>();
        var scrollInput = zoomAction.action.ReadValue<float>();

        InputRotation = lookInput;
        ZoomInput = -scrollInput;
    }

    private void LateUpdate()
    {
        if (!followTransform)
            return;

        var targetRotation = HandleRotation();
        transform.rotation = targetRotation;

        HandleDistance();
        HandleCollisions();

        // Find the smoothed camera orbit position
        var targetPosition = currentFollowPosition - ((targetRotation * Vector3.forward) * currentDistance);

        // Handle framing
        targetPosition += transform.right * followPointFraming.x;
        targetPosition += transform.up * followPointFraming.y;

        // Apply position
        transform.position = targetPosition;
    }

    private Quaternion HandleRotation()
    {
        var inputX = InputRotation.x;
        var inputY = InputRotation.y;
        if (invertX) inputX *= -1f;
        if (invertY) inputY *= -1f;

        // Process rotation input
        var rotationFromInput = Quaternion.Euler(followTransform.up * (inputX * xSensitivity * Time.deltaTime));
        planarDirection = rotationFromInput * planarDirection;
        planarDirection = Vector3.Cross(followTransform.up, Vector3.Cross(planarDirection, followTransform.up));
        var planarRot = Quaternion.LookRotation(planarDirection, followTransform.up);

        targetVerticalAngle -= (inputY * ySensitivity * Time.deltaTime);
        targetVerticalAngle = Mathf.Clamp(targetVerticalAngle, minVerticalAngle, maxVerticalAngle);
        var verticalRot = Quaternion.Euler(targetVerticalAngle, 0, 0);
        var targetRotation = Quaternion.Slerp(transform.rotation, 
            planarRot * verticalRot, 1f - Mathf.Exp(-rotationSharpness * Time.deltaTime));

        return targetRotation;
    }

    private void HandleDistance()
    {
        if (distanceIsObstructed && Mathf.Abs(ZoomInput) > 0f)
        {
            targetDistance = currentDistance;
        }
        targetDistance += ZoomInput * distanceSpeed;
        targetDistance = Mathf.Clamp(targetDistance, minDistance, maxDistance);

        // Find the smoothed follow position
        currentFollowPosition = Vector3.Lerp(currentFollowPosition, 
            followTransform.position, 1f - Mathf.Exp(-followSharpness * Time.deltaTime));
    }

    private void HandleCollisions()
    {
        var closestHit = new RaycastHit();
        closestHit.distance = Mathf.Infinity;
        collisionCount = Physics.SphereCastNonAlloc(currentFollowPosition, 
            collisionRadius, -transform.forward, collisions, targetDistance, collisionLayers, QueryTriggerInteraction.Ignore);
        
        for (int i = 0; i < collisionCount; i++)
        {
            bool isIgnored = false;

            for (int j = 0; j < ignoredColliders.Count; j++)
            {
                if (ignoredColliders[j] == collisions[i].collider)
                {
                    isIgnored = true;
                    break;
                }
            }

            for (int j = 0; j < ignoredColliders.Count; j++)
            {
                if (ignoredColliders[j] == collisions[i].collider)
                {
                    isIgnored = true;
                    break;
                }
            }

            if (!isIgnored && collisions[i].distance < closestHit.distance && collisions[i].distance > 0)
                closestHit = collisions[i];
        }

        // If obstructions detected
        if (closestHit.distance < Mathf.Infinity)
        {
            distanceIsObstructed = true;
            currentDistance = Mathf.Lerp(currentDistance, closestHit.distance,
                1 - Mathf.Exp(-collisionSharpness * Time.deltaTime));
        }
        else
        {
            distanceIsObstructed = false;
            currentDistance = Mathf.Lerp(currentDistance, targetDistance,
                1 - Mathf.Exp(-distanceSharpness * Time.deltaTime));
        }
    }
}
Leave a Comment