reckfellENGINE

Интерполяция: скорость, сглаживание и поворот

Когда нужен MoveToward, как вычислять вес Lerp по времени и почему это не физическая интерполяция.

На этой странице

Выбрать поведение

НужноОперацияСмысл параметра
Пройти не более speed × deltaMoveTowardМаксимальное расстояние шага
Получить долю пути между A и BLerpВес, а не расстояние и не секунды
Каждый шаг уменьшать остаток с заданным периодом полураспадаLerp с весом 1 − 0,5^(delta / halfLife)halfLife — секунды до половины остатка при постоянной цели
Плавно повернутьсяLerpAngle или нормализованные Quaternion.SlerpДоля углового перехода

Lerp с весом 0 даёт A, с 1 — B. Значение за диапазоном может экстраполировать: от (0, 0) к (10, 0) с весом 2 получается (20, 0). Если переход должен остановиться, ограничьте вес или используйте MoveToward по задаче.

Сравнить движение

Добавьте SmoothFollow2D.cs к корню Entity2D отдельного C#-уровня 1.0.0, задайте окно/viewport 640×420, соберите и нажмите F6. Верхняя белая точка идёт со скоростью 120 пикселей/с, нижняя синяя сокращает оставшийся путь вдвое за 0,3 с. Белые кольца отмечают цели.

Постоянная скорость сверху и экспоненциальное сглаживание снизу

SmoothFollow2D.cs
C#
using System;
using Reckfell;

public partial class SmoothFollow2D : Entity2D
{
    [Export] public float Speed { get; set; } = 120;
    [Export] public float HalfLife { get; set; } = 0.3f;
    [Export] public float TargetX { get; set; } = 520;
    public Vector2 LinearPosition { get; private set; } = new(80, 190);
    public Vector2 SmoothPosition { get; private set; } = new(80, 300);

    public override void _Ready()
    {
        bool ru = TranslationServer.GetLocale().StartsWith("ru");
        var title = new Label { Position = new Vector2(24, 20), Text = ru
            ? "Постоянная скорость и сглаживание" : "Constant speed and smoothing" };
        title.AddThemeFontSizeOverride("font_size", 22); AddChild(title);
        AddChild(new Label { Position = new Vector2(24, 65), Text = ru
            ? "MoveToward: 120 пикселей/с. Lerp: половина остатка за 0,3 с."
            : "MoveToward: 120 pixels/s. Lerp: half the remaining gap per 0.3 s." });
    }

    public static float SmoothingWeight(float halfLife, double delta)
    {
        if (!float.IsFinite(halfLife) || halfLife <= 0 || !double.IsFinite(delta) || delta < 0)
            throw new ArgumentOutOfRangeException(nameof(halfLife));
        return (float)(1 - Math.Pow(0.5, delta / halfLife));
    }

    public void Reset()
    {
        LinearPosition = new Vector2(80, 190);
        SmoothPosition = new Vector2(80, 300);
        QueueRedraw();
    }

    public Error Advance(double delta)
    {
        if (!double.IsFinite(delta) || delta < 0 || !float.IsFinite(Speed) || Speed < 0 ||
            !float.IsFinite(HalfLife) || HalfLife <= 0 || !float.IsFinite(TargetX) || TargetX < 40 || TargetX > 600)
            return Error.InvalidParameter;
        float step = (float)Math.Min(Speed * delta, float.MaxValue);
        LinearPosition = LinearPosition.MoveToward(new Vector2(TargetX, 190), step);
        SmoothPosition = SmoothPosition.Lerp(new Vector2(TargetX, 300), SmoothingWeight(HalfLife, delta));
        QueueRedraw();
        return Error.Ok;
    }

    public override void _Process(double delta)
    {
        if (Advance(delta) == Error.Ok) return;
        SetProcess(false);
        ReckfellApi.PrintErr("SmoothFollow2D: invalid settings");
    }

    public override void _Draw()
    {
        DrawRect(new Rect2(0, 0, 640, 420), new Color(0.035f, 0.04f, 0.05f));
        DrawLine(new Vector2(80, 190), new Vector2(TargetX, 190), new Color(0.25f, 0.25f, 0.25f), 2);
        DrawLine(new Vector2(80, 300), new Vector2(TargetX, 300), new Color(0.25f, 0.25f, 0.25f), 2);
        DrawCircle(new Vector2(TargetX, 190), 11, Colors.White, false, 2, true);
        DrawCircle(new Vector2(TargetX, 300), 11, Colors.White, false, 2, true);
        DrawCircle(LinearPosition, 8, Colors.White);
        DrawCircle(SmoothPosition, 8, new Color(0.25f, 0.46f, 1));
    }
}

Через 0,3 с при исходном X = 80 и цели 520 верхняя точка имеет X = 116, нижняя — около 300. MoveToward не перескакивает цель. Сглаживание быстро идёт в начале и замедляется вблизи цели; это не постоянная скорость. Если нужен окончательный останов, задайте допуск и явно поставьте значение в цель.

Что зависит от частоты кадров

Постоянный вес вроде 0,1 в каждом _Process даёт разный итог за секунду при разном числе вызовов. Вес по времени устраняет эту зависимость для неизменной цели и выбранной экспоненциальной модели, с допуском float. В стенде 30 и 60 шагов на одну секунду дают близкий результат. Это не обещание одинакового результата для движущейся цели, разных моментов ввода, столкновений и произвольной симуляции.

delta здесь — время игрового шага. Не умножайте уже рассчитанный вес на delta повторно. Очень большой шаг может резко приблизить к цели; пауза, ограничения шага и шкала времени должны соответствовать правилам вашей игры.

Поворот и преобразование

Углы около +π и −π близки геометрически. LerpAngle выбирает короткий угловой путь; обычный Lerp чисел может повернуть через ноль. Для произвольного 3D-поворота используйте единичные Quaternion и Slerp, а не покомпонентное усреднение Euler.

Transform3D.InterpolateWith интерполирует перенос и разложение базиса, а не физическое движение с коллизиями. Вырожденный масштаб, отражения и произвольный shear требуют отдельного решения; нельзя ожидать, что такая интерполяция сохранит любую геометрию. Сглаживание позиции в этом руководстве также не заменяет физическую интерполяцию между игровыми шагами.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.