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 із док-станції FileSystem на Pivot, щоб додати 3D-модель монстра до сцени.

../../_images/drag_drop_mob.webp

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

image0

Нам потрібна форма зіткнення, щоб наше тіло працювало. Клацніть правою кнопкою миші вузол Mob, корінь сцени, і натисніть Додати дочірній вузол.

image1

Додати CollisionShape3D.

image2

В Інспекторі призначте BoxShape3D властивості Shape.

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

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

Коробка повинна торкатися підлоги і бути трохи тонше моделі. Фізичні рушії працюють таким чином, що якщо сфера гравця торкнеться навіть кута коробки, відбудеться зіткнення. Якщо коробка трохи завелика в порівнянні з 3D-моделлю, ви можете померти на відстані від монстра, і гра буде несправедливою по відношенню до гравців.

image4

Зверніть увагу, що моя коробка вище, ніж монстр. Це нормально в цій грі, тому що ми дивимося на сцену зверху і використовуємо фіксовану перспективу. Форми зіткнення не повинні точно відповідати моделі. Це та форма та розмір, які гра відчуває, коли ви тестуєте її.

Видалення монстрів поза екраном

Ми будемо породжувати монстрів через рівні проміжки часу на рівні гри. Якщо ми не будемо обережні, їх кількість може зрости до нескінченності, а ми цього не хочемо. Кожен екземпляр мобу має як пам’ять, так і вартість обробки, і ми не хочемо платити за це, коли моб знаходиться поза екраном.

Як тільки монстр покидає екран, він нам більше не потрібен, тому ми повинні його видалити. У Godot є вузол, який визначає, коли об’єкти залишають екран, VisibleOnScreenNotifier3D, і ми збираємося використовувати його, щоб знищувати наших мобів.

Примітка

Коли ви продовжуєте створювати екземпляри об’єкта, існує техніка, яку можна використовувати, щоб уникнути витрат на постійне створення та знищення екземплярів, що називається об’єднанням. Він складається з попереднього створення масиву об’єктів і повторного їх використання знову і знову.

When working with GDScript, this usually isn't needed. The main reason to use pools is to avoid freezes with garbage-collected languages like C# or Lua. GDScript uses a different technique to manage memory, reference counting, which doesn't have that caveat. You can learn more about that here: Управління пам'яттю.

Виберіть вузол Mob і додайте дочірній вузол VisibleOnScreenNotifier3D. З’являється інша коробка, цього разу рожева. Коли це вікно повністю залишить екран, вузол випустить сигнал.

image5

Змініть коробку за допомогою помаранчевих точок, щоб вона охопила всю 3D-модель.

image6

Кодування руху монстра

Здійснюємо рух монстра. Ми зробимо це в два кроки. Спочатку ми напишемо скрипт для Mob, який визначає функцію для ініціалізації монстра. Потім ми закодуємо рандомізований механізм породження в сцені main.tscn і викличемо функцію звідти.

Додайте скрипт до Mob.

image7

Ось код руху для початку. Ми визначаємо дві властивості, min_speed і max_speed, щоб визначити випадковий діапазон швидкості, який ми пізніше використаємо для визначення CharacterBody3D.velocity.

extends CharacterBody3D

# 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


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))

Ми отримали випадкову позицію, тепер нам потрібна random_speed. randi_range() буде корисним, оскільки він дає випадкові значення int, і ми будемо використовувати min_speed і max_speed. випадкова_швидкість — це просто ціле число, і ми просто використовуємо його для множення нашого CharacterBody3D.velocity. Після застосування random_speed ми повертаємо CharacterBody3D.velocity Vector3 до гравця.

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

    # We calculate a random speed (integer)
    var random_speed = randi_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)

Залишання екрану

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

Виберіть вузол VisibleOnScreenNotifier3D та у правій частині інтерфейсу перейдіть до панелі Сигнали. Двічі клацніть сигнал screen_exited().

image9

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

image10

Це додасть нову функцію для вас у ваш моб-скрипт, _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
# Maximum speed of the mob in meters per second.
@export var max_speed = 18

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 (integer)
    var random_speed = randi_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()