Движение в 3D
CharacterBody3D: ввод, столкновения, гравитация и прыжок.
Нужен C#-проект: настройка скрипта и сборки.
1. Соберите отдельный уровень
Создайте новый документ через File → Create Scene, оставьте корень Entity3D, назовите его World и сохраните как World.rscene. Добавьте эту иерархию; отступ означает дочерний объект:
World (Entity3D)
├── Floor (StaticBody3D)
│ ├── Visual (MeshInstance3D)
│ └── CollisionShape3D
├── Player (CharacterBody3D)
│ ├── Visual (MeshInstance3D)
│ └── CollisionShape3D
├── Camera3D
└── DirectionalLight3DСоздавайте дочерние объекты с выделенным правильным родителем. Камера в этом уроке — ребёнок World, поэтому не двигается вместе с персонажем.
2. Настройте пол и персонажа
| Объект | Настройки |
|---|---|
Floor | Position: (0, -0.1, 0) |
Floor/Visual | Mesh: новый BoxMesh, внутри него Size: (20, 0.2, 20) |
Floor/CollisionShape3D | Shape: новый BoxShape3D, внутри него Size: (20, 0.2, 20) |
Player | Position: (0, 0.9, 0) |
Player/Visual | Mesh: новый CapsuleMesh, Radius: 0.35, Height: 1.8 |
Player/CollisionShape3D | Shape: новый CapsuleShape3D, Radius: 0.35, Height: 1.8 |
Camera3D | Position: (0, 6, 8), Rotation: (-35, 0, 0), Current: включено |
DirectionalLight3D | Rotation: (-45, -30, 0) |
У всех дочерних мешей и форм оставьте локальную позицию (0, 0, 0) и масштаб (1, 1, 1). Меняйте размеры внутри ресурсов Mesh и Shape, а не растягивайте физическое тело масштабом. Слои и маски столкновений пока оставьте по умолчанию.
Mesh рисует поверхность. Shape задаёт область столкновения, но в обычной игре не видна. StaticBody3D подходит для неподвижного пола. CharacterBody3D предназначен для движения, которым управляет ваш код.
3. Создайте действия ввода
Откройте Project → Project Settings → Input Map. Добавьте действия с точными именами из таблицы, затем назначьте каждому события клавиатуры через кнопку добавления события.
| Действие | Клавиши |
|---|---|
move_left | A и стрелка влево |
move_right | D и стрелка вправо |
move_forward | W и стрелка вверх |
move_back | S и стрелка вниз |
jump | Space |
Действие — имя команды, а клавиша — один из способов её вызвать. Поэтому позже можно добавить геймпад без переписывания основного кода движения. Для WASD удобно выбирать физические клавиши, чтобы расположение не зависело от раскладки.
4. Прикрепите Player.cs
Прикрепите к Player C#-скрипт Scripts/Player.cs с базовым классом CharacterBody3D. Замените его содержимое:
using Reckfell;
public partial class Player : CharacterBody3D
{
[Export] public float Speed { get; set; } = 4.0f;
[Export] public float Gravity { get; set; } = 20.0f;
[Export] public float JumpSpeed { get; set; } = 6.0f;
public override void _PhysicsProcess(double delta)
{
Vector3 velocity = Velocity;
if (!IsOnFloor())
velocity.Y -= Gravity * (float)delta;
if (Input.IsActionJustPressed("jump") && IsOnFloor())
velocity.Y = JumpSpeed;
Vector2 input = Input.GetVector("move_left", "move_right", "move_forward", "move_back");
velocity.X = input.X * Speed;
velocity.Z = input.Y * Speed;
Velocity = velocity;
MoveAndSlide();
}
}Сохраните файлы, соберите C# через Alt + B и запустите World.rscene через F6. Щёлкните по игровому окну для фокуса клавиатуры.
Почему движение устроено так
Velocity — скорость в единицах мира за секунду. MoveAndSlide() перемещает тело, учитывает физический шаг и скользит вдоль препятствий. Поэтому значение Speed здесь не умножается на delta перед записью в Velocity.
Гравитация меняет скорость со временем, поэтому в её расчёте delta нужна. IsOnFloor() сообщает о контакте с полом по результату предыдущего движения. IsActionJustPressed() срабатывает на новое нажатие: удерживание Space не вызывает прыжок каждый кадр.
Input.GetVector() собирает четыре действия в двумерное направление и ограничивает его длину. По диагонали персонаж не должен бежать быстрее. Здесь движение привязано к осям мира, а не к повороту камеры — это намеренно простой первый контроллер.
Если персонаж проваливается, проверьте Shape у обоих тел и слои/маски столкновений. Если ввода нет — имена действий и фокус игрового окна. Решение проблем.