reckfellENGINE

2D character: walking, jumping and walls

A complete C# level: input, gravity, MoveAndSlide and teleporting without a stale-floor jump.

On this page

Run the character

Add the three files below to a C# project. Create a separate level with an Entity2D root, attach CharacterLevel2D, build and press F6. Use a 640×420 or larger window, 60 physics ticks/s and 2D gravity 980 in direction (0, 1). Keep the root and physics branch unscaled/unrotated.

A/D or arrows walk, Space jumps from the floor, and R teleports above. MotionBindings adds only missing Input Map actions and preserves existing project bindings. A blue rectangle represents the character; its actual physics shape is a capsule. It stands on the floor and slides along the right wall.

The 2D character on the floor before the wall

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 });
    }
}
CharacterMotor2D.cs
C#
using System;
using Reckfell;

public partial class CharacterMotor2D : CharacterBody2D
{
    [Export] public float Speed { get; set; } = 240;
    [Export] public float JumpSpeed { get; set; } = 420;
    public bool InputEnabled { get; set; } = true;
    public int Teleports { get; private set; }
    private Vector2? _teleport;
    public override void _Ready()
    {
        MotionBindings.Ensure(); UpDirection = Vector2.Up; FloorSnapLength = 6;
    }
    public void RequestTeleport(Vector2 worldPosition)
    {
        if (!worldPosition.IsFinite()) throw new ArgumentOutOfRangeException(nameof(worldPosition));
        _teleport = worldPosition;
    }
    public override void _PhysicsProcess(double delta)
    {
        bool teleported = false;
        if (_teleport is Vector2 target)
        {
            GlobalPosition = target; Velocity = Vector2.Zero; _teleport = null;
            ResetPhysicsInterpolation(); Teleports++; teleported = true;
        }
        Vector2 velocity = Velocity;
        if (teleported || !IsOnFloor()) velocity += GetGravity() * (float)delta;
        if (!teleported && InputEnabled && IsOnFloor() && Input.IsActionJustPressed("jump")) velocity.Y = -JumpSpeed;
        velocity.X = InputEnabled ? Input.GetAxis("move_left", "move_right") * Speed : 0;
        Velocity = velocity;
        MoveAndSlide();
    }
}
CharacterLevel2D.cs
C#
using Reckfell;

public partial class CharacterLevel2D : Entity2D
{
    public CharacterMotor2D Player { get; private set; } = null!;
    public override void _Ready()
    {
        AddBox(new Vector2(320, 320), new Vector2(640, 40));
        AddBox(new Vector2(560, 180), new Vector2(40, 240));
        Player = new CharacterMotor2D { Position = new Vector2(100, 280), CollisionLayer = 2, CollisionMask = 1 };
        Player.AddChild(new CollisionShape2D { Shape = new CapsuleShape2D { Radius = 12, Height = 40 } });
        Player.AddChild(new Polygon2D { Polygon = new[] { new Vector2(-12,-20), new Vector2(12,-20), new Vector2(12,20), new Vector2(-12,20) }, Color = new Color(.25f,.45f,1) });
        AddChild(Player);
    }
    private void AddBox(Vector2 position, Vector2 size)
    {
        var body = new StaticBody2D { Position = position, CollisionLayer = 1, CollisionMask = 0 };
        body.AddChild(new CollisionShape2D { Shape = new RectangleShape2D { Size = size } });
        Vector2 half = size / 2;
        body.AddChild(new Polygon2D { Polygon = new[] { -half, new Vector2(half.X,-half.Y), half, new Vector2(-half.X,half.Y) }, Color = new Color(.72f,.72f,.76f) });
        AddChild(body);
    }
    public override void _UnhandledKeyInput(InputEvent @event)
    {
        if (@event is InputEventKey { Pressed: true, Echo: false, PhysicalKeycode: Key.R })
            Player.RequestTeleport(ToGlobal(new Vector2(100,100)));
    }
}

Velocity and gravity

Velocity is measured in pixels/s. Do not multiply it by delta before MoveAndSlide; the method accounts for the step. Acceleration from GetGravity, however, is added to velocity with delta. A CharacterBody does not start falling merely because project gravity is configured; the script implements that.

IsOnFloor/IsOnWall describe the last MoveAndSlide result. Floor state can still be absent before the first movement. After horizontal input stops, the wall flag need not remain true: the query is no longer trying to move into the wall. Use a separate ray/shape query for persistent proximity checks.

This controller uses UpDirection = Vector2.Up and negative Y jump velocity. It assumes ordinary vertical gravity. Rotating gravity requires coordinating UpDirection, jump direction and the horizontal axis, rather than changing only a Gravity number.

Teleporting and limits

RequestTeleport stores a world point and applies it in _PhysicsProcess. After assigning GlobalPosition, the script clears Velocity and calls ResetPhysicsInterpolation. It deliberately does not use the old IsOnFloor to allow a jump that tick: the floor before teleporting no longer authorizes an action at the new position.

InputEnabled = false disables control while falling/collisions continue. Pausing is a separate scenario. The example does not add coyote time, jump buffering, stairs, ladders or network synchronization. For no movement, check action names/bindings, the CollisionShape2D direct parent, Layer/Mask and root type. For falling through the floor, begin with physics diagnostics.

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.