422 lines
13 KiB
C#
422 lines
13 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] private bool _startFromClosestPoint = true;
|
||
[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;
|
||
|
||
// Свойства
|
||
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;
|
||
}
|
||
|
||
// Вычисляем длины сегментов
|
||
CalculateCurveLength();
|
||
|
||
// Определяем начальную позицию
|
||
if (_startFromClosestPoint)
|
||
{
|
||
FindClosestPointOnCurve();
|
||
}
|
||
else
|
||
{
|
||
_currentProgress = Mathf.Clamp01(_startProgress);
|
||
_currentDistance = _currentProgress * _totalLength;
|
||
}
|
||
|
||
// Устанавливаем начальную позицию
|
||
UpdatePosition();
|
||
|
||
// Устанавливаем начальное вращение
|
||
if (_rotateToDirection)
|
||
{
|
||
UpdateRotation();
|
||
}
|
||
|
||
_isInitialized = true;
|
||
|
||
if (_debugMode)
|
||
Debug.Log($"BezierRunner инициализирован: Прогресс={_currentProgress:F3}, Дистанция={_currentDistance:F2}/{_totalLength:F2}");
|
||
}
|
||
|
||
/// <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;
|
||
}
|
||
|
||
if (_debugMode)
|
||
Debug.Log($"Длина кривой: {_totalLength:F2} единиц");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Находит ближайшую точку на кривой к текущей позиции раннера
|
||
/// </summary>
|
||
private void FindClosestPointOnCurve()
|
||
{
|
||
Vector3 runnerPosition = transform.position;
|
||
float minDistance = float.MaxValue;
|
||
float bestT = 0f;
|
||
|
||
// Поиск с высоким разрешением
|
||
int samples = 50;
|
||
for (int i = 0; i <= samples; i++)
|
||
{
|
||
float t = i / (float)samples;
|
||
Vector3 pointOnCurve = BezierWay.GetPoint(t);
|
||
float distance = Vector3.Distance(runnerPosition, pointOnCurve);
|
||
|
||
if (distance < minDistance)
|
||
{
|
||
minDistance = distance;
|
||
bestT = t;
|
||
}
|
||
}
|
||
|
||
// Уточняем поиск вокруг найденной точки
|
||
float refinementRange = 1f / samples;
|
||
int refinementSamples = 10;
|
||
for (int i = -refinementSamples; i <= refinementSamples; i++)
|
||
{
|
||
float t = Mathf.Clamp01(bestT + (i / (float)(refinementSamples * samples)));
|
||
Vector3 pointOnCurve = BezierWay.GetPoint(t);
|
||
float distance = Vector3.Distance(runnerPosition, pointOnCurve);
|
||
|
||
if (distance < minDistance)
|
||
{
|
||
minDistance = distance;
|
||
bestT = t;
|
||
}
|
||
}
|
||
|
||
_currentProgress = bestT;
|
||
_currentDistance = bestT * _totalLength;
|
||
|
||
if (_debugMode)
|
||
Debug.Log($"Найдена ближайшая точка: t={bestT:F3}, расстояние={minDistance:F2}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Перемещает объект вдоль кривой
|
||
/// </summary>
|
||
private void Move()
|
||
{
|
||
float deltaDistance = _speed * Time.deltaTime;
|
||
|
||
if (IsMovingForward)
|
||
{
|
||
_currentDistance += deltaDistance;
|
||
|
||
// Проверка на достижение конца
|
||
if (_currentDistance >= _totalLength)
|
||
{
|
||
if (_loop)
|
||
{
|
||
_currentDistance -= _totalLength;
|
||
OnLoopComplete?.Invoke();
|
||
if (_debugMode)
|
||
Debug.Log("Завершен полный цикл кривой");
|
||
}
|
||
else
|
||
{
|
||
_currentDistance = _totalLength;
|
||
OnReachedEnd?.Invoke();
|
||
if (_debugMode)
|
||
Debug.Log("Достигнут конец кривой");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_currentDistance -= deltaDistance;
|
||
|
||
// Проверка на достижение начала
|
||
if (_currentDistance <= 0f)
|
||
{
|
||
if (_loop)
|
||
{
|
||
_currentDistance += _totalLength;
|
||
OnLoopComplete?.Invoke();
|
||
if (_debugMode)
|
||
Debug.Log("Завершен полный цикл кривой");
|
||
}
|
||
else
|
||
{
|
||
_currentDistance = 0f;
|
||
OnReachedStart?.Invoke();
|
||
if (_debugMode)
|
||
Debug.Log("Достигнуто начало кривой");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Обновляем прогресс
|
||
_currentProgress = _currentDistance / _totalLength;
|
||
|
||
// Обновляем позицию и вращение
|
||
UpdatePosition();
|
||
|
||
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>
|
||
/// Устанавливает прогресс вручную (0-1)
|
||
/// </summary>
|
||
public void SetProgress(float progress)
|
||
{
|
||
_currentProgress = Mathf.Clamp01(progress);
|
||
_currentDistance = _currentProgress * _totalLength;
|
||
UpdatePosition();
|
||
|
||
if (_rotateToDirection)
|
||
UpdateRotation();
|
||
|
||
if (_debugMode)
|
||
Debug.Log($"Прогресс установлен на {_currentProgress:F3}");
|
||
}
|
||
|
||
/// <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
|
||
} |