Godot 4: làm game top-down di chuyển 8 hướng, thu thập vật phẩm và quản lý điểm bằng signal

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Sau khi làm được nhân vật platformer, hãy thử một thể loại khác: game nhìn từ trên xuống, nơi nhân vật đi tự do bốn phía và nhặt vật phẩm. Bài này dạy bạn di chuyển 8 hướng với Godot 4 và dùng signal, cơ chế giao tiếp rất đặc trưng của Godot, để cập nhật điểm số.

Cấu trúc scene

Ta cần ba scene nhỏ rồi ghép lại, đúng tinh thần của Godot:

  • player.tscn: CharacterBody2D, Sprite2D, CollisionShape2D, Camera2D.
  • gem.tscn: Area2D, Sprite2D, CollisionShape2D (vật phẩm cần thu thập).
  • main.tscn: Node2D gốc, chứa Player, nhiều Gem và một Label hiển thị điểm bên trong CanvasLayer.

Trong game top-down không có trọng lực, vì vậy hãy vào Project Settings > Physics hoặc đơn giản là không áp trọng lực trong script. Nhớ đặt nhân vật vào nhóm (group) tên player bằng tab Node > Groups để các vật phẩm nhận ra.

Bước 1: Di chuyển 8 hướng

Dùng Input.get_vector để lấy vector hướng từ bốn hành động. Hàm này tự chuẩn hóa nên đi chéo không nhanh hơn đi thẳng.

extends CharacterBody2D

@export var speed: float = 220.0

func _physics_process(_delta):
	var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	velocity = direction * speed
	move_and_slide()

Từ khóa @export hiển thị biến trong Inspector để bạn chỉnh tốc độ mà không sửa code. Dấu gạch dưới trước _delta báo rằng tham số này chưa dùng đến, tránh cảnh báo.

Bước 2: Vật phẩm với Area2D

Area2D phát hiện vật thể chồng lên nó mà không chặn đường. Đây là lựa chọn hợp lý cho đồng xu, gem, vùng kích hoạt. Gắn script cho gốc của gem.tscn:

extends Area2D

signal collected(value: int)

@export var value: int = 1

func _ready():
	body_entered.connect(_on_body_entered)

func _on_body_entered(body):
	if body.is_in_group("player"):
		collected.emit(value)
		queue_free()

Ở đây xuất hiện khái niệm signal. Gem không cần biết ai đang quản lý điểm, nó chỉ "phát tín hiệu" rằng mình đã bị nhặt kèm giá trị. Ai quan tâm thì lắng nghe. Cách này giữ cho các scene độc lập, dễ tái sử dụng, đúng nguyên tắc "gọi xuống, báo hiệu lên" của Godot. Hàm queue_free() xóa node an toàn vào cuối khung hình.

Bước 3: Quản lý điểm trong scene chính

Gắn script cho node gốc của main.tscn. Ta duyệt các Gem trong một group và kết nối signal của chúng:

extends Node2D

@onready var score_label: Label = $CanvasLayer/ScoreLabel

var score: int = 0

func _ready():
	for gem in get_tree().get_nodes_in_group("gems"):
		gem.collected.connect(_on_gem_collected)
	_update_label()

func _on_gem_collected(value: int):
	score += value
	_update_label()

func _update_label():
	score_label.text = "Điểm: %d" % score

Để cách trên hoạt động, hãy thêm node gem vào group gems (đặt group ngay trong scene gem.tscn để mọi bản sao đều có). Từ khóa @onready gán biến sau khi cây node đã sẵn sàng, tránh lỗi tham chiếu rỗng. Đường dẫn $CanvasLayer/ScoreLabel phải đúng với tên node thực tế của bạn.

Bước 4: Rải vật phẩm

Bạn có thể kéo nhiều bản gem.tscn vào scene chính bằng tay. Nếu muốn tạo ngẫu nhiên bằng code, hãy thêm đoạn sau vào _ready của scene chính, trước vòng lặp kết nối signal:

@export var gem_scene: PackedScene

func _spawn_gems(count: int):
	for i in count:
		var gem = gem_scene.instantiate()
		gem.position = Vector2(randf_range(50, 1100), randf_range(50, 600))
		add_child(gem)

Kéo gem.tscn vào ô Gem Scene trong Inspector, và nhớ gọi _spawn_gems(10) trước khi duyệt group để các gem mới sinh cũng được kết nối.

Lỗi thường gặp

  • Không nhặt được: kiểm tra nhân vật nằm trong group player, và collision layer/mask của hai bên có tương tác với nhau.
  • Báo lỗi truy cập node null: sai đường dẫn $... hoặc dùng node trước khi cây sẵn sàng.
  • Nhân vật xuyên qua tường: tường cần StaticBody2D có CollisionShape2D, hoặc dùng TileMap có thiết lập vật lý.
  • Signal không chạy: gem sinh ra bằng code nhưng chưa kết nối signal.

Kết luận

Với CharacterBody2D, Area2D và signal, bạn nắm được bộ ba công cụ cốt lõi của Godot 2D. Hãy thử thêm kẻ địch, đồng hồ đếm ngược và màn hình thắng thua để biến nó thành game hoàn chỉnh.

Nếu muốn học làm game bài bản trên Godot hoặc Unity, hãy khám phá các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment