Загрузка данных
using System;
using UnityEngine;
public enum PathMoveAxis
{
Horizontal, // A/D
Vertical // W/S
}
[DisallowMultipleComponent]
public class Path : MonoBehaviour
{
[Header("Path Type")]
public bool isDoorPath = false;
[Header("Movement")]
[SerializeField] private PathMoveAxis moveAxis = PathMoveAxis.Horizontal;
public PathMoveAxis MoveAxis => moveAxis;
[Header("Main Path -> Main Path Switch (optional)")]
[Tooltip("Trigger collider (empty GO with Collider set to IsTrigger) that enables switching between main paths.")]
[SerializeField] private Collider mainSwitchTrigger;
[Tooltip("Other main path to switch to when inside mainSwitchTrigger and pressing opposite axis.")]
[SerializeField] private Path otherMainPath;
public Collider MainSwitchTrigger => mainSwitchTrigger;
public Path OtherMainPath => otherMainPath;
[Header("Points (auto from children)")]
[SerializeField] private Transform[] points;
[Header("Curve Settings")]
[SerializeField, Range(4, 64)] private int samplesPerSegment = 20;
// Arc-length lookup table (sampled)
private Vector3[] samples; // positions along curve
private float[] sampleCumLen; // cumulative length at each sample
private float totalLen;
public float TotalLength => totalLen;
private void Awake() => Rebuild();
#if UNITY_EDITOR
private void OnValidate()
{
if (!Application.isPlaying) Rebuild();
}
#endif
public void Rebuild()
{
int n = transform.childCount;
points = new Transform[n];
for (int i = 0; i < n; i++) points[i] = transform.GetChild(i);
if (n < 2)
{
totalLen = 0f;
samples = Array.Empty<Vector3>();
sampleCumLen = Array.Empty<float>();
return;
}
int segCount = n - 1;
int steps = Mathf.Max(4, samplesPerSegment);
int sampleCount = segCount * steps + 1;
samples = new Vector3[sampleCount];
sampleCumLen = new float[sampleCount];
int idx = 0;
samples[idx] = points[0].position;
sampleCumLen[idx] = 0f;
idx++;
totalLen = 0f;
Vector3 prev = samples[0];
for (int seg = 0; seg < segCount; seg++)
{
Vector3 p0 = points[Mathf.Max(seg - 1, 0)].position;
Vector3 p1 = points[seg].position;
Vector3 p2 = points[seg + 1].position;
Vector3 p3 = points[Mathf.Min(seg + 2, n - 1)].position;
for (int j = 1; j <= steps; j++)
{
float t = (float)j / steps;
Vector3 pos = CatmullRom(p0, p1, p2, p3, t);
samples[idx] = pos;
totalLen += Vector3.Distance(prev, pos);
sampleCumLen[idx] = totalLen;
prev = pos;
idx++;
}
}
}
public Vector3 EvaluatePosition(float s)
{
if (samples == null || samples.Length == 0) return transform.position;
if (samples.Length == 1 || totalLen <= 0f) return samples[0];
s = Mathf.Clamp(s, 0f, totalLen);
int i = FindSampleIndex(s);
int i2 = Mathf.Min(i + 1, samples.Length - 1);
float a = sampleCumLen[i];
float b = sampleCumLen[i2];
float t = (b <= a) ? 0f : (s - a) / (b - a);
return Vector3.Lerp(samples[i], samples[i2], t);
}
public Vector3 EvaluateTangent(float s)
{
if (samples == null || samples.Length < 2 || totalLen <= 0f) return Vector3.right;
s = Mathf.Clamp(s, 0f, totalLen);
float eps = Mathf.Max(0.001f, totalLen * 0.001f);
float s0 = Mathf.Clamp(s - eps, 0f, totalLen);
float s1 = Mathf.Clamp(s + eps, 0f, totalLen);
Vector3 p0 = EvaluatePosition(s0);
Vector3 p1 = EvaluatePosition(s1);
Vector3 d = p1 - p0;
return d.sqrMagnitude < 1e-8f ? Vector3.right : d.normalized;
}
public float ClosestS(Vector3 worldPos)
{
if (samples == null || samples.Length < 2 || totalLen <= 0f) return 0f;
int best = 0;
float bestD = float.PositiveInfinity;
for (int i = 0; i < samples.Length; i++)
{
float d = (worldPos - samples[i]).sqrMagnitude;
if (d < bestD)
{
bestD = d;
best = i;
}
}
return sampleCumLen[best];
}
private int FindSampleIndex(float s)
{
int low = 0;
int high = sampleCumLen.Length - 2;
while (low <= high)
{
int mid = (low + high) / 2;
float a = sampleCumLen[mid];
float b = sampleCumLen[mid + 1];
if (s < a) high = mid - 1;
else if (s >= b) low = mid + 1;
else return mid;
}
return Mathf.Clamp(low, 0, sampleCumLen.Length - 2);
}
private static Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
float t2 = t * t;
float t3 = t2 * t;
return 0.5f * (
(2f * p1) +
(-p0 + p2) * t +
(2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
(-p0 + 3f * p1 - 3f * p2 + p3) * t3
);
}
private void OnDrawGizmos()
{
int n = transform.childCount;
if (n < 2) return;
Gizmos.color = isDoorPath ? Color.magenta : Color.cyan;
if (samples != null && samples.Length >= 2)
{
for (int i = 0; i < samples.Length - 1; i++)
Gizmos.DrawLine(samples[i], samples[i + 1]);
return;
}
Transform prev = transform.GetChild(0);
for (int i = 1; i < n; i++)
{
Transform cur = transform.GetChild(i);
Gizmos.DrawLine(prev.position, cur.position);
prev = cur;
}
}
}