Up to date
This page is up to date for Godot 4.2
.
If you still find outdated information, please open an issue.
Главная сцена игры¶
Итак, настало время перенести всё, что мы сделали вместе, на играбельную игровую сцену.
Создайте новую сцену и добавьте Node с именем Main
. (Причиной, по которой мы используем Node, а не Node2D, является то, что узел будет контейнером для обработки игровой логики. Это не требует именно двумерного функционала.)
Нажмите на кнопку Instance (Экземпляр, выглядящий как значок звена цепи) и выберите ваш сохраненный player.tscn
.
Теперь добавьте следующие узлы в виде дочерних элементов Main
и назовите их как показано ниже (значения указаны в секундах):
Timer (названный
MobTimer
) - чтобы контролировать частоту появления мобовTimer (названный
ScoreTimer
) - чтобы каждую секунду увеличивать счетTimer (названный
StartTimer
) - чтобы дать задержку перед стартом игрыMarker2D (названный
StartPosition
) - чтобы указать начальную позицию игрока
Задайте значение Wait Time
для каждого из узлов Timer
следующим образом:
MobTimer
:0.5
ScoreTimer
:1
StartTimer
:2
Кроме того, установите для свойства One Shot
узла StartTimer
значение "Вкл" и для свойства Position
узла StartPosition
установите значение (240, 450)
.
Добавление мобов¶
Узел Main будет порождать новых мобов, и мы хотим, чтобы они появлялись в случайном месте на краю экрана. Добавьте узел Path2D с именем MobPath
как дочерний элемент узла Main
. Когда вы выберете Path2D
, вы увидите несколько новых кнопок в верхней части редактора:
Выберите среднюю ("Добавить точку") и нарисуйте путь щелчками мыши, чтобы добавить точки в показанных углах. Чтобы точки привязывались к сетке, убедитесь, что выбраны "Использовать привязку к сетке" и "Использовать умную привязку". Эти опции можно найти слева от кнопки "Заблокировать узел", они отображаются в виде магнита с точками и магнита с пересекающимися линиями.
Важно
Нарисуйте путь в порядке по часовой стрелке, иначе ваши мобы будут появляться с направлением наружу, а не внутрь!
Поместив точку "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
в окне Инспектора под "Script Variables".
Значение этого свойства можно присвоить двумя способами:
Перетащите
Mob.tscn
из панели "Файловая система" в свойство Mob Scene.Нажмите стрелочку вниз рядом с "[пусто]" и выберите "Загрузить" ("Load"). Затем выберите
mob.tscn
.
Затем выберите экземпляр сцены «Player» в узле «Main» в панели «Сцена» и откройте панель Узла на боковой панели. Убедитесь, что в панели Узла выбрана вкладка Сигналы(Signals).
Вы должны увидеть список сигналов для узла Player
. В списке найдите и дважды щелкните по сигналу hit
(или щелкните по нему правой кнопкой мыши и выберите "Присоединить..."). Это откроет диалоговое окно подключения сигнала. Мы хотим создать новую функцию с именем game_over
, которая будет обрабатывать то, что должно произойти, когда игра заканчивается. Введите "game_over" в поле "Receiver Method"("Метод получения") в нижней части диалогового окна подключения сигнала и нажмите "Присоединить". Добавьте следующий код в новую функцию, а также функцию 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();
}
Теперь присоедините сигнал timeout()
каждого из узлов Timer (StartTimer
, ScoreTimer
и MobTimer
) к главному скрипту. StartTimer
запустит два других таймера. ScoreTimer
будет увеличивать счет на 1.
func _on_score_timer_timeout():
score += 1
func _on_start_timer_timeout():
$MobTimer.start()
$ScoreTimer.start()
private void OnScoreTimerTimeout()
{
_score++;
}
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 direction perpendicular to the path direction.
var direction = mob_spawn_location.rotation + PI / 2
# Set the mob's position to a random location.
mob.position = mob_spawn_location.position
# 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)
private void OnMobTimerTimeout()
{
// Note: Normally it is best to use explicit types rather than the `var`
// keyword. However, var is acceptable to use here because the types are
// obviously Mob and PathFollow2D, since they appear later on the line.
// 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
в панели «Файловая система» и выбрать «Установить как основную сцену».
У вас должна быть возможность перемещать игрока, видеть, как появляются мобы, и видеть, как игрок исчезает, когда его бьет моб.
When you're sure everything is working, remove the call to new_game()
from
_ready()
and replace it with pass
.
Чего не хватает нашей игре? Какого-нибудь пользовательского интерфейса. В следующем уроке мы добавим заглавный экран и отобразим очки игрока.