← Назад к вопросам

Написать код для интерполяции позиции объекта к цели

1.0 Junior🔥 241 комментариев
#C# и ООП#Unity Core#Физика и математика

Условие

Дополните код для плавного перемещения объекта к целевой позиции.

Требования

  1. Объект должен двигаться к цели с постоянной скоростью
  2. Движение должно быть плавным
  3. Объект должен останавливаться при достижении цели
  4. Код должен быть оптимизирован для вызова в Update

Начальный код

Класс Mover с полями target (Vector3) и speed (float). Нужно реализовать метод Update для перемещения к цели.

Подсказки

  • Используйте Vector3.MoveTowards или собственную реализацию
  • Не забудьте про Time.deltaTime
  • Проверяйте достижение цели

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Интерполяция позиции объекта

Основная задача: плавно передвигать объект к цели с постоянной скоростью. Есть несколько подходов: 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 ошибок при накоплении

Этот метод обеспечивает надёжное, эффективное и простое движение объектов.

Написать код для интерполяции позиции объекта к цели | PrepBro