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
- Mở lại scene
player.tscn. - Chọn node gốc
Player, thêm một node conArea3Dvà đổi tên thànhMobDetector.

- Chọn
MobDetector, thêm một node conCollisionShape3D. - 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:
- Chọn node
MobDetector. - Sang tab Node (bên cạnh Inspector), nhấp đúp vào tín hiệu
body_entered(body: Node3D). - Chọn node nhận là
Playervà 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:
- Mở lại scene
main.tscn. - Chọn node
Playerbên trong cây scene củaMain. - Sang tab Node ở bảng bên phải, nhấp đúp vào tín hiệu
hitcủa Player và kết nối vào nodeMain.

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