Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: #77
258 lines
8.2 KiB
C#
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
|
|
}
|
|
}
|
|
|