reckfellENGINE

Векторы: направление, расстояние и отражение

Полный 2D-пример наведения, знак угла, Dot/Cross и отличие Bounce от Reflect.

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

Направить вектор к цели

Создайте C#-уровень 1.0.0 с корнем Entity2D, добавьте VectorAim2D.cs и прикрепите к корню. Для картинки как ниже задайте окно/viewport 640×420, соберите проект и нажмите F6. Двигайте мышь: синяя стрелка идёт от начала (100, 270) к цели, подпись показывает длину и угол. GetLocalMousePosition получает точку в том же локальном пространстве, где рисуется стрелка.

Вектор от начала к цели и его проекция на ось X

VectorAim2D.cs
C#
using System;
using Reckfell;

public partial class VectorAim2D : Entity2D
{
    [Export] public bool UseMouse { get; set; } = true;
    public Vector2 OriginPoint => new(100, 270);
    public Vector2 Target { get; private set; } = new(490, 150);
    public Vector2 Offset => Target - OriginPoint;
    public Vector2 Direction => Offset.Normalized();
    private Label _status = null!;
    private bool _ru;
    private readonly Color _blue = new(0.25f, 0.46f, 1);

    public override void _Ready()
    {
        _ru = TranslationServer.GetLocale().StartsWith("ru");
        var title = new Label { Position = new Vector2(24, 20), Text = _ru
            ? "Векторы 2D: начало, цель и направление" : "2D vectors: origin, target and direction" };
        title.AddThemeFontSizeOverride("font_size", 22);
        AddChild(title);
        _status = new Label { Position = new Vector2(24, 58) };
        AddChild(_status);
        UpdateCaption();
    }

    public Error SetTarget(Vector2 target)
    {
        if (!target.IsFinite()) return Error.InvalidParameter;
        Target = target;
        if (_status is not null) UpdateCaption();
        QueueRedraw();
        return Error.Ok;
    }

    public override void _Process(double delta)
    {
        if (!UseMouse) return;
        Vector2 mouse = GetLocalMousePosition().Clamp(new Vector2(40, 115), new Vector2(600, 370));
        if (!mouse.IsEqualApprox(Target)) SetTarget(mouse);
    }

    private void UpdateCaption()
    {
        string angle = Offset.IsZeroApprox() ? "--" :
            FormattableString.Invariant($"{Mathf.RadToDeg(Vector2.Right.AngleTo(Direction)):F1}");
        _status.Text = _ru
            ? FormattableString.Invariant($"X вправо, Y вниз. Длина {Offset.Length():F1}; угол {angle}°")
            : FormattableString.Invariant($"X right, Y down. Length {Offset.Length():F1}; angle {angle}°");
    }

    private void Arrow(Vector2 from, Vector2 to, Color color)
    {
        Vector2 offset = to - from;
        if (offset.IsZeroApprox()) return;
        Vector2 direction = offset.Normalized();
        DrawLine(from, to, color, 3, true);
        DrawLine(to, to - direction.Rotated(0.45f) * 12, color, 3, true);
        DrawLine(to, to - direction.Rotated(-0.45f) * 12, color, 3, true);
    }

    public override void _Draw()
    {
        DrawRect(new Rect2(0, 0, 640, 420), new Color(0.035f, 0.04f, 0.05f));
        Arrow(OriginPoint, OriginPoint + Vector2.Right * 160, new Color(0.5f, 0.5f, 0.5f));
        Arrow(OriginPoint, OriginPoint + Vector2.Down * 90, new Color(0.3f, 0.3f, 0.3f));
        Vector2 projection = OriginPoint + Vector2.Right * Offset.Dot(Vector2.Right);
        DrawLine(Target, projection, new Color(0.3f, 0.35f, 0.45f), 1, true);
        Arrow(OriginPoint, Target, _blue);
        DrawCircle(OriginPoint, 5, Colors.White);
        DrawCircle(Target, 7, Colors.White);
    }
}

Offset = Target − OriginPoint. Для цели (400, 270) получается (300, 0), а нормализованное направление — (1, 0). Если позиции совпадают, Normalized возвращает нулевой вектор; направление и угол к совпавшей цели выбирать бессмысленно. Пример показывает «--» вместо такого угла.

Выбрать величину

ВыражениеДля чего
offset.Length()Расстояние
offset.LengthSquared()Сравнение с radius × radius без квадратного корня
offset.Normalized() × speedСкорость с заданной длиной, если offset не нулевой
position + velocity × deltaСледующая геометрическая позиция без учёта столкновений

У (3, 4) длина 5, квадрат длины 25, Normalized — (0,6; 0,8). Нормализация убирает расстояние. Не используйте её там, где нужна сама длина смещения. Для движения физического тела выберите соответствующий API столкновений, а не прямое присваивание Position по этой формуле.

Dot, Cross и угол

Dot двух единичных направлений равен косинусу угла: 1 — одно направление, 0 — перпендикулярно, −1 — противоположно. Для проверки конуса обзора сравнивайте Dot с cos(половины угла), предварительно нормализовав направления и проверив ненулевое смещение.

В 2D Cross возвращает число x₁y₂ − y₁x₂. Right.Cross(Down) = 1, Right.AngleTo(Down) = π/2. Из-за направленного вниз Y положительный угол на экране идёт по часовой стрелке. Orthogonal для Right даёт Up. В 3D Cross возвращает Vector3: Right.Cross(Up) = Back, то есть +Z; Forward — −Z.

Отскок от поверхности

Для отскока скорости от поверхности используйте Bounce с нормалью длины 1. В этом SDK Reflect вычисляет 2 × dot(v,n) × n − v, а Bounce — противоположный результат. При v = (1, 1) и n = Up отскок равен (1, −1), Reflect даёт (−1, 1). Переносить имя Reflect из другого API без проверки нельзя.

Slide убирает компоненту по единичной нормали. Он полезен как геометрическая операция, но сам не ищет поверхность и не перемещает физическое тело. Большие координаты и почти нулевые направления требуют выбранных вами допусков; сравнение float на точное равенство не является универсальным тестом близости.

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

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

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

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