Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Разработка сцены с мобом

В этой части вы будете программировать монстров, которых мы будем называть мобами. В следующем уроке мы будем распределять их случайным образом по всей игровой области.

Давайте разработаем самих монстров в новой сцене. Структура узлов будет похожа на сцену player.tscn.

Создайте сцену, корнем которой снова является узел CharacterBody3D. Назовите его Mob. Добавьте узел Node3D в качестве его дочернего узла, назовите его Pivot. Перетащите файл mob.glb из панели Файловая система на узел Pivot, чтобы добавить 3D-модель монстра в сцену.

../../_images/drag_drop_mob.webp

Вы можете переименовать только что созданный узел mob в Character.

изображение0

Нам нужна форма столкновения, чтобы наше тело работало. Щёлкните правой кнопкой мыши на узле Mob, корне сцены, и выберите Добавить дочерний узел.

изображение1

Добавьте CollisionShape3D.

изображение2

В инспекторе назначьте BoxShape3D свойству Shape.

../../_images/08.create_box_shape3D.webp

Мы должны изменить его размер, чтобы он лучше соответствовал 3D-модели. Вы можете сделать это интерактивно, щёлкая и перетаскивая оранжевые точки.

Параллелепипед должен касаться пола и быть немного тоньше, чем модель. Физические движки работают таким образом, что если сфера игрока коснется даже угла параллепипеда, произойдет столкновение. Если параллепипед будет слишком большой по сравнению с 3D-моделью, вы можете умереть на расстоянии от монстра, и игра будет казаться несправедливой по отношению к игрокам.

изображение4

Обратите внимание, что мой габаритный контейнер выше, чем монстр. В этой игре это нормально, потому что мы смотрим на сцену сверху и используем фиксированную перспективу. Формы столкновений не обязательно должны точно соответствовать модели. Их форма и размер должны определяться тем, как игра ощущается при тестировании.

Удаление монстров за пределами экрана

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

Как только монстр покидает экран, он нам больше не нужен, поэтому мы можем его удалить. В Godot есть узел, который определяет, когда объекты покидают экран, VisibleOnScreenNotifier3D, и мы собираемся использовать его для уничтожения наших мобов.

Примечание

When you keep instantiating an object, there's a technique you can use to avoid the cost of creating and destroying instances all the time called pooling. It consists of pre-creating an array of objects and reusing them over and over.

При работе с GDScript это обычно не требуется. Основная причина использования пулов — избежать зависаний в языках со сборкой мусора, таких как C# или Lua. GDScript использует другую технику управления памятью — подсчёт ссылок, у которой нет этого недостатка. Подробнее об этом можно узнать здесь: Управление памятью.

Выберите узел Mob и добавьте VisibleOnScreenNotifier3D в качестве его дочернего элемента. Появится еще один параллелепипед, на этот раз розовый. Когда этоn параллелепипед полностью покинет экран, узел отправит сигнал.

изображение5

Измените его размер с помощью оранжевых точек, пока он не покроет всю 3D-модель.

изображение6

Программирование движения мобов

Давайте реализуем движение монстра. Мы сделаем это в два этапа. Сначала мы напишем скрипт для узла Mob, который определит функцию для инициализации монстра. Затем мы запрограммируем механизм случайного появления в сцене main.tscn и вызовем функцию оттуда.

Добавьте скрипт к Mob.

изображение7

Вот код движения для начала. Мы определяем два свойства, min_speed и max_speed, чтобы определить случайный диапазон скорости, который мы в будущем используем для задания значения CharacterBody3D.velocity.

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10.0
# Maximum speed of the mob in meters per second.
@export var max_speed = 18.0


func _physics_process(_delta):
    move_and_slide()

Аналогично игроку, мы перемещаем моба каждый кадр, вызывая метод CharacterBody3D.move_and_slide(). На этот раз мы не обновляем velocity каждый кадр: мы хотим, чтобы монстр двигался с постоянной скоростью и покинул экран, даже если он врежется в препятствие.

Нам нужно определить ещё одну функцию для расчёта CharacterBody3D.velocity. Эта функция повернёт монстра в сторону игрока и случайным образом вычислит угол его движения и скорость.

В качестве аргументов функция принимает start_position, позицию появления моба, и player_position.

Мы расположим моба с помощью start_position и повернем его к игроку с помощью метода look_at_from_position(), а также вычисляем угол, поворачивая его на случайную величину вокруг оси Y. Ниже, randf_range() выводит случайное значение между -PI / 4 радианами и PI / 4 радианами.

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -45 and +45 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

We got a random position, now we need a random_speed. randf_range() will be useful as it gives random float values, and we will use min_speed and max_speed. random_speed is just an float, and we just use it to multiply our CharacterBody3D.velocity. After random_speed is applied, we rotate CharacterBody3D.velocity Vector3 towards the player.

func initialize(start_position, player_position):
    # ...

    # We calculate a random speed (float)
    var random_speed = randf_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

Уход с экрана

Нам всё еще нужно уничтожать мобов, когда они покидают экран. Для этого мы подключим сигнал нашего узла VisibleOnScreenNotifier3D screen_exited к Mob.

Выберите узел VisibleOnScreenNotifier3D и в правой части интерфейса перейдите к панели Signals. Дважды щелкните сигнал screen_exited().

изображение9

Подключите сигнал к Mob

изображение10

Это добавит новую функцию _on_visible_on_screen_notifier_3d_screen_exited(). Из неё вызовите метод queue_free(). Это уничтожит экземпляр моба, который его вызвал.

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

Наш монстр готов вступить в игру! В следующей части вы будете порождать монстров на игровом уровне.

Вот, для справки, полный сценарий mob.gd.

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10.0
# Maximum speed of the mob in meters per second.
@export var max_speed = 18.0

func _physics_process(_delta):
    move_and_slide()

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -45 and +45 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

    # We calculate a random speed (float)
    var random_speed = randf_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()