05. Xuất hiện quái vật (Spawning monsters)
Trong bài học này, chúng ta sẽ lập trình để quái vật xuất hiện liên tục từ các vị trí ngẫu nhiên dọc theo đường viền bao quanh màn hình. Kết thúc phần này, màn chơi của bạn sẽ tràn ngập những con quái vật bay lượn trên sân đấu.

Điều chỉnh độ phân giải màn hình
Trước khi vẽ đường sinh quái vật, chúng ta sẽ điều chỉnh độ phân giải cửa sổ game cho gọn gàng: 1. Vào menu Project -> Project Settings...

- Chọn tab General, tìm đến mục Display -> Window ở danh mục bên trái.
- Ở khung bên phải, chỉnh kích thước:
- Viewport Width:
720- Viewport Height:540 - Đóng hộp thoại cài đặt.

Tạo đường sinh quái vật (Spawn Path)
Giống như trong game 2D, chúng ta sẽ vẽ một đường bao quanh tầm nhìn của camera và sử dụng node PathFollow3D để lấy các tọa độ ngẫu nhiên dọc theo đường đó.
Tuy nhiên trong không gian 3D, khi vẽ đường ở góc nhìn trên cao ta sẽ khó nhìn thấy giới hạn khung hình của camera. Do đó, chúng ta sẽ dùng một số khối hình trụ (cylinders) tạm thời làm mốc định vị.
Thêm 4 cột mốc định vị (Placeholder Cylinders)
- Trong scene
main.tscn, chia màn hình thành 2 viewports (Ctrl + 2). Đảm bảo khung nhìn bên dưới đang chọnCamera3Dvà bật chế độ Preview.

- Chọn node
Main, thêm một node conNode3Dvà đổi tên thànhCylinders(dùng làm nhóm chứa).

- Chọn
Cylinders, thêm node conMeshInstance3D. - Trong bảng Inspector, tại thuộc tính Mesh, chọn New CylinderMesh.

- Đổi góc nhìn của khung hình trên thành góc nhìn từ trên đỉnh xuống (Top View, phím tắt số
7trên bàn phím số Numpad).

- Di chuyển hình trụ đến góc trên bên trái, sao cho nó nằm ngay sát ngoài tầm nhìn của camera (quan sát qua khung Preview ở dưới).

- Nhân bản hình trụ bằng phím
Ctrl + Dvà kéo xuống góc dưới bên trái. - Chọn cả 2 hình trụ (giữ phím
Shift), nhấnCtrl + Dđể nhân bản và kéo chúng sang 2 góc bên phải.

Đổi màu cho các cột mốc
Để 4 cột mốc dễ nhận biết hơn màu trắng mặc định:
1. Chọn cả 4 node MeshInstance3D trong cây scene (bấm chọn cái đầu rồi giữ Shift chọn cái cuối).

- Trong Inspector, mở mục Material -> tại ô số 0 chọn New StandardMaterial3D.

- Nhấp vào khối cầu chất liệu vừa tạo, mở mục Albedo và chọn một màu nổi bật (ví dụ: màu cam rực rỡ).


- Bạn có thể bấm thu gọn mũi tên của node
Cylinderstrong cây scene và ẩn biểu tượng con mắt đi khi không cần thiết.

Vẽ đường Path3D
- Chọn node
Main, thêm một node conPath3D. - Trên thanh công cụ 3D phía trên viewport, bạn sẽ thấy xuất hiện 4 biểu tượng của công cụ vẽ đường cong. Nhấn vào nút có dấu cộng màu xanh lá cây (Add Point).

- Lần lượt nhấp chuột vào tâm của 4 khối trụ cam để tạo 4 điểm góc.
- Nhấn vào biểu tượng Close Curve (Đóng đường cong) trên thanh công cụ để khép kín đường bao thành một hình chữ nhật bao quanh sân đấu.


- Thêm một node con
PathFollow3Dvào bên trongPath3D. - Đổi tên 2 node này cho rõ nghĩa:
- Đổi
Path3DthànhSpawnPath. - ĐổiPathFollow3DthànhSpawnLocation.

Cấu trúc cây node của Main lúc này:
Main
├── Ground
├── DirectionalLight3D
├── Player
├── CameraPivot
├── Cylinders
└── SpawnPath (Path3D)
└── SpawnLocation (PathFollow3D)
Lập trình sinh quái vật theo chu kỳ
1. Gắn Script vào Main
- Nhấp chuột phải vào node
Main, chọn Attach Script. - Chúng ta khai báo một biến export
mob_scenekiểuPackedSceneđể có thể kéo scenemob.tscnvào trong editor:
extends Node
@export var mob_scene: PackedScene
- Chuyển sang màn hình 3D, chọn node
Main. Trong bảng Inspector, kéo filemob.tscntừ bảng FileSystem vào ô Mob Scene.

2. Thêm bộ đếm thời gian (MobTimer)
- Chọn node
Main, nhấnCtrl + Athêm một nodeTimer. - Đổi tên thành
MobTimer.

- Trong bảng Inspector:
- Đặt Wait Time thành
0.5giây (cứ mỗi 0.5 giây quái vật sẽ xuất hiện 1 con). - Tích chọn Autostart để timer tự động chạy ngay khi bắt đầu game.

- Chuyển sang tab Node (bên cạnh Inspector), nhấp đúp vào tín hiệu
timeout()và kết nối vào nodeMain.


3. Logic sinh quái vật trong mã nguồn
Trong hàm _on_mob_timer_timeout(), chúng ta thực hiện 5 bước:
1. Tạo một bản sao mới của mob: var mob = mob_scene.instantiate().
2. Chọn một vị trí ngẫu nhiên dọc theo đường SpawnPath bằng thuộc tính progress_ratio = randf().
3. Lấy vị trí của người chơi $Player.position.
4. Gọi hàm mob.initialize(spawn_pos, player_pos).
5. Đưa quái vật vào cây scene bằng add_child(mob).
Toàn bộ mã nguồn hoàn chỉnh của Main
extends Node
@export var mob_scene: PackedScene
func _on_mob_timer_timeout():
# Tạo một instance mới của Mob scene.
var mob = mob_scene.instantiate()
# Chọn một vị trí ngẫu nhiên trên SpawnPath.
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
# Gán tỉ lệ ngẫu nhiên từ 0.0 đến 1.0 dọc theo đường cong
mob_spawn_location.progress_ratio = randf()
var player_position = $Player.position
mob.initialize(mob_spawn_location.position, player_position)
# Đưa mob vào scene Main
add_child(mob)
using Godot;
public partial class Main : Node
{
[Export]
public PackedScene MobScene { get; set; }
private void OnMobTimerTimeout()
{
// Tạo một instance mới của Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Chọn vị trí ngẫu nhiên trên SpawnPath.
var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
mobSpawnLocation.ProgressRatio = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Position;
mob.Initialize(mobSpawnLocation.Position, playerPosition);
// Đưa mob vào scene Main.
AddChild(mob);
}
}
Kiểm thử kết quả
Nhấn phím F6 để chạy main.tscn. Bạn sẽ thấy:
- Cứ mỗi nửa giây, một con quái vật sẽ xuất hiện từ ngoài rìa màn hình và bay thẳng ngang qua sân đấu hướng về phía người chơi.
- Khi bay hết qua bên kia màn hình, chúng sẽ tự động biến mất và giải phóng bộ nhớ.

Hiện tại nếu các quái vật bay va vào nhau, chúng sẽ trượt và đẩy nhau. Ở bài tiếp theo, chúng ta sẽ cấu hình các lớp vật lý (Physics Layers) và lập trình cơ chế nhảy lên đầu để đè bẹp chúng!