reckfellENGINE

CharacterBody: скольжение, столкновение и отражение

Сравнение MoveAndSlide и MoveAndCollide, полный персонаж 3D и ограниченный цикл отражений 2D.

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

Выберите метод движения

МетодПередатьПолучить
MoveAndSlideЗаранее задать Velocity в единицах/сbool наличия столкновения; состояние пола/стены/потолка и список slide-контактов
MoveAndCollideПеремещение за этот вызов, обычно velocity × deltaKinematicCollision либо null; путь, остаток и нормаль контакта

MoveAndSlide умеет скользить и корректирует Velocity. Он не добавляет гравитацию или правила управления сам. MoveAndCollide останавливает движение при столкновении; дальнейшее скольжение/отражение задаёт ваш код. testOnly проверяет движение без перемещения тела. Не применяйте оба метода для одной и той же полной скорости за один шаг: получится лишнее движение.

После MoveAndSlide читайте GetSlideCollisionCount/GetSlideCollision. Эти контакты относятся к последнему вызову, а не ко всем соседям. Для MoveAndCollide используйте возвращённый объект. Различайте GetTravel и GetRemainder; остаток нужен для продолжения движения после контакта, а не повторного применения исходного отрезка.

Пол, склон и платформа

Grounded различает пол, стены и потолок относительно UpDirection и FloorMaxAngle. Угол API задаётся в радианах, например Mathf.DegToRad(45). Floating подходит движению без понятия пола. FloorSnapLength помогает удерживаться на поверхности в подходящем направлении; snap не является разрешением прыгать и не должен отменять движение вверх. FloorStopOnSlope, FloorConstantSpeed, SafeMargin и MaxSlides решают разные задачи. Не исправляйте неверный масштаб/форму случайным увеличением margin.

Платформы требуют согласованного физического движения, фильтров platform layers и политики ухода. GetPlatformVelocity и фактическая скорость движения отличаются от запрошенной Velocity. Этот пример проверяет плоский пол и стену; он не сертифицирует все склоны, вращающиеся платформы или автоматический подъём по ступеням.

Законченный персонаж 3D

Добавьте три файла ниже. Прикрепите CharacterLevel3D к корню Entity3D отдельного уровня, соберите и запустите F6. Настройки: 60 шагов/с, гравитация 9.8 вниз; единичный масштаб. WASD/стрелки двигают по мировым X/Z, Space прыгает, R переносит наверх. Направление пока не зависит от камеры. GetVector ограничивает диагональ, чтобы она не давала лишнюю скорость. Пример 2D с теми же правилами: ходьба и прыжок.

MotionBindings.cs
C#
using Reckfell;

public static class MotionBindings
{
    public static void Ensure()
    {
        Bind("move_left", Key.A, Key.Left); Bind("move_right", Key.D, Key.Right);
        Bind("move_forward", Key.W, Key.Up); Bind("move_back", Key.S, Key.Down);
        Bind("jump", Key.Space);
    }
    private static void Bind(string action, params Key[] keys)
    {
        if (InputMap.HasAction(action)) return;
        InputMap.AddAction(action);
        foreach (Key key in keys) InputMap.ActionAddEvent(action, new InputEventKey { PhysicalKeycode = key });
    }
}
CharacterMotor3D.cs
C#
using System;
using Reckfell;

public partial class CharacterMotor3D : CharacterBody3D
{
    [Export] public float Speed { get; set; } = 5;
    [Export] public float JumpSpeed { get; set; } = 5;
    public bool InputEnabled { get; set; } = true;
    public int Teleports { get; private set; }
    private Vector3? _teleport;
    public override void _Ready()
    {
        MotionBindings.Ensure(); UpDirection = Vector3.Up; FloorSnapLength = .2f;
    }
    public void RequestTeleport(Vector3 worldPosition)
    {
        if (!worldPosition.IsFinite()) throw new ArgumentOutOfRangeException(nameof(worldPosition));
        _teleport = worldPosition;
    }
    public override void _PhysicsProcess(double delta)
    {
        bool teleported = false;
        if (_teleport is Vector3 target)
        {
            GlobalPosition = target; Velocity = Vector3.Zero; _teleport = null;
            ResetPhysicsInterpolation(); Teleports++; teleported = true;
        }
        Vector3 velocity = Velocity;
        if (teleported || !IsOnFloor()) velocity += GetGravity() * (float)delta;
        if (!teleported && InputEnabled && IsOnFloor() && Input.IsActionJustPressed("jump")) velocity.Y = JumpSpeed;
        Vector2 input = InputEnabled ? Input.GetVector("move_left", "move_right", "move_forward", "move_back") : Vector2.Zero;
        velocity.X = input.X * Speed; velocity.Z = input.Y * Speed;
        Velocity = velocity;
        MoveAndSlide();
    }
}
CharacterLevel3D.cs
C#
using Reckfell;

public partial class CharacterLevel3D : Entity3D
{
    public CharacterMotor3D Player { get; private set; } = null!;
    public override void _Ready()
    {
        AddBox(new Vector3(0,-.25f,0),new Vector3(16,.5f,12));
        AddBox(new Vector3(4,2,0),new Vector3(.5f,4,8));
        Player = new CharacterMotor3D { Position = new Vector3(0,.9f,0), CollisionLayer = 2, CollisionMask = 1 };
        Player.AddChild(new CollisionShape3D { Shape = new CapsuleShape3D { Radius = .35f, Height = 1.8f } });
        Player.AddChild(new MeshInstance3D { Mesh = new CapsuleMesh { Radius = .35f, Height = 1.8f }, MaterialOverride = Material(new Color(.25f,.45f,1)) });
        AddChild(Player);
        var camera = new Camera3D { Position = new Vector3(7,6,9), Current = true }; AddChild(camera); camera.LookAt(ToGlobal(new Vector3(0,1,0)));
    }
    private static StandardMaterial3D Material(Color color) => new() { AlbedoColor = color, ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded };
    private void AddBox(Vector3 position,Vector3 size)
    {
        var body = new StaticBody3D { Position = position, CollisionLayer = 1, CollisionMask = 0 };
        body.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = size } });
        body.AddChild(new MeshInstance3D { Mesh = new BoxMesh { Size = size }, MaterialOverride = Material(new Color(.55f,.55f,.6f)) }); AddChild(body);
    }
    public override void _UnhandledKeyInput(InputEvent @event)
    {
        if (@event is InputEventKey { Pressed: true, Echo: false, PhysicalKeycode: Key.R })
            Player.RequestTeleport(ToGlobal(new Vector3(0,3,0)));
    }
}

Отражение через MoveAndCollide

Для отдельного движущегося предмета создайте CharacterBody2D с дочерним CollisionShape2D/CircleShape2D и прикрепите BounceMover2D. Стены — StaticBody2D с включёнными формами; у предмета Mask должен видеть их Layer. Класс не использует гравитацию и ввод. Его можно проверить в отдельном уровне либо добавить рядом с персонажем. Для воспроизведения задайте CircleShape2D.Radius = 12. Добавьте видимый Sprite2D/Polygon2D или включите Visible Collision Shapes в отладочном меню редактора: сама физическая форма не рисуется в игре.

BounceMover2D.cs
C#
using Reckfell;

public partial class BounceMover2D : CharacterBody2D
{
    [Export] public Vector2 InitialVelocity { get; set; } = new(220,150);
    public int Bounces { get; private set; }
    public override void _Ready() { MotionMode = MotionModeEnum.Floating; Velocity = InitialVelocity; }
    public override void _PhysicsProcess(double delta)
    {
        Vector2 remaining = Velocity * (float)delta;
        for (int i = 0; i < 4 && remaining.LengthSquared() > .000001f; i++)
        {
            KinematicCollision2D? hit = MoveAndCollide(remaining);
            if (hit is null) break;
            Vector2 normal = hit.GetNormal();
            if (normal.IsZeroApprox()) break;
            Velocity = Velocity.Bounce(normal);
            remaining = hit.GetRemainder().Bounce(normal);
            Bounces++;
        }
    }
}

Код отражает и скорость, и оставшийся путь. Цикл ограничен четырьмя столкновениями на шаг; непотраченный остаток после лимита отбрасывается. Это явный компромисс для примера, а не универсальный высокоскоростной solver. Нулевая нормаль прекращает цикл. Для снаряда с особыми правилами попадания используйте отдельную логику запросов и проверяйте нужные скорости/формы.

Если на месте ожидаемого скольжения происходит остановка — проверьте выбранный метод и реакцию на остаток. Если движение слишком медленное — нет ли delta дважды. Если слишком быстрое — не передана ли скорость напрямую в MoveAndCollide. Статус пола после телепорта должен обновиться новым движением; прежнее значение нельзя использовать для лишнего прыжка.

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

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

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

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