The main game scene¶
Now it's time to bring everything we did together into a playable game scene.
Create a new scene and add a Node named Main
.
(The reason we are using Node instead of Node2D is because this node will
be a container for handling game logic. It does not require 2D functionality itself.)
Click the Instance button (represented by a chain link icon) and select your saved
Player.tscn
.

次に Main
の子供として以下のノードを追加します(値は秒単位です)。
Timer(
MobTimer
という名前) - モブが出現する頻度を制御するTimer (
ScoreTimer
という名前) - 一秒ごとに得点を上げるTimer (
StartTimer
という名前) - 開始する前に遅延させるPosition2D (
StartPosition
という名前) - プレイヤーの開始位置を示す
各 Timer
ノードの Wait Time
プロパティを次のように設定します:
MobTimer
:0.5
ScoreTimer
:1
StartTimer
:2
さらに、 StartTimer
の One Shot
プロパティを「On」に設定し、 StartPosition
ノードの Position
を (240, 450)
に設定します。
モブの生成¶
メインノードは新しいモブを生成し、画面の端のランダムな場所に表示するようにします。 Main
の子として MobPath
という名前のPath2Dノードを追加します。 Path2D
を選択すると、エディタの上部にいくつかの新しいボタンが表示されます:

中央のアイコン([点を追加])を選択し、表示されているコーナーをクリックしてポイントを追加してパスを描画します。ポイントをグリッドにスナップするには、[グリッドスナップを使う]が選択されていることを確認します。このオプションは、[ロック]ボタンの左側にあり、「交差する線と磁石」のアイコンで表示されています。

重要
時計回りにパスを描画します。そうしないと、モブは内側ではなく外側を向いて発生します!

画像にポイント 4
を配置した後、「カーブを閉じる」ボタンをクリックすると、カーブが完成します。
パスが定義されたので、 MobPath
の子としてPathFollow2Dノードを追加し、 MobSpawnLocation
という名前を付けます。このノードは自動的に回転し、パスの移動に従うので、パスに沿ってランダムな位置と方向を選択できます。
シーンは次のようになります:

Mainスクリプト¶
スクリプトを 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
We also add a call to randomize()
here so that the random number
generator generates different random numbers each time the game is run:
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();
}
Click the Main
node and you will see the Mob Scene
property in the Inspector
under "Script Variables".
このプロパティの値は、ふたつの方法で指定できます:
「ファイルシステム」パネルから
Mob.tscn
をドラッグし、Mob
プロパティにドロップします。[空] の隣にある下矢印をクリックして「読み込み」を選び、
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();
}
次に、各Timerノード (StartTimer
、ScoreTimer
、および MobTimer
) の timeout()
シグナルをメインスクリプトに接続します。StartTimer
は他の2つのタイマーを開始します。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);
}
In _on_MobTimer_timeout()
, we will create a mob instance, pick a random
starting location along the Path2D
, and set the mob in motion. The
PathFollow2D
node will automatically rotate as it follows the path, so we
will use that to select the mob's direction as well as its position.
When we spawn a mob, we'll pick a random value between 150.0
and
250.0
for how fast each mob will move (it would be boring if they were
all moving at the same speed).
注意点として、新しいインスタンスは 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);
}
重要
Why PI
? In functions requiring angles, Godot uses radians,
not degrees. Pi represents a half turn in radians, about
3.1415
(there is also TAU
which is equal to 2 * PI
).
If you're more comfortable working with degrees, you'll need to
use the deg2rad()
and rad2deg()
functions to convert
between the two.
シーンのテスト¶
Let's test the scene to make sure everything is working. Add this new_game
call to _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
を選択してください。
ちなみに
If you had already set another scene as the "Main Scene", you can right
click Main.tscn
in the FileSystem dock and select "Set As Main Scene".
プレイヤーを移動でき、モブが発生したり、モブに当たった時にプレイヤーが消えるようになっているはずです。
全て動作していることが確認できたら、 _ready()
から new_game()
の呼び出しを削除してください。
What's our game lacking? Some user interface. In the next lesson, we'll add a title screen and display the player's score.