ui_code_a_life_bar.rst 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. .. _doc_ui_code_a_life_bar:
  2. Control the game's UI with code
  3. ===============================
  4. Intro
  5. -----
  6. In this tutorial, you will connect a character to a life bar and animate
  7. the health loss.
  8. .. figure:: img/lifebar_tutorial_final_result.gif
  9. Here's what you'll create: the bar and the counter animate when
  10. the character takes a hit. They fade when it dies.
  11. You will learn:
  12. - How to **connect** a character to a GUI with signals
  13. - How to **control** a GUI with GDscript
  14. - How to **animate** a life bar with the :ref:`Tween <class_Tween>` node
  15. If you want to learn how to set up the interface instead, check out the
  16. step-by-step UI tutorials:
  17. - Create a main menu screen
  18. - Create a game user interface
  19. When you code a game, you want to build the core gameplay first: the
  20. main mechanics, player input, win and loss conditions. The UI comes a
  21. bit later. You want to keep all the elements that make up your project
  22. separate if possible. Each character should be in its own scene, with
  23. its own scripts, and so should the UI elements. This prevents bugs,
  24. keeps your project manageable, and allows different team members to work
  25. on different parts of the game.
  26. Once the core gameplay and the UI are ready, you'll need to connect them
  27. somehow. In our example, we have the Enemy who attacks the Player at
  28. constant time intervals. We want the life bar to update when the Player
  29. takes damage.
  30. To do this, we will use **signals**.
  31. .. note::
  32. Signals are Godot's version of the Observer pattern. They allow us to send out some message. Other nodes can connect to the object that **emits** the signal and receive the information. It's a powerful tool we use a lot for User Interface and achievement systems. You don't want to use them everywhere, though. Connecting two nodes adds some coupling between them. When there's a lot of connections, they become hard to manage.
  33. For more information, check out the `signals video tutorial <https://youtu.be/l0BkQxF7X3E>`_ on GDquest.
  34. Download and explore the start project
  35. --------------------------------------
  36. Download the Godot project: :download:`ui_code_life_bar.zip <files/ui_code_life_bar.zip>`. It contains all the assets and scripts you
  37. need to get started. Extract the .zip archive to get two folders: `start` and `end`.
  38. Load the ``start`` project in Godot. In the ``FileSystem`` dock,
  39. double click on LevelMockup.tscn to open it. It's an RPG game's mockup
  40. where 2 characters face each other. The pink enemy attacks and damages
  41. the green square at regular time intervals, until its death. Feel free
  42. to try out the game: the basic combat mechanics already work. But as the
  43. character isn't connected to the life bar, the ``GUI`` doesn't do
  44. anything.
  45. .. note::
  46. This is typical of how you'd code a game: you implement the core gameplay first, handle the player's death, and only then you'll add the interface. That's because the UI listens to what's happening in the game. So it can't work if other systems aren't in place yet.
  47. If you design the UI before you prototype and test the gameplay, chances are it won't work well and you'll have to re-create it from scratch.
  48. The scene contains a background sprite, a GUI, and two characters.
  49. .. figure:: img/lifebar_tutorial_life_bar_step_tut_LevelMockup_scene_tree.png
  50. The scene tree, with the GUI scene set to display its children
  51. The GUI scene encapsulates all of the game's Graphical User Interface. It comes with
  52. a barebones script where we get the path to nodes that exist inside the
  53. scene:
  54. .. tabs::
  55. .. code-tab:: gdscript GDScript
  56. onready var number_label = $Bars/LifeBar/Count/Background/Number
  57. onready var bar = $Bars/LifeBar/TextureProgress
  58. onready var tween = $Tween
  59. .. code-tab:: csharp
  60. public class Gui : MarginContainer
  61. {
  62. private Tween _tween;
  63. private Label _numberLabel;
  64. private TextureProgress _bar;
  65. public override void _Ready()
  66. {
  67. // C# doesn't have an onready feature, this works just the same.
  68. _bar = (TextureProgress) GetNode("Bars/LifeBar/TextureProgress");
  69. _tween = (Tween) GetNode("Tween");
  70. _numberLabel = (Label) GetNode("Bars/LifeBar/Count/Background/Number");
  71. }
  72. }
  73. - ``number_label`` displays a life count as a number. It's a ``Label``
  74. node
  75. - ``bar`` is the life bar itself. It's a ``TextureProgress`` node
  76. - ``tween`` is a component-style node that can animate and control any
  77. value or method from any other node
  78. .. note::
  79. The project uses a simple organisation that works for game jams and tiny games.
  80. At the root of the project, in the `res://` folder, you will find the `LevelMockup`. That's the main game scene and the one we will work with. All the components that make up the game are in the `scenes/` folder. The `assets/` folder contains the game sprites and the font for the HP counter. In the `scripts/` folder you will find the enemy, the player, and the GUI controller scripts.
  81. Click the edit scene icon to the right of the node in the scene tree to open the scene in the editor. You'll see the LifeBar and EnergyBar are sub-scenes themselves.
  82. .. figure:: img/lifebar_tutorial_Player_with_editable_children_on.png
  83. The scene tree, with the Player scene set to display its children
  84. Set up the Lifebar with the Player's max\_health
  85. ------------------------------------------------
  86. We have to tell the GUI somehow what the player's current health is, to
  87. update the lifebar's texture, and to display the remaining health in the
  88. HP counter in the top left corner of the screen. To do this we send the
  89. player's health to the GUI every time they take damage. The GUI will then
  90. update the ``Lifebar`` and ``Number`` nodes with this value.
  91. We could stop here to display the number, but we need to initialize the
  92. bar's ``max_value`` for it to update in the right proportions. The first
  93. step is thus to tell the ``GUI`` what the green character's
  94. ``max_health`` is.
  95. .. tip::
  96. The bar, a `TextureProgress`, has a `max_value` of `100` by default. If you don't need to display the character's health with a number, you don't need to change its `max_value` property. You send a percentage from the `Player` to the `GUI` instead: `health / max_health * 100`.
  97. .. figure:: img/lifebar_tutorial_TextureProgress_default_max_value.png
  98. Click the script icon to the right of the ``GUI`` in the Scene dock to
  99. open its script. In the ``_ready`` function, we're going to store the
  100. ``Player``'s ``max_health`` in a new variable and use it to set the
  101. ``bar``'s ``max_value``:
  102. .. tabs::
  103. .. code-tab:: gdscript GDScript
  104. func _ready():
  105. var player_max_health = $"../Characters/Player".max_health
  106. bar.max_value = player_max_health
  107. .. code-tab:: csharp
  108. public override void _Ready()
  109. {
  110. // Add this below _bar, _tween, and _numberLabel.
  111. var player = (Player) GetNode("../Characters/Player");
  112. _bar.MaxValue = player.MaxHealth;
  113. }
  114. Let's break it down. ``$"../Characters/Player"`` is a shorthand that
  115. goes one node up in the scene tree, and retrieves the
  116. ``Characters/Player`` node from there. It gives us access to the node.
  117. The second part of the statement, ``.max_health``, accesses the
  118. ``max_health`` on the Player node.
  119. The second line assigns this value to ``bar.max_value``. You could
  120. combine the two lines into one, but we'll need to use
  121. ``player_max_health`` again later in the tutorial.
  122. ``Player.gd`` sets the ``health`` to ``max_health`` at the start of the
  123. game, so we could work with this. Why do we still use ``max_health``?
  124. There are two reasons:
  125. We don't have the guarantee that ``health`` will always equal
  126. ``max_health``: a future version of the game may load a level where
  127. the player already lost some health.
  128. .. note::
  129. When you open a scene in the game, Godot creates nodes one by one, following the order in your Scene dock, from top to bottom. `GUI` and `Player` are not part of the same node branch. To make sure they both exist when we access each other, we have to use the `_ready` function. Godot calls `_ready` right after it loaded all nodes, before the game starts. It's the perfect function to set everything up and prepare the game session.
  130. Learn more about _ready: :doc:`scripting_continued`
  131. Update health with a signal when the player takes a hit
  132. -------------------------------------------------------
  133. Our GUI is ready to receive the ``health`` value updates from the
  134. ``Player``. To achieve this we're going to use **signals**.
  135. .. note::
  136. There are many useful built-in signals like `enter_tree` and `exit_tree`, that all nodes emit when they are respectively created and destroyed. You can also create your own using the `signal` keyword. On the `Player` node, you'll find two signals we created for you: `died` and `health_changed`.
  137. Why don't we directly get the ``Player`` node in the ``_process``
  138. function and look at the health value? Accessing nodes this way creates
  139. tight coupling between them. If you did it sparingly it may work. As
  140. your game grows bigger, you may have many more connections. If you get
  141. nodes this way it gets complex quickly. Not only that: you
  142. need to listen to the state change constantly in the ``_process``
  143. function. This check happens 60 times a second and you'll likely break
  144. the game because of the order in which the code runs.
  145. On a given frame you may look at another node's property *before* it was
  146. updated: you get a value from the last frame. This leads to obscure
  147. bugs that are hard to fix. On the other hand, a signal is emitted right
  148. after a change happened. It **guarantees** you're getting a fresh piece
  149. of information. And you will update the state of your connected node
  150. *right after* the change happened.
  151. .. note::
  152. The Observer pattern, that signals derive from, still adds a bit of coupling between node branches. But it's generally lighter and more secure than accessing nodes directly to communicate between two separate classes. It can be okay for a parent node to get values from its children. But you'll want to favor signals if you're working with two separate branches.
  153. Read Game Programming Patterns for more information on the `Observer pattern <http://gameprogrammingpatterns.com/observer.html>`_.
  154. The `full book <http://gameprogrammingpatterns.com/contents.html>`_ is available online for free.
  155. With this in mind, let's connect the ``GUI`` to the ``Player``. Click on
  156. the ``Player`` node in the scene dock to select it. Head down to the
  157. Inspector and click on the Node tab. This is the place to connect nodes
  158. to listen to the one you selected.
  159. The first section lists custom signals defined in ``Player.gd``:
  160. - ``died`` is emitted when the character died. We will use it in a
  161. moment to hide the UI.
  162. - ``health_changed`` is emitted when the character got hit.
  163. .. figure:: img/lifebar_tutorial_health_changed_signal.png
  164. We're connecting to the health\_changed signal
  165. Select ``health_changed`` and click on the Connect button in the bottom
  166. right corner to open the Connect Signal window. On the left side you can
  167. pick the node that will listen to this signal. Select the ``GUI`` node.
  168. The right side of the screen lets you pack optional values with the
  169. signal. We already took care of it in ``Player.gd``. In general I
  170. recommend not to add too many arguments using this window as they're
  171. less convenient than doing it from the code.
  172. .. figure:: img/lifebar_tutorial_connect_signal_window_health_changed.png
  173. The Connect Signal window with the GUI node selected
  174. .. tip::
  175. You can optionally connect nodes from the code. However doing it from the editor has two advantages:
  176. 1. Godot can write new callback functions for you in the connected script
  177. 2. An emitter icon appears next to the node that emits the signal in the Scene dock
  178. At the bottom of the window you will find the path to the node you
  179. selected. We're interested in the second row called "Method in Node".
  180. This is the method on the ``GUI`` node that gets called when the signal
  181. is emitted. This method receives the values sent with the signal and
  182. lets you process them. If you look to the right, there is a "Make
  183. Function" radio button that is on by default. Click the connect button
  184. at the bottom of the window. Godot creates the method inside the ``GUI``
  185. node. The script editor opens with the cursor inside a new
  186. ``_on_Player_health_changed`` function.
  187. .. note::
  188. When you connect nodes from the editor, Godot generates a
  189. method name with the following pattern: ``_on_EmitterName_signal_name``.
  190. If you wrote the method already, the "Make Function" option will keep
  191. it. You may replace the name with anything you'd like.
  192. .. figure:: img/lifebar_tutorial_godot_generates_signal_callback.png
  193. Godot writes the callback method for you and takes you to it
  194. Inside the parentheses after the function name, add a ``player_health``
  195. argument. When the player emits the ``health_changed`` signal, it will send
  196. its current ``health`` alongside it. Your code should look like:
  197. .. tabs::
  198. .. code-tab:: gdscript GDScript
  199. func _on_Player_health_changed(player_health):
  200. pass
  201. .. code-tab:: csharp
  202. public void OnPlayerHealthChanged(int playerHealth)
  203. {
  204. }
  205. .. note::
  206. The engine does not convert PascalCase to snake_case, for C# examples we'll be using
  207. PascalCase for method names & camelCase for method parameters, which follows the official `C#
  208. naming conventions. <https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions>`_
  209. .. figure:: img/lifebar_tutorial_player_gd_emits_health_changed_code.png
  210. In Player.gd, when the Player emits the health\_changed signal, it also
  211. sends its health value
  212. Inside ``_on_Player_health_changed``, let's call a second function called
  213. ``update_health`` and pass it the ``player_health`` variable.
  214. .. note::
  215. We could directly update the health value on `LifeBar` and `Number`. There are two reasons to use this method instead:
  216. 1. The name makes it clear for our future selves and teammates that when the player took damage, we update the health count on the GUI
  217. 2. We will reuse this method a bit later
  218. Create a new ``update_health`` method below ``_on_Player_health_changed``.
  219. It takes a new\_value as its only argument:
  220. .. tabs::
  221. .. code-tab:: gdscript GDScript
  222. func update_health(new_value):
  223. pass
  224. .. code-tab:: csharp
  225. public void UpdateHealth(int health)
  226. {
  227. }
  228. This method needs to:
  229. - set the ``Number`` node's ``text`` to ``new_value`` converted to a
  230. string
  231. - set the ``TextureProgress``'s ``value`` to ``new_value``
  232. .. tabs::
  233. .. code-tab:: gdscript GDScript
  234. func update_health(new_value):
  235. number_label.text = str(new_value)
  236. bar.value = new_value
  237. .. code-tab:: csharp
  238. public void UpdateHealth(int health)
  239. {
  240. _numberLabel.Text = health.ToString();
  241. _bar.Value = health;
  242. }
  243. .. tip::
  244. ``str`` is a built-in function that converts about any value to
  245. text. ``Number``'s ``text`` property requires a string, so we can't
  246. assign it to ``new_value`` directly
  247. Also call ``update_health`` at the end of the ``_ready`` function to
  248. initialize the ``Number`` node's ``text`` with the right value at the
  249. start of the game. Press F5 to test the game: the life bar updates with
  250. every attack!
  251. .. figure:: img/lifebar_tutorial_LifeBar_health_update_no_anim.gif
  252. Both the Number node and the TextureProgress update when the Player
  253. takes a hit
  254. Animate the loss of life with the Tween node
  255. --------------------------------------------
  256. Our interface is functional, but it could use some animation. That's a
  257. good opportunity to introduce the ``Tween`` node, an essential tool to
  258. animate properties. ``Tween`` animates anything you'd like from a start
  259. to an end state over a certain duration. For example, it can animate the
  260. health on the ``TextureProgress`` from its current level to the
  261. ``Player``'s new ``health`` when the character takes damage.
  262. The ``GUI`` scene already contains a ``Tween`` child node stored in the
  263. ``tween`` variable. Let's now use it. We have to make some changes to
  264. ``update_health``.
  265. We will use the ``Tween`` node's ``interpolate_property`` method. It
  266. takes seven arguments:
  267. 1. A reference to the node who owns the property to animate
  268. 2. The property's identifier as a string
  269. 3. The starting value
  270. 4. The end value
  271. 5. The animation's duration in seconds
  272. 6. The type of the transition
  273. 7. The easing to use in combination with the equation.
  274. The last two arguments combined correspond to an easing
  275. equation. This controls how the value evolves from the start to
  276. the end point.
  277. Click the script icon next to the ``GUI`` node to open it again. The
  278. ``Number`` node needs text to update itself, and the ``Bar`` needs a
  279. float or an integer. We can use ``interpolate_property`` to animate a
  280. number, but not to animate text directly. We're going to use it to
  281. animate a new ``GUI`` variable named ``animated_health``.
  282. At the top of the script, define a new variable, name it
  283. ``animated_health``, and set its value to 0. Navigate back to the ``update_health`` method and
  284. clear its content. Let's animate the ``animated_health`` value. Call the
  285. ``Tween`` node's ``interpolate_property`` method:
  286. .. tabs::
  287. .. code-tab:: gdscript GDScript
  288. func update_health(new_value):
  289. tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6, Tween.TRANS_LINEAR, Tween.EASE_IN)
  290. .. code-tab:: csharp
  291. // Add this to the top of your class.
  292. private float _animatedHealth = 0;
  293. public void UpdateHealth(int health)
  294. {
  295. _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
  296. Tween.EaseType.In);
  297. }
  298. Let's break down the call:
  299. ::
  300. tween.interpolate_property(self, "animated_health", ...
  301. We target ``animated_health`` on ``self``, that is to say the ``GUI``
  302. node. ``Tween``'s interpolate\_property takes the property's name as a
  303. string. That's why we write it as ``"animated_health"``.
  304. ::
  305. ... _health", animated_health, new_value, 0.6 ...
  306. The starting point is the current value the bar's at. We still have to
  307. code this part, but it's going to be ``animated_health``. The end point
  308. of the animation is the ``Player``'s ``health`` after the
  309. ``health_changed``: that's ``new_value``. And ``0.6`` is the animation's
  310. duration in seconds.
  311. ::
  312. ... 0.6, tween.TRANS_LINEAR, Tween.EASE_IN)
  313. The last two arguments are constants from the ``Tween`` class.
  314. ``TRANS_LINEAR`` means the animation should be linear. ``EASE_IN``
  315. doesn't do anything with a linear transition, but we must provide this
  316. last argument or we'll get an error.
  317. The animation will not play until we activated the ``Tween`` node with
  318. ``tween.start()``. We only have to do this once if the node is not
  319. active. Add this code after the last line:
  320. .. tabs::
  321. .. code-tab:: gdscript GDScript
  322. if not tween.is_active():
  323. tween.start()
  324. .. code-tab:: csharp
  325. if (!_tween.IsActive())
  326. {
  327. _tween.Start();
  328. }
  329. .. note::
  330. Although we could animate the `health` property on the `Player`, we shouldn't. Characters should lose life instantly when they get hit. It makes it a lot easier to manage their state, like to know when one died. You always want to store animations in a separate data container or node. The `tween` node is perfect for code-controlled animations. For hand-made animations, check out `AnimationPlayer`.
  331. Assign the animated\_health to the LifeBar
  332. ------------------------------------------
  333. Now the ``animated_health`` variable animates but we don't update the
  334. actual ``Bar`` and ``Number`` nodes anymore. Let's fix this.
  335. So far, the update\_health method looks like this:
  336. .. tabs::
  337. .. code-tab:: gdscript GDScript
  338. func update_health(new_value):
  339. tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6, Tween.TRANS_LINEAR, Tween.EASE_IN)
  340. if not tween.is_active():
  341. tween.start()
  342. .. code-tab:: csharp
  343. public void UpdateHealth(int health)
  344. {
  345. _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
  346. Tween.EaseType.In);
  347. if(!_tween.IsActive())
  348. {
  349. _tween.Start();
  350. }
  351. }
  352. In this specific case, because ``number_label`` takes text, we need to
  353. use the ``_process`` method to animate it. Let's now update the
  354. ``Number`` and ``TextureProgress`` nodes like before, inside of
  355. ``_process``:
  356. .. tabs::
  357. .. code-tab:: gdscript GDScript
  358. func _process(delta):
  359. number_label.text = str(animated_health)
  360. bar.value = animated_health
  361. .. code-tab:: csharp
  362. public override void _Process(float delta)
  363. {
  364. _numberLabel.Text = _animatedHealth.ToString();
  365. _bar.Value = _animatedHealth;
  366. }
  367. .. note::
  368. `number_label` and `bar` are variables that store references to the `Number` and `TextureProgress` nodes.
  369. Play the game to see the bar animate smoothly. But the text displays
  370. decimal number and looks like a mess. And considering the style of the
  371. game, it'd be nice for the life bar to animate in a choppier fashion.
  372. .. figure:: img/lifebar_tutorial_number_animation_messed_up.gif
  373. The animation is smooth, but the number is broken
  374. We can fix both problems by rounding out ``animated_health``. Use a
  375. local variable named ``round_value`` to store the rounded
  376. ``animated_health``. Then assign it to ``number_label.text`` and
  377. ``bar.value``:
  378. .. tabs::
  379. .. code-tab:: gdscript GDScript
  380. func _process(delta):
  381. var round_value = round(animated_health)
  382. number_label.text = str(round_value)
  383. bar.value = round_value
  384. .. code-tab:: csharp
  385. public override void _Process(float delta)
  386. {
  387. var roundValue = Mathf.Round(_animatedHealth);
  388. _numberLabel.Text = roundValue.ToString();
  389. _bar.Value = roundValue;
  390. }
  391. Try the game again to see a nice blocky animation.
  392. .. figure:: img/lifebar_tutorial_number_animation_working.gif
  393. By rounding out animated\_health, we kill two birds with one stone
  394. .. tip::
  395. Every time the player takes a hit, the ``GUI`` calls
  396. ``_on_Player_health_changed``, which in turn calls ``update_health``. This
  397. updates the animation and the ``number_label`` and ``bar`` follow in
  398. ``_process``. The animated life bar that shows the health going down gradually
  399. is a trick. It makes the GUI feel alive. If the ``Player`` takes 3 damage,
  400. it happens in an instant.
  401. Fade the bar when the Player dies
  402. ---------------------------------
  403. When the green character dies, it plays a death animation and fades out.
  404. At this point, we shouldn't show the interface anymore. Let's fade the
  405. bar as well when the character died. We will reuse the same ``Tween``
  406. node as it manages multiple animations in parallel for us.
  407. First, the ``GUI`` needs to connect to the ``Player``'s ``died`` signal
  408. to know when it died. Press :kbd:`F1` to jump back to the 2D
  409. Workspace. Select the ``Player`` node in the Scene dock and click on the
  410. Node tab next to the Inspector.
  411. Find the ``died`` signal, select it, and click the Connect button.
  412. .. figure:: img/lifebar_tutorial_player_died_signal_enemy_connected.png
  413. The signal should already have the Enemy connected to it
  414. In the Connecting Signal window, connect to the ``GUI`` node again. The
  415. Path to Node should be ``../../GUI`` and the Method in Node should show
  416. ``_on_Player_died``. Leave the Make Function option on and click Connect
  417. at the bottom of the window. This will take you to the ``GUI.gd`` file
  418. in the Script Workspace.
  419. .. figure:: img/lifebar_tutorial_player_died_connecting_signal_window.png
  420. You should get these values in the Connecting Signal window
  421. .. note::
  422. You should see a pattern by now: every time the GUI needs a new piece of information, we emit a new signal. Use them wisely: the more connections you add, the harder they are to track.
  423. To animate a fade on a UI element, we have to use its ``modulate``
  424. property. ``modulate`` is a ``Color`` that multiplies the colors of our
  425. textures.
  426. .. note::
  427. `modulate` comes from the `CanvasItem` class, All 2D and UI nodes inherit from it. It lets you toggle the visibility of the node, assign a shader to it, and modify it using a color with `modulate`.
  428. ``modulate`` takes a ``Color`` value with 4 channels: red, green, blue
  429. and alpha. If we darken any of the first three channels it darkens the
  430. interface. If we lower the alpha channel, our interface fades out.
  431. We're going to tween between two color values: from a white with an
  432. alpha of ``1``, that is to say at full opacity, to a pure white with an
  433. alpha value of ``0``, completely transparent. Let's add two variables at
  434. the top of the ``_on_Player_died`` method and name them ``start_color``
  435. and ``end_color``. Use the ``Color()`` constructor to build two
  436. ``Color`` values.
  437. .. tabs::
  438. .. code-tab:: gdscript GDScript
  439. func _on_Player_died():
  440. var start_color = Color(1.0, 1.0, 1.0, 1.0)
  441. var end_color = Color(1.0, 1.0, 1.0, 0.0)
  442. .. code-tab:: csharp
  443. public void OnPlayerDied()
  444. {
  445. var startColor = new Color(1.0f, 1.0f, 1.0f);
  446. var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
  447. }
  448. ``Color(1.0, 1.0, 1.0)`` corresponds to white. The fourth argument,
  449. respectively ``1.0`` and ``0.0`` in ``start_color`` and ``end_color``,
  450. is the alpha channel.
  451. We then have to call the ``interpolate_property`` method of the
  452. ``Tween`` node again:
  453. .. tabs::
  454. .. code-tab:: gdscript GDScript
  455. tween.interpolate_property(self, "modulate", start_color, end_color, 1.0, Tween.TRANS_LINEAR, Tween.EASE_IN)
  456. .. code-tab:: csharp
  457. _tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
  458. Tween.EaseType.In);
  459. This time, we change the ``modulate`` property and have it animate from
  460. ``start_color`` to the ``end_color``. The duration is of one second,
  461. with a linear transition. Here again, because the transition is linear,
  462. the easing does not matter. Here's the complete ``_on_Player_died``
  463. method:
  464. .. tabs::
  465. .. code-tab:: gdscript GDScript
  466. func _on_Player_died():
  467. var start_color = Color(1.0, 1.0, 1.0, 1.0)
  468. var end_color = Color(1.0, 1.0, 1.0, 0.0)
  469. tween.interpolate_property(self, "modulate", start_color, end_color, 1.0, Tween.TRANS_LINEAR, Tween.EASE_IN)
  470. .. code-tab:: csharp
  471. public void OnPlayerDied()
  472. {
  473. var startColor = new Color(1.0f, 1.0f, 1.0f);
  474. var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
  475. _tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
  476. Tween.EaseType.In);
  477. }
  478. And that is it. You may now play the game to see the final result!
  479. .. figure:: img/lifebar_tutorial_final_result.gif
  480. The final result. Congratulations for getting there!
  481. .. note::
  482. Using the exact same techniques, you can change the color of the bar when the Player gets poisoned, turn the bar red when its health drops low, shake the UI when they take a critical hit... the principle is the same: emit a signal to forward the information from the `Player` to the `GUI` and let the `GUI` process it.