Сейчас я расскажу о структуре программы Годот Bullet Shower.

В этой игре мы реализовали обработку столкновений игрока с большим количеством объектов (например, пуль или врагов) без создания сотен экземпляров в сцене.

Вместо того чтобы добавлять в сцену десятки нод 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()

Вот и всё. Мы дошли до конца этого материала, и я хочу сказать вам большое спасибо. Спасибо за то, что дочитали до этих строк, за ваше внимание и интерес.

Если у вас остались вопросы, замечания или просто есть что добавить — не стесняйтесь, пишите в комментариях. Мне важно знать ваше мнение.

А у меня на сегодня всё. До новых встреч!

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх