モブシーンをデザイン

このパートでは、モンスター(ここではモブと呼びます)のコードを作成します。次のレッスンでは、モンスターをプレイエリアのあちこちにランダムに配置します。

新しいシーンでモンスター自体を設計しましょう。ノード構造は player.tscn シーンに似ています。

もう一度 CharacterBody3D ノードをルートとしてシーンを作ります。このノードを Mob と名付けます。次に、その子ノードとして Node3D ノードを追加し、 Pivot と名付けます。そして、 ファイルシステム ドックから mob.glb ファイルを Pivot にドラッグ&ドロップすることで、モンスターの3Dモデルがシーンに追加されます。

../../_images/drag_drop_mob.webp

新たに作成された mob ノードの名前を Character に変更できます。

image0

体が機能するために衝突形状を追加する必要があります。シーンのルートノード Mob を右クリックし、 子ノードを追加 をクリックしてください。

image1

CollisionShape3Dを追加します。

image2

Inspector で、 Shape プロパティに BoxShape3D を割り当てます。

../../_images/08.create_box_shape3D.webp

3 Dモデルによりよく適応するために、そのサイズを変更しなければなりません。オレンジ色の点をクリックしてドラッグすることで、インタラクティブに操作できます。

衝突ボックスは地面に接触していて、モデルより少し小さくする必要があります。物理エンジンは、プレイヤーの衝突球体がこの衝突ボックスの端に触れただけでも、衝突したと判定します。もし衝突ボックスが 3D モデルより少し大きかった場合、モンスターからまだ距離があるのに死んでしまい、プレイヤーは不公平だと感じるでしょう。

image4

ボックスがモンスターより高さがあることに注意してください。このゲームでは、シーンを上から見て、固定されたパースペクティブを使っているので、大丈夫です。コリジョン形状は、モデルと完全に一致する必要はありません。ゲームをテストしたときの感触で、形や大きさを決めるといいでしょう。

古い「モンスター」を削除する

私たちはこのゲームレベルで一定時間ごとにモンスターを生成します。もし私たちがうっかりしていたら、それらの数は無限大に増えるかもしれませんが、私たちはそれを望んでいません。モブの各インスタンスにはメモリと処理コストがあり、モブが画面の外にいるときは、私たちはそのためにコストを払いたくありません。

モンスターが画面から離れたら、もう必要ないので、削除してしまいましょう。Godotには、 VisibleOnScreenNotifier3D という、ノードが画面から離れるのを検知するノードがあるので、それを使ってモブを破壊することにします。

注釈

オブジェクトのインスタンスを生成し続ける場合に、インスタンスを常に生成・破棄するコストを回避するための手法にプーリングというものがあります。これは、オブジェクトの配列をあらかじめ作成しておき、それを何度も再利用するというものです。

GDScript で作業する場合、このことを気にする必要はありません。プールを使用する主な理由は、C# や Lua のようなガベージコレクション言語でのフリーズを回避するためです。GDScriptでは、参照カウントという別の手法でメモリを管理しており、この手法はフリーズに注意する必要はありません。メモリ管理の詳細については メモリ管理 で学ぶことができます。

Mob ノードを選択し、VisibleOnScreenNotifier3Dをその子として追加してください。すると、今度はピンク色のボックスが現れます。このボックスが完全に画面から消えると、ノードがシグナルを発信します。

image5

3D モデル全体を覆うように、オレンジのドットでサイズを変更します。

\ image6

モブの動きをコード化する

それでは、モンスターの動きを組み込んでみましょう。2ステップに分けて行います。まず、モンスターを初期化する関数を含むスクリプトを、 Mob ノードに作成します。次に、ランダム生成メカニズムを main.tscn シーン内に作成し、モンスターを初期化する関数を実行します。

Mob ノードにスクリプトを追加します。

\ image7

これが最初の動きのコードです。 min_speedmax_speed の2つのプロパティを定義し、ランダムな速度範囲を定義します。これは後で CharacterBody3D.velocity で使います。

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10
# Maximum speed of the mob in meters per second.
@export var max_speed = 18


func _physics_process(_delta):
    move_and_slide()

プレイヤーと同様に、 CharacterBody3D.move_and_slide() 関数を呼び出してモブを毎フレーム動かします。このとき、 velocity は毎フレーム更新しません。たとえ障害物に当たったとしても、モンスターが画面外に出るまで等速で動かすのです。

他に CharacterBody3D.velocity を計算する関数を定義する必要があります。この関数はモンスターをプレイヤーの方へ向かせ動きの角度と速度をランダムにします。

この関数は引数に start_position (モブの出現位置)と player_position (プレイヤーの位置)をとります。

モブを start_position に置いて look_at_from_position() を使いプレイヤーの方に向かせ、Y軸を中心にランダムなだけ回転させて角度をランダムにします。以下の randf_range()-PI / 4 から PI / 4 までのランダムな角度を出力します。

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -45 and +45 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

ランダムな場所を得て、次は random_speed が必要です。 randi_range() がランダムな整数範囲を得るのに有用で、これに min_speedmax_speed を使います。 random_speed はただの整数で、 CharacterBody3D.velocity に掛けるだけです。 random_speed が適用された後、 CharacterBody3D.velocity Vector3をプレイヤーの方へ回転させます。

func initialize(start_position, player_position):
    # ...

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

画面から離れる

画面を離れた時にモブを破壊する必要があります。 そのためには、 VisibleOnScreenNotifier3D ノードの `screen_exited シグナルを Mob に接続します。

エディタ上部の3Dラベルをクリックして、3D ビューポートに戻ります。またCtrl + F2(macOSではOpt + 2)を押すこともできます。

\ image8

Select the VisibleOnScreenNotifier3D node and on the right side of the interface, navigate to the Signals dock. Double-click the screen_exited() signal.

\ image9

シグナルをMobへ接続します

\ image10

これでモブスクリプトに _on_visible_on_screen_notifier_3d_screen_exited() という新しい関数が追加されます。ここから queue_free() メソッドを呼びます。この関数はこれが呼ばれたインスタンスを破壊します。

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

私たちのモンスターがゲームに参加する準備ができました。次のパートでは、ゲームレベルにモンスターを出現させます。

参考までに、mob.gdスクリプトの完成版を以下に示します。

extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10
# Maximum speed of the mob in meters per second.
@export var max_speed = 18

func _physics_process(_delta):
    move_and_slide()

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -45 and +45 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()