Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

Conception de la scène du mob

Dans cette partie, vous allez coder les monstres, que nous appellerons mobs. Dans la prochaine leçon, nous les ferons apparaître aléatoirement autour de la zone de jeu.

Let's design the monsters themselves in a new scene. The node structure is going to be similar to the player.tscn scene.

Create a scene with, once again, a CharacterBody3D node as its root. Name it Mob. Add a child node Node3D, name it Pivot. And drag and drop the file mob.glb from the FileSystem dock onto the Pivot to add the monster's 3D model to the scene.

../../_images/drag_drop_mob.webp

You can rename the newly created mob node into Character.

image0

We need a collision shape for our body to work. Right-click on the Mob node, the scene's root, and click Add Child Node.

image1

Add a CollisionShape3D.

image2

In the Inspector, assign a BoxShape3D to the Shape property.

../../_images/08.create_box_shape3D.jpg

Nous devons changer sa taille pour mieux s'adapter au modèle 3D. Vous pouvez le faire interactivement en faisant glisser les points orange.

La boîte devrait toucher le sol et être un peu plus fine que le modèle. Les moteurs physiques fonctionnent de telle sorte que si la sphère du joueur touche ne serait-ce que le coin de la boîte, une collision se produira. Si la boîte est un petit peu trop large par rapport au modèle 3D, vous risquez de mourir à une certaine distance du monstre, et le jeu semblera injuste pour les joueurs.

image4

Remarquez que ma boîte est plus grande que le monstre. Ce n'est pas grave dans ce jeu car nous regardons la scène d'en haut et utilisons une perspective fixe. Les formes de collision n'ont pas besoin de correspondre exactement au modèle. C'est votre ressenti quand vous testez le jeu qui doit dicter leur forme et leur taille.

Suppression des monstres hors écran

We're going to spawn monsters at regular time intervals in the game level. If we're not careful, their count could increase to infinity, and we don't want that. Each mob instance has both a memory and a processing cost, and we don't want to pay for it when the mob is outside the screen.

Once a monster leaves the screen, we don't need it anymore, so we should delete it. Godot has a node that detects when objects leave the screen, VisibleOnScreenNotifier3D, and we're going to use it to destroy our mobs.

Note

When you keep instancing 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, you don't need to worry about this. 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: Gestion de la mémoire.

Select the Mob node and add a child node VisibleOnScreenNotifier3D. Another box, pink this time, appears. When this box completely leaves the screen, the node will emit a signal.

image5

Redimensionnez-la en utilisant les points orange jusqu'à ce qu'elle couvre l'ensemble du modèle 3D.

image6

Coder le mouvement du mob

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.tscn 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, which we will later use to define 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()

Similarly to the player, we move the mob every frame by calling the function CharacterBody3D.move_and_slide(). 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.

We need to define another function to calculate the CharacterBody3D.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, randf_range() outputs a random value between -PI / 4 radians and PI / 4 radians.

# 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. randi_range() will be useful as it gives random int values, and we will use min_speed and max_speed. random_speed is just an integer, 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 (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)

Quitter l'écran

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

Revenez à la fenêtre d'affichage 3D en cliquant sur le bouton 3D en haut de l'éditeur. Vous pouvez également appuyer sur Ctrl + F2 (Alt + 2 sur macOS).

image8

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

image9

Connect the signal to the Mob

image10

This will take you back to the script editor and add a new function for you, _on_visible_on_screen_notifier_3d_screen_exited(). From it, call the queue_free() method. This function destroy the instance it's called on.

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()

Notre monstre est prêt à entrer dans le jeu ! Dans la partie suivante, nous ferons apparaître des monstres dans le niveau de jeu.

Voici le script complet Mob.gd pour référence.

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 -90 and +90 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()