Den Spieler töten

We can kill enemies by jumping on them, but the player still can't die. Let's fix this.

We want to detect being hit by an enemy differently from squashing them. We want the player to die when they're moving on the floor, but not if they're in the air. We could use vector math to distinguish the two kinds of collisions. Instead, though, we will use an Area node, which works well for hitboxes.

Hitbox with the Area node

Head back to the Player scene and add a new Area node. Name it MobDetector. Add a CollisionShape node as a child of it.

image0

In the Inspector, assign a cylinder shape to it.

image1

Here is a trick you can use to make the collisions only happen when the player is on the ground or close to it. You can reduce the cylinder's height and move it up to the top of the character. This way, when the player jumps, the shape will be too high up for the enemies to collide with it.

image2

You also want the cylinder to be wider than the sphere. This way, the player gets hit before colliding and being pushed on top of the monster's collision box.

The wider the cylinder, the more easily the player will get killed.

Next, select the MobDetector node again, and in the Inspector, turn off its Monitorable property. This makes it so other physics nodes cannot detect the area. The complementary Monitoring property allows it to detect collisions. Then, remove the Collision -> Layer and set the mask to the "enemies" layer.

image3

When areas detect a collision, they emit signals. We're going to connect one to the Player node. In the Node tab, double-click the body_entered signal and connect it to the Player.

image4

The MobDetector will emit body_entered when a KinematicBody or a RigidBody node enters it. As it only masks the "enemies" physics layers, it will only detect the Mob nodes.

Code-mäßig werden wir zwei Dinge tun: Ein Signal senden welches wir später verwenden werden um das Spiel zu beenden und den Spieler zu zerstören. Wir können diese Vorgänge in eine die() Funktion verpacken um dem Code eine aussagekräftige Beschriftung zu geben.

# Emitted when the player was hit by a mob.
# Put this at the top of the script.
signal hit


# And this function at the bottom.
func die():
    emit_signal("hit")
    queue_free()


func _on_MobDetector_body_entered(_body):
    die()

Testen sie das Spiel erneut indem sie F5 Drücken. Wenn alles richtig eingereichtet ist, sollte der Charakter sterben sobald ein Gegner gegen ihn läuft.

Beachten Sie jedoch, dass dies vollständig von der Größe und Position der Kollisionsformen des Players und des Mobs abhängt. Möglicherweise müssen Sie sie etwas verschieben oder ihre größe verändern, um ein gutes Spielgefühl zu erreichen.

Das Spiel beenden

Wir können das Signal hit des Players verwenden um das Spiel zu beenden. Alles was wir dazu tun müssen, ist das Signal mit der Main Node zu verbinden und als Reaktion den MobTimer auszuschalten.

Öffnen Sie Main.tscn, wählen sie das Player Node und verbinden Sie in dem Node Panel das hit Signal mit der Main Node.

image5

Rufen Sie den Timer in der _on_Player_hit() Funktion ab und Stoppen Sie ihn.

func _on_Player_hit():
    $MobTimer.stop()

Wenn Sie das Spiel jetzt testen, hören die Monster auf zu spawnen sobald Sie sterben und die verbleibenden Monster werden den Bildschirmbereich verlassen.

Sie können sich selbst auf die Schulter klopfen: Sie haben einen Vollständigen Prototypen für ein 3D Spiel erstellt, auch wenn er noch etwas unfertig ist.

Als nächstest fügen wir einen Punkestand, die Option das Spiel zu wiederholen und Sie werden sehen wie Sie mit ein paar minimalen Animationen das Spiel lebendiger gestalten können.

Code-Checkpoint

Hier sind die vollständigen Skripte der Main, Mob und Spieler Nodes als Referenz. Sie können sie verwenden um Ihren Code zu vergleichen und zu überprüfen.

Beginnend mit Main.gd.

extends Node

export(PackedScene) var mob_scene


func _ready():
    randomize()


func _on_MobTimer_timeout():
    # Create a new instance of the Mob scene.
    var mob = mob_scene.instance()

    # Choose a random location on the SpawnPath.
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
    # And give it a random offset.
    mob_spawn_location.unit_offset = randf()

    # Communicate the spawn location and the player's location to the mob.
    var player_position = $Player.transform.origin
    mob.initialize(mob_spawn_location.translation, player_position)

    # Spawn the mob by adding it to the Main scene.
    add_child(mob)


func _on_Player_hit():
    $MobTimer.stop()

Als nächstes Mob.gd.

extends KinematicBody

# Emitted when the player jumped on the mob.
signal squashed

# 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 squash():
    emit_signal("squashed")
    queue_free()


func _on_VisibilityNotifier_screen_exited():
    queue_free()

Schließlich das längste Skript, Player.gd.

extends KinematicBody

# Emitted when a mob hit the player.
signal hit

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 20
# Vertical impulse applied to the character upon bouncing over a mob in meters per second.
export var bounce_impulse = 16

var velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)

    velocity.x = direction.x * speed
    velocity.z = direction.z * speed

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        velocity.y += jump_impulse

    velocity.y -= fall_acceleration * delta
    velocity = move_and_slide(velocity, Vector3.UP)

    for index in range(get_slide_count()):
        var collision = get_slide_collision(index)
        if collision.collider.is_in_group("mob"):
            var mob = collision.collider
            if Vector3.UP.dot(collision.normal) > 0.1:
                mob.squash()
                velocity.y = bounce_impulse


func die():
    emit_signal("hit")
    queue_free()


func _on_MobDetector_body_entered(_body):
    die()

Wir sehen uns in der nächsten Lektion, um den Punktestand und die Option das Spiel zu wiederholen hinzuzufügen.