428 lines
12 KiB
C#
428 lines
12 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
public class BezierRunner : MonoBehaviour
|
||
{
|
||
[Header("Ссылки")]
|
||
public BezierWay BezierWay;
|
||
|
||
[Header("Настройки движения")]
|
||
[SerializeField] private float _speed = 5f;
|
||
[SerializeField] private bool _loop = true;
|
||
public bool _rotateToDirection = true;
|
||
[SerializeField] private float _rotationSpeed = 5f;
|
||
[SerializeField] private Vector3 _rotationOffset = Vector3.zero;
|
||
|
||
[Header("Настройки старта")]
|
||
[SerializeField][Range(0, 1)] private float _startProgress = 0f;
|
||
|
||
[Header("Отладка")]
|
||
[SerializeField] private bool _debugMode = false;
|
||
[SerializeField] private Color _debugColor = Color.magenta;
|
||
|
||
// Приватные поля
|
||
private float _currentProgress = 0f; // Прогресс от 0 до 1
|
||
private float _currentDistance = 0f; // Пройденная дистанция
|
||
private float _totalLength = 0f; // Общая длина кривой
|
||
private List<float> _segmentLengths = new List<float>(); // Длины сегментов
|
||
private List<float> _segmentStartDistances = new List<float>(); // Накопленные дистанции
|
||
private bool _isInitialized = false;
|
||
|
||
private List<float> _lengthToT = new List<float>(); // Длина → t
|
||
private List<float> _tToLength = new List<float>(); // t → длина
|
||
private int _lutResolution = 200; // Точность таблицы
|
||
|
||
// Свойства
|
||
public float Speed { get => _speed; set => _speed = Mathf.Max(0, value); }
|
||
public float CurrentProgress => _currentProgress;
|
||
public bool IsLooping => _loop;
|
||
public bool IsMovingForward { get; private set; } = true;
|
||
public Vector3 Velocity
|
||
{
|
||
get
|
||
{
|
||
if (!_isInitialized || BezierWay == null || _speed <= 0)
|
||
return Vector3.zero;
|
||
|
||
Vector3 direction = GetCurrentTangent().normalized;
|
||
|
||
// Учитываем направление движения (вперед/назад)
|
||
if (!IsMovingForward)
|
||
direction = -direction;
|
||
|
||
return direction * _speed;
|
||
}
|
||
}
|
||
|
||
// События
|
||
public System.Action OnLoopComplete;
|
||
public System.Action OnReachedEnd;
|
||
public System.Action OnReachedStart;
|
||
|
||
void Update()
|
||
{
|
||
if (!_isInitialized || BezierWay == null) return;
|
||
|
||
Move();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Инициализация раннера
|
||
/// </summary>
|
||
public void Initialize()
|
||
{
|
||
if (BezierWay == null)
|
||
{
|
||
Debug.LogError("BezierRunner: Не назначен BezierWay!");
|
||
return;
|
||
}
|
||
|
||
if (BezierWay.PointsCount < 2)
|
||
{
|
||
Debug.LogError($"BezierRunner: Недостаточно точек ({BezierWay.PointsCount})");
|
||
return;
|
||
}
|
||
|
||
// Строим таблицу для равномерной скорости
|
||
BuildLengthToTLookupTable();
|
||
|
||
|
||
_currentProgress = Mathf.Clamp01(_startProgress);
|
||
_currentDistance = _currentProgress; // Теперь distance = normalizedLength
|
||
|
||
UpdatePositionByNormalizedLength();
|
||
|
||
if (_rotateToDirection)
|
||
{
|
||
UpdateRotation();
|
||
}
|
||
|
||
_isInitialized = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Вычисляет общую длину кривой и длины сегментов
|
||
/// </summary>
|
||
private void CalculateCurveLength()
|
||
{
|
||
_segmentLengths.Clear();
|
||
_segmentStartDistances.Clear();
|
||
_totalLength = 0f;
|
||
|
||
int segments = 100; // Количество сегментов для аппроксимации длины
|
||
Vector3 prevPoint = BezierWay.GetPoint(0);
|
||
float currentDistance = 0f;
|
||
|
||
for (int i = 1; i <= segments; i++)
|
||
{
|
||
float t = i / (float)segments;
|
||
Vector3 currentPoint = BezierWay.GetPoint(t);
|
||
float segmentLength = Vector3.Distance(prevPoint, currentPoint);
|
||
currentDistance += segmentLength;
|
||
|
||
// Сохраняем длину сегмента (каждый 1/segments часть)
|
||
if (i % (segments / 10) == 0 || i == segments)
|
||
{
|
||
_segmentLengths.Add(segmentLength);
|
||
_segmentStartDistances.Add(currentDistance - segmentLength);
|
||
}
|
||
|
||
prevPoint = currentPoint;
|
||
}
|
||
|
||
_totalLength = currentDistance;
|
||
|
||
// Нормализуем длины сегментов
|
||
for (int i = 0; i < _segmentLengths.Count; i++)
|
||
{
|
||
_segmentLengths[i] /= _totalLength;
|
||
_segmentStartDistances[i] /= _totalLength;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
private float GetNormalizedLengthFromT(float t)
|
||
{
|
||
int index = Mathf.RoundToInt(t * _lutResolution);
|
||
index = Mathf.Clamp(index, 0, _lutResolution);
|
||
return _tToLength[index];
|
||
}
|
||
|
||
private void UpdatePositionByNormalizedLength()
|
||
{
|
||
float t = GetTFromNormalizedLength(_currentDistance);
|
||
transform.position = BezierWay.GetPoint(t);
|
||
|
||
// Сохраняем прогресс для обратной совместимости
|
||
_currentProgress = t;
|
||
}
|
||
|
||
|
||
public void SetProgress(float progress)
|
||
{
|
||
_currentProgress = Mathf.Clamp01(progress);
|
||
_currentDistance = GetNormalizedLengthFromT(_currentProgress);
|
||
UpdatePositionByNormalizedLength();
|
||
|
||
if (_rotateToDirection)
|
||
UpdateRotation();
|
||
}
|
||
|
||
private void BuildLengthToTLookupTable()
|
||
{
|
||
_lengthToT.Clear();
|
||
_tToLength.Clear();
|
||
|
||
float totalLength = 0f;
|
||
Vector3 prevPoint = BezierWay.GetPoint(0);
|
||
_tToLength.Add(0f);
|
||
_lengthToT.Add(0f);
|
||
|
||
// Проходим по кривой с мелким шагом
|
||
for (int i = 1; i <= _lutResolution; i++)
|
||
{
|
||
float t = i / (float)_lutResolution;
|
||
Vector3 currentPoint = BezierWay.GetPoint(t);
|
||
float segmentLength = Vector3.Distance(prevPoint, currentPoint);
|
||
totalLength += segmentLength;
|
||
|
||
_tToLength.Add(totalLength);
|
||
prevPoint = currentPoint;
|
||
}
|
||
|
||
_totalLength = totalLength;
|
||
|
||
// Нормализуем длины (0-1)
|
||
for (int i = 0; i <= _lutResolution; i++)
|
||
{
|
||
float normalizedLength = _tToLength[i] / _totalLength;
|
||
_tToLength[i] = normalizedLength;
|
||
}
|
||
|
||
// Строим обратное отображение (длина → t)
|
||
for (int i = 0; i <= _lutResolution; i++)
|
||
{
|
||
float t = i / (float)_lutResolution;
|
||
float length = _tToLength[i];
|
||
_lengthToT.Add(length);
|
||
}
|
||
}
|
||
|
||
private float GetTFromNormalizedLength(float normalizedLength)
|
||
{
|
||
if (normalizedLength <= 0) return 0;
|
||
if (normalizedLength >= 1) return 1;
|
||
|
||
// Бинарный поиск в таблице
|
||
int index = _lengthToT.BinarySearch(normalizedLength);
|
||
if (index >= 0) return index / (float)_lutResolution;
|
||
|
||
index = ~index;
|
||
if (index == 0) return 0;
|
||
if (index >= _lengthToT.Count) return 1;
|
||
|
||
// Линейная интерполяция
|
||
float tPrev = (index - 1) / (float)_lutResolution;
|
||
float tNext = index / (float)_lutResolution;
|
||
float lengthPrev = _lengthToT[index - 1];
|
||
float lengthNext = _lengthToT[index];
|
||
|
||
float lerpFactor = (normalizedLength - lengthPrev) / (lengthNext - lengthPrev);
|
||
return Mathf.Lerp(tPrev, tNext, lerpFactor);
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Перемещает объект вдоль кривой
|
||
/// </summary>
|
||
private void Move()
|
||
{
|
||
float deltaLength = _speed * Time.deltaTime / _totalLength; // Нормализованное изменение
|
||
|
||
if (IsMovingForward)
|
||
{
|
||
_currentDistance += deltaLength;
|
||
|
||
if (_currentDistance >= 1f)
|
||
{
|
||
if (_loop)
|
||
{
|
||
_currentDistance -= 1f;
|
||
OnLoopComplete?.Invoke();
|
||
}
|
||
else
|
||
{
|
||
_currentDistance = 1f;
|
||
OnReachedEnd?.Invoke();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_currentDistance -= deltaLength;
|
||
|
||
if (_currentDistance <= 0f)
|
||
{
|
||
if (_loop)
|
||
{
|
||
_currentDistance += 1f;
|
||
OnLoopComplete?.Invoke();
|
||
}
|
||
else
|
||
{
|
||
_currentDistance = 0f;
|
||
OnReachedStart?.Invoke();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
UpdatePositionByNormalizedLength();
|
||
|
||
if (_rotateToDirection)
|
||
{
|
||
UpdateRotation();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обновляет позицию объекта на кривой
|
||
/// </summary>
|
||
private void UpdatePosition()
|
||
{
|
||
Vector3 newPosition = BezierWay.GetPoint(_currentProgress);
|
||
transform.position = newPosition;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обновляет вращение объекта вдоль кривой
|
||
/// </summary>
|
||
private void UpdateRotation()
|
||
{
|
||
Vector3 tangent = BezierWay.GetTangent(_currentProgress);
|
||
|
||
if (tangent != Vector3.zero)
|
||
{
|
||
Quaternion targetRotation = Quaternion.LookRotation(tangent) * Quaternion.Euler(_rotationOffset);
|
||
|
||
if (_rotationSpeed > 0)
|
||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, _rotationSpeed * Time.deltaTime);
|
||
else
|
||
transform.rotation = targetRotation;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Меняет направление движения
|
||
/// </summary>
|
||
public void ReverseDirection()
|
||
{
|
||
IsMovingForward = !IsMovingForward;
|
||
if (_debugMode)
|
||
Debug.Log($"Направление изменено: {(IsMovingForward ? "вперед" : "назад")}");
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Устанавливает скорость
|
||
/// </summary>
|
||
public void SetSpeed(float speed)
|
||
{
|
||
_speed = Mathf.Max(0, speed);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Телепортирует объект в указанную точку на кривой
|
||
/// </summary>
|
||
public void TeleportToPoint(float progress)
|
||
{
|
||
SetProgress(progress);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает текущую точку на кривой
|
||
/// </summary>
|
||
public Vector3 GetCurrentPoint()
|
||
{
|
||
return BezierWay.GetPoint(_currentProgress);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает текущую касательную
|
||
/// </summary>
|
||
public Vector3 GetCurrentTangent()
|
||
{
|
||
return BezierWay.GetTangent(_currentProgress);
|
||
}
|
||
|
||
#region Отладка
|
||
|
||
void OnDrawGizmos()
|
||
{
|
||
if (!_debugMode || BezierWay == null) return;
|
||
|
||
// Рисуем позицию раннера
|
||
Gizmos.color = _debugColor;
|
||
Gizmos.DrawWireSphere(transform.position, 0.3f);
|
||
|
||
// Рисуем направление движения
|
||
if (_isInitialized && BezierWay.PointsCount >= 2)
|
||
{
|
||
Vector3 tangent = GetCurrentTangent();
|
||
Gizmos.color = Color.red;
|
||
Gizmos.DrawRay(transform.position, tangent * 1.5f);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Публичные методы для внешнего управления
|
||
|
||
/// <summary>
|
||
/// Запускает движение с начала
|
||
/// </summary>
|
||
public void StartFromBeginning()
|
||
{
|
||
SetProgress(0f);
|
||
IsMovingForward = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запускает движение с конца
|
||
/// </summary>
|
||
public void StartFromEnd()
|
||
{
|
||
SetProgress(1f);
|
||
IsMovingForward = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Останавливает движение (скорость = 0)
|
||
/// </summary>
|
||
public void Stop()
|
||
{
|
||
_speed = 0f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возобновляет движение с последней скоростью
|
||
/// </summary>
|
||
public void Resume(float previousSpeed)
|
||
{
|
||
_speed = previousSpeed;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обновляет ссылку на кривую
|
||
/// </summary>
|
||
public void SetBezierWay(BezierWay bezierWay)
|
||
{
|
||
BezierWay = bezierWay;
|
||
Initialize();
|
||
}
|
||
|
||
#endregion
|
||
} |