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 のコンパクトなサイズに設定します。
プロジェクト -> プロジェクト設定...(Project -> Project Settings...)に移動します。

まだ インプットマップ が開いているなら、 一般 タブを開きます。
In the left menu, navigate down to Display -> Window. On the right, set the
Viewport Width to 720 and the Viewport Height to 540.

出現パスの作成
2Dゲームのチュートリアルで行ったように、パスを設計し、その上のランダムな位置をサンプリングするために PathFollow3D ノードを使用します。
しかし、3D の場合、パスを描くのは少し複雑です。モンスターが画面のすぐ外に現れるように、ゲームビューの周囲にパスを描きたいのです。しかし、パスを描くと、カメラのプレビューではそれが見えません。
ビューの制限を確認するために、プレースホルダーメッシュを使用します。ビューポートは引き続き2つの部分に分割されており、下部にカメラプレビューが表示されているはずです。もし表示されていない場合は、 Ctrl + 2 (Cmd + 2 on macOS) を押してビューを2分割してください。次に Camera3D ノードを選択し、下部ビューポートにある Preview チェックボックスをクリックします。

プレースホルダー用のシリンダーを追加する
プレースホルダメッシュを追加しましょう。新しい Node3D を Main ノードの子として追加し Cylinders と名付けます。これをシリンダーのグループとして使います。 Cylinders を選択し、 MeshInstance3D ノードを追加します

インスペクター 内で、 Mesh プロパティに CylinderMesh を割り当てます。

一番上のビューポートを、ビューポートの左上にあるメニューを使って上からの平行投影ビューに設定します。または、キーパッドの7キーを押すこともできます。

グリッドが気になる場合は、非表示にできます。ツールバーの View メニューに移動し、View Grid をクリックして切り替えてください。

次に、下部ビューポートのカメラプレビューを見ながら、円柱を地面の平面上で移動させます。その際、グリッドスナップを使用することをお勧めします。グリッドスナップは、ツールバーの磁石アイコンをクリックするか、Y キーを押して切り替えることができます。

円柱をカメラの視界の左上隅、すぐ外側に移動させてください。

メッシュのコピーを作成し、ゲームエリアの周囲に配置していきます。ノードを複製するには、Ctrl + D`(macOSでは :kbd:`Ctrl + D)を押します。または、Scene *ドック内でノードを右クリックし、*Duplicate を選択することもできます。コピーを青い Z 軸に沿って下に移動させ、カメラプレビューのすぐ外側に配置してください。
Shift キーを押しながら未選択の円柱をクリックして、両方の円柱を選択し、複製します。

赤いX軸をドラッグして右へ移動します。

白ではちょっと見にくいですよね? 新しいマテリアルを与えて、目立たせてあげましょう。
3D では、マテリアルがサーフェスの色や光の反射などの視覚的プロパティを定義します。これを利用して、メッシュの色を変更することができます。
4 つのシリンダーをすべて一度に更新することができます。シーン(Scene)のドック内のメッシュ インスタンスをすべて選択します。これを行うには、最初の 1 つをクリックし、最後の 1 つを Shift キーを押しながらクリックします。

インスペクタ で、 マテリアル セクションを開いて StandardMaterial3D を 0 に割り当てます。

球体アイコンをクリックしてマテリアルリソースを開きます。マテリアルのプレビューと、プロパティが詰まったセクションの長いリストが表示されます。これらを使って、金属や岩、さらには水など、さまざまな表面を作成できます。
Albedo セクションを開きましょう。
明るいオレンジ色のような、背景と対照的な色に設定してください。

これで、シリンダーをガイドとして使用できるようになりました。シーン(Scene)のドックで、Cylindersの横にあるグレーの矢印をクリックして、シリンダーを折りたたみます。また、Cylindersの横にある目のアイコンをクリックすると、その可視性を切り替えることができます。

Main ノードの子ノード Path3D を追加します。ツールバーに4つのアイコンが出現します。緑色の "+" アイコンの Add Point ツールをクリックしましょう。

注釈
アイコンにカーソルを合わせると、そのツールの説明をツールチップで見ることができます。
各シリンダーの中心をクリックして、ポイントを作成します。次に、ツールバーの曲線を閉じる(Close Curve) アイコンをクリックし、パスを閉じます。点が少しずれている場合は、その点をクリックしてドラッグすることで位置を変更できます。

パスはこのようになります。

ここからランダムな場所を得るため、 PathFollow3D ノードが必要です。 PathFollow3D を Path3D の子として追加します。2つのノードにそれぞれ 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.tscn を ファイルシステム から インスペクタ の Mob Scene スロットにドラッグしましょう。

新しいTimerノードを Main の子として追加します。 MobTimer と名付けましょう。

インスペクター(Inspector)でWait Timeを0.5秒に設定し、ゲーム実行時に自動的に起動するように Autostartをオンにします。

タイマーは、timeoutシグナルを、Wait Timeが終了する度に出します。デフォルトでは、自動的にリスタートし、1周期でシグナルを発信します。Mainノードからこのシグナルに接続することで、0.5秒ごとにモンスターを出現させることができます。
With the MobTimer still selected, head to the Signals dock on the right, and
double-click the timeout signal.

Main ノードに接続します。

これでスクリプトに戻り、新しく空の _on_mob_timer_timeout() 関数が追加されます。
モブの出現ロジックをコーディングしてみましょう。次のように行います:
モブシーンをインスタンス化します。
出現パスのランダムな位置をサンプリングします。
プレイヤーの位置を取得します。
モブの
initialize()メソッドを呼び、ランダムな位置とプレイヤーの位置を渡します。Mainノードの子としてモブを追加します。
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 が無いのでエラーが発生します!
参考までに、main.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);
}
}
You can test the scene by pressing F6 (Cmd + R on macOS). You should see the monsters spawn and move in a straight line.

今のところ、モブは互いにぶつかり合い、すれ違うだけです。この点については、次回以降で触れていきます。