Designing the mob scene

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

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

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

image0

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

image1

Добавьте CollisionShape.

image2

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

image3

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

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

image4

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

Removing monsters off-screen

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

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

Примечание

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

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

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

image5

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

image6

Coding the mob's movement

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

Attach a script to the Mob.

image7

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

extends KinematicBody

# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)

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

Вы можете увидеть предупреждение в GDScript о том, что возвращаемое значение из move_and_slide() не используется. Это ожидаемо. Вы можете просто проигнорировать предупреждение или, если хотите скрыть его полностью, добавить комментарий # warning-ignore:return_value_discarded прямо над строкой move_and_slide(velocity). Подробнее о системе предупреждений GDScript см. в Система предупреждений GDScript.

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

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

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

# We will call this function from the Main scene.
func initialize(start_position, player_position):
    # We position the mob and turn it so that it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # And rotate it randomly so it doesn't move exactly toward the player.
    rotate_y(rand_range(-PI / 4, PI / 4))

Затем мы снова вычисляем случайную скорость с помощью rand_range() и используем её для вычисления скорости.

Мы начинаем с создания 3D-вектора, направленного вперёд, умножаем его на наш random_speed, и, наконец, поворачиваем его с помощью метода Vector3 класса rotated().

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

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

Leaving the screen

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

Вернитесь в 3D окно просмотра, нажав на ярлык 3D в верхней части редактора. Вы также можете нажать Ctrl + F2 (Alt + 2 на macOS).

image8

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

image9

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

image10

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

func _on_VisibilityNotifier_screen_exited():
    queue_free()

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

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

extends KinematicBody

# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)

func initialize(start_position, player_position):
    look_at_from_position(start_position, player_position, Vector3.UP)
    rotate_y(rand_range(-PI / 4, PI / 4))

    var random_speed = rand_range(min_speed, max_speed)
    velocity = Vector3.FORWARD * random_speed
    velocity = velocity.rotated(Vector3.UP, rotation.y)


func _on_VisibilityNotifier_screen_exited():
    queue_free()