1. Написание эффективных скриптов на GDScript для управления игровым персонажем
Написание эффективных скриптов на GDScript для управления игровым персонажем
Почему персонаж в вашем инди-проекте двигается вяло, «проскальзывает» по стенам или зависает в воздухе после прыжка? В девяти случаях из десяти проблема не в настройках физики, а в том, как написан скрипт. GDScript — язык, специально заточенный под Godot, — даёт мощные инструменты для управления персонажем, но только если вы понимаете, как они работают внутри. Эта статья — путь от базовой структуры скрипта до полноценного контроллера персонажа с ходьбой, прыжками и взаимодействием с окружением.
Структура скрипта: что вызывается, когда и почему
Каждый скрипт в Godot наследуется от одного из базовых узлов движка. Для управления персонажем чаще всего используется класс CharacterBody2D (для 2D) или CharacterBody3D (для 3D). Именно этот узел встроенной физикой обрабатывает столкновения и предоставляет метод move_and_slide().
Любой GDScript-файл начинается с объявления расширяемого класса:
После этого строки кода делятся на три категории, которые стоит понимать чётко:
_physics_process(delta) вызывается каждый физический кадр (по умолчанию 60 раз в секунду) и отвечает за движение, гравитацию, коллизии. _process(delta) вызывается каждый визуальный кадр и подходит для анимации, UI, нефизических обновлений.apply_gravity(), handle_jump(), update_animation().Ключевое правило: вся физика персонажа живёт в _physics_process, а не в _process. Разница в том, что _physics_process привязан к фиксированному шагу физического движка, и именно внутри него вызывается move_and_slide(). Если поместить движение в _process, персонаж будет дёргаться на слабых машинах, потому что визуальный кадр непостоянен.
Обработка ввода: от нажатия к направлению
Godot предоставляет систему Input Map — абстрактный слой между физическими клавишами и игровыми действиями. Вместо того чтобы проверять «нажата ли клавиша A», вы проверяете «нажато ли действие "move_left"». Это даёт два преимущества: переназначение клавиш без переписывания кода и поддержку геймпадов «из коробки».
Действия настраиваются в Project → Input Map. Стандартный набор для 2D-платформера:
| Действие | Клавиша по умолчанию |
|---|---|
| move_right | D, → |
| move_left | A, ← |
| jump | Space |
Получить направление движения — это получить вектор из двух осей. Метод Input.get_axis() принимает два действия и возвращает число от −1 до 1:
Если нажата только move_right, вернётся 1.0. Только move_left — -1.0. Обе или ни одна — 0.0. Это значение сразу можно умножить на скорость и присвоить горизонтальной составляющей вектора скорости.
Для прыжка используется Input.is_action_just_pressed("jump") — он возвращает true только в тот кадр, когда кнопка была нажата, а не удерживается. Это критически важно: если использовать is_action_pressed, персонаж будет «прилипать» к потолку при удержании пробела.
Векторы: язык движения в Godot
Вектор — это структура данных, хранящая несколько чисел под одним именем. В Godot: Vector2(x, y) для 2D и Vector3(x, y, z) для 3D. Вектор скорости персонажа velocity — это именно вектор: его компонента x отвечает за горизонтальное движение, y — за вертикальное.
Почему не две отдельные переменные speed_x и speed_y? Потому что вектор позволяет оперировать движением как единым целым. Например, нормализация — velocity.normalized() — даёт направление без учёта длины. Или сложение: гравитация каждый кадр прибавляет вектор (0, gravity * delta) к текущей скорости, и это одна строка вместо двух.
Важный нюанс: не нормализуйте velocity перед передачей в move_and_slide(). Нормализация убирает информацию о скорости — персонаж будет двигаться с одинаковой скоростью независимо от того, бежит он или идёт. Нормализация нужна только для получения направления, например, для поворота спрайта.
Ходьба: первый рабочий скрипт
Соберём всё вместе. Вот минимальный контроллер, который заставляет персонажа ходить влево-вправо:
Разберём по строкам. @export var speed := 200.0 — переменная со значением по умолчанию 200 пикселей в секунду. Декоратор @export делает её видимой в инспекторе Godot, и вы сможете менять скорость прямо в редакторе, не трогая код. Тип выводится автоматически благодаря оператору := (это называется type inference).
velocity.x = direction * speed — вот здесь векторная природа velocity проявляется в полной мере. Мы меняем только горизонтальную компоненту, оставляя вертикальную нетронутой. Если вертикальная скорость равна нулю — персонаж стоит. Если в будущем добавим гравитацию — она будет работать независимо.
move_and_slide() — метод, который берёт текущий вектор velocity, перемещает узел на соответствующее расстояние с учётом delta (времени кадра), и при столкновении со стеной или полом «скользит» вдоль поверхности, а не застревает. Именно поэтому он называется slide, а не stop. После вызова move_and_slide() свойство velocity может быть изменено движком: если персонаж упёрся в стену, соответствующая компонента обнулится.
Гравитация и прыжок: добавляем вертикаль
Ходьба по горизонтали — это только половина платформера. Нужна гравитация, которая тянет персонажа вниз, и прыжок, который даёт мгновенный импульс вверх.
Гравитация — это не константа скорости, а ускорение: каждый кадр к вертикальной составляющей velocity прибавляется некая величина. Чем дольше персонаж падает, тем быстрее он летит. Это соответствует реальной физике и выглядит естественно.
В Godot есть встроенная константа гравитации — ProjectSettings.get_setting("physics/2d/default_gravity"). Но для платформера лучше задать свою, потому что «физически правильная» гравитация часто ощущается слишком слабой для игрового feel.
Обратите внимание на знак jump_velocity := -400.0. В Godot ось Y направлена вниз: положительные значения — это движение вниз, отрицательные — вверх. Поэтому прыжок задаётся отрицательным числом. Это частый источник ошибок у тех, кто приходит из других движков.
Метод is_on_floor() возвращает true, если после предыдущего вызова move_and_slide() персонаж стоял на поверхности. Это означает, что is_on_floor() корректно работает только после move_and_slide(). Если проверять состояние пола до вызова — результат будет некорректным. В приведённом коде мы проверяем is_on_floor() в начале кадра, что опирается на результат предыдущего кадра — для большинства случаев этого достаточно.
Тонкая настройка: койот-тайм и буфер прыжка
Сырой код выше работает, но ощущается неудобно: если игрок нажал прыжок на один кадр раньше, чем персонаж коснулся земли, ничего не произойдёт. И наоборот — если отпустил кнопку на кадр раньше, чем приземлился, прыжок «потеряется». Два приёма решают эту проблему.
Койот-тайм (coyote time) — небольшое окно после срыва с края платформы, в течение которого прыжок всё ещё доступен. Назван в честь персонажа мультфильмов, который бежит по воздуху, прежде чем упасть.
Буфер прыжка (jump buffer) — запоминание нажатия прыжка на несколько кадров вперёд. Если персонаж приземлится в течение этого окна, прыжок произойдёт автоматически.
Реализация через таймеры-счётчики:
Здесь coyote_time и jump_buffer_time задаются в секундах и доступны для настройки в инспекторе. Типичные значения: 0.1–0.15 секунды. Этого достаточно, чтобы игрок не замечал «несправедливых» отказов в прыжке, но мало, чтобы можно было абьюзить механику.
Взаимодействие с миром: лучи, зоны и сигналы
Персонаж не существует в вакууме. Он должен собирать предметы, получать урон от врагов, активировать механизмы. В Godot для этого есть несколько инструментов.
RayCast2D — невидимый луч, который проверяет столкновение с физическими телами на определённом расстоянии. Используется, например, для определения, смотрит ли персонаж в стену, или для стрельбы «в первое препятствие». Работает без вызова в _physics_process — обновляется автоматически каждый физический кадр.
Но scale.x = -1 отзеркаливает и дочерние узлы — хитбоксы, райкасты, оружие. Если это нежелательно, лучше использовать отдельный узел-контейнер для визуальной части и инвертировать только его, либо использовать свойство flip_h у Sprite2D.
Для анимации в Godot используется узел AnimationPlayer или более специализированный AnimatedSprite2D. Переключение анимаций по состоянию персонажа — типичный паттерн:
Этот скрипт — надёжный фундамент для 2D-платформера. Он обрабатывает ввод через абстрактные действия, применяет гравитацию как ускорение, реализует прыжок с койот-таймом и буфером, поворачивает спрайт и переключает анимации. Каждый элемент можно расширять: добавить двойной прыжок (отдельный счётчик), стенный прыжок (проверка is_on_wall()), ускорение и торможение вместо мгновенной смены скорости (линейная интерполяция через lerp).
Главный принцип эффективного GDScript: каждая строка должна отвечать на вопрос «что делает персонаж прямо сейчас». Если скрипт читается как описание поведения — он написан правильно. Если приходится разбираться, почему переменная t влияет на прыжок — пора рефакторить.