Головна сцена гри¶
Тепер настав час об'єднати все, що ми зробили, разом, в ігрову сцену.
Створіть нову сцену та додайте Node <class_Node>`*(Вузол)* з ім'ям ``Main`. (Ми використовуємо Node, а не Node2D, тому що саме цей вузол буде контейнером для обробки логіки гри. Він не вимагає 2D-функціональності.)
Клацніть кнопку Створити екземпляр дочірньої сцени (іконка ланцюжка) і виберіть збережений Player.tscn
.

Тепер додайте Main
наступні вузли-нащадки та назвіть їх як показано (значення в секундах):
Timer (названий
MobTimer
) - щоб контролювати, як часто виникають мобиTimer (названий
ScoreTimer
) - збільшуватиме рахунок щосекундиTimer (названий
StartTimer
) - щоб дати затримку перед запускомPosition2D (названий
StartPosition
) - для визначення стартової позиції гравця
Встановіть властивість Wait Time
кожного з вузлів Timer
наступним чином:
MobTimer
:0.5
ScoreTimer
:1
StartTimer
:2
Крім того, встановіть властивість One Shot
(одна спроба) в StartTimer
на "включено" і задайте Position
(позиція) в StartPosition
на (240, 450)
.
Поява мобів¶
Головний вузол породжує нових мобів, і ми хочемо, щоб вони з’являлись у випадковому місці на краю екрана. Додайте вузол Path2D (Шлях 2D), нащадком Main
і назвіть MobPath
. Вибравши Path2D
, ви побачите нові кнопки вгорі редактора:

Виберіть середню ("Додати точку") та намалюйте шлях, клацнувши, щоб додати точки у вказаних кутах. Щоб точки прикріпилися до сітки (ґратки), переконайтеся, що опції "Використати до ґратки" та "Використати прив'язку", обидві включені. Ці опції можна знайти зліва від кнопки «Блокування», виглядає як магніт з кількома лініями, що перетинаються.

Важливо
Малюйте доріжку за годинниковою стрілкою , інакше ваші моби будуть виникати спрямовані назовні, а не всередину!

Після розміщення четвертої точки на зображенні натисніть кнопку "Закрити криву", і ваша крива буде завершена.
Тепер, коли шлях визначений, додайте вузол PathFollow2D (слідувати за шляхом 2D) нащадком MobPath
та назвіть його MobSpawnLocation
. Цей вузол автоматично обертатиметься та рухатиметься по шляху, тому ми можемо використовувати його для вибору випадкової позиції та напрямку вздовж шляху.
Ваше сцена має виглядати так:

Головний скрипт¶
Додайте скрипт до вузла Main
. У верхній частині скрипту ми використовуємо export (PackedScene)
, щоб ми могли вибрати сцену Mob
, екземпляр якої ми хочемо створити.
extends Node
export(PackedScene) var mob_scene
var score
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 int Score;
}
// Copy `player.gdns` to `main.gdns` and replace `Player` with `Main`.
// Attach the `main.gdns` file to the Main node.
// Create two files `main.cpp` and `main.hpp` next to `entry.cpp` in `src`.
// This code goes in `main.hpp`. We also define the methods we'll be using here.
#ifndef MAIN_H
#define MAIN_H
#include <AudioStreamPlayer.hpp>
#include <CanvasLayer.hpp>
#include <Godot.hpp>
#include <Node.hpp>
#include <PackedScene.hpp>
#include <PathFollow2D.hpp>
#include <RandomNumberGenerator.hpp>
#include <Timer.hpp>
#include "hud.hpp"
#include "player.hpp"
class Main : public godot::Node {
GODOT_CLASS(Main, godot::Node)
int score;
HUD *_hud;
Player *_player;
godot::Node2D *_start_position;
godot::PathFollow2D *_mob_spawn_location;
godot::Timer *_mob_timer;
godot::Timer *_score_timer;
godot::Timer *_start_timer;
godot::AudioStreamPlayer *_music;
godot::AudioStreamPlayer *_death_sound;
godot::Ref<godot::RandomNumberGenerator> _random;
public:
godot::Ref<godot::PackedScene> mob_scene;
void _init() {}
void _ready();
void game_over();
void new_game();
void _on_MobTimer_timeout();
void _on_ScoreTimer_timeout();
void _on_StartTimer_timeout();
static void _register_methods();
};
#endif // MAIN_H
Ми також додаємо сюди виклик randomize()
, щоб генератор випадкових чисел генерував різні випадкові числа кожного разу під час запуску гри:
func _ready():
randomize()
public override void _Ready()
{
GD.Randomize();
}
// This code goes in `main.cpp`.
#include "main.hpp"
#include <SceneTree.hpp>
#include "mob.hpp"
void Main::_ready() {
_hud = get_node<HUD>("HUD");
_player = get_node<Player>("Player");
_start_position = get_node<godot::Node2D>("StartPosition");
_mob_spawn_location = get_node<godot::PathFollow2D>("MobPath/MobSpawnLocation");
_mob_timer = get_node<godot::Timer>("MobTimer");
_score_timer = get_node<godot::Timer>("ScoreTimer");
_start_timer = get_node<godot::Timer>("StartTimer");
// Uncomment these after adding the nodes in the "Sound effects" section of "Finishing up".
//_music = get_node<godot::AudioStreamPlayer>("Music");
//_death_sound = get_node<godot::AudioStreamPlayer>("DeathSound");
_random = (godot::Ref<godot::RandomNumberGenerator>)godot::RandomNumberGenerator::_new();
_random->randomize();
}
Виберіть вузол Main
і побачите в Інспекторі під "Script Variables" (Властивості скрипта) властивість Mob
.
Ви можете змінити значення цієї властивості двома шляхами:
Drag
Mob.tscn
from the "FileSystem" dock and drop it in the Mob Scene property.Клацнути стрілочку вниз поряд з "[empty]"(пусто) і вибрати "Load" (Завантажити). Виберіть
Mob.tscn
.
Далі виберіть вузол Player
на вкладці Сцена і виберіть вкладку "Вузол". Переконайтесь, що на вкладці "Вузол" вибрано "Сигнали".
Ви повинні побачити список сигналів для вузла Player
. Знайдіть у списку сигнал hit
і двічі клацніть по ньому (або клацніть по ньому правою клавішею мишки і виберіть "Під'єднати..."). Це відкриє діалогове вікно підключення сигналу. Ми хочемо створити нову функцію з назвою game_over
, яка буде працювати з тим, що має відбуватися, коли гра закінчується. Введіть "game_over" у поле "Метод отримувач" внизу вікна "З’єднати сигнал з методом". Додайте наступний код до нової функції, а також функцію 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<Position2D>("StartPosition");
player.Start(startPosition.Position);
GetNode<Timer>("StartTimer").Start();
}
// This code goes in `main.cpp`.
void Main::game_over() {
_score_timer->stop();
_mob_timer->stop();
}
void Main::new_game() {
score = 0;
_player->start(_start_position->get_position());
_start_timer->start();
}
Тепер підключіть сигнал timeout()
кожного з вузлів Timer ( StartTimer
, ScoreTimer
і MobTimer
) до головного скрипту. StartTimer
запустить інші два таймери. ScoreTimer
буде збільшувати рахунок на 1.
func _on_ScoreTimer_timeout():
score += 1
func _on_StartTimer_timeout():
$MobTimer.start()
$ScoreTimer.start()
public void OnScoreTimerTimeout()
{
Score++;
}
public void OnStartTimerTimeout()
{
GetNode<Timer>("MobTimer").Start();
GetNode<Timer>("ScoreTimer").Start();
}
// This code goes in `main.cpp`.
void Main::_on_ScoreTimer_timeout() {
score += 1;
}
void Main::_on_StartTimer_timeout() {
_mob_timer->start();
_score_timer->start();
}
// Also add this to register all methods and the mob scene property.
void Main::_register_methods() {
godot::register_method("_ready", &Main::_ready);
godot::register_method("game_over", &Main::game_over);
godot::register_method("new_game", &Main::new_game);
godot::register_method("_on_MobTimer_timeout", &Main::_on_MobTimer_timeout);
godot::register_method("_on_ScoreTimer_timeout", &Main::_on_ScoreTimer_timeout);
godot::register_method("_on_StartTimer_timeout", &Main::_on_StartTimer_timeout);
godot::register_property("mob_scene", &Main::mob_scene, (godot::Ref<godot::PackedScene>)nullptr);
}
У функції _on_MobTimer_timeout()
ми створимо екземпляр моба, підберемо випадкове початкове місце уздовж Path2D
та приведемо моба у рух. Вузол PathFollow2D
буде автоматично обертатися по мірі просування по шляху, так що ми будемо використовувати його, щоб вибрати напрямок моба, а також його позицію. Під час появи моба, ми вибираємо випадкове значення між 150.0
і 250.0
визначаючи швидкість руху (було б нудно, якби всі вони рухалися з однаковою швидкістю).
Зауважте, що новий екземпляр потрібно додати до сцени за допомогою add_child()
.
func _on_MobTimer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instance()
# Choose a random location on Path2D.
var mob_spawn_location = get_node("MobPath/MobSpawnLocation")
mob_spawn_location.offset = randi()
# 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 += rand_range(-PI / 4, PI / 4)
mob.rotation = direction
# Choose the velocity for the mob.
var velocity = Vector2(rand_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)
public 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.
var mob = (Mob)MobScene.Instance();
// Choose a random location on Path2D.
var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
mobSpawnLocation.Offset = GD.Randi();
// 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);
}
// This code goes in `main.cpp`.
void Main::_on_MobTimer_timeout() {
// Create a new instance of the Mob scene.
godot::Node *mob = mob_scene->instance();
// Choose a random location on Path2D.
_mob_spawn_location->set_offset((real_t)_random->randi());
// Set the mob's direction perpendicular to the path direction.
real_t direction = _mob_spawn_location->get_rotation() + (real_t)Math_PI / 2;
// Set the mob's position to a random location.
mob->set("position", _mob_spawn_location->get_position());
// Add some randomness to the direction.
direction += _random->randf_range((real_t)-Math_PI / 4, (real_t)Math_PI / 4);
mob->set("rotation", direction);
// Choose the velocity for the mob.
godot::Vector2 velocity = godot::Vector2(_random->randf_range(150.0, 250.0), 0.0);
mob->set("linear_velocity", velocity.rotated(direction));
// Spawn the mob by adding it to the Main scene.
add_child(mob);
}
Важливо
Чому PI
? У функціях, що вимагають кутів, Godot використовує радіани , а не градуси. Пі являє собою половину повороту в радіанах, близько 3.1415
(є також TAU
, що дорівнює 2 * PI
). Якщо вам зручніше працювати з градусами, вам потрібно буде скористатися функціями deg2rad()
та rad2deg()
для перетворення між ними.
Тестування сцени¶
Давайте протестуємо сцену, щоб переконати, що все працює. Додайте new_game
до _ready()
:
func _ready():
randomize()
new_game()
public override void _Ready()
{
NewGame();
}
// This code goes in `main.cpp`.
void Main::_ready() {
new_game();
}
Також визначимо Main
, як нашу "Головну Сцену" - вона буде запускатися автоматично при кожному запуску гри. Натисніть кнопку "Відтворити" і виберіть Main.tscn
при появі запиту.
Порада
Якщо ви вже встановили іншу сцену як "Головну сцену", ви можете клацнути правою кнопкою мишки по Main.tscn
на панелі Файлова система і вибрати "Встановити головною сценою".
Ви мали би успішно переміщати повсюди гравця, спостерігати, як народжуються моби, і бачити, як зникає гравець при зіткненні з мобом.
Переконавшись, що все працює видаліть виклик new_game()
з _ready()
.
Чого не вистачає нашій грі? Деякого інтерфейсу. На наступному уроці ми додамо титульний екран і відображення рахунку гравця.