В этой игре мы реализовали обработку столкновений игрока с большим количеством объектов (например, пуль или врагов) без создания сотен экземпляров в сцене.
Вместо того чтобы добавлять в сцену десятки нод Area2D или RigidBody2D (каждый со своей формой и скриптом), мы обрабатываем всё программно. Это освобождает редактор от визуального «мусора» и позволяет игре работать быстрее, особенно когда объектов очень много.
Настройка игры: делаем курсор невидимым
Первое, что мы сделаем при запуске игры — уберём стандартный курсор мыши. Это нужно, чтобы он не отвлекал игрока и не мешал обзору, особенно если в игре есть собственный прицел или курсор-указатель.
Как это реализовано в Годот?
В функции _ready(), которая вызывается автоматически при загрузке сцены, мы обращаемся к глобальному объекту Input и вызываем метод set_mouse_mode. В качестве аргумента передаём режим Input.MOUSE_MODE_HIDDEN, который буквально означает «курсор скрыт».
func _ready(): Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
Отслеживаем положение игрока в реальном времени
В Godot есть специальная функция _input(event), которая автоматически вызывается каждый раз, когда происходит какое-либо событие ввода: движение мыши, нажатие клавиши или касание экрана.
Внутри этой функции мы получаем доступ к спрайту игрока и узнаём его координаты с помощью свойства position. Затем мы можем отобразить эти координаты на экране — например, вывести их в консоль, обновить текст в интерфейсе или нарисовать прямо в окне игры.
func _input(input_event: InputEvent) -> void:
if input_event is InputEventMouseMotion:
position = input_event.position - Vector2(0, 16)
Реакция на столкновение с пулями: меняем спрайт игрока
Когда игрок сталкивается с пулей (или несколькими одновременно), игра должна отреагировать на это событие. В нашем случае реакция простая, но наглядная — мы меняем спрайт персонажа, показывая, что он получил урон или задел опасный объект.
func _on_body_shape_entered(_body_id: RID, _body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
touching += 1
if touching >= 1:
sprite.frame = 1
Если игрок не касается пуль, то спрайт меняется на другой
func _on_body_shape_exited(_body_id: RID, _body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
touching -= 1
if touching == 0:
sprite.frame = 0
Как управлять тысячами объектов без потери производительности
В большинстве игр для обработки столкновений используют отдельные ноды (например, Area2D, CollisionShape2D), добавляя их в сцену для каждого объекта. Этот подход удобен и нагляден, но у него есть серьёзный недостаток: когда объектов становится много (сотни или тысячи), игра начинает тормозить. Каждая нода требует памяти и процессорного времени.
Настройка параметров пуль
Перед тем как пули начнут летать, нам нужно определить их основные характеристики. Это позволит управлять ими гибко и без лишнего кода.
Количество пуль — задаём переменную, которая определяет, сколько пуль одновременно будет находиться на экране. Это важно для контроля производительности.
Минимальная и максимальная скорость — каждая пуля будет двигаться со случайной скоростью в этих пределах. Благодаря этому полёт выглядит естественно: одни пули летят быстрее, другие — медленнее.
Изображение пули — загружаем текстуру (картинку), которая будет визуально отображать пулю на экране. Без неё игрок не увидит, куда летят снаряды.
Список для хранения пуль — создаём массив или список, в котором будут храниться все активные пули. Это удобно, потому что мы можем легко перебирать их, обновлять позиции и проверять столкновения.
Форма пули (коллизия) — определяем геометрическую форму (например, круг или прямоугольник), которая будет использоваться физическим движком для расчёта столкновений. Даже если пуля — это просто картинка, движок должен знать, какая у неё «зона поражения».
const BULLET_COUNT = 500
const SPEED_MIN = 20
const SPEED_MAX = 80
const bullet_image := preload("res://bullet.png")
var bullets := []
var shape := RID()
Определим класс для пуль, опишем там позицию пули, её скорость и тело
class Bullet:
var position := Vector2()
var speed := 1.0
var body := RID()
В начале функции _ready создаем круглую форму пули для физического движка и установим радиус этой формы
func _ready() -> void:
shape = PhysicsServer2D.circle_shape_create()
PhysicsServer2D.shape_set_data(shape, 8)
Создадим 500 пуль, установим им случайную скорость, и инициируем им физическое тело и добавим форму.
for _i in BULLET_COUNT:
var bullet := Bullet.new()
bullet.speed = randf_range(SPEED_MIN, SPEED_MAX)
bullet.body = PhysicsServer2D.body_create()
PhysicsServer2D.body_set_space(bullet.body, get_world_2d().get_space())
PhysicsServer2D.body_add_shape(bullet.body, shape)
Shape = RID. Этой форме мы установим тип окружности и радиус и не дадим пулям сталкивать друг с другом
PhysicsServer2D.body_set_collision_mask(bullet.body, 0)
Расположим пули случайным образом
bullet.position = Vector2(
randf_range(0, get_viewport_rect().size.x) + get_viewport_rect().size.x,
randf_range(0, get_viewport_rect().size.y)
)
Физический сервер должен обновляться при изменении позиции пули.
PhysicsServer2D.body_set_state(bullet.body, PhysicsServer2D.BODY_STATE_TRANSFORM, transform2d)
И добавляем пули в список.
bullets.push_back(bullet)
Перерисовываем канвас
func _process(_delta: float) -> void:
queue_redraw()
В функции _physics_process при любом перемещении любой пули мы должны уведомить об этом физический движок
PhysicsServer2D.body_set_state(bullet.body, PhysicsServer2D.BODY_STATE_TRANSFORM, transform2d)
С помощью draw_texture мы рисуем сразу все пули.
func _draw() -> void:
var offset := -bullet_image.get_size() * 0.5
for bullet: Bullet in bullets:
draw_texture(bullet_image, bullet.position + offset)
Потом происходит процедура очистки(чтобы не было ошибок в консоли движка)
func _exit_tree() -> void:
for bullet: Bullet in bullets:
PhysicsServer2D.free_rid(bullet.body)
PhysicsServer2D.free_rid(shape)
bullets.clear()
Вот и всё. Мы дошли до конца этого материала, и я хочу сказать вам большое спасибо. Спасибо за то, что дочитали до этих строк, за ваше внимание и интерес.
Если у вас остались вопросы, замечания или просто есть что добавить — не стесняйтесь, пишите в комментариях. Мне важно знать ваше мнение.
А у меня на сегодня всё. До новых встреч!
