Главная сцена игры
Итак, настало время перенести всё, что мы сделали вместе, на играбельную игровую сцену.
Создайте новую сцену и добавьте Node с именем Main. (Причиной, по которой мы используем Node, а не Node2D, является то, что узел будет контейнером для обработки игровой логики. Это не требует именно двумерного функционала.)
Нажмите на кнопку Instance (Экземпляр, выглядящий как иконка цепи) и выберите ваш сохраненный player.tscn.
Теперь добавьте следующие узлы в виде дочерних элементов Main и назовите их как показано ниже:
Timer (названный
MobTimer) - чтобы контролировать частоту появления мобовTimer (названный
ScoreTimer) - чтобы каждую секунду увеличивать счетTimer (названный
StartTimer) - чтобы дать задержку перед стартом игрыMarker2D (названный
StartPosition) - чтобы указать начальную позицию игрока
Задайте значение Wait Time для каждого из узлов Timer следующим образом (значения в секундах):
MobTimer:0.5ScoreTimer:1StartTimer:2
Кроме того, установите для свойства One Shot узла StartTimer значение "Вкл" и для свойства Position узла StartPosition установите значение (240, 450).
Добавление мобов
The Main node will be spawning new mobs, and we want them to appear at a random
location on the edge of the screen. Click the Main node in the Scene dock, then
add a child Path2D node named MobPath. When you select
Path2D, you will see some new buttons at the top of the editor:
Выберите среднюю ("Добавить точку") и нарисуйте путь щелчками мыши, чтобы добавить точки в показанных углах. Чтобы точки привязывались к сетке, убедитесь, что нажаты кнопки "Использовать привязку к сетке" и "Использовать умную привязку". Эти опции можно найти слева от кнопки "Заблокировать узел", они отображаются в виде магнита с точками и магнита с пересекающимися линиями.
Важно
Нарисуйте путь по часовой стрелке, иначе ваши мобы будут направлены наружу, а не внутрь!
Поместив точку 4 на изображение, нажмите кнопку "Сомкнуть кривую", и она будет завершена.
Теперь, когда путь определен, добавьте узел PathFollow2D как дочерний элемент MobPath и назовите его MobSpawnLocation. Этот узел будет автоматически вращаться и следовать по пути при его перемещении, поэтому мы можем использовать его для выбора случайной позиции и направления вдоль пути.
Ваша сцена должна выглядеть так:
Главный скрипт
Добавьте скрипт к Main. В верхней части скрипта мы пишем @export var mob_scene: PackedScene, что позволяет нам выбрать сцену Mob, экземпляр которой мы хотим сделать.
extends Node
@export var mob_scene: PackedScene
var score
using Godot;
public partial class Main : Node
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
[Export]
public PackedScene MobScene { get; set; }
private int _score;
}
Выберите узел Main и вы увидите свойство MobScene в окне Инспектора под "Main.gd".
Значение этого свойства можно присвоить двумя способами:
Перетащите
mob.tscnиз панели "Файловая система" в свойство Mob Scene.Нажмите стрелочку вниз рядом с "[пусто]" и выберите "Загрузить" ("Load"). Затем выберите
mob.tscn.
Затем выберите экземпляр сцены Player в узле Main в панели Сцена и откройте панель Узла на боковой панели. Убедитесь, что в панели Узла выбрана вкладка Сигналы(Signals).
Вы должны увидеть список сигналов для узла Player. В списке найдите и дважды щелкните по сигналу hit (или щелкните по нему правой кнопкой мыши и выберите "Присоединить..."). Это откроет диалоговое окно подключения сигнала. Мы хотим создать новую функцию с именем game_over, которая будет обрабатывать то, что должно произойти, когда игра заканчивается. Введите "game_over" в поле "Receiver Method"("Метод-приемник") в нижней части диалогового окна подключения сигнала и нажмите "Присоединить". Вы хотите чтобы сигнал hit отправлялся из Player и обрабатывался в скрипте Main. Добавьте следующий код в новую функцию, а также функцию new_game, которая настроит всё для новой игры:
func game_over():
$ScoreTimer.stop()
$MobTimer.stop()
func new_game():
score = 0
$Player.start($StartPosition.position)
$StartTimer.start()
public void GameOver()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Timer>("ScoreTimer").Stop();
}
public void NewGame()
{
_score = 0;
var player = GetNode<Player>("Player");
var startPosition = GetNode<Marker2D>("StartPosition");
player.Start(startPosition.Position);
GetNode<Timer>("StartTimer").Start();
}
Now we'll connect the timeout() signal of each Timer node (StartTimer,
ScoreTimer, and MobTimer) to the main script. For each of the three
timers, select the timer in the Scene dock, open the Signals tab of the Node
dock, then double-click the timeout() signal in the list. This will open a new
signal connection dialog. The default settings in this dialog should be fine, so
select Connect to create a new signal connection.
Once all three timers have this set up, you should be able to see each timer
have a Signal connection for their respective timeout() signal, showing in
green, within their respective Signals tabs:
(For MobTimer):
_on_mob_timer_timeout()(For ScoreTimer):
_on_score_timer_timeout()(For StartTimer):
_on_start_timer_timeout()
Now we define how each of these timers operate by adding the code below. Notice
that StartTimer will start the other two timers, and that ScoreTimer
will increment the score by 1.
func _on_score_timer_timeout():
score += 1
func _on_start_timer_timeout():
$MobTimer.start()
$ScoreTimer.start()
// We also specified this function name in PascalCase in the editor's connection window.
private void OnScoreTimerTimeout()
{
_score++;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnStartTimerTimeout()
{
GetNode<Timer>("MobTimer").Start();
GetNode<Timer>("ScoreTimer").Start();
}
В функции _on_mob_timer_timeout() мы создадим экземпляр моба, выберем случайное начальное местоположение вдоль Path2D и приведем его в движение. Узел PathFollow2D будет автоматически поворачивать его по направлению пути, поэтому мы воспользуемся этим, чтобы выбрать направление моба и его позицию. При создании моба, мы получим случайное значение от 150.0 до 250.0 - это определит скорость движения каждого моба (было бы скучно, если бы они все двигались с одинаковой скоростью).
Обратите внимание, что новый экземпляр должен быть добавлен в сцену с помощью функции add_child().
func _on_mob_timer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instantiate()
# Choose a random location on Path2D.
var mob_spawn_location = $MobPath/MobSpawnLocation
mob_spawn_location.progress_ratio = randf()
# Set the mob's position to the random location.
mob.position = mob_spawn_location.position
# Set the mob's direction perpendicular to the path direction.
var direction = mob_spawn_location.rotation + PI / 2
# Add some randomness to the direction.
direction += randf_range(-PI / 4, PI / 4)
mob.rotation = direction
# Choose the velocity for the mob.
var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
mob.linear_velocity = velocity.rotated(direction)
# Spawn the mob by adding it to the Main scene.
add_child(mob)
// We also specified this function name in PascalCase in the editor's connection window.
private void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Choose a random location on Path2D.
var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
mobSpawnLocation.ProgressRatio = GD.Randf();
// Set the mob's direction perpendicular to the path direction.
float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;
// Set the mob's position to a random location.
mob.Position = mobSpawnLocation.Position;
// Add some randomness to the direction.
direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
mob.Rotation = direction;
// Choose the velocity.
var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0);
mob.LinearVelocity = velocity.Rotated(direction);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
}
Важно
Почему PI? В функциях, требующих углы, Godot использует радианы, а не градусы. Число Пи представляет собой пол-оборота в радианах, примерно 3.1415 (также есть переменная TAU, которая равна 2 * PI). Если вам удобнее работать с градусами, вам нужно использовать функции deg2rad() и rad2deg().
Тестирование сцены
Давайте протестируем сцену, чтобы убедиться, что все работает. Добавьте в _ready() вызов new_game:
func _ready():
new_game()
public override void _Ready()
{
NewGame();
}
Также давайте назначим сцену Main в качестве нашей "Главной сцены", которая запускается автоматически при запуске игры. Нажмите кнопку "Запустить проект" и выберите main.tscn при появлении запроса.
Совет
Если вы уже установили другую сцену в качестве «Основной сцены», вы можете щелкнуть правой кнопкой мыши по main.tscn в панели «Файловая система» и выбрать «Установить как основную сцену».
У вас должна быть возможность перемещать игрока, видеть, как появляются мобы, и видеть, как игрок исчезает, когда его касается моб.
Когда вы убедитесь, что всё работает, удалите вызов new_game() из _ready() и замените его на pass.
Чего не хватает нашей игре? Какого-нибудь пользовательского интерфейса. В следующем уроке мы добавим заглавный экран и отобразим очки игрока.