06.heads_up_display.rst 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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, 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. 1. Under "Theme Overrides > Fonts", choose "New Font".
  29. .. image:: img/custom_font1.png
  30. 2. Click on the "Font" you added, and under "Font/Data/0",
  31. choose "Load" and select the "Xolonium-Regular.ttf" file.
  32. .. image:: img/custom_font2.png
  33. Once you've done this on the ``ScoreLabel``, you can click the down arrow next
  34. to the Font property and choose "Copy", then "Paste" it in the same place
  35. on the other two Control nodes.
  36. Set the "Font Size" property of the ``ScoreLabel`` under "Theme Overrides > Font Sizes".
  37. A setting of ``64`` works well.
  38. .. image:: img/custom_font3.png
  39. .. note:: **Anchors and Margins:** ``Control`` nodes have a position and size,
  40. but they also have anchors and margins. Anchors define the origin -
  41. the reference point for the edges of the node. Margins update
  42. automatically when you move or resize a control node. They represent
  43. the distance from the control node's edges to its anchor.
  44. Arrange the nodes as shown below. Click the "Layout" button to set a Control
  45. node's layout:
  46. .. image:: img/ui_anchor.png
  47. You can drag the nodes to place them manually, or for more precise placement,
  48. use the following settings:
  49. ScoreLabel
  50. ~~~~~~~~~~
  51. - *Layout* : "Top Wide"
  52. - *Text* : ``0``
  53. - *Align* : "Center"
  54. Message
  55. ~~~~~~~~~~~~
  56. - *Layout* : "HCenter Wide"
  57. - *Text* : ``Dodge the Creeps!``
  58. - *Align* : "Center"
  59. - *Autowrap* : "On"
  60. StartButton
  61. ~~~~~~~~~~~
  62. - *Text* : ``Start``
  63. - *Layout* : "Center Bottom"
  64. - *Margin* :
  65. - Top: ``-200``
  66. - Bottom: ``-100``
  67. On the ``MessageTimer``, set the ``Wait Time`` to ``2`` and set the ``One Shot``
  68. property to "On".
  69. Now add this script to ``HUD``:
  70. .. tabs::
  71. .. code-tab:: gdscript GDScript
  72. extends CanvasLayer
  73. signal start_game
  74. .. code-tab:: csharp
  75. public class HUD : CanvasLayer
  76. {
  77. // Don't forget to rebuild the project so the editor knows about the new signal.
  78. [Signal]
  79. public delegate void StartGameEventHandler();
  80. }
  81. .. code-tab:: cpp
  82. // Copy `player.gdns` to `hud.gdns` and replace `Player` with `HUD`.
  83. // Attach the `hud.gdns` file to the HUD node.
  84. // Create two files `hud.cpp` and `hud.hpp` next to `entry.cpp` in `src`.
  85. // This code goes in `hud.hpp`. We also define the methods we'll be using here.
  86. #ifndef HUD_H
  87. #define HUD_H
  88. #include <Button.hpp>
  89. #include <CanvasLayer.hpp>
  90. #include <Godot.hpp>
  91. #include <Label.hpp>
  92. #include <Timer.hpp>
  93. class HUD : public godot::CanvasLayer {
  94. GODOT_CLASS(HUD, godot::CanvasLayer)
  95. godot::Label *_score_label;
  96. godot::Label *_message_label;
  97. godot::Timer *_start_message_timer;
  98. godot::Timer *_get_ready_message_timer;
  99. godot::Button *_start_button;
  100. godot::Timer *_start_button_timer;
  101. public:
  102. void _init() {}
  103. void _ready();
  104. void show_get_ready();
  105. void show_game_over();
  106. void update_score(const int score);
  107. void _on_StartButton_pressed();
  108. void _on_StartMessageTimer_timeout();
  109. void _on_GetReadyMessageTimer_timeout();
  110. static void _register_methods();
  111. };
  112. #endif // HUD_H
  113. The ``start_game`` signal tells the ``Main`` node that the button
  114. has been pressed.
  115. .. tabs::
  116. .. code-tab:: gdscript GDScript
  117. func show_message(text):
  118. $Message.text = text
  119. $Message.show()
  120. $MessageTimer.start()
  121. .. code-tab:: csharp
  122. public void ShowMessage(string text)
  123. {
  124. var message = GetNode<Label>("Message");
  125. message.Text = text;
  126. message.Show();
  127. GetNode<Timer>("MessageTimer").Start();
  128. }
  129. .. code-tab:: cpp
  130. // This code goes in `hud.cpp`.
  131. #include "hud.hpp"
  132. void HUD::_ready() {
  133. _score_label = get_node<godot::Label>("ScoreLabel");
  134. _message_label = get_node<godot::Label>("MessageLabel");
  135. _start_message_timer = get_node<godot::Timer>("StartMessageTimer");
  136. _get_ready_message_timer = get_node<godot::Timer>("GetReadyMessageTimer");
  137. _start_button = get_node<godot::Button>("StartButton");
  138. _start_button_timer = get_node<godot::Timer>("StartButtonTimer");
  139. }
  140. void HUD::_register_methods() {
  141. godot::register_method("_ready", &HUD::_ready);
  142. godot::register_method("show_get_ready", &HUD::show_get_ready);
  143. godot::register_method("show_game_over", &HUD::show_game_over);
  144. godot::register_method("update_score", &HUD::update_score);
  145. godot::register_method("_on_StartButton_pressed", &HUD::_on_StartButton_pressed);
  146. godot::register_method("_on_StartMessageTimer_timeout", &HUD::_on_StartMessageTimer_timeout);
  147. godot::register_method("_on_GetReadyMessageTimer_timeout", &HUD::_on_GetReadyMessageTimer_timeout);
  148. godot::register_signal<HUD>("start_game", godot::Dictionary());
  149. }
  150. This function is called when we want to display a message
  151. temporarily, such as "Get Ready".
  152. .. tabs::
  153. .. code-tab:: gdscript GDScript
  154. func show_game_over():
  155. show_message("Game Over")
  156. # Wait until the MessageTimer has counted down.
  157. await($MessageTimer, "timeout")
  158. $Message.text = "Dodge the\nCreeps!"
  159. $Message.show()
  160. # Make a one-shot timer and wait for it to finish.
  161. yield(get_tree().create_timer(1), "timeout")
  162. $StartButton.show()
  163. .. code-tab:: csharp
  164. async public void ShowGameOver()
  165. {
  166. ShowMessage("Game Over");
  167. var messageTimer = GetNode<Timer>("MessageTimer");
  168. await ToSignal(messageTimer, "timeout");
  169. var message = GetNode<Label>("Message");
  170. message.Text = "Dodge the\nCreeps!";
  171. message.Show();
  172. await ToSignal(GetTree().CreateTimer(1), "timeout");
  173. GetNode<Button>("StartButton").Show();
  174. }
  175. .. code-tab:: cpp
  176. // This code goes in `hud.cpp`.
  177. // There is no `yield` in GDNative, so we need to have every
  178. // step be its own method that is called on timer timeout.
  179. void HUD::show_get_ready() {
  180. _message_label->set_text("Get Ready");
  181. _message_label->show();
  182. _get_ready_message_timer->start();
  183. }
  184. void HUD::show_game_over() {
  185. _message_label->set_text("Game Over");
  186. _message_label->show();
  187. _start_message_timer->start();
  188. }
  189. This function is called when the player loses. It will show "Game Over" for 2
  190. seconds, then return to the title screen and, after a brief pause, show the
  191. "Start" button.
  192. .. note:: When you need to pause for a brief time, an alternative to using a
  193. Timer node is to use the SceneTree's ``create_timer()`` function. This
  194. can be very useful to add delays such as in the above code, where we
  195. want to wait some time before showing the "Start" button.
  196. .. tabs::
  197. .. code-tab:: gdscript GDScript
  198. func update_score(score):
  199. $ScoreLabel.text = str(score)
  200. .. code-tab:: csharp
  201. public void UpdateScore(int score)
  202. {
  203. GetNode<Label>("ScoreLabel").Text = score.ToString();
  204. }
  205. .. code-tab:: cpp
  206. // This code goes in `hud.cpp`.
  207. void HUD::update_score(const int p_score) {
  208. _score_label->set_text(godot::Variant(p_score));
  209. }
  210. This function is called by ``Main`` whenever the score changes.
  211. Connect the ``timeout()`` signal of ``MessageTimer`` and the ``pressed()``
  212. signal of ``StartButton`` and add the following code to the new functions:
  213. .. tabs::
  214. .. code-tab:: gdscript GDScript
  215. func _on_StartButton_pressed():
  216. $StartButton.hide()
  217. emit_signal("start_game")
  218. func _on_MessageTimer_timeout():
  219. $Message.hide()
  220. .. code-tab:: csharp
  221. public void OnStartButtonPressed()
  222. {
  223. GetNode<Button>("StartButton").Hide();
  224. EmitSignal(nameof(StartGame));
  225. }
  226. public void OnMessageTimerTimeout()
  227. {
  228. GetNode<Label>("Message").Hide();
  229. }
  230. .. code-tab:: cpp
  231. // This code goes in `hud.cpp`.
  232. void HUD::_on_StartButton_pressed() {
  233. _start_button_timer->stop();
  234. _start_button->hide();
  235. emit_signal("start_game");
  236. }
  237. void HUD::_on_StartMessageTimer_timeout() {
  238. _message_label->set_text("Dodge the\nCreeps");
  239. _message_label->show();
  240. _start_button_timer->start();
  241. }
  242. void HUD::_on_GetReadyMessageTimer_timeout() {
  243. _message_label->hide();
  244. }
  245. Connecting HUD to Main
  246. ~~~~~~~~~~~~~~~~~~~~~~
  247. Now that we're done creating the ``HUD`` scene, go back to ``Main``. Instance
  248. the ``HUD`` scene in ``Main`` like you did the ``Player`` scene. The scene tree
  249. should look like this, so make sure you didn't miss anything:
  250. .. image:: img/completed_main_scene.png
  251. Now we need to connect the ``HUD`` functionality to our ``Main`` script. This
  252. requires a few additions to the ``Main`` scene:
  253. In the Node tab, connect the HUD's ``start_game`` signal to the ``new_game()``
  254. function of the Main node by typing "new_game" in the "Receiver Method" in the
  255. "Connect a Signal" window. Verify that the green connection icon now appears
  256. next to ``func new_game()`` in the script.
  257. In ``new_game()``, update the score display and show the "Get Ready" message:
  258. .. tabs::
  259. .. code-tab:: gdscript GDScript
  260. $HUD.update_score(score)
  261. $HUD.show_message("Get Ready")
  262. .. code-tab:: csharp
  263. var hud = GetNode<HUD>("HUD");
  264. hud.UpdateScore(Score);
  265. hud.ShowMessage("Get Ready!");
  266. .. code-tab:: cpp
  267. _hud->update_score(score);
  268. _hud->show_get_ready();
  269. In ``game_over()`` we need to call the corresponding ``HUD`` function:
  270. .. tabs::
  271. .. code-tab:: gdscript GDScript
  272. $HUD.show_game_over()
  273. .. code-tab:: csharp
  274. GetNode<HUD>("HUD").ShowGameOver();
  275. .. code-tab:: cpp
  276. _hud->show_game_over();
  277. Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in sync
  278. with the changing score:
  279. .. tabs::
  280. .. code-tab:: gdscript GDScript
  281. $HUD.update_score(score)
  282. .. code-tab:: csharp
  283. GetNode<HUD>("HUD").UpdateScore(Score);
  284. .. code-tab:: cpp
  285. _hud->update_score(score);
  286. Now you're ready to play! Click the "Play the Project" button. You will be asked
  287. to select a main scene, so choose ``Main.tscn``.
  288. Removing old creeps
  289. ~~~~~~~~~~~~~~~~~~~
  290. If you play until "Game Over" and then start a new game right away, the creeps
  291. from the previous game may still be on the screen. It would be better if they
  292. all disappeared at the start of a new game. We just need a way to tell *all* the
  293. mobs to remove themselves. We can do this with the "group" feature.
  294. In the ``Mob`` scene, select the root node and click the "Node" tab next to the
  295. Inspector (the same place where you find the node's signals). Next to "Signals",
  296. click "Groups" and you can type a new group name and click "Add".
  297. .. image:: img/group_tab.png
  298. Now all mobs will be in the "mobs" group. We can then add the following line to
  299. the ``new_game()`` function in ``Main``:
  300. .. tabs::
  301. .. code-tab:: gdscript GDScript
  302. get_tree().call_group("mobs", "queue_free")
  303. .. code-tab:: csharp
  304. // Note that for calling Godot-provided methods with strings,
  305. // we have to use the original Godot snake_case name.
  306. GetTree().CallGroup("mobs", "queue_free");
  307. .. code-tab:: cpp
  308. get_tree()->call_group("mobs", "queue_free");
  309. The ``call_group()`` function calls the named function on every node in a
  310. group - in this case we are telling every mob to delete itself.
  311. The game's mostly done at this point. In the next and last part, we'll polish it
  312. a bit by adding a background, looping music, and some keyboard shortcuts.