428 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}