07. Xử lý khi người chơi bị hạ gục (Killing the player)


Hiện tại chúng ta đã có thể tiêu diệt quái vật bằng cách nhảy dẫm lên đầu chúng, nhưng người chơi thì vẫn bất tử khi bị quái húc trúng trên mặt đất. Trong bài này, chúng ta sẽ lập trình để người chơi bị tiêu diệt và dừng trò chơi.

Chúng ta muốn người chơi bị thua khi đang di chuyển trên mặt đất và bị quái đâm trúng, nhưng không bị tính là thua khi đang nhảy trên không. Thay vì dùng công thức toán học vector phức tạp, chúng ta sẽ sử dụng node Area3D để tạo một vùng nhận sát thương (Hitbox).


Tạo Hitbox với Area3D

  1. Mở lại scene player.tscn.
  2. Chọn node gốc Player, thêm một node con Area3D và đổi tên thành MobDetector.

  1. Chọn MobDetector, thêm một node con CollisionShape3D.
  2. Trong bảng Inspector, tại thuộc tính Shape, chọn New CylinderShape3D (Hình trụ va chạm).

Mẹo tinh chỉnh Hitbox:

  • Chiều cao: Giảm chiều cao của hình trụ xuống và dịch chuyển nó lên nửa trên của nhân vật. Khi người chơi nhảy lên cao, hình trụ này sẽ ở trên cao vượt qua tầm với của quái vật, giúp tránh bị chết oan trong lúc đang nhảy.
  • Độ rộng (Bán kính): Chỉnh hình trụ rộng hơn hình cầu va chạm của Player một chút. Nhờ vậy, người chơi sẽ nhận sát thương ngay khi quái vật áp sát thay vì bị đẩy trượt lên đỉnh hộp quái vật.


Cấu hình thuộc tính cho MobDetector

Chọn node MobDetector: 1. Trong bảng Inspector: - Tắt ô Monitorable (để các đối tượng khác không thể dò tìm thấy vùng này). - Đảm bảo ô Monitoring được bật (để vùng này chủ động phát hiện kẻ địch đi vào). 2. Mở rộng mục Collision: - Layer: Tắt hết (để trống). - Mask: Chỉ chọn layer 2 (enemies).

Nhờ chỉ bật mask enemies, MobDetector sẽ chỉ lắng nghe sự kiện khi có quái vật (Mob) đi vào vùng của nó.


Lập trình xử lý khi bị đánh trúng

Khi một thân vật lý đi vào vùng kiểm tra, MobDetector sẽ phát ra tín hiệu body_entered:

  1. Chọn node MobDetector.
  2. Sang tab Node (bên cạnh Inspector), nhấp đúp vào tín hiệu body_entered(body: Node3D).
  3. Chọn node nhận là Player và nhấn Connect.

Trong script player.gd, chúng ta thực hiện: - Khai báo một tín hiệu mới hit ở đầu file. - Viết hàm die() phát ra tín hiệu hit rồi tự xóa nhân vật bằng queue_free(). - Gọi die() trong hàm xử lý sự kiện _on_mob_detector_body_entered.

# Phát ra khi người chơi bị quái vật húc trúng
signal hit

func die():
    hit.emit()
    queue_free()

func _on_mob_detector_body_entered(_body):
    die()

Kết thúc trò chơi trong Main scene

Khi người chơi hy sinh, chúng ta cần dừng việc sinh quái vật:

  1. Mở lại scene main.tscn.
  2. Chọn node Player bên trong cây scene của Main.
  3. Sang tab Node ở bảng bên phải, nhấp đúp vào tín hiệu hit của Player và kết nối vào node Main.

  1. Trong script main.gd, dừng đồng hồ MobTimer:
func _on_player_hit():
    $MobTimer.stop()

Khi $MobTimer dừng lại, hàm _on_mob_timer_timeout() sẽ không được gọi nữa, ngăn chặn việc sinh thêm quái vật và loại bỏ hoàn toàn nguy cơ phát sinh lỗi gọi đến $Player.position khi node Player đã bị xóa khỏi cây scene.


Toàn bộ mã nguồn cập nhật

Player (player.gd)

extends CharacterBody3D

signal hit

@export var speed = 14
@export var fall_acceleration = 75
@export var jump_impulse = 20
@export var bounce_impulse = 16

var target_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.basis = Basis.looking_at(direction)

    # Vận tốc mặt đất
    target_velocity.x = direction.x * speed
    target_velocity.z = direction.z * speed

    # Trọng lực
    if not is_on_floor():
        target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Xử lý Nhảy
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = jump_impulse

    # Xử lý đè bẹp quái vật
    for index in range(get_slide_collision_count()):
        var collision = get_slide_collision(index)

        if collision.get_collider() == null:
            continue

        if collision.get_collider().is_in_group("mob"):
            var mob = collision.get_collider()
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                mob.squash()
                target_velocity.y = bounce_impulse
                break

    velocity = target_velocity
    move_and_slide()


func die():
    hit.emit()
    queue_free()


func _on_mob_detector_body_entered(_body):
    die()

Main (main.gd)

extends Node

@export var mob_scene: PackedScene


func _on_mob_timer_timeout():
    var mob = mob_scene.instantiate()

    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
    mob_spawn_location.progress_ratio = randf()

    var player_position = $Player.position
    mob.initialize(mob_spawn_location.position, player_position)

    add_child(mob)


func _on_player_hit():
    $MobTimer.stop()

Kiểm thử

Nhấn F5 để chơi thử: - Bạn vẫn có thể nhảy lên đạp bẹp quái vật. - Nhưng nếu bạn đứng yên hoặc chạy va phải quái vật trên mặt đất, nhân vật Player sẽ lập tức biến mất và quái vật mới sẽ ngừng sinh ra!