reckfellENGINE

Персонаж 2D: ходьба, прыжок и стены

Законченный уровень C#: ввод, гравитация, MoveAndSlide и телепорт без ложного прыжка.

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

Запустить персонажа

Добавьте три файла ниже в C#-проект. Создайте отдельный уровень с корнем Entity2D, прикрепите CharacterLevel2D, соберите проект и нажмите F6. Окно — 640×420 или больше; физика — 60 шагов/с, гравитация 2D — 980 с направлением (0, 1). Корень и физическую ветвь оставьте без масштаба/поворота.

A/D или стрелки — ходьба, Space — прыжок с пола, R — перенос наверх. MotionBindings добавляет только отсутствующие действия Input Map; существующие привязки проекта сохраняются. Синий прямоугольник обозначает персонажа, физическая форма — капсула. Она стоит на полу и скользит вдоль правой стены.

Персонаж 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 });
    }
}
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 — скорость в пикселях/с. Перед MoveAndSlide не умножайте её на delta: метод учитывает шаг сам. Ускорение GetGravity, напротив, добавляется к скорости с delta. CharacterBody не начинает падать автоматически только потому, что в проекте включена гравитация; это делает скрипт.

IsOnFloor/IsOnWall относятся к результату последнего MoveAndSlide. До первого движения пола может ещё не быть в состоянии тела. После остановки горизонтального движения флаг стены не обязан оставаться true: запрос больше не пытается двигаться в стену. Для постоянной проверки соседства нужен отдельный ray/shape-query.

В этой схеме UpDirection = Vector2.Up, прыжок имеет отрицательную Y-скорость. Она рассчитана на обычную вертикальную гравитацию. Для вращающейся гравитации нужно согласовать UpDirection, направление прыжка и горизонтальную ось, а не заменить только число Gravity.

Перенос и ограничения

RequestTeleport сохраняет мировую точку и применяется в _PhysicsProcess. После установки GlobalPosition скрипт очищает Velocity и вызывает ResetPhysicsInterpolation. На этом шаге он не использует прежний IsOnFloor для прыжка: пол до переноса уже не является основанием для действия в новой точке.

InputEnabled = false отключает управление, но тело продолжает падать и сталкиваться. Для паузы это другой сценарий. Пример не добавляет coyote time, буфер прыжка, ступени, лестницы или сетевую синхронизацию. Если персонаж не движется, проверьте имена/привязки действий, прямого родителя CollisionShape2D, Layer/Mask и тип корня. Если падает сквозь пол — начните с диагностики физики.

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

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

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

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