Разработка сцены для моба¶
В этой части вы будете кодировать монстров, которых мы будем называть мобами. В следующем уроке мы будем распределять их случайным образом по всей игровой области.
Давайте разработаем самих монстров в новой сцене. Структура узлов будет похожа на сцену Player.
Создайте сцену, корнем которой снова является узел KinematicBody. Назовите его Mob. Добавьте узел Spatial в качестве его дочернего узла, назовите его Pivot. Перетащите файл mob.glb
из панели FileSystem на узел Pivot, чтобы добавить 3D-модель монстра в сцену. Вы можете переименовать только что созданный узел mob в Character.
Нам нужна форма столкновения, чтобы наше тело работало. Щёлкните правой кнопкой мыши на узле Mob, корне сцены, и выберите Add Child Node.
Добавьте CollisionShape.
В инспекторе назначьте BoxShape свойству Shape.
Мы должны изменить его размер, чтобы он лучше соответствовал 3D-модели. Вы можете сделать это интерактивно, щёлкая и перетаскивая оранжевые точки.
Габаритный контейнер должен касаться пола и быть немного тоньше, чем модель. Физические движки работают таким образом, что если сфера игрока коснется даже угла габаритного контейнера, произойдет столкновение. Если габаритный контейнер будет слишком большой по сравнению с 3D-моделью, вы можете умереть на расстоянии от монстра, и игра будет казаться несправедливой по отношению к игрокам.
Обратите внимание, что мой габаритный контейнер выше, чем монстр. В этой игре это нормально, потому что мы смотрим на сцену сверху и используем фиксированную перспективу. Формы столкновений не обязательно должны точно соответствовать модели. Их форма и размер должны определяться тем, как игра ощущается при тестировании.
Удаление монстров за пределами экрана¶
Мы собираемся порождать монстров через регулярные промежутки времени на игровом уровне. Если мы не будем осторожны, их количество может увеличиться до бесконечности, а мы этого не хотим. Каждый экземпляр монстра имеет стоимость памяти и обработки, и мы не хотим платить за это, когда монстр находится за пределами экрана.
Как только монстр покидает экран, он нам больше не нужен, поэтому мы можем его удалить. В Godot есть узел, который определяет, когда объекты покидают экран, VisibilityNotifier, и мы собираемся использовать его для уничтожения наших мобов.
Примечание
Когда вы постоянно создаёте экземпляр объекта в играх, существует техника, которую можно использовать, чтобы избежать затрат на постоянное создание и уничтожение экземпляров, называемая объединением. Она заключается в предварительном создании массива объектов и повторном их использовании.
При работе с GDScript вам не нужно беспокоиться об этом. Основная причина использования пулов заключается в том, чтобы избежать зависания в языках с мусорными коллекциями, таких как C# или Lua. GDScript использует другую технику управления памятью, подсчет ссылок, которая не имеет этого предостережения. Вы можете узнать больше об этом здесь Управление памятью.
Выберите узел Mob и добавьте VisibilityNotifier в качестве его дочернего элемента. Появится еще один квадрат, на этот раз розовый. Когда это поле полностью покинет экран, узел издаст сигнал.
Измените его размер с помощью оранжевых точек, пока он не покроет всю 3D-модель.
Прописываем движения мобов¶
Давайте реализуем движение монстра. Мы сделаем это в два этапа. Сначала мы напишем скрипт на узле Mob, который определит функцию для инициализации монстра. Затем мы закодируем механизм случайного появления в сцене Main и вызовем функцию оттуда.
Добавьте скрипт к сцене Mob.
Вот код движения для начала. Мы определяем два свойства, 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)
public class Mob : KinematicBody
{
// 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 = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_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))
// We will call this function from the Main scene
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// We position the mob and turn it so that it looks at the player.
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
// And rotate it randomly so it doesn't move exactly toward the player.
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
}
Затем мы снова вычисляем случайную скорость с помощью 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)
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// ...
// We calculate a random speed.
float randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
// We calculate a forward velocity that represents the speed.
_velocity = Vector3.Forward * randomSpeed;
// 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).
Выберите узел VisibilityNotifier и в правой части интерфейса перейдите в панель Node. Дважды щёлкните на сигнале screen_exited().
Подключите сигнал к Mob.
Это вернёт вас в редактор скриптов и добавит новую функцию _on_VisibilityNotifier_screen_exited()
. Из неё вызовите метод queue_free()
. Это уничтожит экземпляр моба, когда поле VisibilityNotifier покинет экран.
func _on_VisibilityNotifier_screen_exited():
queue_free()
// We also specified this function name in PascalCase in the editor's connection window
public void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
Наш монстр готов вступить в игру! В следующей части вы будете порождать монстров на игровом уровне.
Вот, для справки, полный сценарий 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()
public class Mob : KinematicBody
{
// Minimum speed of the mob in meters per second
[Export]
public int MinSpeed = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_velocity);
}
// We will call this function from the Main scene
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
var randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
_velocity = Vector3.Forward * randomSpeed;
_velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
}
// We also specified this function name in PascalCase in the editor's connection window
public void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
}