Векторы: направление, расстояние и отражение
Полный 2D-пример наведения, знак угла, Dot/Cross и отличие Bounce от Reflect.
Направить вектор к цели
Создайте C#-уровень 1.0.0 с корнем Entity2D, добавьте VectorAim2D.cs и прикрепите к корню. Для картинки как ниже задайте окно/viewport 640×420, соберите проект и нажмите F6. Двигайте мышь: синяя стрелка идёт от начала (100, 270) к цели, подпись показывает длину и угол. GetLocalMousePosition получает точку в том же локальном пространстве, где рисуется стрелка.
VectorAim2D.cs
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 на точное равенство не является универсальным тестом близости.