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.

Węzeł CanvasLayer pozwala nam narysować nasze elementy interfejsu użytkownika na warstwie będącą przed resztą gry, tak aby wyświetlane przez niego informacje nie były zakryte żadnymi elementami gry, takimi jak gracz lub przeciwnik.

HUD powinien wyświetlić następujące informacje:

  • Wynik, zmieniany przez ScoreTimer.

  • Wiadomości takie jak "Koniec Gry" lub "Przygotuj Się!"

  • Przycisk "Start" rozpoczyna grę.

Podstawowym węzłem elementów interfejsu użytkownika jest Control. Aby utworzyć nasz interfejs użytkownika, będziemy używać dwóch typów węzła ref:Control <class_Control> - Label oraz Button.

Utwórz je jako dzieci węzła HUD:

  • Label nazwany ScoreLabel.

  • Label nazwany Message.

  • Przycisk Button o nazwie StartButton.

  • Timer o nazwie MessageTimer.

Domyślna czcionka dla węzłów Control jest mała i nie skaluje się dobrze. W zasobach gry znajduje się plik czcionki o nazwie "Xolonium-Regular.ttf". Aby użyć tej czcionki, wykonaj następujące czynności dla każdego z trzech węzłów Control:

  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.

Informacja

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.

Uporządkuj węzły w sposób pokazany poniżej. Kliknąć przycisk "Układ", aby ustawić dla węzła Control układ:

../../_images/ui_anchor.png

Węzły można przeciągnąć ręcznie lub, aby uzyskać bardziej precyzyjne rozmieszczenie, należy użyć następujących ustawień:

ScoreLabel

  • Layout : "Top Wide"

  • Tekst: 0

  • Wyrównanie: "Wyśrodkowany"

Wiadomość

  • Układ : "Środek"

  • Tekst : Dodge the Creeps!

  • Wyrównanie: "Wyśrodkowany"

  • Autowrap : "On"

StartButton

  • Tekst: Start

  • Układ: "Środek Dół"

  • Margines:

    • Góra: -200

    • Dół: -100

Ta funkcja jest wywoływana, gdy chcemy tymczasowo wyświetlić komunikat, taki jak "Przygotuj się". W MessageTimer ustaw Czas oczekiwania na 2 i ustaw właściwość Jednokrotny na "Włączone".

Teraz dodaj ten skrypt do HUD:

extends CanvasLayer

signal start_game

Sygnał start_game informuje węzeł Main o naciśnięciu przycisku.

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

Ta funkcja jest wywoływana, gdy chcemy tymczasowo wyświetlić komunikat, taki jak "Przygotuj się". W MessageTimer ustaw Czas oczekiwania na 2 i ustaw właściwość Jednokrotny na "Włączone".

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()

Ta funkcja jest wywoływana, gdy gracz przegrywa. Przez 2 sekundy na ekranie będzie wyświetlał się napis "Game Over" (Koniec Gry), następnie powróci do ekranu tytułowego i pojawi się przycisk "Start".

Informacja

When you need to pause for a brief time, an alternative to using a Timer node is to use the SceneTree's create_timer() function. This can be very useful to add delays such as in the above code, where we want to wait some time before showing the "Start" button.

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

Funkcja ta jest wywoływana w Main przy każdej zmianie wyniku.

Podłącz timeout() do sygnału MessageTimer i pressed() do sygnału StartButton i dodaj następujący kod do nowej funkcji:

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

func _on_MessageTimer_timeout():
    $Message.hide()

Podłączenie HUD do Main

Teraz, kiedy już stworzyłeś scenę HUD, zapisz ją i wróć do Main`. Stwórz instancję HUD w Main tak, jak zrobiłeś to Player scenę i umieść ją na dole drzewa. Pełne drzewo powinno wyglądać tak, więc upewnij się, że niczego nie przegapiłeś:

../../_images/completed_main_scene.png

Teraz musimy podłączyć funkcję HUD do naszego skryptu Main. Wymaga to paru poprawek do sceny Main:

In the Node tab, connect the HUD's start_game signal to the new_game() function of the Main node by typing "new_game" in the "Receiver Method" in the "Connect a Signal" window. Verify that the green connection icon now appears next to func new_game() in the script.

W new_game(), zaktualizuj wyświetlany wynik i pokaż komunikat "Przygotuj się":

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

W game_over() musimy wywołać odpowiednią funkcję w HUD:

$HUD.show_game_over()

Na koniec dodaj to do on_on_ScoreTimer_timeout(), aby wyświetlacz był zsynchronizowany ze zmieniającym się wynikiem:

$HUD.update_score(score)

Teraz jesteś gotowy do gry! Kliknąć przycisk "Uruchom projekt". Zostaniesz poproszony o wybranie głównej sceny, więc wybierz Main.tscn.

Removing old creeps

If you play until "Game Over" and then start a new game right away, the creeps from the previous game may still be on the screen. It would be better if they all disappeared at the start of a new game. We just need a way to tell all the mobs to remove themselves. We can do this with the "group" feature.

In the Mob scene, select the root node and click the "Node" tab next to the Inspector (the same place where you find the node's signals). Next to "Signals", click "Groups" and you can type a new group name and click "Add".

../../_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")

The call_group() function calls the named function on every node in a group - in this case we are telling every mob to delete itself.

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.