Написать код для интерполяции позиции объекта к цели
Условие
Дополните код для плавного перемещения объекта к целевой позиции.
Требования
- Объект должен двигаться к цели с постоянной скоростью
- Движение должно быть плавным
- Объект должен останавливаться при достижении цели
- Код должен быть оптимизирован для вызова в Update
Начальный код
Класс Mover с полями target (Vector3) и speed (float). Нужно реализовать метод Update для перемещения к цели.
Подсказки
- Используйте Vector3.MoveTowards или собственную реализацию
- Не забудьте про Time.deltaTime
- Проверяйте достижение цели
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Интерполяция позиции объекта
Основная задача: плавно передвигать объект к цели с постоянной скоростью. Есть несколько подходов: Vector3.MoveTowards (встроенный метод), Vector3.Lerp (интерполяция) или ручной расчёт. Рассмотрю оба варианта.
Вариант 1: Vector3.MoveTowards (рекомендуется)
using UnityEngine;
public class Mover : MonoBehaviour
{
[SerializeField] private Vector3 target;
[SerializeField] private float speed = 5f;
private bool isMoving = false;
void Update()
{
// Проверяем достигли ли цели
if (Vector3.Distance(transform.position, target) < 0.01f)
{
transform.position = target; // Точно устанавливаем позицию
isMoving = false;
return;
}
// Двигаемся к цели с постоянной скоростью
transform.position = Vector3.MoveTowards(
transform.position,
target,
speed * Time.deltaTime
);
isMoving = true;
}
/// <summary>Установить новую целевую позицию</summary>
public void SetTarget(Vector3 newTarget)
{
target = newTarget;
}
/// <summary>Получить текущую целевую позицию</summary>
public Vector3 GetTarget() => target;
/// <summary>Проверить движется ли объект</summary>
public bool IsMoving() => isMoving;
}
Вариант 2: Ручная реализация
public class MoverManual : MonoBehaviour
{
[SerializeField] private Vector3 target;
[SerializeField] private float speed = 5f;
private bool isMoving = false;
private const float DISTANCE_THRESHOLD = 0.01f;
void Update()
{
// Вычисляем направление к цели
Vector3 direction = target - transform.position;
float distance = direction.magnitude;
// Если достигли цели
if (distance < DISTANCE_THRESHOLD)
{
transform.position = target;
isMoving = false;
return;
}
// Нормализуем направление
Vector3 normalizedDirection = direction.normalized;
// Вычисляем расстояние для движения на этот кадр
float moveDistance = speed * Time.deltaTime;
// Если расстояние больше чем нужно двигаться до цели
// то устанавливаем точно на цель
if (moveDistance >= distance)
{
transform.position = target;
isMoving = false;
}
else
{
// Иначе двигаемся на расчитанное расстояние
transform.position += normalizedDirection * moveDistance;
isMoving = true;
}
}
public void SetTarget(Vector3 newTarget) => target = newTarget;
public Vector3 GetTarget() => target;
public bool IsMoving() => isMoving;
}
Вариант 3: С использованием Vector3.Lerp
⚠️ Важно: Lerp не рекомендуется для движения с постоянной скоростью!
Lerp интерполирует по времени (0-1), а не по расстоянию. Однако можно использовать, если понимать разницу:
public class MoverLerp : MonoBehaviour
{
[SerializeField] private Vector3 target;
[SerializeField] private Vector3 startPosition;
[SerializeField] private float speed = 5f;
private float journeyLength = 0f;
private float startTime = 0f;
private bool isMoving = false;
void Start()
{
startPosition = transform.position;
journeyLength = Vector3.Distance(startPosition, target);
startTime = Time.time;
}
void Update()
{
// Расчитываем прогресс пути
float distanceCovered = (Time.time - startTime) * speed;
float fractionOfJourney = distanceCovered / journeyLength;
if (fractionOfJourney < 1f)
{
transform.position = Vector3.Lerp(
startPosition,
target,
fractionOfJourney
);
isMoving = true;
}
else
{
transform.position = target;
isMoving = false;
}
}
public void SetTarget(Vector3 newTarget)
{
if (!isMoving)
{
target = newTarget;
startPosition = transform.position;
journeyLength = Vector3.Distance(startPosition, target);
startTime = Time.time;
}
}
}
Вариант 4: С ускорением и торможением (бонус)
public class MoverWithEasing : MonoBehaviour
{
[SerializeField] private Vector3 target;
[SerializeField] private float speed = 5f;
[SerializeField] private float accelerationDistance = 1f; // Расстояние для ускорения
[SerializeField] private float decelerationDistance = 1f; // Расстояние для торможения
private Vector3 startPosition;
private float totalDistance;
private bool isMoving = false;
void Update()
{
Vector3 direction = target - transform.position;
float distanceRemaining = direction.magnitude;
// Проверяем достижение цели
if (distanceRemaining < 0.01f)
{
transform.position = target;
isMoving = false;
return;
}
// Расчитываем текущую скорость с учётом ускорения/торможения
float currentSpeed = speed;
// Ускорение в начале пути
if (distanceRemaining > totalDistance - accelerationDistance)
{
float accelerationProgress = 1f - ((distanceRemaining - (totalDistance - accelerationDistance)) / accelerationDistance);
currentSpeed *= accelerationProgress;
}
// Торможение в конце пути
if (distanceRemaining < decelerationDistance)
{
float decelerationProgress = distanceRemaining / decelerationDistance;
currentSpeed *= decelerationProgress;
}
// Двигаемся с учётом скорости
Vector3 normalizedDirection = direction.normalized;
transform.position += normalizedDirection * currentSpeed * Time.deltaTime;
isMoving = true;
}
public void SetTarget(Vector3 newTarget)
{
target = newTarget;
startPosition = transform.position;
totalDistance = Vector3.Distance(startPosition, target);
}
public bool IsMoving() => isMoving;
}
Сравнение методов
| Метод | Плюсы | Минусы | Использование |
|---|---|---|---|
| MoveTowards | Просто, встроенный, постоянная скорость | Нет контроля разворота | ✓ Рекомендуется |
| Ручной расчёт | Максимальный контроль | Больше кода | Сложные случаи |
| Lerp | Можно с анимацией | НЕ для постоянной скорости | Не для этой задачи |
| С ускорением | Натурально выглядит | Сложнее | Для полировки |
Практический пример использования
public class PlayerController : MonoBehaviour
{
private Mover mover;
void Start()
{
mover = GetComponent<Mover>();
}
void Update()
{
// Кликаем для установки целевой позиции
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
mover.SetTarget(hit.point);
}
}
// Выводим информацию
Debug.Log($"Движется: {mover.IsMoving()}");
}
}
Оптимизация
1. Кэширование расстояния:
private float cachedDistance = float.MaxValue;
private float distanceUpdateInterval = 0.1f; // Обновляем каждые 0.1 сек
private float distanceUpdateTimer = 0f;
void Update()
{
distanceUpdateTimer += Time.deltaTime;
if (distanceUpdateTimer >= distanceUpdateInterval)
{
cachedDistance = Vector3.Distance(transform.position, target);
distanceUpdateTimer = 0f;
}
}
2. Стопор для очень маленьких расстояний:
const float ZERO_SPEED_DISTANCE = 0.001f;
if (distanceRemaining < ZERO_SPEED_DISTANCE)
{
transform.position = target;
}
Рекомендация
Используй Вариант 1 (Vector3.MoveTowards) потому что:
- ✓ Встроенный метод, оптимизированный в Unity
- ✓ Гарантирует постоянную скорость
- ✓ Автоматически обрабатывает достижение цели
- ✓ Минимум кода
- ✓ Нет floating-point ошибок при накоплении
Этот метод обеспечивает надёжное, эффективное и простое движение объектов.