using Core; using UnityEngine; namespace Common.Visual { /// /// Common trajectory preview component for slingshot-style mechanics. /// Displays a line showing the predicted arc of a launched projectile. /// Supports multiple API overloads for different use cases. /// [RequireComponent(typeof(LineRenderer))] public class TrajectoryPreview : MonoBehaviour { [Header("Trajectory Settings")] [Tooltip("Number of points in trajectory line")] [SerializeField] private int trajectoryPoints = 50; [Tooltip("Time step between trajectory points (seconds)")] [SerializeField] private float timeStep = 0.1f; [Tooltip("Ground level Y position (trajectory stops here)")] [SerializeField] private float groundLevel = -10f; [Header("Visual")] [Tooltip("Color of trajectory line")] [SerializeField] private Color lineColor = Color.yellow; [Tooltip("Width of trajectory line")] [SerializeField] private float lineWidth = 0.1f; private LineRenderer _lineRenderer; private bool _isLocked; private float _lockTimer; private float _lockDuration; #region Unity Lifecycle private void Awake() { _lineRenderer = GetComponent(); if (_lineRenderer != null) { _lineRenderer.startWidth = lineWidth; _lineRenderer.endWidth = lineWidth; _lineRenderer.startColor = lineColor; _lineRenderer.endColor = lineColor; _lineRenderer.positionCount = trajectoryPoints; _lineRenderer.enabled = false; } } private void Update() { if (_isLocked) { _lockTimer += Time.deltaTime; if (_lockTimer >= _lockDuration) { _isLocked = false; Hide(); } } } #endregion #region Public API - Visibility /// /// Show the trajectory preview line. /// Clears any existing trajectory data so nothing displays until UpdateTrajectory is called. /// public void Show() { if (_lineRenderer != null) { // Clear old trajectory data _lineRenderer.positionCount = 0; // Enable the line renderer _lineRenderer.enabled = true; } } /// /// Hide the trajectory preview line (unless locked) /// public void Hide() { if (_isLocked) return; if (_lineRenderer != null) { _lineRenderer.enabled = false; } } /// /// Lock the trajectory display for a duration (keeps showing after launch) /// public void LockTrajectory(float duration) { _isLocked = true; _lockTimer = 0f; _lockDuration = duration; if (_lineRenderer != null) { _lineRenderer.enabled = true; } } /// /// Force hide the trajectory immediately, clearing any lock state. /// Use this when transitioning turns or resetting the slingshot. /// public void ForceHide() { _isLocked = false; _lockTimer = 0f; if (_lineRenderer != null) { _lineRenderer.enabled = false; } } #endregion #region Public API - Update Trajectory (Multiple Overloads) /// /// Update trajectory from velocity and gravity directly. /// Most explicit - caller calculates everything. /// public void UpdateTrajectory(Vector2 startPos, Vector2 velocity, float gravity) { if (_lineRenderer == null) return; CalculateAndSetTrajectory(startPos, velocity, gravity); } /// /// Update trajectory from launch force and mass. /// Calculates velocity as: v = (direction * force) / mass /// public void UpdateTrajectory(Vector2 startPos, Vector2 direction, float force, float mass, float gravity) { if (_lineRenderer == null) return; if (mass <= 0f) { Logging.Warning("[TrajectoryPreview] Cannot calculate trajectory with zero or negative mass!"); return; } Vector2 velocity = (direction * force) / mass; CalculateAndSetTrajectory(startPos, velocity, gravity); } /// /// Update trajectory from prefab's Rigidbody2D properties. /// Reads mass and gravityScale from prefab, calculates gravity automatically. /// public void UpdateTrajectory(Vector2 startPos, Vector2 direction, float force, GameObject prefab) { if (_lineRenderer == null || prefab == null) return; var rb = prefab.GetComponent(); if (rb == null) { Logging.Warning($"[TrajectoryPreview] Prefab '{prefab.name}' has no Rigidbody2D!"); return; } float mass = rb.mass; float gravity = Physics2D.gravity.magnitude * rb.gravityScale; if (mass <= 0f) { Logging.Warning($"[TrajectoryPreview] Prefab '{prefab.name}' has zero mass!"); return; } Vector2 velocity = (direction * force) / mass; CalculateAndSetTrajectory(startPos, velocity, gravity); } #endregion #region Internal Calculation /// /// Calculate and set trajectory points using kinematic formula. /// Uses: y = y0 + v*t - 0.5*g*t^2 /// private void CalculateAndSetTrajectory(Vector2 startPos, Vector2 velocity, float gravity) { Vector3[] points = new Vector3[trajectoryPoints]; for (int i = 0; i < trajectoryPoints; i++) { float time = i * timeStep; // Kinematic equations float x = startPos.x + velocity.x * time; float y = startPos.y + velocity.y * time - 0.5f * gravity * time * time; points[i] = new Vector3(x, y, 0); // Stop at ground level if (y <= groundLevel) { // Fill remaining points at ground level for (int j = i; j < trajectoryPoints; j++) { float tGround = j * timeStep; float xGround = startPos.x + velocity.x * tGround; points[j] = new Vector3(xGround, groundLevel, 0); } break; } } _lineRenderer.positionCount = trajectoryPoints; _lineRenderer.SetPositions(points); } #endregion #region Configuration /// /// Set the number of trajectory points (for performance tuning) /// public void SetTrajectoryPoints(int points) { trajectoryPoints = Mathf.Max(5, points); if (_lineRenderer != null) { _lineRenderer.positionCount = trajectoryPoints; } } /// /// Set the time step between points /// public void SetTimeStep(float step) { timeStep = Mathf.Max(0.01f, step); } #endregion } }