06.heads_up_display.rst 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. .. _doc_your_first_2d_game_heads_up_display:
  2. Heads up display
  3. ================
  4. The final piece our game needs is a User Interface (UI) to display things like
  5. score, a "game over" message, and a restart button.
  6. Create a new scene, click the "Other Node" button and add a :ref:`CanvasLayer <class_CanvasLayer>` node named
  7. ``HUD``. "HUD" stands for "heads-up display", an informational display that
  8. appears as an overlay on top of the game view.
  9. The :ref:`CanvasLayer <class_CanvasLayer>` node lets us draw our UI elements on
  10. a layer above the rest of the game, so that the information it displays isn't
  11. covered up by any game elements like the player or mobs.
  12. The HUD needs to display the following information:
  13. - Score, changed by ``ScoreTimer``.
  14. - A message, such as "Game Over" or "Get Ready!"
  15. - A "Start" button to begin the game.
  16. The basic node for UI elements is :ref:`Control <class_Control>`. To create our
  17. UI, we'll use two types of :ref:`Control <class_Control>` nodes: :ref:`Label
  18. <class_Label>` and :ref:`Button <class_Button>`.
  19. Create the following as children of the ``HUD`` node:
  20. - :ref:`Label <class_Label>` named ``ScoreLabel``.
  21. - :ref:`Label <class_Label>` named ``Message``.
  22. - :ref:`Button <class_Button>` named ``StartButton``.
  23. - :ref:`Timer <class_Timer>` named ``MessageTimer``.
  24. Click on the ``ScoreLabel`` and type a number into the ``Text`` field in the
  25. Inspector. The default font for ``Control`` nodes is small and doesn't scale
  26. well. There is a font file included in the game assets called
  27. "Xolonium-Regular.ttf". To use this font, do the following:
  28. Under "Theme Overrides > Fonts", choose "Load" and select the "Xolonium-Regular.ttf" file.
  29. .. image:: img/custom_font_load_font.webp
  30. The font size is still too small, increase it to ``64`` under "Theme Overrides > Font Sizes".
  31. Once you've done this with the ``ScoreLabel``, repeat the changes for the ``Message`` and ``StartButton`` nodes.
  32. .. image:: img/custom_font_size.webp
  33. .. note:: **Anchors:** ``Control`` nodes have a position and size,
  34. but they also have anchors. Anchors define the origin -
  35. the reference point for the edges of the node.
  36. Arrange the nodes as shown below.
  37. You can drag the nodes to place them manually, or for more precise placement,
  38. use "Anchor Presets".
  39. .. image:: img/ui_anchor.webp
  40. ScoreLabel
  41. ~~~~~~~~~~
  42. 1. Add the text ``0``.
  43. 2. Set the "Horizontal Alignment" and "Vertical Alignment" to ``Center``.
  44. 3. Choose the "Anchor Preset" ``Center Top``.
  45. Message
  46. ~~~~~~~~~~~~
  47. 1. Add the text ``Dodge the Creeps!``.
  48. 2. Set the "Horizontal Alignment" and "Vertical Alignment" to ``Center``.
  49. 3. Set the "Autowrap Mode" to ``Word``, otherwise the label will stay on one line.
  50. 4. Under "Control - Layout/Transform" set "Size X" to ``480`` to use the entire width of the screen.
  51. 5. Choose the "Anchor Preset" ``Center``.
  52. StartButton
  53. ~~~~~~~~~~~
  54. 1. Add the text ``Start``.
  55. 2. Under "Control - Layout/Transform", set "Size X" to ``200`` and "Size Y" to ``100``
  56. to add a little bit more padding between the border and text.
  57. 3. Choose the "Anchor Preset" ``Center Bottom``.
  58. 4. Under "Control - Layout/Transform", set "Position Y" to ``580``.
  59. On the ``MessageTimer``, set the ``Wait Time`` to ``2`` and set the ``One Shot``
  60. property to "On".
  61. Now add this script to ``HUD``:
  62. .. tabs::
  63. .. code-tab:: gdscript GDScript
  64. extends CanvasLayer
  65. # Notifies `Main` node that the button has been pressed
  66. signal start_game
  67. .. code-tab:: csharp
  68. using Godot;
  69. public partial class HUD : CanvasLayer
  70. {
  71. // Don't forget to rebuild the project so the editor knows about the new signal.
  72. [Signal]
  73. public delegate void StartGameEventHandler();
  74. }
  75. We now want to display a message temporarily,
  76. such as "Get Ready", so we add the following code
  77. .. tabs::
  78. .. code-tab:: gdscript GDScript
  79. func show_message(text):
  80. $Message.text = text
  81. $Message.show()
  82. $MessageTimer.start()
  83. .. code-tab:: csharp
  84. public void ShowMessage(string text)
  85. {
  86. var message = GetNode<Label>("Message");
  87. message.Text = text;
  88. message.Show();
  89. GetNode<Timer>("MessageTimer").Start();
  90. }
  91. 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.
  92. .. tabs::
  93. .. code-tab:: gdscript GDScript
  94. func show_game_over():
  95. show_message("Game Over")
  96. # Wait until the MessageTimer has counted down.
  97. await $MessageTimer.timeout
  98. $Message.text = "Dodge the Creeps!"
  99. $Message.show()
  100. # Make a one-shot timer and wait for it to finish.
  101. await get_tree().create_timer(1.0).timeout
  102. $StartButton.show()
  103. .. code-tab:: csharp
  104. async public void ShowGameOver()
  105. {
  106. ShowMessage("Game Over");
  107. var messageTimer = GetNode<Timer>("MessageTimer");
  108. await ToSignal(messageTimer, Timer.SignalName.Timeout);
  109. var message = GetNode<Label>("Message");
  110. message.Text = "Dodge the Creeps!";
  111. message.Show();
  112. await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout);
  113. GetNode<Button>("StartButton").Show();
  114. }
  115. This function is called when the player loses. It will show "Game Over" for 2
  116. seconds, then return to the title screen and, after a brief pause, show the
  117. "Start" button.
  118. .. note:: When you need to pause for a brief time, an alternative to using a
  119. Timer node is to use the SceneTree's ``create_timer()`` function. This
  120. can be very useful to add delays such as in the above code, where we
  121. want to wait some time before showing the "Start" button.
  122. Add the code below to ``HUD`` to update the score
  123. .. tabs::
  124. .. code-tab:: gdscript GDScript
  125. func update_score(score):
  126. $ScoreLabel.text = str(score)
  127. .. code-tab:: csharp
  128. public void UpdateScore(int score)
  129. {
  130. GetNode<Label>("ScoreLabel").Text = score.ToString();
  131. }
  132. Connect the ``timeout()`` signal of ``MessageTimer`` and the ``pressed()``
  133. signal of ``StartButton``, and add the following code to the new functions:
  134. .. tabs::
  135. .. code-tab:: gdscript GDScript
  136. func _on_start_button_pressed():
  137. $StartButton.hide()
  138. start_game.emit()
  139. func _on_message_timer_timeout():
  140. $Message.hide()
  141. .. code-tab:: csharp
  142. private void OnStartButtonPressed()
  143. {
  144. GetNode<Button>("StartButton").Hide();
  145. EmitSignal(SignalName.StartGame);
  146. }
  147. private void OnMessageTimerTimeout()
  148. {
  149. GetNode<Label>("Message").Hide();
  150. }
  151. Connecting HUD to Main
  152. ~~~~~~~~~~~~~~~~~~~~~~
  153. Now that we're done creating the ``HUD`` scene, go back to ``Main``. Instance
  154. the ``HUD`` scene in ``Main`` like you did the ``Player`` scene. The scene tree
  155. should look like this, so make sure you didn't miss anything:
  156. .. image:: img/completed_main_scene.webp
  157. Now we need to connect the ``HUD`` functionality to our ``Main`` script. This
  158. requires a few additions to the ``Main`` scene:
  159. In the Node tab, connect the HUD's ``start_game`` signal to the ``new_game()``
  160. function of the Main node by clicking the "Pick" button in the "Connect a Signal"
  161. window and selecting the ``new_game()`` method or type "new_game" below "Receiver Method"
  162. in the window. Verify that the green connection icon now appears next to
  163. ``func new_game()`` in the script.
  164. In ``new_game()``, update the score display and show the "Get Ready" message:
  165. .. tabs::
  166. .. code-tab:: gdscript GDScript
  167. $HUD.update_score(score)
  168. $HUD.show_message("Get Ready")
  169. .. code-tab:: csharp
  170. var hud = GetNode<HUD>("HUD");
  171. hud.UpdateScore(_score);
  172. hud.ShowMessage("Get Ready!");
  173. In ``game_over()`` we need to call the corresponding ``HUD`` function:
  174. .. tabs::
  175. .. code-tab:: gdscript GDScript
  176. $HUD.show_game_over()
  177. .. code-tab:: csharp
  178. GetNode<HUD>("HUD").ShowGameOver();
  179. Finally, add this to ``_on_score_timer_timeout()`` to keep the display in sync
  180. with the changing score:
  181. .. tabs::
  182. .. code-tab:: gdscript GDScript
  183. $HUD.update_score(score)
  184. .. code-tab:: csharp
  185. GetNode<HUD>("HUD").UpdateScore(_score);
  186. .. warning::
  187. Remember to remove the call to ``new_game()`` from
  188. ``_ready()`` if you haven't already, otherwise
  189. your game will start automatically.
  190. Now you're ready to play! Click the "Play the Project" button. You will be asked
  191. to select a main scene, so choose ``main.tscn``.
  192. Removing old creeps
  193. ~~~~~~~~~~~~~~~~~~~
  194. If you play until "Game Over" and then start a new game right away, the creeps
  195. from the previous game may still be on the screen. It would be better if they
  196. all disappeared at the start of a new game. We just need a way to tell *all* the
  197. mobs to remove themselves. We can do this with the "group" feature.
  198. In the ``Mob`` scene, select the root node and click the "Node" tab next to the
  199. Inspector (the same place where you find the node's signals). Next to "Signals",
  200. click "Groups" and you can type a new group name and click "Add".
  201. .. image:: img/group_tab.webp
  202. Now all mobs will be in the "mobs" group. We can then add the following line to
  203. the ``new_game()`` function in ``Main``:
  204. .. tabs::
  205. .. code-tab:: gdscript GDScript
  206. get_tree().call_group("mobs", "queue_free")
  207. .. code-tab:: csharp
  208. // Note that for calling Godot-provided methods with strings,
  209. // we have to use the original Godot snake_case name.
  210. GetTree().CallGroup("mobs", Node.MethodName.QueueFree);
  211. The ``call_group()`` function calls the named function on every node in a
  212. group - in this case we are telling every mob to delete itself.
  213. The game's mostly done at this point. In the next and last part, we'll polish it
  214. a bit by adding a background, looping music, and some keyboard shortcuts.