Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
測試場景
現在是時候將我們所做的一切整合到一個可玩的遊戲場景中了。
創建新場景,然後加入一個 Node 節點,並命名為``Main``。(我們之所以使用 Node 而不是 Node2D ,是因為這個節點是處理遊戲邏輯的容器,本身並不需 2D 的功能。)
點擊 產生實體 按鈕(由鏈條圖示表示)並選擇保存的 player.tscn 。
現在,將下列節點新增為 Main 的子節點,並依照所示命名:
Timer (名稱為
MobTimer)——用以控制怪物產生的頻率Timer (名稱為
ScoreTimer)——用以每秒增加成績Timer (名稱為
StartTimer)——用以在開始前延遲Marker2D <class_Marker2D>`(命名為 ``StartPosition`)—用來標示玩家的起始位置
將每個 Timer 節點的 等待時間 屬性設定如下(數值單位為秒):
MobTimer:0.5ScoreTimer:1StartTimer:2
另外,將 StartTimer 的 One Shot 屬性設為「開啟」,並將 StartPosition 節點的 Position 設為 (240, 450) 。
產生怪物
Main 節點將會產生新的怪物,而我們希望它們在螢幕邊緣的隨機位置出現。於 Scene 面板點選 Main 節點,新增一個名為 MobPath 的子節點 Path2D。選取 Path2D 後,你會在編輯器上方看到一些新的按鈕:
選擇中間的按鈕 (「新增控制點」),然後點擊顯示的轉角處來新增控制點並繪製路徑。若要將控制點吸附到網格上,請開啟「使用網格吸附」與「使用吸附」選項,這兩個選項在「鎖定」按鈕的左邊,顯示的圖示是一個磁鐵跟幾條相交的線。
重要
請確定以 順時針 方向繪製路徑,不然的話怪物會 向外 產生而不是 向內 產生!
在圖片內放置了點 4 以後,點擊「關閉曲線」按鈕,就可以完成繪製曲線。
現在路徑已經定義好了,接著我們新增一個 PathFollow2D 節點作為 MobPath 的子節點,並將其命名為 MobSpawnLocation 。這個節點會在移動的時候自動旋轉並跟著路徑移動,所以我們可以用 MobPath 來在路徑上隨機選擇位置與方向。
現在場景看起來會這樣:
Main(主要)腳本
為 Main 新增一個腳本。在腳本的頂部,我們會用 export (PackedScene) 來選擇要實體化的 Mob 場景。
extends Node
@export var mob_scene: PackedScene
var score
using Godot;
public partial class Main : Node
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
[Export]
public PackedScene MobScene { get; set; }
private int _score;
}
點擊 Main 節點,您會在 Inspector(屬性檢視器)中「Main.gd」下方看到 Mob Scene 屬性。
要為這個屬性賦值有兩個方法:
從「檔案系統」面板中拖移
Mob.tscn到Mob屬性來。點擊「[空]」旁邊的下拉箭頭,並選擇「載入」。接著選擇
Mob.tscn。
Next, select the instance of the Player scene under Main node in the Scene dock,
and access the Signals dock on the sidebar.
接著應該可以看到所有 Player 節點的訊號。點兩下列表中的 hit 訊號 (或是右鍵點擊然後選擇 [連接...])。這樣便可以打開訊號連接對話框。這裡我們要把新函式命名為 game_over ,這個函式會負責處理遊戲結束時要做的事。在 [連接訊號] 視窗中的 [Receiver 方法] 內輸入「game_over」,並點擊 [連接]。將下列程式碼加到新建立的函式當中,接著新增一個 new_game 函式,負責在遊戲開始時搞定一切:
func game_over():
$ScoreTimer.stop()
$MobTimer.stop()
func new_game():
score = 0
$Player.start($StartPosition.position)
$StartTimer.start()
public void GameOver()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Timer>("ScoreTimer").Stop();
}
public void NewGame()
{
_score = 0;
var player = GetNode<Player>("Player");
var startPosition = GetNode<Marker2D>("StartPosition");
player.Start(startPosition.Position);
GetNode<Timer>("StartTimer").Start();
}
接著我們要把每個 Timer 節點(StartTimer、ScoreTimer、MobTimer)的 timeout() 訊號連到主腳本。對每個計時器,在 Scene 面板選中後,打開右側 Node 面板的 Signals 分頁,接著在清單中按兩下 timeout() 訊號。會開啟新的訊號連線對話框,使用預設設定即可,點選 Connect 建立連線。
三個計時器都設定完成後,你應該能在各自的 Signals 分頁中看到其 timeout() 訊號已顯示為綠色並完成連線:
(MobTimer):
_on_mob_timer_timeout()(ScoreTimer):
_on_score_timer_timeout()(StartTimer):
_on_start_timer_timeout()
現在我們以以下程式碼定義各計時器的行為。請注意,StartTimer 會啟動另外兩個計時器,而 ScoreTimer 會將分數加 1。
func _on_score_timer_timeout():
score += 1
func _on_start_timer_timeout():
$MobTimer.start()
$ScoreTimer.start()
// We also specified this function name in PascalCase in the editor's connection window.
private void OnScoreTimerTimeout()
{
_score++;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnStartTimerTimeout()
{
GetNode<Timer>("MobTimer").Start();
GetNode<Timer>("ScoreTimer").Start();
}
接著我們在 _on_MobTimer_timeout() 中建立怪物的實體,並沿著 Path2D 來隨機選擇一個起始位置,然後讓怪物開始移動。在跟隨路徑時 PathFollow2D 節點會自動旋轉,所以我們在使用這個節點來選擇怪物的位置的同時也會一起選擇方向。
需要注意的是,新建立的節點必須要使用 add_child() 來將節點新增到場景中。
func _on_mob_timer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instantiate()
# Choose a random location on Path2D.
var mob_spawn_location = $MobPath/MobSpawnLocation
mob_spawn_location.progress_ratio = randf()
# Set the mob's position to the random location.
mob.position = mob_spawn_location.position
# Set the mob's direction perpendicular to the path direction.
var direction = mob_spawn_location.rotation + PI / 2
# Add some randomness to the direction.
direction += randf_range(-PI / 4, PI / 4)
mob.rotation = direction
# Choose the velocity for the mob.
var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
mob.linear_velocity = velocity.rotated(direction)
# Spawn the mob by adding it to the Main scene.
add_child(mob)
// We also specified this function name in PascalCase in the editor's connection window.
private void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Choose a random location on Path2D.
var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
mobSpawnLocation.ProgressRatio = GD.Randf();
// Set the mob's direction perpendicular to the path direction.
float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;
// Set the mob's position to a random location.
mob.Position = mobSpawnLocation.Position;
// Add some randomness to the direction.
direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
mob.Rotation = direction;
// Choose the velocity.
var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0);
mob.LinearVelocity = velocity.Rotated(direction);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
}
重要
為什麼要使用 PI (圓周率)呢?在需要角度的函式中,GDScript 會使用 弧度 而不是角度。如果你比較喜歡用角度的話,則需要使用 deg2rad() (角度轉弧度)與 rad2deg() (弧度轉角度)兩個函式來轉換弧度與角度。
測試場景
來測試一下場景,看看是不是所有東西都正常了。將下面這段程式碼新增至 _ready() 中:
func _ready():
new_game()
public override void _Ready()
{
NewGame();
}
同時,將 Main 設為「主要場景」——也就是遊戲啟動後會自動執行的場景。點擊「執行」按鈕,當提示框出現後選擇 Main.tscn 。
小訣竅
如果你已經將別的場景設定為“主場景”了,你可以在“檔案系統”面板上右鍵點擊 main.tscn 並選擇“設為主場景”。
現在應該可以到處移動玩家了,並會看到怪物產生出來。玩家在被怪物撞到後會消失。
當您確定一切運作正常後,請從 _ready() 中移除呼叫 new_game() 的程式碼,並將其替換為 pass 。
我們的遊戲還缺點啥?缺使用者介面。在下一課中,我們將會新增標題介面並且顯示玩家的分數。