reckfellENGINE

CharacterBody: sliding, collisions and reflection

MoveAndSlide versus MoveAndCollide, a complete 3D character and a bounded 2D reflection loop.

On this page

Choose a movement method

MethodSupplyObtain
MoveAndSlideSet Velocity in units/s beforehandA collision bool; floor/wall/ceiling state and slide contacts
MoveAndCollideDisplacement for this call, usually velocity × deltaKinematicCollision or null, with travel, remainder and contact normal

MoveAndSlide performs sliding and adjusts Velocity. It does not supply gravity or controls. MoveAndCollide stops at collision; your code defines subsequent sliding/reflection. testOnly checks motion without moving the body. Do not apply both methods to the same full velocity in one tick, which would move twice.

After MoveAndSlide, read GetSlideCollisionCount/GetSlideCollision. Those contacts describe the last call, not every neighbor. For MoveAndCollide, use its return value. Distinguish GetTravel from GetRemainder: continue with remaining displacement, rather than applying the original whole segment again.

Floors, slopes and platforms

Grounded classifies floors, walls and ceilings relative to UpDirection and FloorMaxAngle. API angles are radians, for example Mathf.DegToRad(45). Floating suits movement without a floor concept. FloorSnapLength helps retain surface attachment in the appropriate direction; snapping does not authorize jumps and should not cancel upward motion. FloorStopOnSlope, FloorConstantSpeed, SafeMargin and MaxSlides serve different purposes. Random margin increases do not repair incorrect scale/geometry.

Platforms need coordinated physics movement, platform-layer filters and a leave policy. GetPlatformVelocity and actual movement velocity differ from requested Velocity. This example checks flat floors and walls, not every slope, rotating platform or automatic stair step.

Complete 3D character

Add the three files below. Attach CharacterLevel3D to an Entity3D root in a separate level, build and press F6. Use 60 ticks/s, downward gravity 9.8 and unit scale. WASD/arrows move on world X/Z, Space jumps and R teleports above. Direction is not yet camera-relative. GetVector bounds diagonal input to avoid extra speed. The corresponding 2D walk/jump example uses the same rules.

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)));
    }
}

Reflection with MoveAndCollide

For a separate moving object, create a CharacterBody2D with a child CollisionShape2D/CircleShape2D and attach BounceMover2D. Use StaticBody2D walls with enabled shapes, and make the mover Mask detect their Layer. The class uses neither gravity nor input. Test it in a separate level or beside the character. Set CircleShape2D.Radius = 12 to reproduce the check. Add a visible Sprite2D/Polygon2D or enable Visible Collision Shapes in the editor debug menu: the physics shape itself is not drawn in the game.

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++;
        }
    }
}

The code reflects velocity and remaining travel. It permits four collisions per tick, then discards unused displacement. This is an explicit example tradeoff, not a universal high-speed solver. A zero normal ends the loop. Projectiles with special hit rules need separate query logic and checks at required speeds/shapes.

Unexpected stopping instead of sliding suggests the wrong method or remainder response. Slow movement can indicate delta applied twice. Excess speed can mean passing velocity directly to MoveAndCollide. After teleporting, a new movement must refresh floor state; old state must not permit an extra jump.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.