123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- .. _doc_your_first_2d_game_heads_up_display:
- 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, click the "Other Node" button and add a :ref:`CanvasLayer <class_CanvasLayer>` node named
- ``HUD``. "HUD" stands for "heads-up display", an informational display that
- appears as an overlay on top of the game view.
- The :ref:`CanvasLayer <class_CanvasLayer>` node lets us draw our UI elements on
- a layer above the rest of the game, so that the information it displays isn't
- covered up by any game elements like the player or mobs.
- The HUD needs to display the following information:
- - Score, changed by ``ScoreTimer``.
- - A message, such as "Game Over" or "Get Ready!"
- - A "Start" button to begin the game.
- The basic node for UI elements is :ref:`Control <class_Control>`. To create our
- UI, we'll use two types of :ref:`Control <class_Control>` nodes: :ref:`Label
- <class_Label>` and :ref:`Button <class_Button>`.
- Create the following as children of the ``HUD`` node:
- - :ref:`Label <class_Label>` named ``ScoreLabel``.
- - :ref:`Label <class_Label>` named ``Message``.
- - :ref:`Button <class_Button>` named ``StartButton``.
- - :ref:`Timer <class_Timer>` named ``MessageTimer``.
- Click on the ``ScoreLabel`` and type a number into the ``Text`` field in the
- Inspector. The default font for ``Control`` nodes is small and doesn't scale
- well. There is a font file included in the game assets called
- "Xolonium-Regular.ttf". To use this font, do the following:
- Under "Theme Overrides > Fonts", choose "Load" and select the "Xolonium-Regular.ttf" file.
- .. image:: img/custom_font_load_font.webp
- The font size is still too small, increase it to ``64`` under "Theme Overrides > Font Sizes".
- Once you've done this with the ``ScoreLabel``, repeat the changes for the ``Message`` and ``StartButton`` nodes.
- .. image:: img/custom_font_size.webp
- .. note:: **Anchors:** ``Control`` nodes have a position and size,
- but they also have anchors. Anchors define the origin -
- the reference point for the edges of the node.
- Arrange the nodes as shown below.
- You can drag the nodes to place them manually, or for more precise placement,
- use "Anchor Presets".
- .. image:: img/ui_anchor.webp
- ScoreLabel
- ~~~~~~~~~~
- 1. Add the text ``0``.
- 2. Set the "Horizontal Alignment" and "Vertical Alignment" to ``Center``.
- 3. Choose the "Anchor Preset" ``Center Top``.
- Message
- ~~~~~~~~~~~~
- 1. Add the text ``Dodge the Creeps!``.
- 2. Set the "Horizontal Alignment" and "Vertical Alignment" to ``Center``.
- 3. Set the "Autowrap Mode" to ``Word``, otherwise the label will stay on one line.
- 4. Under "Control - Layout/Transform" set "Size X" to ``480`` to use the entire width of the screen.
- 5. Choose the "Anchor Preset" ``Center``.
- StartButton
- ~~~~~~~~~~~
- 1. Add the text ``Start``.
- 2. Under "Control - Layout/Transform", set "Size X" to ``200`` and "Size Y" to ``100``
- to add a little bit more padding between the border and text.
- 3. Choose the "Anchor Preset" ``Center Bottom``.
- 4. Under "Control - Layout/Transform", set "Position Y" to ``580``.
- On the ``MessageTimer``, set the ``Wait Time`` to ``2`` and set the ``One Shot``
- property to "On".
- Now add this script to ``HUD``:
- .. tabs::
- .. code-tab:: gdscript GDScript
- extends CanvasLayer
- # Notifies `Main` node that the button has been pressed
- signal start_game
- .. code-tab:: csharp
- using Godot;
- public partial class HUD : CanvasLayer
- {
- // Don't forget to rebuild the project so the editor knows about the new signal.
- [Signal]
- public delegate void StartGameEventHandler();
- }
- We now want to display a message temporarily,
- such as "Get Ready", so we add the following code
- .. tabs::
- .. code-tab:: gdscript GDScript
- func show_message(text):
- $Message.text = text
- $Message.show()
- $MessageTimer.start()
- .. code-tab:: csharp
- public void ShowMessage(string text)
- {
- var message = GetNode<Label>("Message");
- message.Text = text;
- message.Show();
- GetNode<Timer>("MessageTimer").Start();
- }
- We also need to process what happens when the player loses. The code below will show "Game Over" for 2 seconds, then return to the title screen and, after a brief pause, show the "Start" button.
- .. tabs::
- .. code-tab:: gdscript GDScript
- func show_game_over():
- show_message("Game Over")
- # Wait until the MessageTimer has counted down.
- await $MessageTimer.timeout
- $Message.text = "Dodge the Creeps!"
- $Message.show()
- # Make a one-shot timer and wait for it to finish.
- await get_tree().create_timer(1.0).timeout
- $StartButton.show()
- .. code-tab:: csharp
- async public void ShowGameOver()
- {
- ShowMessage("Game Over");
- var messageTimer = GetNode<Timer>("MessageTimer");
- await ToSignal(messageTimer, Timer.SignalName.Timeout);
- var message = GetNode<Label>("Message");
- message.Text = "Dodge the Creeps!";
- message.Show();
- await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout);
- GetNode<Button>("StartButton").Show();
- }
- This function is called when the player loses. It will show "Game Over" for 2
- seconds, then return to the title screen and, after a brief pause, show the
- "Start" button.
- .. note:: 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.
- Add the code below to ``HUD`` to update the score
- .. tabs::
- .. code-tab:: gdscript GDScript
- func update_score(score):
- $ScoreLabel.text = str(score)
- .. code-tab:: csharp
- public void UpdateScore(int score)
- {
- GetNode<Label>("ScoreLabel").Text = score.ToString();
- }
- Connect the ``pressed()`` signal of ``StartButton`` and the ``timeout()``
- signal of ``MessageTimer`` to the ``HUD`` node, and add the following code to the new functions:
- .. tabs::
- .. code-tab:: gdscript GDScript
- func _on_start_button_pressed():
- $StartButton.hide()
- start_game.emit()
- func _on_message_timer_timeout():
- $Message.hide()
- .. code-tab:: csharp
- // We also specified this function name in PascalCase in the editor's connection window.
- private void OnStartButtonPressed()
- {
- GetNode<Button>("StartButton").Hide();
- EmitSignal(SignalName.StartGame);
- }
- // We also specified this function name in PascalCase in the editor's connection window.
- private void OnMessageTimerTimeout()
- {
- GetNode<Label>("Message").Hide();
- }
- Connecting HUD to Main
- ~~~~~~~~~~~~~~~~~~~~~~
- Now that we're done creating the ``HUD`` scene, go back to ``Main``. Instance
- the ``HUD`` scene in ``Main`` like you did the ``Player`` scene. The scene tree
- should look like this, so make sure you didn't miss anything:
- .. image:: img/completed_main_scene.webp
- Now we need to connect the ``HUD`` functionality to our ``Main`` script. This
- requires a few additions to the ``Main`` scene:
- In the Node tab, connect the HUD's ``start_game`` signal to the ``new_game()``
- function of the Main node by clicking the "Pick" button in the "Connect a Signal"
- window and selecting the ``new_game()`` method or type "new_game" below "Receiver Method"
- in the window. Verify that the green connection icon now appears next to
- ``func new_game()`` in the script.
- In ``new_game()``, update the score display and show the "Get Ready" message:
- .. tabs::
- .. code-tab:: gdscript GDScript
- $HUD.update_score(score)
- $HUD.show_message("Get Ready")
- .. code-tab:: csharp
- var hud = GetNode<HUD>("HUD");
- hud.UpdateScore(_score);
- hud.ShowMessage("Get Ready!");
- In ``game_over()`` we need to call the corresponding ``HUD`` function:
- .. tabs::
- .. code-tab:: gdscript GDScript
- $HUD.show_game_over()
- .. code-tab:: csharp
- GetNode<HUD>("HUD").ShowGameOver();
- Finally, add this to ``_on_score_timer_timeout()`` to keep the display in sync
- with the changing score:
- .. tabs::
- .. code-tab:: gdscript GDScript
- $HUD.update_score(score)
- .. code-tab:: csharp
- GetNode<HUD>("HUD").UpdateScore(_score);
- .. warning::
- Remember to remove the call to ``new_game()`` from
- ``_ready()`` if you haven't already, otherwise
- your game will start automatically.
- Now you're ready to play! Click the "Play the Project" button.
- 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" to open the group overview
- and the "+" button to open the "Create New Group" dialog.
- .. image:: img/group_tab.webp
- Name the group ``mobs`` and click "ok" to add a new scene group.
- .. image:: img/add_group_dialog.webp
- Now all mobs will be in the "mobs" group.
- .. image:: img/scene_group_mobs.webp
- We can then add the following line to the ``new_game()`` function in ``Main``:
- .. tabs::
- .. code-tab:: gdscript GDScript
- get_tree().call_group("mobs", "queue_free")
- .. code-tab:: csharp
- // Note that for calling Godot-provided methods with strings,
- // we have to use the original Godot snake_case name.
- GetTree().CallGroup("mobs", Node.MethodName.QueueFree);
- 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.
|