メインシーン¶
今まで作成したすべてをまとめ、遊べるゲームにするときが来ました。
新しいシーンを作成し、 Main
という名前の Node を追加します。 (Node2D ではなく、Nodeを使用する理由は、これはゲームロジックを扱うノードであり、これ自体に2D機能が不要であるためです。)
インスタンス ボタン(チェーン リンク アイコンで表示)をクリックし、保存した 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
また、ここで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 Scene
プロパティがあります。
このプロパティの値は、ふたつの方法で指定できます:
「ファイルシステム」パネルから
Mob.tscn
をドラッグし、 Mob Scene プロパティにドロップします。[空] の隣にある下矢印をクリックして「読み込み」を選び、
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);
}
_on_MobTimer_timeout()
では、モブ インスタンスを作成し、Path2D
に沿ってランダムに開始場所を選び、モブを動作させることにしました。PathFollow2D
ノードはパスに沿って自動的に回転するので、これを使用して mob の方向とその位置を選択します。モブ を生成するときは、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()
という関数を使って両者を変換しなければならないでしょう。
シーンのテスト¶
シーンをテストして、すべてが動作していることを確認してみましょう。これを _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
を選択してください。
ちなみに
すでに他のシーンをメインシーンに設定していた場合は、ファイルシステム("FileSystem") ドックの Main.tscn
を右クリックして「メインシーンとして設定」("Set As Main Scene") を選択してください。
プレイヤーを移動でき、モブが発生したり、モブに当たった時にプレイヤーが消えるようになっているはずです。
全て動作していることが確認できたら、 _ready()
から new_game()
の呼び出しを削除してください。
私たちのゲームには何が足りないのでしょう?それは、ユーザーインターフェイスです。次のレッスンでは、タイトル画面を追加して、プレイヤーのスコアを表示することにします。