Spawning monsters¶
В этой части мы будем порождать монстров по пути случайным образом. К концу у вас будут монстры, бродящие по игровому полю.
Дважды щёлкните на Main.tscn
в панели FileSystem, чтобы открыть сцену Main.
Прежде чем рисовать путь, мы изменим разрешение игры. Наша игра имеет размер окна по умолчанию 1024x600
. Мы установим его на 720x540
, маленькую красивую рамку.
Go to Project -> Project Settings.
В левом меню перейдите вниз к Display -> Window. Справа установите значение Width на 720
и значение Height на 540
.
Creating the spawn path¶
Как и в учебнике по 2D-играм, вы собираетесь спроектировать путь и использовать узел PathFollow для выборки случайных мест на нём.
Однако в 3D рисовать путь немного сложнее. Мы хотим, чтобы он был вокруг игрового вида, чтобы монстры появлялись прямо за экраном. Но если мы нарисуем путь, мы не увидим его из предварительного просмотра камеры.
Чтобы определить границы вида, мы можем использовать некоторые сетки. Ваш экран всё ещё должен быть разделён на две части, с предварительным просмотром камеры в нижней части. Если это не так, нажмите Ctrl + 2 (Cmd + 2 на macOS), чтобы разделить вид на две части. Выберите узел Camera и установите флажок Preview в нижнем окне просмотра.
Adding placeholder cylinders¶
Давайте добавим сетки-заполнители. Добавьте новый узел Spatial в качестве дочернего узла Main и назовите его Cylinders. Мы будем использовать его для группировки цилиндров. В качестве дочернего узла добавьте узел MeshInstance.
В инспекторе назначьте CylinderMesh свойству Mesh.
Установите верхний экран на ортогональный вид сверху с помощью меню в левом верхнем углу экрана. Также можно нажать клавишу 7 на клавиатуре.
Для меня сетка немного отвлекает. Вы можете переключить её, перейдя в меню View на панели инструментов и выбрав View Grid.
Теперь нужно переместить цилиндр вдоль плоскости земли, глядя на предварительный просмотр камеры в нижнем окне просмотра. Я рекомендую использовать для этого привязку к сетке. Её можно переключить, нажав на значок магнита на панели инструментов или нажав Y.
Поместите цилиндр так, чтобы он находился прямо за пределами обзора камеры в левом верхнем углу.
Мы собираемся создать копии сетки и разместить их вокруг игровой области. Нажмите Ctrl + D (Cmd + D на macOS), чтобы продублировать узел. Вы также можете щёлкнуть узел правой кнопкой мыши в панели Scene и выбрать Duplicate. Переместите копию вниз вдоль синей оси Z, пока она не окажется прямо за пределами предварительного просмотра камеры.
Выберите оба цилиндра, нажав клавишу Shift и щёлкнув по невыбранному цилиндру продублируйте их.
Переместите их вправо, перетащив красную ось X.
Их немного трудно разглядеть в белом цвете, не так ли? Давайте выделим их, придав им новый материал.
В 3D, материалы определяют визуальные свойства поверхности, такие как цвет, отражение света и многое другое. Мы можем использовать их для изменения цвета сетки.
Мы можем обновить все четыре цилиндра одновременно. Выберите все экземпляры сетки в панели Scene. Для этого щёлкните на первом из них и щёлкните с зажатым Shift на последнем.
В инспекторе, раскройте раздел Material и назначьте SpatialMaterial в слот 0.
Щёлкните значок сферы, чтобы открыть ресурс материала. Вы получите предварительный просмотр материала и длинный список разделов, заполненных свойствами. С их помощью можно создавать всевозможные поверхности, от металла до камня или воды.
Разверните раздел Albedo и установите цвет, контрастирующий с фоном, например, ярко-оранжевый.
Теперь мы можем использовать цилиндры в качестве направляющих. Сложите их в панели Scene, нажав на серую стрелку рядом с ними. Продвигаясь вперёд, вы также можете переключить их видимость, нажав на значок глаза рядом с Цилиндрами.
Добавьте узел Path в качестве дочернего узла Main. На панели инструментов появятся четыре значка. Щёлкните инструмент Add Point (добавить точку) - значок с зелёным знаком "+".
Примечание
Наведя курсор на любой значок, можно увидеть всплывающую подсказку с описанием инструмента.
Щёлкните в центре каждого цилиндра, чтобы создать точку. Затем нажмите на значок Close Curve (Закрыть кривую) на панели инструментов, чтобы закрыть контур. Если какая-либо точка немного смещена, можно щёлкнуть и перетащить её, чтобы изменить положение.
Ваш путь должен выглядеть следующим образом.
Для выборки случайных позиций на нём нам нужен узел PathFollow. Добавьте узел PathFollow в качестве дочернего узла Path. Переименуйте эти два узла в SpawnPath и SpawnLocation соответственно. Это более точно описывает то, для чего мы будем их использовать.
После этого мы готовы к написанию кода механизма порождения.
Spawning monsters randomly¶
Щелкните правой кнопкой мыши на узле Main и прикрепите к нему новый скрипт.
Сначала мы экспортируем переменную в инспекторе, чтобы мы могли присвоить ей Mob.tscn
или любого другого монстра.
Затем, поскольку мы собираемся порождать монстров процедурно, мы хотим случайно вычислять числа каждый раз, когда играем в игру. Если этого не сделать, монстры всегда будут появляться в одной и той же последовательности.
extends Node
export (PackedScene) var mob_scene
func _ready():
randomize()
public class Main : Node
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
#pragma warning disable 649
// We assign this in the editor, so we don't need the warning about not being assigned.
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
}
}
Мы хотим порождать мобов через регулярные промежутки времени. Для этого нам нужно вернуться на сцену и добавить таймер. Но перед этим нам нужно назначить файл Mob.tscn
свойству mob_scene
.
Вернитесь на 3D-экран и выберите узел Main. Перетащите Mob.tscn
из панели FileSystem в слот Mob Scene в инспекторе.
Добавьте новый узел Timer как дочерний узел Main. Назовите его MobTimer.
В инспекторе установите Wait Time на 0.5
секунд и включите Autostart, чтобы он автоматически запускался, когда мы запустим игру.
Таймеры издают сигнал timeout
каждый раз, когда они достигают конца своего времени ожидания (Wait Time). По умолчанию они перезапускаются автоматически, испуская сигнал в цикле. Мы можем подключиться к этому сигналу из узла Main, чтобы порождать монстров каждые 0.5
секунды.
Когда MobTimer всё ещё выбран, перейдите в панель Node справа и дважды щёлкните по сигналу timeout
.
Подключите его к узлу Main.
Это вернёт вас к скрипту с новой пустой функцией _on_MobTimer_timeout()
.
Давайте напишем логику порождения мобов. Мы собираемся:
Instantiate the mob scene.
Выборка случайной позиции на пути порождения.
Получить позицию игрока.
Вызовите метод
initialize()
моба, передав ему случайную позицию и позицию игрока.Add the mob as a child of the Main node.
func _on_MobTimer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instance()
# Choose a random location on the SpawnPath.
# We store the reference to the SpawnLocation node.
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
# And give it a random offset.
mob_spawn_location.unit_offset = randf()
var player_position = $Player.transform.origin
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
// We also specified this function name in PascalCase in the editor's connection window
public void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = (Mob)MobScene.Instance();
// Choose a random location on the SpawnPath.
// We store the reference to the SpawnLocation node.
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
// And give it a random offset.
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
}
Выше, randf()
выдаёт случайное значение между 0
и 1
, что и ожидает узел PathFollow узла unit_offset
.
Вот, для справки, полный скрипт Main.gd
.
extends Node
export (PackedScene) var mob_scene
func _ready():
randomize()
func _on_MobTimer_timeout():
var mob = mob_scene.instance()
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
mob_spawn_location.unit_offset = randf()
var player_position = $Player.transform.origin
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
}
public void OnMobTimerTimeout()
{
Mob mob = (Mob)MobScene.Instance();
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
}
}
Вы можете протестировать сцену, нажав F6. Вы должны увидеть, что монстры появляются и движутся по прямой линии.
Пока что они сталкиваются и скользят друг по другу, когда их пути пересекаются. Мы рассмотрим это в следующей части.