Untitled

 avatar
unknown
plain_text
8 months ago
11 kB
10
Indexable
using UnityEngine;
using UnityEngine.InputSystem;

public class TopDownCameraController : MonoBehaviour
{
    [Header("Target Settings")]
    [SerializeField] private Transform target;
    
    [Header("Camera Position")]
    [Range(8f, 20f)]
    [SerializeField] private float height = 10f;
    [SerializeField] private float minHeight = 8f;
    [SerializeField] private float maxHeight = 20f;
    [SerializeField] private float scrollSensitivity = 2f;
    
    [Header("Follow Settings")]
    [SerializeField] private float followSmoothness = 5f;
    [SerializeField] private float rotationSmoothness = 8f;
    [SerializeField] private float movementThreshold = 0.1f;
    [SerializeField] private float rotationStabilityThreshold = 0.05f;
    
    [Header("Planetary Gravity Integration")]
    [SerializeField] private bool usePlanetaryOrientation = true;
    [SerializeField] private float gravityOrientationBlend = 3f;
    [SerializeField] private float orientationStabilityTime = 1f;
    
    [Header("Debug")]
    [SerializeField] private bool showDebugRays = false;
    
    private Camera cameraComponent;
    private PlanetGravityAffected playerGravityComponent;
    private Vector3 currentUpDirection = Vector3.up;
    private Vector3 targetUpDirection = Vector3.up;
    private Vector3 stableUpDirection = Vector3.up;
    
    // Cache for smooth calculations
    private Vector3 smoothedPlayerPosition;
    private Quaternion smoothedCameraRotation;
    private Vector3 lastPlayerPosition;
    private float timeSinceLastMovement = 0f;
    private bool isPlayerMoving = false;
    private float orientationStabilityTimer = 0f;
    
    // Input handling
    private Mouse mouse;
    
    private void Start()
    {
        cameraComponent = GetComponent<Camera>();
        mouse = Mouse.current;
        
        // Find player if not assigned
        if (target == null)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            if (player != null)
            {
                target = player.transform;
            }
        }
        
        // Get the player's gravity component for planetary orientation
        if (target != null)
        {
            playerGravityComponent = target.GetComponent<PlanetGravityAffected>();
            lastPlayerPosition = target.position;
        }
        
        // Initialize positions
        if (target != null)
        {
            smoothedPlayerPosition = target.position;
            UpdateCameraPosition();
        }
        
        smoothedCameraRotation = transform.rotation;
        stableUpDirection = currentUpDirection;
    }
    
    private void Update()
    {
        if (target == null) return;
        
        HandleScrollInput();
        CheckPlayerMovement();
        
        if (showDebugRays)
        {
            DrawDebugRays();
        }
    }
    
    private void FixedUpdate()
    {
        if (target == null) return;
        
        UpdatePlanetaryOrientation();
        UpdateCameraPosition();
        UpdateCameraRotation();
    }
    
    private void HandleScrollInput()
    {
        if (mouse != null && mouse.scroll.ReadValue() != Vector2.zero)
        {
            float scrollValue = mouse.scroll.ReadValue().y;
            float heightChange = scrollValue * scrollSensitivity * Time.deltaTime;
            SetHeight(height - heightChange);
        }
    }
    
    private void CheckPlayerMovement()
    {
        if (target == null) return;
        
        float movementDistance = Vector3.Distance(target.position, lastPlayerPosition);
        isPlayerMoving = movementDistance > movementThreshold * Time.deltaTime;
        
        if (isPlayerMoving)
        {
            timeSinceLastMovement = 0f;
        }
        else
        {
            timeSinceLastMovement += Time.deltaTime;
        }
        
        lastPlayerPosition = target.position;
    }
    
    private void UpdatePlanetaryOrientation()
    {
        if (!usePlanetaryOrientation || playerGravityComponent == null)
        {
            targetUpDirection = Vector3.up;
            currentUpDirection = Vector3.up;
            stableUpDirection = Vector3.up;
            return;
        }
        
        // Get the current planet affecting the player
        PlanetaryGravity currentPlanet = GetNearestPlanet();
        
        if (currentPlanet != null)
        {
            // Calculate the "up" direction from the planet's center to the player
            Vector3 directionFromPlanet = (target.position - currentPlanet.transform.position).normalized;
            targetUpDirection = directionFromPlanet;
        }
        else
        {
            targetUpDirection = Vector3.up;
        }
        
        // Only update orientation if player is moving or enough time has passed for stability
        bool shouldUpdateOrientation = isPlayerMoving || timeSinceLastMovement < orientationStabilityTime;
        
        if (shouldUpdateOrientation)
        {
            // Check if the change in orientation is significant enough to warrant an update
            float orientationDifference = Vector3.Angle(currentUpDirection, targetUpDirection);
            
            if (orientationDifference > rotationStabilityThreshold || isPlayerMoving)
            {
                // Smoothly blend to the new orientation
                float blendSpeed = isPlayerMoving ? gravityOrientationBlend : gravityOrientationBlend * 0.5f;
                currentUpDirection = Vector3.Slerp(currentUpDirection, targetUpDirection, blendSpeed * Time.fixedDeltaTime);
                orientationStabilityTimer = 0f;
            }
            else
            {
                orientationStabilityTimer += Time.fixedDeltaTime;
            }
        }
        else
        {
            // When player is stationary for a while, lock to stable orientation
            if (orientationStabilityTimer >= orientationStabilityTime)
            {
                stableUpDirection = currentUpDirection;
            }
            currentUpDirection = stableUpDirection;
        }
    }
    
    private void UpdateCameraPosition()
    {
        // Use different smoothing based on whether player is moving
        float positionSmoothness = isPlayerMoving ? followSmoothness : followSmoothness * 2f;
        
        // Smooth the player position to reduce camera jitter
        smoothedPlayerPosition = Vector3.Lerp(smoothedPlayerPosition, target.position, positionSmoothness * Time.fixedDeltaTime);
        
        // Calculate the desired camera position above the player
        Vector3 desiredPosition = smoothedPlayerPosition + (currentUpDirection * height);
        
        // Move the camera to the desired position with appropriate smoothing
        transform.position = Vector3.Lerp(transform.position, desiredPosition, positionSmoothness * Time.fixedDeltaTime);
    }
    
    private void UpdateCameraRotation()
    {
        // Calculate the rotation to look down at the player while maintaining proper "up" orientation
        Vector3 directionToPlayer = (smoothedPlayerPosition - transform.position).normalized;
        
        // Ensure we're looking down at the player with the correct up direction
        Quaternion targetRotation = Quaternion.LookRotation(directionToPlayer, GetCameraUpDirection());
        
        // Use different rotation smoothing based on movement and stability
        float rotationSpeed = rotationSmoothness;
        if (!isPlayerMoving && timeSinceLastMovement > orientationStabilityTime)
        {
            rotationSpeed *= 0.3f; // Much slower rotation when stationary
        }
        
        // Smooth the rotation
        smoothedCameraRotation = Quaternion.Slerp(smoothedCameraRotation, targetRotation, rotationSpeed * Time.fixedDeltaTime);
        transform.rotation = smoothedCameraRotation;
    }
    
    private Vector3 GetCameraUpDirection()
    {
        if (!usePlanetaryOrientation)
        {
            return Vector3.up;
        }
        
        // For planetary cameras, we want the "up" to be tangent to the planet surface
        // This means the camera's up should be perpendicular to both the planet's radius and the camera's forward direction
        Vector3 planetRadial = currentUpDirection;
        Vector3 cameraForward = (smoothedPlayerPosition - transform.position).normalized;
        
        // Calculate a tangent vector on the planet surface
        Vector3 tangent = Vector3.Cross(planetRadial, Vector3.Cross(cameraForward, planetRadial)).normalized;
        
        return tangent;
    }
    
    private PlanetaryGravity GetNearestPlanet()
    {
        PlanetaryGravity nearestPlanet = null;
        float nearestDistance = float.MaxValue;
        
        foreach (var planet in PlanetaryGravity.AllPlanets)
        {
            if (planet == null) continue;
            
            float distance = Vector3.Distance(target.position, planet.transform.position);
            if (distance < nearestDistance)
            {
                nearestDistance = distance;
                nearestPlanet = planet;
            }
        }
        
        return nearestPlanet;
    }
    
    private void DrawDebugRays()
    {
        if (target == null) return;
        
        // Draw the up direction
        Debug.DrawRay(target.position, currentUpDirection * height, Color.blue, 0f);
        
        // Draw the camera to player direction
        Debug.DrawLine(transform.position, target.position, Color.red, 0f);
        
        // Draw camera's up direction
        Debug.DrawRay(transform.position, transform.up * 2f, Color.green, 0f);
        
        // Draw camera's forward direction
        Debug.DrawRay(transform.position, transform.forward * 3f, Color.yellow, 0f);
    }
    
    public void SetTarget(Transform newTarget)
    {
        target = newTarget;
        
        if (target != null)
        {
            playerGravityComponent = target.GetComponent<PlanetGravityAffected>();
            smoothedPlayerPosition = target.position;
            lastPlayerPosition = target.position;
            timeSinceLastMovement = 0f;
        }
    }
    
    public void SetHeight(float newHeight)
    {
        height = Mathf.Clamp(newHeight, minHeight, maxHeight);
    }
    
    // Method to enable/disable planetary orientation at runtime
    public void SetPlanetaryOrientation(bool enable)
    {
        usePlanetaryOrientation = enable;
        if (!enable)
        {
            targetUpDirection = Vector3.up;
        }
    }
    
    // Validate height range in inspector
    private void OnValidate()
    {
        minHeight = 8f;
        maxHeight = 20f;
        height = Mathf.Clamp(height, minHeight, maxHeight);
        
        if (minHeight >= maxHeight)
        {
            maxHeight = minHeight + 1f;
        }
    }
}
Editor is loading...
Leave a Comment