Files
AppleHillsProduction/Assets/Scripts/Common/Visual/TrajectoryPreview.cs
2025-12-05 12:18:29 +01:00

258 lines
8.2 KiB
C#

using Core;
using UnityEngine;
namespace Common.Visual
{
/// <summary>
/// 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.
/// </summary>
[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<LineRenderer>();
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
/// <summary>
/// Show the trajectory preview line.
/// Clears any existing trajectory data so nothing displays until UpdateTrajectory is called.
/// </summary>
public void Show()
{
if (_lineRenderer != null)
{
// Clear old trajectory data
_lineRenderer.positionCount = 0;
// Enable the line renderer
_lineRenderer.enabled = true;
}
}
/// <summary>
/// Hide the trajectory preview line (unless locked)
/// </summary>
public void Hide()
{
if (_isLocked) return;
if (_lineRenderer != null)
{
_lineRenderer.enabled = false;
}
}
/// <summary>
/// Lock the trajectory display for a duration (keeps showing after launch)
/// </summary>
public void LockTrajectory(float duration)
{
_isLocked = true;
_lockTimer = 0f;
_lockDuration = duration;
if (_lineRenderer != null)
{
_lineRenderer.enabled = true;
}
}
/// <summary>
/// Force hide the trajectory immediately, clearing any lock state.
/// Use this when transitioning turns or resetting the slingshot.
/// </summary>
public void ForceHide()
{
_isLocked = false;
_lockTimer = 0f;
if (_lineRenderer != null)
{
_lineRenderer.enabled = false;
}
}
#endregion
#region Public API - Update Trajectory (Multiple Overloads)
/// <summary>
/// Update trajectory from velocity and gravity directly.
/// Most explicit - caller calculates everything.
/// </summary>
public void UpdateTrajectory(Vector2 startPos, Vector2 velocity, float gravity)
{
if (_lineRenderer == null) return;
CalculateAndSetTrajectory(startPos, velocity, gravity);
}
/// <summary>
/// Update trajectory from launch force and mass.
/// Calculates velocity as: v = (direction * force) / mass
/// </summary>
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);
}
/// <summary>
/// Update trajectory from prefab's Rigidbody2D properties.
/// Reads mass and gravityScale from prefab, calculates gravity automatically.
/// </summary>
public void UpdateTrajectory(Vector2 startPos, Vector2 direction, float force, GameObject prefab)
{
if (_lineRenderer == null || prefab == null) return;
var rb = prefab.GetComponent<Rigidbody2D>();
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
/// <summary>
/// Calculate and set trajectory points using kinematic formula.
/// Uses: y = y0 + v*t - 0.5*g*t^2
/// </summary>
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
/// <summary>
/// Set the number of trajectory points (for performance tuning)
/// </summary>
public void SetTrajectoryPoints(int points)
{
trajectoryPoints = Mathf.Max(5, points);
if (_lineRenderer != null)
{
_lineRenderer.positionCount = trajectoryPoints;
}
}
/// <summary>
/// Set the time step between points
/// </summary>
public void SetTimeStep(float step)
{
timeStep = Mathf.Max(0.01f, step);
}
#endregion
}
}