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.
Checking the stable version of the documentation...
Разработка сцены с мобом
В этой части вы будете программировать монстров, которых мы будем называть мобами. В следующем уроке мы будем распределять их случайным образом по всей игровой области.
Давайте разработаем самих монстров в новой сцене. Структура узлов будет похожа на сцену player.tscn
.
Создайте сцену, корнем которой снова является узел CharacterBody3D. Назовите его Mob
. Добавьте узел Node3D в качестве его дочернего узла, назовите его Pivot
. Перетащите файл mob.glb
из панели Файловая система на узел Pivot
, чтобы добавить 3D-модель монстра в сцену.

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

Мы должны изменить его размер, чтобы он лучше соответствовал 3D-модели. Вы можете сделать это интерактивно, щёлкая и перетаскивая оранжевые точки.
Параллелепипед должен касаться пола и быть немного тоньше, чем модель. Физические движки работают таким образом, что если сфера игрока коснется даже угла параллепипеда, произойдет столкновение. Если параллепипед будет слишком большой по сравнению с 3D-моделью, вы можете умереть на расстоянии от монстра, и игра будет казаться несправедливой по отношению к игрокам.
Обратите внимание, что мой габаритный контейнер выше, чем монстр. В этой игре это нормально, потому что мы смотрим на сцену сверху и используем фиксированную перспективу. Формы столкновений не обязательно должны точно соответствовать модели. Их форма и размер должны определяться тем, как игра ощущается при тестировании.
Удаление монстров за пределами экрана
Мы собираемся порождать монстров через регулярные промежутки времени на игровом уровне. Если мы не будем осторожны, их количество может увеличиться до бесконечности, а мы этого не хотим. Каждый экземпляр монстра требует памяти и времени на обработку, и мы не хотим зря тратить ресурсы используемого устройства, когда монстр находится за пределами экрана.
Как только монстр покидает экран, он нам больше не нужен, поэтому мы можем его удалить. В Godot есть узел, который определяет, когда объекты покидают экран, VisibleOnScreenNotifier3D, и мы собираемся использовать его для уничтожения наших мобов.
Примечание
Когда вы постоянно создаёте экземпляр объекта в играх, существует техника, которую можно использовать, чтобы избежать затрат на постоянное создание и уничтожение экземпляров, называемая пулингом. Она заключается в предварительном создании массива объектов и повторном их использовании.
При работе с GDScript вам не нужно беспокоиться об этом. Основная причина использования пулов заключается в том, чтобы избежать зависания в языках со сборщиком мусора, таких как C# или Lua. GDScript использует другую технику управления памятью, подсчет ссылок, которая не имеют такой проблемы. Вы можете узнать больше об этом здесь Управление памятью.
Выберите узел Mob
и добавьте VisibleOnScreenNotifier3D в качестве его дочернего элемента. Появится еще один параллелепипед, на этот раз розовый. Когда этоn параллелепипед полностью покинет экран, узел отправит сигнал.
Измените его размер с помощью оранжевых точек, пока он не покроет всю 3D-модель.
Программирование движения мобов
Давайте реализуем движение монстра. Мы сделаем это в два этапа. Сначала мы напишем скрипт для узла Mob
, который определит функцию для инициализации монстра. Затем мы запрограммируем механизм случайного появления в сцене main.tscn
и вызовем функцию оттуда.
Добавьте скрипт к Mob
.
Вот код движения для начала. Мы определяем два свойства, 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()
using Godot;
public partial class Mob : CharacterBody3D
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// Minimum speed of the mob in meters per second
[Export]
public int MinSpeed { get; set; } = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed { get; set; } = 18;
public override void _PhysicsProcess(double delta)
{
MoveAndSlide();
}
}
Аналогично игроку, мы перемещаем моба каждый кадр, вызывая метод 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))
// This function will be called from the Main scene.
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// We position the mob by placing it at startPosition
// and rotate it towards playerPosition, so it looks at the player.
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
// Rotate this mob randomly within range of -45 and +45 degrees,
// so that it doesn't move directly towards the player.
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
}
Мы получили случайную позицию, теперь нам нужно получить random_speed
. В данном случае пригодится функция randi_range()
, так как она возвращает случайные целые числа в определённом диапазоне, поэтому мы используем min_speed
и max_speed
в качестве предельных значений. random_speed
просто является целым числом, и мы просто используем его для умножения на CharacterBody3D.velocity
. После умножения random_speed
на CharacterBody3D.velocity
, мы поворачиваем Vector3 CharacterBody3D.velocity
в сторону игрока.
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)
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// ...
// We calculate a random speed (integer).
int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
// We calculate a forward velocity that represents the speed.
Velocity = Vector3.Forward * randomSpeed;
// 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
.
Вернитесь в 3D окно просмотра, нажав на ярлык 3D в верхней части редактора. Вы также можете нажать Ctrl + F2 (Opt + 2 на macOS).
Выберите узел VisibleOnScreenNotifier3D и в правой части интерфейса перейдите во вкладку Узел. Дважды щёлкните на сигнале screen_exited()
.
Подключите сигнал к Mob
Это добавит новую функцию _on_visible_on_screen_notifier_3d_screen_exited()
. Из неё вызовите метод queue_free()
. Это уничтожит экземпляр моба, который его вызвал.
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
// We also specified this function name in PascalCase in the editor's connection window.
private void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
Наш монстр готов вступить в игру! В следующей части вы будете порождать монстров на игровом уровне.
Вот, для справки, полный сценарий 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()
using Godot;
public partial class Mob : CharacterBody3D
{
// Minimum speed of the mob in meters per second.
[Export]
public int MinSpeed { get; set; } = 10;
// Maximum speed of the mob in meters per second.
[Export]
public int MaxSpeed { get; set; } = 18;
public override void _PhysicsProcess(double delta)
{
MoveAndSlide();
}
// This function will be called from the Main scene.
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// We position the mob by placing it at startPosition
// and rotate it towards playerPosition, so it looks at the player.
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
// Rotate this mob randomly within range of -45 and +45 degrees,
// so that it doesn't move directly towards the player.
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
// We calculate a random speed (integer).
int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
// We calculate a forward velocity that represents the speed.
Velocity = Vector3.Forward * randomSpeed;
// 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);
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
}