- Obstacles - Tiles - Object pooling - Monster spawns - Scoring - Minigame End Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Co-authored-by: AlexanderT <alexander@foolhardyhorizons.com> Reviewed-on: #6
366 lines
13 KiB
C#
366 lines
13 KiB
C#
using UnityEngine;
|
|
using System;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace GogoGaga.OptimizedRopesAndCables
|
|
{
|
|
[ExecuteAlways]
|
|
[RequireComponent(typeof(LineRenderer))]
|
|
public class Rope : MonoBehaviour
|
|
{
|
|
public event Action OnPointsChanged;
|
|
|
|
[Header("Rope Transforms")]
|
|
[Tooltip("The rope will start at this point")]
|
|
[SerializeField] private Transform startPoint;
|
|
public Transform StartPoint => startPoint;
|
|
|
|
[Tooltip("This will move at the center hanging from the rope, like a necklace, for example")]
|
|
[SerializeField] private Transform midPoint;
|
|
public Transform MidPoint => midPoint;
|
|
|
|
[Tooltip("The rope will end at this point")]
|
|
[SerializeField] private Transform endPoint;
|
|
public Transform EndPoint => endPoint;
|
|
|
|
[Header("Rope Settings")]
|
|
[Tooltip("How many points should the rope have, 2 would be a triangle with straight lines, 100 would be a very flexible rope with many parts")]
|
|
[Range(2, 100)] public int linePoints = 10;
|
|
|
|
[Tooltip("Value highly dependent on use case, a metal cable would have high stiffness, a rubber rope would have a low one")]
|
|
public float stiffness = 350f;
|
|
|
|
[Tooltip("0 is no damping, 50 is a lot")]
|
|
public float damping = 15f;
|
|
|
|
[Tooltip("How long is the rope, it will hang more or less from starting point to end point depending on this value")]
|
|
public float ropeLength = 15;
|
|
|
|
[Tooltip("The Rope width set at start (changing this value during run time will produce no effect)")]
|
|
public float ropeWidth = 0.1f;
|
|
|
|
[Header("Rational Bezier Weight Control")]
|
|
[Tooltip("Adjust the middle control point weight for the Rational Bezier curve")]
|
|
[Range(1, 15)] public float midPointWeight = 1f;
|
|
private const float StartPointWeight = 1f; //these need to stay at 1, could be removed but makes calling the rational bezier function easier to read and understand
|
|
private const float EndPointWeight = 1f;
|
|
|
|
[Header("Midpoint Position")]
|
|
[Tooltip("Position of the midpoint along the line between start and end points")]
|
|
[Range(0.25f, 0.75f)] public float midPointPosition = 0.5f;
|
|
|
|
private Vector3 currentValue;
|
|
private Vector3 currentVelocity;
|
|
private Vector3 targetValue;
|
|
public Vector3 otherPhysicsFactors { get; set; }
|
|
private const float valueThreshold = 0.01f;
|
|
private const float velocityThreshold = 0.01f;
|
|
|
|
private LineRenderer lineRenderer;
|
|
private bool isFirstFrame = true;
|
|
|
|
private Vector3 prevStartPointPosition;
|
|
private Vector3 prevEndPointPosition;
|
|
private float prevMidPointPosition;
|
|
private float prevMidPointWeight;
|
|
|
|
private float prevLineQuality;
|
|
private float prevRopeWidth;
|
|
private float prevstiffness;
|
|
private float prevDampness;
|
|
private float prevRopeLength;
|
|
|
|
|
|
public bool IsPrefab => gameObject.scene.rootCount == 0;
|
|
|
|
// Track initialization state
|
|
private bool isInitialized = false;
|
|
|
|
/// <summary>
|
|
/// Public method to explicitly initialize the rope.
|
|
/// Call this after setting up endpoints if creating ropes at runtime.
|
|
/// </summary>
|
|
/// <returns>True if initialization was successful, false otherwise</returns>
|
|
public bool Initialize()
|
|
{
|
|
// Skip if already initialized
|
|
if (isInitialized)
|
|
return true;
|
|
|
|
InitializeLineRenderer();
|
|
if (AreEndPointsValid())
|
|
{
|
|
currentValue = GetMidPoint();
|
|
targetValue = currentValue;
|
|
currentVelocity = Vector3.zero;
|
|
SetSplinePoint(); // Ensure initial spline point is set correctly
|
|
isInitialized = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
// Use the same initialization method to avoid code duplication
|
|
Initialize();
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
InitializeLineRenderer();
|
|
if (AreEndPointsValid())
|
|
{
|
|
RecalculateRope();
|
|
SimulatePhysics();
|
|
}
|
|
else
|
|
{
|
|
lineRenderer.positionCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitializeLineRenderer()
|
|
{
|
|
if (!lineRenderer)
|
|
{
|
|
lineRenderer = GetComponent<LineRenderer>();
|
|
}
|
|
|
|
lineRenderer.startWidth = ropeWidth;
|
|
lineRenderer.endWidth = ropeWidth;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (IsPrefab)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (AreEndPointsValid())
|
|
{
|
|
SetSplinePoint();
|
|
|
|
if (!Application.isPlaying && (IsPointsMoved() || IsRopeSettingsChanged()))
|
|
{
|
|
SimulatePhysics();
|
|
NotifyPointsChanged();
|
|
}
|
|
|
|
prevStartPointPosition = startPoint.position;
|
|
prevEndPointPosition = endPoint.position;
|
|
prevMidPointPosition = midPointPosition;
|
|
prevMidPointWeight = midPointWeight;
|
|
|
|
prevLineQuality = linePoints;
|
|
prevRopeWidth = ropeWidth;
|
|
prevstiffness = stiffness;
|
|
prevDampness = damping;
|
|
prevRopeLength = ropeLength;
|
|
}
|
|
}
|
|
|
|
private bool AreEndPointsValid()
|
|
{
|
|
return startPoint != null && endPoint != null;
|
|
}
|
|
|
|
private void SetSplinePoint()
|
|
{
|
|
if (lineRenderer.positionCount != linePoints + 1)
|
|
{
|
|
lineRenderer.positionCount = linePoints + 1;
|
|
}
|
|
|
|
Vector3 mid = GetMidPoint();
|
|
targetValue = mid;
|
|
mid = currentValue;
|
|
|
|
if (midPoint != null)
|
|
{
|
|
midPoint.position = GetRationalBezierPoint(startPoint.position, mid, endPoint.position, midPointPosition, StartPointWeight, midPointWeight, EndPointWeight);
|
|
}
|
|
|
|
for (int i = 0; i < linePoints; i++)
|
|
{
|
|
Vector3 p = GetRationalBezierPoint(startPoint.position, mid, endPoint.position, i / (float)linePoints, StartPointWeight, midPointWeight, EndPointWeight);
|
|
lineRenderer.SetPosition(i, p);
|
|
}
|
|
|
|
lineRenderer.SetPosition(linePoints, endPoint.position);
|
|
}
|
|
|
|
private float CalculateYFactorAdjustment(float weight)
|
|
{
|
|
//float k = 0.360f; //after testing this seemed to be a good value for most cases, more accurate k is available.
|
|
float k = Mathf.Lerp(0.493f, 0.323f, Mathf.InverseLerp(1, 15, weight)); //K calculation that is more accurate, interpolates between precalculated values.
|
|
float w = 1f + k * Mathf.Log(weight);
|
|
return w;
|
|
}
|
|
|
|
private Vector3 GetMidPoint()
|
|
{
|
|
Vector3 startPointPosition = startPoint.position;
|
|
Vector3 endPointPosition = endPoint.position;
|
|
Vector3 midpos = Vector3.Lerp(startPointPosition, endPointPosition, midPointPosition);
|
|
float yFactor = (ropeLength - Mathf.Min(Vector3.Distance(startPointPosition, endPointPosition), ropeLength)) / CalculateYFactorAdjustment(midPointWeight);
|
|
midpos.y -= yFactor;
|
|
return midpos;
|
|
}
|
|
|
|
private Vector3 GetRationalBezierPoint(Vector3 p0, Vector3 p1, Vector3 p2, float t, float w0, float w1, float w2)
|
|
{
|
|
//scale each point by its weight (can probably remove w0 and w2 if the midpoint is the only adjustable weight)
|
|
Vector3 wp0 = w0 * p0;
|
|
Vector3 wp1 = w1 * p1;
|
|
Vector3 wp2 = w2 * p2;
|
|
|
|
//calculate the denominator of the rational Bézier curve
|
|
float denominator = w0 * Mathf.Pow(1 - t, 2) + 2 * w1 * (1 - t) * t + w2 * Mathf.Pow(t, 2);
|
|
//calculate the numerator and devide by the demoninator to get the point on the curve
|
|
Vector3 point = (wp0 * Mathf.Pow(1 - t, 2) + wp1 * 2 * (1 - t) * t + wp2 * Mathf.Pow(t, 2)) / denominator;
|
|
|
|
return point;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the start point of the rope
|
|
/// </summary>
|
|
public void SetStartPoint(Transform newStartPoint, bool recalculateRope = false)
|
|
{
|
|
startPoint = newStartPoint;
|
|
if (recalculateRope)
|
|
RecalculateRope();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the end point of the rope
|
|
/// </summary>
|
|
public void SetEndPoint(Transform newEndPoint, bool recalculateRope = false)
|
|
{
|
|
endPoint = newEndPoint;
|
|
if (recalculateRope)
|
|
RecalculateRope();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the mid point of the rope
|
|
/// </summary>
|
|
public void SetMidPoint(Transform newMidPoint, bool recalculateRope = false)
|
|
{
|
|
midPoint = newMidPoint;
|
|
if (recalculateRope)
|
|
RecalculateRope();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a point along the rope at the specified position (0-1)
|
|
/// </summary>
|
|
public Vector3 GetPointAt(float position)
|
|
{
|
|
position = Mathf.Clamp01(position);
|
|
Vector3 mid = GetMidPoint();
|
|
return GetRationalBezierPoint(startPoint.position, mid, endPoint.position, position, StartPointWeight, midPointWeight, EndPointWeight);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force recalculation of the rope
|
|
/// </summary>
|
|
public void RecalculateRope()
|
|
{
|
|
if (!isInitialized)
|
|
{
|
|
Initialize();
|
|
}
|
|
|
|
if (AreEndPointsValid())
|
|
{
|
|
SetSplinePoint();
|
|
SimulatePhysics();
|
|
NotifyPointsChanged();
|
|
}
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
if (IsPrefab)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (AreEndPointsValid())
|
|
{
|
|
if (!isFirstFrame)
|
|
{
|
|
SimulatePhysics();
|
|
}
|
|
|
|
isFirstFrame = false;
|
|
}
|
|
}
|
|
|
|
private void SimulatePhysics()
|
|
{
|
|
float dampingFactor = Mathf.Max(0, 1 - damping * Time.fixedDeltaTime);
|
|
Vector3 acceleration = (targetValue - currentValue) * stiffness * Time.fixedDeltaTime;
|
|
currentVelocity = currentVelocity * dampingFactor + acceleration + otherPhysicsFactors;
|
|
currentValue += currentVelocity * Time.fixedDeltaTime;
|
|
|
|
if (Vector3.Distance(currentValue, targetValue) < valueThreshold && currentVelocity.magnitude < velocityThreshold)
|
|
{
|
|
currentValue = targetValue;
|
|
currentVelocity = Vector3.zero;
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
if (!AreEndPointsValid())
|
|
return;
|
|
|
|
Vector3 midPos = GetMidPoint();
|
|
// Uncomment if you need to visualize midpoint
|
|
// Gizmos.color = Color.red;
|
|
// Gizmos.DrawSphere(midPos, 0.2f);
|
|
}
|
|
|
|
|
|
private void NotifyPointsChanged()
|
|
{
|
|
OnPointsChanged?.Invoke();
|
|
}
|
|
|
|
private bool IsPointsMoved()
|
|
{
|
|
var startPointMoved = startPoint.position != prevStartPointPosition;
|
|
var endPointMoved = endPoint.position != prevEndPointPosition;
|
|
return startPointMoved || endPointMoved;
|
|
}
|
|
|
|
private bool IsRopeSettingsChanged()
|
|
{
|
|
var lineQualityChanged = !Mathf.Approximately(linePoints, prevLineQuality);
|
|
var ropeWidthChanged = !Mathf.Approximately(ropeWidth, prevRopeWidth);
|
|
var stiffnessChanged = !Mathf.Approximately(stiffness, prevstiffness);
|
|
var dampnessChanged = !Mathf.Approximately(damping, prevDampness);
|
|
var ropeLengthChanged = !Mathf.Approximately(ropeLength, prevRopeLength);
|
|
var midPointPositionChanged = !Mathf.Approximately(midPointPosition, prevMidPointPosition);
|
|
var midPointWeightChanged = !Mathf.Approximately(midPointWeight, prevMidPointWeight);
|
|
|
|
return lineQualityChanged
|
|
|| ropeWidthChanged
|
|
|| stiffnessChanged
|
|
|| dampnessChanged
|
|
|| ropeLengthChanged
|
|
|| midPointPositionChanged
|
|
|| midPointWeightChanged;
|
|
}
|
|
}
|
|
} |