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.

モブシーンをデザイン

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

新しいシーンでモンスター自体を設計しましょう。ノード構造は 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 という、ノードが画面から離れるのを検知するノードがあるので、それを使ってモブを破壊することにします。

注釈

When you keep instantiating an object, there's a technique you can use to avoid the cost of creating and destroying instances all the time called pooling. It consists of pre-creating an array of objects and reusing them over and over.

When working with GDScript, this usually isn't needed. The main reason to use pools is to avoid freezes with garbage-collected languages like C# or Lua. GDScript uses a different technique to manage memory, reference counting, which doesn't have that caveat. You can learn more about that here: メモリ管理.

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.0
# Maximum speed of the mob in meters per second.
@export var max_speed = 18.0


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))

We got a random position, now we need a random_speed. randf_range() will be useful as it gives random float values, and we will use min_speed and max_speed. random_speed is just an float, and we just use it to multiply our CharacterBody3D.velocity. After random_speed is applied, we rotate CharacterBody3D.velocity Vector3 towards the player.

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

    # We calculate a random speed (float)
    var random_speed = randf_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 に接続します。

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.0
# Maximum speed of the mob in meters per second.
@export var max_speed = 18.0

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 (float)
    var random_speed = randf_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()