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 (
StratTimer
라고 이름지음) - 시작하기 전에 지연시간을 주기 위해 사용Position2D (
StartPosition
이라고 이름지음) - 플레이어의 시작 지점을 표시하기 위해 사용
각 Timer
마다 Wait Time
속성을 다음과 같이 설정하세요:
MobTimer
:0.5
ScoreTimer
:1
StartTimer
:2
그리고, StartTimer
속성의 One Shot
을 "사용(On)"으로 설정하고 StartPosition
노드의 Position
을 (240, 450)
으로 설정하세요.
몹 스폰하기¶
메인 노드는 새로운 몹을 스폰할 것이고 우리는 몹이 화면 모서리 아무 위치에서나 나타나도록 만들고 싶습니다. Path2D 노드를 Main
의 자식으로 추가하고 MobPath
라고 이름지으세요. Path2D
를 선택하면 편집기 위쪽에 새로운 버튼들이 나타납니다:

가운데 버튼 "점 추가(Add Point)"을 선택하고 점을 추가하기 위해 표시된 모서리를 클릭해서 경로를 그리세요. 점이 그리드에 스냅(snap)되도록 하려면 "격자 스냅 사용(Use Grid Snap)" 및 "스냅 사용(Use Snap)"이 모두 선택되어 있는지 확인하세요. 이 옵션은 "잠금(Lock)" 버튼 왼쪽에서 찾을 수 있으며, 각각 점과 교차선 옆에 자석이 있는 모양입니다.

중요
시계 방향 으로 그리세요, 그렇지 않으면 몹들은 안쪽 이 아닌 바깥쪽 으로 향할 것입니다!

이미지에서 4
개의 점을 찍고 난 후, "곡선 닫기(Close Curve)" 버튼을 누르면 곡선이 완성됩니다.
이제 경로를 정의하기 위해 PathFollow2D 노드를 MobPath
의 자식으로 추가한 후 MobSpawnLocation
이라고 이름지으세요. 이 노드는 자동으로 회전하고 이동하면서 경로를 따라갈 것입니다, 그러므로 경로를 따라 임의의 위치와 방향을 선택하기 위해 이것을 사용할 수 있습니다.
씬 트리는 다음과 같아야 합니다:

메인 스크립트¶
Main
에 스크립트를 추가합니다. 스크립트의 위에 export (PackedScene)
를 사용해서 우리가 인스턴스화하길 원하는 몹 씬을 고를 수 있도록 만듭니다.
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".
다음 두 가지 방법으로 이 속성의 값을 할당할 수 있습니다:
Drag
Mob.tscn
from the "FileSystem" dock and drop it in the Mob Scene property."[비었음]([empty])" 옆에 있는 아래쪽 화살표를 클릭하고 "불러오기(Load)"를 선택하세요.
Mob.tscn
을 선택하세요.
다음으로 씬(Scene) 독에서 Player
노드를 선택하고 사이드바에서 노드(Node) 독에 접근하세요. 노드(Node) 독에서 시그널(Signal) 탭을 선택하세요.
Player
노드에 대한 시그널 목록이 표시되어야 합니다. 목록에서 hit
시그널을 찾아 두 번 클릭하세요(또는 마우스 오른쪽 단추로 클릭하고 "연결...(Connect...)" 선택). 그러면 시그널 연결 대화 상자가 열립니다. 우리는 게임이 끝났을 때 일어날 일을 처리할 game_over
라는 새로운 함수를 만들고 싶습니다. 신호 연결 대화 상자 아래쪽의 "받는 메서드(Receiver Method)" 상자에 "game_over"를 입력하고 "연결(Connect)"을 클릭하세요. 다음 코드를 새 함수에 추가하고 새 게임에 대한 모든 것을 설정하는 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
는 다른 두 타이머를 작동시킵니다. 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 Scene"으로 Main
을 지정해 보겠습니다. "재생(Play)" 버튼을 누르고 메시지가 표시되면 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.