モブシーンをデザイン

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

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

まず、シーンを作成するために、根ノードとして KinematicBody ノードを使用します。このノードを Mob と名付けます。次に、その子ノードとして Spatial ノードを追加し、Pivot と名付けます。そして、ファイルシステムパネルから mob.glb ファイルを Pivot にドラッグ&ドロップすることで、モンスターの3Dモデルがシーンに追加されます。新しく作成した mob ノードを Character に名前変更することもできます。

image0

私たちの実体は、衝突形状を追加する必要があります。シーンのルートノード Mob を右クリックし、 サブノードを追加 をクリックしてください。

image1

*CollisionShape*の追加。

image2

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

image3

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

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

image4

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

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

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

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

注釈

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

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

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

image5

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

\ image6

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

Let's implement the monster's motion. We're going to do this in two steps. First, we'll write a script on the Mob that defines a function to initialize the monster. We'll then code the randomized spawn mechanism in the Main scene and call the function from there.

Attach a script to the Mob.

\ image7

Here's the movement code to start with. We define two properties, min_speed and max_speed, to define a random speed range. We then define and initialize the velocity.

extends KinematicBody

# 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

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)

Similarly to the player, we move the mob every frame by calling KinematicBody's move_and_slide() method. This time, we don't update the velocity every frame: we want the monster to move at a constant speed and leave the screen, even if it were to hit an obstacle.

You may see a warning in GDScript that the return value from move_and_slide() is unused. This is expected. You can simply ignore the warning or, if you want to hide it entirely, add the comment # warning-ignore:return_value_discarded just above the move_and_slide(velocity) line. To read more about the GDScript warning system, see GDScript警告システム.

We need to define another function to calculate the start velocity. This function will turn the monster towards the player and randomize both its angle of motion and its velocity.

The function will take a start_position, the mob's spawn position, and the player_position as its arguments.

We position the mob at start_position and turn it towards the player using the look_at_from_position() method, and randomize the angle by rotating a random amount around the Y axis. Below, rand_range() outputs a random value between -PI / 4 radians and PI / 4 radians.

# We will call this function from the Main scene.
func initialize(start_position, player_position):
    # We position the mob and turn it so that it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # And rotate it randomly so it doesn't move exactly toward the player.
    rotate_y(rand_range(-PI / 4, PI / 4))

We then calculate a random speed using rand_range() once again and we use it to calculate the velocity.

We start by creating a 3D vector pointing forward, multiply it by our random_speed, and finally rotate it using the Vector3 class's rotated() method.

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

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

Leaving the screen

We still have to destroy the mobs when they leave the screen. To do so, we'll connect our VisibilityNotifier node's screen_exited signal to the Mob.

Head back to the 3D viewport by clicking on the 3D label at the top of the editor. You can also press Ctrl + F2 (Alt + 2 on macOS).

\ image8

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

\ image9

シグナルの*Mob*への接続。

\ image10

This will take you back to the script editor and add a new function for you, _on_VisibilityNotifier_screen_exited(). From it, call the queue_free() method. This will destroy the mob instance when the VisibilityNotifier 's box leaves the screen.

func _on_VisibilityNotifier_screen_exited():
    queue_free()

Our monster is ready to enter the game! In the next part, you will spawn monsters in the game level.

Here is the complete Mob.gd script for reference.

extends KinematicBody

# 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

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)

func initialize(start_position, player_position):
    look_at_from_position(start_position, player_position, Vector3.UP)
    rotate_y(rand_range(-PI / 4, PI / 4))

    var random_speed = rand_range(min_speed, max_speed)
    velocity = Vector3.FORWARD * random_speed
    velocity = velocity.rotated(Vector3.UP, rotation.y)


func _on_VisibilityNotifier_screen_exited():
    queue_free()