Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
괴물 소환하기
이 부분에서는 무작위로 경로를 따라 괴물을 소환할 것입니다. 마지막에는 게임 보드에 괴물이 돌아다니게 될 것입니다.

파일시스템 독에서 main.tscn을 더블 클릭하여 Main 씬을 엽니다.
경로를 그리기 전에 게임 해상도를 변경하겠습니다. 우리 게임의 기본 창 크기는 1152x648입니다. 작은 상자 크기인 720x540으로 설정하겠습니다.
프로젝트 -> 프로젝트 설정으로 이동하세요.

입력 맵이 여전히 열려 있으면, 일반 탭으로 전환합니다.
왼쪽 메뉴에서 표시 -> 창으로 이동합니다. 오른쪽에서 너비를 720으로, 높이를 540으로 설정합니다.

소환 경로 만들기
2D 게임 튜토리얼에서 했던 것처럼, 경로를 디자인하고 PathFollow3D 노드를 사용하여 경로의 임의 위치를 샘플링할 것입니다.
하지만 3D에서는 경로를 그리는 것이 조금 더 복잡합니다. 몬스터가 화면 바로 바깥에 나타나도록 게임 뷰 주변에 경로를 그려야 합니다. 하지만 경로를 그리면 카메라 미리보기에서는 경로가 보이지 않습니다.
뷰의 한계를 찾기 위해 플레이스홀더 메시를 사용할 수 있습니다. 뷰포트는 여전히 카메라 미리보기가 하단에 있는 두 부분으로 분할되어 있어야 합니다. 그렇지 않은 경우 Ctrl + 2(macOS에서는 Cmd + 2)를 눌러 뷰를 두 개로 분할합니다. Camera3D 노드를 선택하고 하단 뷰포트에서 미리보기 체크박스를 클릭합니다.

자리표시자 원통 추가하기
플레이스홀더 메시를 추가해 보겠습니다. Main 노드의 자식으로 새 Node3D를 추가하고 이름을 Cylinders로 지정합니다. 이 노드를 사용하여 실린더를 그룹화합니다. Cylinders를 선택하고 자식 노드 MeshInstance3D를 추가합니다

인스펙터에서 메시 속성에 CylinderMesh를 할당합니다.

뷰포트의 왼쪽 상단에 있는 메뉴를 사용하여 상단 뷰포트를 상단 직교 뷰로 설정합니다. 또는 키패드의 7 키를 눌러도 됩니다.

격자가 산만할 수 있습니다. 툴바의 보기 메뉴로 이동하여 격자 보기를 클릭하여 전환할 수 있습니다.

이제 아래쪽 뷰포트에서 카메라 미리보기를 보면서 원통을 평면을 따라 이동하려고 합니다. 격자 스냅을 사용하는 것이 좋습니다. 도구 모음에서 자석 아이콘을 클릭하거나 Y를 눌러 전환할 수 있습니다.

원통을 왼쪽 상단 모서리에 있는 카메라 시야 바로 바깥쪽에 위치하도록 이동합니다.

메시의 사본을 만들어 게임 영역 주변에 배치하겠습니다. Ctrl + D(macOS에서는 Cmd + D)를 눌러 노드를 복제합니다. 씬 독에서 노드를 마우스 오른쪽 버튼으로 클릭하고 복제를 선택할 수도 있습니다. 카메라의 미리보기 바로 바깥쪽이 될 때까지 파란색 Z축을 따라 복사본을 아래로 이동합니다.
Shift 키를 누른 상태에서 선택하지 않은 실린더를 클릭하여 두 실린더를 모두 선택하고 복제합니다.

빨간색 X축을 드래그하여 오른쪽으로 이동합니다.

흰색은 좀 눈에 잘 띄지 않나요? 새로운 소재를 사용하여 눈에 띄게 만들어 봅시다.
3D에서 머티리얼은 색상, 빛의 반사 방식 등 표면의 시각적 속성을 정의합니다. 머티리얼을 사용하여 메시의 색상을 변경할 수 있습니다.
네 개의 실린더를 한 번에 모두 업데이트할 수 있습니다. 씬 독에서 모든 메시 인스턴스를 선택합니다. 이렇게 하려면 첫 번째 인스턴스를 클릭하고 마지막 인스턴스를 Shift 키로 클릭하면 됩니다.

인스펙터에서 머티리얼 섹션을 확장하고 슬롯 0에 StandardMaterial3D를 할당합니다.

구체 아이콘을 클릭하여 머티리얼 리소스를 엽니다. 머티리얼의 미리보기와 프로퍼티로 채워진 긴 섹션 목록이 표시됩니다. 이를 사용하여 금속부터 바위나 물까지 모든 종류의 표면을 만들 수 있습니다.
알베도 섹션을 펼칩니다.
밝은 주황색과 같이 배경과 대비되는 색상으로 설정합니다.

이제 실린더를 가이드로 사용할 수 있습니다. 옆에 회색 화살표를 클릭하여 씬 독에서 실린더를 접습니다. 앞으로는 Cylinders 옆에 눈 아이콘을 클릭하여 표시 여부를 전환할 수도 있습니다.

Main 노드에 하위 노드 Path3D를 추가합니다. 도구 모음에 네 개의 아이콘이 나타납니다. 초록색 "+" 기호가 있는 아이콘인 점 추가 툴을 클릭합니다.

참고
아이콘을 마우스로 가리키면 해당 툴에 대한 툴팁이 표시됩니다.
각 원통의 중앙을 클릭하여 점을 만듭니다. 그런 다음 툴바에서 곡선 닫기 아이콘을 클릭하여 경로를 닫습니다. 점이 약간 벗어난 경우 해당 점을 클릭하고 드래그하여 위치를 변경할 수 있습니다.

경로는 다음과 같아야 합니다.

그 위에 임의의 위치를 샘플링하려면 PathFollow3D 노드가 필요합니다. Path3D의 자식으로 PathFollow3D를 추가합니다.두 노드의 이름을 각각 SpawnLocation와 SpawnPath로 바꿉니다. 이렇게 하면 용도를 더 잘 설명할 수 있습니다.

이제 소환 메커니즘을 코딩할 준비가 되었습니다.
괴물을 무작위로 소환하기
Main 노드를 오른쪽 클릭하여 새 스크립트에 붙입니다.
먼저 변수를 인스펙터로 내보내서 mob.tscn 또는 다른 몬스터를 할당할 수 있도록 합니다.
extends Node
@export var mob_scene: PackedScene
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; }
}
일정한 시간 간격으로 몹을 소환하고 싶습니다. 이렇게 하려면 씬으로 돌아가 타이머를 추가해야 합니다. 하지만 그 전에 mob.tscn 파일을 위의 mob_scene 속성에 할당해야 합니다(그렇지 않으면 null입니다!)
3D 화면으로 돌아가서 Main 노드를 선택합니다. 파일시스템 독에서 인스펙터의 Mob Scene 슬롯으로 mob.tscn을 드래그합니다.

새 Timer 노드를 Main의 자식으로 추가합니다. MobTimer로 이름을 짓습니다.

인스펙터에서 대기 시간을 0.5초로 설정하고 게임을 실행할 때 자동으로 시작되도록 자동시작을 켭니다.

타이머는 대기 시간이 끝날 때마다 timeout 시그널을 방출합니다. 기본적으로 타이머는 자동으로 다시 시작되어 주기적으로 신호를 방출합니다. Main 노드에서 이 신호에 연결하여 0.5초마다 괴물을 소환할 수 있습니다.
MobTimer를 계속 선택한 상태에서 오른쪽의 노드 독으로 이동하여 timeout 시그널을 더블 클릭합니다.

Main 노드에 연결합니다.

그러면 스크립트로 돌아가서 새로운 빈 _on_mob_timer_timeout() 함수가 생깁니다.
몹 소환 로직을 코딩해 보겠습니다. 해보겠습니다:
몹 씬을 인스턴스화합니다.
소환 경로에서 무작위 위치를 샘플링합니다.
플레이어의 위치를 가져옵니다.
몹의 'initialize()' 메서드를 호출하여 무작위 위치와 플레이어의 위치를 전달합니다.
몹을 메인 노드의 자식으로 추가하세요.
func _on_mob_timer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instantiate()
# Choose a random location on the SpawnPath.
# We store the reference to the SpawnLocation node.
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
# And give it a random offset.
mob_spawn_location.progress_ratio = randf()
var player_position = $Player.position
mob.initialize(mob_spawn_location.position, player_position)
# Spawn the mob by adding it to the Main scene.
add_child(mob)
// We also specified this function name in PascalCase in the editor's connection window.
private void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Choose a random location on the SpawnPath.
// We store the reference to the SpawnLocation node.
var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
// And give it a random offset.
mobSpawnLocation.ProgressRatio = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Position;
mob.Initialize(mobSpawnLocation.Position, playerPosition);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
}
위의 randf() 는 0 과 1 사이의 임의의 값을 생성하며, 이는 PathFollow 노드의 progress_ratio 가 예상하는 값입니다: 0은 경로의 시작, 1은 경로의 끝입니다. 우리가 설정한 경로는 카메라의 뷰포트 주변이므로 0과 1 사이의 임의의 값은 뷰포트의 가장자리를 따라 임의의 위치가 됩니다!
메인 장면에서 Player 를 제거하면 다음 줄이 표시됩니다
var player_position = $Player.position
Vector3 playerPosition = GetNode<Player>("Player").Position;
$Player 가 없기 때문에 오류가 발생합니다!
참조용 전체 Sprite.gd 파일은 다음과 같습니다.
extends Node
@export var mob_scene: PackedScene
func _on_mob_timer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instantiate()
# Choose a random location on the SpawnPath.
# We store the reference to the SpawnLocation node.
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
# And give it a random offset.
mob_spawn_location.progress_ratio = randf()
var player_position = $Player.position
mob.initialize(mob_spawn_location.position, player_position)
# Spawn the mob by adding it to the Main scene.
add_child(mob)
using Godot;
public partial class Main : Node
{
[Export]
public PackedScene MobScene { get; set; }
private void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Choose a random location on the SpawnPath.
// We store the reference to the SpawnLocation node.
var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
// And give it a random offset.
mobSpawnLocation.ProgressRatio = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Position;
mob.Initialize(mobSpawnLocation.Position, playerPosition);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
}
}
F6을 눌러 장면을 테스트할 수 있습니다. 괴물이 소환되고 일직선으로 움직이는 것을 볼 수 있을 것입니다.

지금은 경로가 교차할 때 서로 부딪히고 미끄러집니다. 이 부분은 다음 편에서 다루겠습니다.