422 lines
13 KiB
C#
Raw 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] 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
}