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

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

Давайте разработаем самих монстров в новой сцене. Структура узлов будет похожа на сцену 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

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

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

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

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

Примечание

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

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

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

image5

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

image6

Прописываем движения мобов

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

Добавьте скрипт к сцене 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)

Уход с экрана

Нам всё еще нужно уничтожать мобов, когда они покидают экран. Для этого мы подключим сигнал нашего узла 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()