Heads up display

The final piece our game needs is a User Interface (UI) to display things like score, a "game over" message, and a restart button.

Create a new scene, and add a CanvasLayer node named HUD. "HUD" stands for "heads-up display", an informational display that appears as an overlay on top of the game view.

使用 CanvasLayer 節點可以讓我們在遊戲上方的圖層上繪製 UI 元素,這樣上面顯示的資訊就不會被其他遊戲元素如玩家或怪物給蓋住。

HUD 需要顯示如下資訊:

  • 分數,會通過 ScoreTimer 來更改。

  • 訊息,如「Gamer Over」或「請準備!」

  • 用來開始遊戲的「Start」按鈕。

UI 元素所使用的基本節點是 Control 。我們需要使用兩種類型的 Control 節點來給這個遊戲建立 UI: Label (標籤)與 Button (按鈕)。

HUD 節點建立下列子節點:

  • Label ,命名為 ScoreLabel

  • Label ,命名為 Message

  • Button ,命名為 StartButton

  • Timer ,命名為 MessageTimer

點擊 ScoreLabel 並在屬性面板中的 Text 輸入一個數字。 Control 節點的預設字體很小,而且也縮放得不是很好。遊戲素材裡有包含了一個字體檔,檔名為「Xolonium-Regular.ttf」。依照下列步驟來使用這個字體:

  1. Under Theme overrides > Fonts click on the empty box and select "New DynamicFont"

../../_images/custom_font1.png
  1. Click on the "DynamicFont" you added, and under Font > FontData, choose "Load" and select the "Xolonium-Regular.ttf" file.

../../_images/custom_font2.png

Set the "Size" property under Settings, 64 works well.

../../_images/custom_font3.png

Once you've done this on the ScoreLabel, you can click the down arrow next to the Font property and choose "Copy", then "Paste" it in the same place on the other two Control nodes.

備註

Anchors and Margins: Control nodes have a position and size, but they also have anchors and margins. Anchors define the origin - the reference point for the edges of the node. Margins update automatically when you move or resize a control node. They represent the distance from the control node's edges to its anchor.

依照下圖排列節點。點擊「畫面配置」按鈕來設定 Control 節點的配置:

../../_images/ui_anchor.png

可以拖移節點來手動排列,或是使用下列設定來更精確地排放:

ScoreLabel

  • 畫面配置 :「上延伸」

  • Text0

  • Align :「Center」

訊息

  • 畫面配置 :「水平中央延長」

  • TextDodge the Creeps!

  • Align :「Center」

  • Autostrap : 「開啟」

StartButton

  • TextStart

  • 畫面配置 :「中下」

  • Margin

    • Top: -200

    • Bottom: -100

MessageTimerWait Time 設為 2 ,將 One Shot 設為「開啟」。

現在將這個腳本新增至 HUD

extends CanvasLayer

signal start_game

start_game 訊號會告訴 Main 節點按鈕被按下。

func show_message(text):
    $Message.text = text
    $Message.show()
    $MessageTimer.start()

當我們想要暫時顯示訊息(如「Get Ready」)時可以呼叫這個函式。

func show_game_over():
    show_message("Game Over")
    # Wait until the MessageTimer has counted down.
    yield($MessageTimer, "timeout")

    $Message.text = "Dodge the\nCreeps!"
    $Message.show()
    # Make a one-shot timer and wait for it to finish.
    yield(get_tree().create_timer(1), "timeout")
    $StartButton.show()

當玩家輸了之後會呼叫這個函式。會在畫面上顯示 2 秒「Game Over」,然後回到標題畫面、暫停一下,最後顯示「Start」按鈕。

備註

需要暫停一下下的時候,除了使用 Timer 節點外也可以使用 SceneTree(場景樹)的 create_timer() 函式。當需要像上面的程式碼一樣新增一點延遲的時候很有用,上面的例子中我們用它來在顯示「Start」按鈕前增加一點點等待時間。

func update_score(score):
    $ScoreLabel.text = str(score)

這個函式會由 Main 在分數改動的時候呼叫。

連接 MessageTimertimeout() 訊號與 ``StartButtonpressed() 訊號,並新增下列程式碼到新函式當中:

func _on_StartButton_pressed():
    $StartButton.hide()
    emit_signal("start_game")

func _on_MessageTimer_timeout():
    $Message.hide()

將 HUD 場景連接至 Main 場景

我們現在做好 HUD 場景了。讓我們回到 Main。像剛才實體化 Player 場景一樣,在 Main 場景中實體化 HUD。場景樹會看起來像這樣,檢查一下有沒有漏了什麼:

../../_images/completed_main_scene.png

我們現在需要把 HUD 的功能連接到 Main 腳本。所以我們需要在 Main 場景內新增一點點東西:

在節點分頁中,設定 HUD 的 start_game 訊號的連接,在「連接訊號」視窗中將「Receiver 方法」設為「new_game」來將訊號連接到 Main 節點的 new_game() 函式。之後檢查看看腳本中的 ``func new_game()``旁邊有沒有出現綠色的連線圖示。

new_game() 中,需要更新分數並顯示「Get Ready」訊息:

$HUD.update_score(score)
$HUD.show_message("Get Ready")

game_over() 中我們需要呼叫對應的 HUD 函式:

$HUD.show_game_over()

最後,將這段程式碼加到 _on_ScoreTimer_timeout() 來讓分數改變的時候能同步顯示:

$HUD.update_score(score)

現在已經可以執行遊戲了!點擊「執行專案」按鈕。接著會被要求選擇一個主場景,這裡我們選 Main.tscn

刪除舊的怪物

如果一直玩到「Game Over」並直接開始新遊戲的話,上一場遊戲的怪物可能還在畫面上。開始遊戲的時候如果能讓舊的怪物消失會更好。而我們只需要告訴 所有 怪物,刪除自己。我們可以在這裡使用「群組」功能。

Mob 場景中,先選擇根節點,然後點擊屬性面板旁邊的「節點」分頁(跟設定節點訊號的地方相同)。點擊「訊號」旁邊的「群組」,接著編輯器會讓你設定新群組名稱,最後點擊「新增」。

../../_images/group_tab.png

Now all mobs will be in the "mobs" group. We can then add the following line to the new_game() function in Main:

get_tree().call_group("mobs", "queue_free")

call_group() 函式會呼叫群組中所有節點的指定名稱的函式——在這裡我們用來讓所有怪物刪除自己。

The game's mostly done at this point. In the next and last part, we'll polish it a bit by adding a background, looping music, and some keyboard shortcuts.