part_six.rst 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. .. _doc_fps_tutorial_part_six:
  2. Part 6
  3. ======
  4. Part Overview
  5. -------------
  6. In this part we're going to add a main menu and pause menu,
  7. add a respawn system for the player, and change/move the sound system so we can use it from any script.
  8. This is the last part of the FPS tutorial, by the end of this you will have a solid base to build amazing FPS games with Godot!
  9. .. image:: img/FinishedTutorialPicture.png
  10. .. note:: You are assumed to have finished :ref:`doc_fps_tutorial_part_five` before moving on to this part of the tutorial.
  11. The finished project from :ref:`doc_fps_tutorial_part_four` will be the starting project for part 6
  12. Let's get started!
  13. Adding the main menu
  14. --------------------
  15. First, open up ``Main_Menu.tscn`` and take a look at how the scene is set up.
  16. The main menu is broken up into three different panels, each representing a different
  17. 'screen' of our main menu.
  18. .. note:: The ``Background_Animation`` node is just so the background of the menu is a bit more interesting than a solid color.
  19. It's a camera looking around the skybox, nothing fancy.
  20. Feel free to expand all of the nodes and see how their set up. Remember to keep only ``Start_Menu`` visible
  21. when you're done, as that's the screen we want to show first when we enter the main menu.
  22. Select ``Main_Menu`` (the root node) and create a new script called ``Main_Menu.gd``. Add the following:
  23. ::
  24. extends Control
  25. var start_menu
  26. var level_select_menu
  27. var options_menu
  28. export (String, FILE) var testing_area_scene
  29. export (String, FILE) var space_level_scene
  30. export (String, FILE) var ruins_level_scene
  31. func _ready():
  32. start_menu = $Start_Menu
  33. level_select_menu = $Level_Select_Menu
  34. options_menu = $Options_Menu
  35. $Start_Menu/Button_Start.connect("pressed", self, "start_menu_button_pressed", ["start"])
  36. $Start_Menu/Button_Open_Godot.connect("pressed", self, "start_menu_button_pressed", ["open_godot"])
  37. $Start_Menu/Button_Options.connect("pressed", self, "start_menu_button_pressed", ["options"])
  38. $Start_Menu/Button_Quit.connect("pressed", self, "start_menu_button_pressed", ["quit"])
  39. $Level_Select_Menu/Button_Back.connect("pressed", self, "level_select_menu_button_pressed", ["back"])
  40. $Level_Select_Menu/Button_Level_Testing_Area.connect("pressed", self, "level_select_menu_button_pressed", ["testing_scene"])
  41. $Level_Select_Menu/Button_Level_Space.connect("pressed", self, "level_select_menu_button_pressed", ["space_level"])
  42. $Level_Select_Menu/Button_Level_Ruins.connect("pressed", self, "level_select_menu_button_pressed", ["ruins_level"])
  43. $Options_Menu/Button_Back.connect("pressed", self, "options_menu_button_pressed", ["back"])
  44. $Options_Menu/Button_Fullscreen.connect("pressed", self, "options_menu_button_pressed", ["fullscreen"])
  45. $Options_Menu/Check_Button_VSync.connect("pressed", self, "options_menu_button_pressed", ["vsync"])
  46. $Options_Menu/Check_Button_Debug.connect("pressed", self, "options_menu_button_pressed", ["debug"])
  47. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  48. var globals = get_node("/root/Globals")
  49. $Options_Menu/HSlider_Mouse_Sensitivity.value = globals.mouse_sensitivity
  50. $Options_Menu/HSlider_Joypad_Sensitivity.value = globals.joypad_sensitivity
  51. func start_menu_button_pressed(button_name):
  52. if button_name == "start":
  53. level_select_menu.visible = true
  54. start_menu.visible = false
  55. elif button_name == "open_godot":
  56. OS.shell_open("https://godotengine.org/")
  57. elif button_name == "options":
  58. options_menu.visible = true
  59. start_menu.visible = false
  60. elif button_name == "quit":
  61. get_tree().quit()
  62. func level_select_menu_button_pressed(button_name):
  63. if button_name == "back":
  64. start_menu.visible = true
  65. level_select_menu.visible = false
  66. elif button_name == "testing_scene":
  67. set_mouse_and_joypad_sensitivity()
  68. get_node("/root/Globals").load_new_scene(testing_area_scene)
  69. elif button_name == "space_level":
  70. set_mouse_and_joypad_sensitivity()
  71. get_node("/root/Globals").load_new_scene(space_level_scene)
  72. elif button_name == "ruins_level":
  73. set_mouse_and_joypad_sensitivity()
  74. get_node("/root/Globals").load_new_scene(ruins_level_scene)
  75. func options_menu_button_pressed(button_name):
  76. if button_name == "back":
  77. start_menu.visible = true
  78. options_menu.visible = false
  79. elif button_name == "fullscreen":
  80. OS.window_fullscreen = !OS.window_fullscreen
  81. elif button_name == "vsync":
  82. OS.vsync_enabled = $Options_Menu/Check_Button_VSync.pressed
  83. elif button_name == "debug":
  84. pass
  85. func set_mouse_and_joypad_sensitivity():
  86. var globals = get_node("/root/Globals")
  87. globals.mouse_sensitivity = $Options_Menu/HSlider_Mouse_Sensitivity.value
  88. globals.joypad_sensitivity = $Options_Menu/HSlider_Joypad_Sensitivity.value
  89. Most of the code here relates to making UIs, which is outside of the purpose of this tutorial series.
  90. **We're only going to look at the UI related code briefly.**
  91. .. tip:: See :ref:`doc_ui_main_menu` and the tutorials following for better ways to make GUIs and UIs!
  92. Let's look at the global variables first.
  93. * ``start_menu``: A variable to hold the ``Start_Menu`` :ref:`Panel <class_Panel>`.
  94. * ``level_select_menu``: A variable to hold the ``Level_Select_Menu`` :ref:`Panel <class_Panel>`.
  95. * ``options_menu``: A variable to hold the ``Options_Menu`` :ref:`Panel <class_Panel>`.
  96. * ``testing_area_scene``: The path to the ``Testing_Area.tscn`` file, so we can change to it from this scene.
  97. * ``space_level_scene``: The path to the ``Space_Level.tscn`` file, so we can change to it from this scene.
  98. * ``ruins_level_scene``: The path to the ``Ruins_Level.tscn`` file, so we can change to it from this scene.
  99. .. warning:: You'll have to set the paths to the correct files in the editor before testing this script! Otherwise it will not work!
  100. ______
  101. Now let's go over ``_ready``
  102. First we get all of the :ref:`Panel <class_Panel>` nodes and assign them to the proper variables.
  103. Next we connect all of the buttons ``pressed`` signals to their respective ``[panel_name_here]_button_pressed`` functions.
  104. We then set the mouse mode to ``MOUSE_MODE_VISIBLE`` to ensure whenever we return to this scene our mouse will be visible.
  105. Then we get a singleton, called ``Globals``. We then set the values for the :ref:`HSlider <class_HSlider>` nodes so their values line up with the mouse and joypad sensitivity
  106. in the singleton.
  107. .. note:: We have not made the ``Globals`` singleton yet, so don't worry! We're going to make it soon!
  108. ______
  109. In ``start_menu_pressed``, we check to see which button is pressed.
  110. Based on the button pressed, we either change the currently visible panel, quit the application, or open the Godot engine website.
  111. ______
  112. In ``level_select_menu_button_pressed``, we check to see which button is pressed.
  113. If the ``back`` button has been pressed, we change the currently visible panels so we return to the main menu.
  114. If one of the scene changing buttons are pressed, we fist call ``set_mouse_and_joypad_sensitivity`` so our singleton has the values from the :ref:`HSlider <class_HSlider>` nodes.
  115. Then we tell the singleton to change nodes using it's ``load_new_scene`` function, passing in the file path of the scene we're wanting to change to.
  116. .. note:: Don't worry about the singleton, we'll get there soon!
  117. ______
  118. In ``options_menu_button_pressed``, we check to see which button is pressed.
  119. If the ``back`` button has been pressed, we change the currently visible panels so we return to the main menu.
  120. If the ``fullscreen`` button is pressed we toggle the :ref:`OS <class_OS>`'s full screen mode by setting it to the flipped version of it's current value.
  121. If the ``vsync`` button is pressed we set the :ref:`OS <class_OS>`'s Vsync based on the state of the Vsync check button.
  122. ______
  123. Finally, lets take a look at ``set_mouse_and_joypad_sensitivity``.
  124. First we get the ``Globals`` singleton and assign it to a local variable.
  125. We then set the ``mouse_sensitivity`` and ``joypad_sensitvity`` variables to the values in their respective :ref:`HSlider <class_HSlider>` node counterparts.
  126. Making the ``Globals`` singleton
  127. --------------------------------
  128. Now, for this all to work we need to create the ``Globals`` singleton. Make a new script in the ``Script`` tab and call it ``Globals.gd``.
  129. Add the following to ``Globals.gd``.
  130. ::
  131. extends Node
  132. var mouse_sensitivity = 0.08
  133. var joypad_sensitivity = 2
  134. func _ready():
  135. pass
  136. func load_new_scene(new_scene_path):
  137. get_tree().change_scene(new_scene_path)
  138. As you can see, it's quite small and simple. As this part progresses we will
  139. keeping adding complexities to ``Global.gd``, but for now all it is doing is holding two variables for us, and abstracting how we change scenes.
  140. * ``mouse_sensitivity``: The current sensitivity for our mouse, so we can load it in ``Player.gd``.
  141. * ``joypad_sensitivity``: The current sensitivity for our joypad, so we can load it in ``Player.gd``.
  142. Right now all we're using ``Globals.gd`` for is a way to carry variables across scenes. Because the sensitivity for our mouse and joypad are
  143. stored in ``Globals.gd``, any changes we make in one scene (like ``Main_Menu``) effect the sensitivity for our player.
  144. All we're doing in ``load_new_scene`` is calling :ref:`SceneTree <class_SceneTree>`'s ``change_scene`` function, passing in the scene path given in ``load_new_scene``.
  145. That's all of the code needed for ``Globals.gd`` right now! Before we can test the main menu, we first need to set ``Globals.gd`` as an autoload script.
  146. Open up the project settings and click the ``AutoLoad`` tab.
  147. .. image:: img/AutoloadAddSingleton.png
  148. Then select the path to ``Globals.gd`` in the ``Path`` field by clicking the button beside it. Make sure the name in the ``Node Name`` field is ``Globals``. If you
  149. have everything like the picture above, then press ``Add``!
  150. This will make ``Globals.gd`` a singleton/autoload script, which will allow us to access it from anywhere in any scene.
  151. .. tip:: For more information on singleton/autoload scripts, see :ref:`doc_singletons_autoload`.
  152. Now that ``Globals.gd`` is a singleton/autoload script, you can test the main menu!
  153. You may also want to change the main scene from ``Testing_Area.tscn`` to ``Main_Menu.tscn`` so when we export the game we start at the main menu. You can do this
  154. through the project settings, under the ``General`` tab. Then in the ``Application`` category, click the ``Run`` subcategory and you can change the main scene by changing
  155. the value in ``Main Scene``.
  156. .. warning:: You'll have to set the paths to the correct files in ``Main_Menu`` in the editor before testing the main menu!
  157. Otherwise you will not be able to change scenes from the level select menu/screen.
  158. Adding the debug menu
  159. ---------------------
  160. Now let's add a simple debugging scene so we can track things like FPS in game. Open up ``Debug_Display.tscn``.
  161. You can see it's a :ref:`Panel <class_Panel>` positioned in the top right corner of the screen. It has three :ref:`Labels <class_Label>`,
  162. one for displaying the FPS the game is running at, one for showing what OS the game is running on, and a label for showing the Godot version the game is running with.
  163. Let's add the code needed to fill these :ref:`Labels <class_Label>`. Select ``Debug_Display`` and create a new script called ``Debug_Display.gd``. Add the following:
  164. ::
  165. extends Control
  166. func _ready():
  167. $OS_Label.text = "OS:" + OS.get_name()
  168. $Engine_Label.text = "Godot version:" + Engine.get_version_info()["string"]
  169. func _process(delta):
  170. $FPS_Label.text = "FPS:" + str(Engine.get_frames_per_second())
  171. Let's go over what this script does.
  172. ______
  173. In ``_ready`` we set the ``OS_Label``'s text to the name provided in :ref:`OS <class_OS>` using the ``get_name`` function. This will return the
  174. name of the OS (or Operating System) that Godot was compiled for. For example, when you are running Windows it will return ``Windows``, while when you
  175. are running Linux it will return ``X11``.
  176. Then we set the ``Engine_Label``'s text to the version info provided by ``Engine.get_version_info``. ``Engine.get_version_info`` returns a dictionary full
  177. of useful information about the version Godot is currently running with. We only care for the string version for the purposes of this display, so we get the string
  178. and assign that as the ``text`` in ``Engine_Label``. See :ref:`Engine <class_Engine>` for more information on the values ``get_version_info`` returns.
  179. In ``_process`` we set the text of the ``FPS_Label`` to ``Engine.get_frames_per_second``, but because ``get_frames_per_second`` returns a integer, we have to cast
  180. it to a string using ``str`` before we can add it to our label.
  181. ______
  182. Now let's jump back to ``Main_Menu.gd`` and change the following in ``options_menu_button_pressed``:
  183. ::
  184. elif button_name == "debug":
  185. pass
  186. to this instead:
  187. ::
  188. elif button_name == "debug":
  189. get_node("/root/Globals").set_debug_display($Options_Menu/Check_Button_Debug.pressed)
  190. This will call a new function in our singleton called ``set_debug_display``, so let's add that next!
  191. ______
  192. Open up ``Globals.gd`` and add the following global variables:
  193. ::
  194. # ------------------------------------
  195. # All of the GUI/UI related variables
  196. var canvas_layer = null
  197. const DEBUG_DISPLAY_SCENE = preload("res://Debug_Display.tscn")
  198. var debug_display = null
  199. # ------------------------------------
  200. * ``canvas_layer``: A canvas layer so our GUI/UI is always drawn on top.
  201. * ``DEBUG_DISPLAY``: The debug display scene we worked on earlier.
  202. * ``debug_display``: A variable to hold the debug display when there is one.
  203. Now that we have our global variables defined, we need to add a few lines to ready so we have a canvas layer to use in ``canvas_layer``.
  204. Change ``_ready`` to the following:
  205. ::
  206. func _ready():
  207. canvas_layer = CanvasLayer.new()
  208. add_child(canvas_layer)
  209. Now in ``_ready`` we're creating a new canvas layer and adding it as a child of the autoload script.
  210. The reason we're adding a :ref:`CanvasLayer <class_CanvasLayer>` is so all of our GUI and UI nodes we instance/spawn in ``Globals.gd``
  211. are always drawn on top of everything else.
  212. When adding nodes to a singleton/autoload, you have to be careful not to lose reference to any of the child nodes.
  213. This is because nodes will not be freed/destroyed when you change scene, meaning you can run into memory problems if you are
  214. instancing/spawning lots of nodes and are not freeing them.
  215. ______
  216. Now we need to add ``set_debug_display`` to ``Globals.gd``:
  217. ::
  218. func set_debug_display(display_on):
  219. if display_on == false:
  220. if debug_display != null:
  221. debug_display.queue_free()
  222. debug_display = null
  223. else:
  224. if debug_display == null:
  225. debug_display = DEBUG_DISPLAY_SCENE.instance()
  226. canvas_layer.add_child(debug_display)
  227. Let's go over what's happening.
  228. First we check to see if we're trying to turn on the debug display, or turn it off.
  229. If we are turning off the display, we then check to see if ``debug_display`` is not equal to ``null``. If ``debug_display`` is not equal to ``null``, then we
  230. most have a debug display currently active. If we have a debug display active, we free it using ``queue_free`` and then assign ``debug_display`` to ``null``.
  231. If we are turning on the display, we then check to make sure we do not already have a debug display active. We do this by making sure ``debug_display`` is equal to ``null``.
  232. If ``debug_display`` is ``null``, we instance a new ``DEBUG_DISPLAY_SCENE``, and add it as a child of ``canvas_layer``.
  233. ______
  234. With that done, we can now toggle the debug display on and off by switching the :ref:`CheckButton <class_CheckButton>` in the ``Options_Menu`` panel. Go give it a try!
  235. Notice how the debug display stays even when you change scenes from the ``Main_Menu.tscn`` to another scene (like ``Testing_Area.tscn``). This is the beauty of
  236. instancing/spawning nodes in a singleton/autoload and adding them as children to the singleton/autoload. Any of the nodes added as children of the singleton/autoload will
  237. stay for as long as the game is running, without any additional work on our part!
  238. Adding a pause menu
  239. -------------------
  240. Let's add a pause menu so we can return to the main menu when we press the ``ui_cancel`` action.
  241. Open up ``Pause_Popup.tscn``.
  242. Notice how the root node in ``Pause_Popup`` is a :ref:`WindowDialog <class_WindowDialog>`. :ref:`WindowDialog <class_WindowDialog>` inherits from
  243. :ref:`Popup <class_Popup>`, which means :ref:`WindowDialog <class_WindowDialog>` can act like a popup.
  244. Select ``Pause_Popup`` and scroll down all the way till you get to the ``Pause`` menu in the inspector. Notice how the pause mode is set to
  245. ``process`` instead of ``inherit`` like it is normally set by default. This makes it where it will continue to process even when the game is paused,
  246. which we need in order to interact with the UI elements.
  247. Now that we've looked at how ``Pause_Popup.tscn`` is set up, lets write the code to make it work. Normally we'd attach a script to the root node of
  248. the scene, ``Pause_Popup`` in this case, but since we'll need to receive a couple of signals in ``Globals.gd``, we'll write all of the code for
  249. the pop up there.
  250. Open up ``Globals.gd`` and add the following global variables:
  251. ::
  252. const MAIN_MENU_PATH = "res://Main_Menu.tscn"
  253. const POPUP_SCENE = preload("res://Pause_Popup.tscn")
  254. var popup = null
  255. * ``MAIN_MENU_PATH``: The path to the main menu scene.
  256. * ``POPUP_SCENE``: The pop up scene we looked at earlier.
  257. * ``popup``: A variable to hold the pop up scene.
  258. Now we need to add ``_process`` to ``Globals.gd`` so we can respond when the ``ui_cancel`` action is pressed.
  259. Add the following to ``_process``:
  260. ::
  261. func _process(delta):
  262. if Input.is_action_just_pressed("ui_cancel"):
  263. if popup == null:
  264. popup = POPUP_SCENE.instance()
  265. popup.get_node("Button_quit").connect("pressed", self, "popup_quit")
  266. popup.connect("popup_hide", self, "popup_closed")
  267. popup.get_node("Button_resume").connect("pressed", self, "popup_closed")
  268. canvas_layer.add_child(popup)
  269. popup.popup_centered()
  270. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  271. get_tree().paused = true
  272. Let's go over what's happening here.
  273. ______
  274. First we check to see if the ``ui_cancel`` action is pressed. Then we check to make sure we do not already
  275. have a ``popup`` open by checking to see if ``popup`` is equal to ``null``.
  276. If we do not have a pop up open, we instance ``POPUP_SCENE`` and assign it to ``popup``.
  277. We then get the quit button and assign it's ``pressed`` signal to ``popup_quit``, which we will be adding shortly.
  278. Next we assign both the ``popup_hide`` signal from the :ref:`WindowDialog <class_WindowDialog>` and the ``pressed`` signal from the resume button
  279. to ``popup_closed``, which we will be adding shortly.
  280. Then we add ``popup`` as a child of ``canvas_layer`` so it's drawn on top. We then tell ``popup`` to pop up at the center of the screen using ``popup_centered``.
  281. Next we make sure the mouse mode is ``MOUSE_MODE_VISIBLE`` to we can interact with the pop up. If we did not do this, we would not be able to interact with the pop up
  282. in any scene where the mouse mode is ``MOUSE_MODE_CAPTURED``.
  283. Finally, get pause the entire :ref:`SceneTree <class_SceneTree>`.
  284. .. note:: For more information on pausing in Godot, see :ref:`doc_pausing_games`
  285. ______
  286. Now we need to add the functions we've connected the signals to. Let's add ``popup_closed`` first.
  287. Add the following to ``Globals.gd``:
  288. ::
  289. func popup_closed():
  290. get_tree().paused = false
  291. if popup != null:
  292. popup.queue_free()
  293. popup = null
  294. ``popup_closed`` will resume the game and destroy the pop up if there is one.
  295. ``popup_quit`` is similar, but we're also making sure the mouse is visible and changing scenes to the title screen.
  296. Add the following to ``Globals.gd``:
  297. ::
  298. func popup_quit():
  299. get_tree().paused = false
  300. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  301. if popup != null:
  302. popup.queue_free()
  303. popup = null
  304. load_new_scene(MAIN_MENU_PATH)
  305. ``popup_quit`` will resume the game, set the mouse mode to ``MOUSE_MODE_VISIBLE`` to ensure the mouse is visible in the main menu, destroy
  306. the pop up if there is one, and change scenes to the main menu.
  307. ______
  308. Before we're ready to test the pop up, we should change one thing in ``Player.gd``.
  309. Open up ``Player.gd`` and in ``process_input``, change the code for capturing/freeing the cursor to the following:
  310. ::
  311. if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
  312. Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  313. Now instead of capturing/freeing the mouse, we check to see if the current mouse mode is ``MOUSE_MODE_VISIBLE``. If it is, we set it back to
  314. ``MOUSE_MODE_CAPTURED``.
  315. Because the pop up makes the mouse mode ``MOUSE_MODE_VISIBLE`` whenever you pause, we no longer have to worry about freeing the cursor in ``Player.gd``.
  316. ______
  317. Now the pause menu pop up is finished. You can now pause at any point in the game and return to the main menu!
  318. Starting the respawn system
  319. ---------------------------
  320. Since our player can lose all their health, it would be ideal if our players died and respawned too, so let's add that!
  321. First, open up ``Player.tscn`` and expand ``HUD``. Notice how there's a :ref:`ColorRect <class_ColorRect>` called ``Death_Screen``.
  322. When the player dies, we're going to make ``Death_Screen`` visible, and show them how long they have to wait before they're able to respawn.
  323. Open up ``Player.gd`` and add the following global variables:
  324. ::
  325. const RESPAWN_TIME = 4
  326. var dead_time = 0
  327. var is_dead = false
  328. var globals
  329. * ``RESPAWN_TIME``: The amount of time (in seconds) it takes to respawn.
  330. * ``dead_time``: A variable to track how long the player has been dead.
  331. * ``is_dead``: A variable to track whether or not the player is currently dead.
  332. * ``globals``: A variable to hold the ``Globals.gd`` singleton.
  333. ______
  334. We now need to add a couple lines to ``_ready``, so we can use ``Globals.gd``. Add the following to ``_ready``:
  335. ::
  336. globals = get_node("/root/Globals")
  337. global_transform.origin = globals.get_respawn_position()
  338. Now we're getting the ``Globals.gd`` singleton and assigning it to ``globals``. We also set our global position
  339. using the origin from our global :ref:`Transform <class_Transform>` to the position returned by ``globals.get_respawn_position``.
  340. .. note:: Don't worry, we'll add ``get_respawn_position`` further below!
  341. ______
  342. Next we need to make a few changes to ``physics_process``. Change ``physics_processing`` to the following:
  343. ::
  344. func _physics_process(delta):
  345. if !is_dead:
  346. process_input(delta)
  347. process_view_input(delta)
  348. process_movement(delta)
  349. if (grabbed_object == null):
  350. process_changing_weapons(delta)
  351. process_reloading(delta)
  352. process_UI(delta)
  353. process_respawn(delta)
  354. Now we're not processing input or movement input when we're dead. We're also now calling ``process_respawn``, but we haven't written
  355. ``process_respawn`` yet, so let's change that.
  356. ______
  357. Let's add ``process_respawn``. Add the following to ``Player.gd``:
  358. ::
  359. func process_respawn(delta):
  360. # If we just died
  361. if health <= 0 and !is_dead:
  362. $Body_CollisionShape.disabled = true
  363. $Feet_CollisionShape.disabled = true
  364. changing_weapon = true
  365. changing_weapon_name = "UNARMED"
  366. $HUD/Death_Screen.visible = true
  367. $HUD/Panel.visible = false
  368. $HUD/Crosshair.visible = false
  369. dead_time = RESPAWN_TIME
  370. is_dead = true
  371. if grabbed_object != null:
  372. grabbed_object.mode = RigidBody.MODE_RIGID
  373. grabbed_object.apply_impulse(Vector3(0,0,0), -camera.global_transform.basis.z.normalized() * OBJECT_THROW_FORCE / 2)
  374. grabbed_object.collision_layer = 1
  375. grabbed_object.collision_mask = 1
  376. grabbed_object = null
  377. if is_dead:
  378. dead_time -= delta
  379. var dead_time_pretty = str(dead_time).left(3)
  380. $HUD/Death_Screen/Label.text = "You died\n" + dead_time_pretty + " seconds till respawn"
  381. if dead_time <= 0:
  382. global_transform.origin = globals.get_respawn_position()
  383. $Body_CollisionShape.disabled = false
  384. $Feet_CollisionShape.disabled = false
  385. $HUD/Death_Screen.visible = false
  386. $HUD/Panel.visible = true
  387. $HUD/Crosshair.visible = true
  388. for weapon in weapons:
  389. var weapon_node = weapons[weapon]
  390. if weapon_node != null:
  391. weapon_node.reset_weapon()
  392. health = 100
  393. grenade_amounts = {"Grenade":2, "Sticky Grenade":2}
  394. current_grenade = "Grenade"
  395. is_dead = false
  396. Let's go through what this function is doing.
  397. ______
  398. First we check to see if we just died by checking to see if ``health`` is equal or less than ``0`` and ``is_dead`` is ``false``.
  399. If we just died, we disable our collision shapes for the player. We do this to make sure we're not blocking anything with our dead body.
  400. We next set ``changing_weapon`` to ``true`` and set ``changing_weapon_name`` to ``UNARMED``. This is so if we are using a weapon, we put it away
  401. when we die.
  402. We then make the ``Death_Screen`` :ref:`ColorRect <class_ColorRect>` visible so we get a nice grey overlay over everything. We then make the rest of the UI,
  403. the ``Panel`` and ``Crosshair`` nodes, invisible.
  404. Next we set ``dead_time`` to ``RESPAWN_TIME`` so we can start counting down how long we've been dead. We also set ``is_dead`` to ``true`` so we know we've died.
  405. If we are holding an object when we died, we need to throw it. We first check to see if we are holding an object or not. If we are, we then throw it,
  406. using the same code as the throwing code we added in :ref:`doc_fps_tutorial_part_five`.
  407. ______
  408. Then we check to see if we are dead. If we are, we then remove ``delta`` from ``dead_time``.
  409. We then make a new variable called ``dead_time_pretty``, where we convert ``dead_time`` to a string, using only the first three characters starting from the left. This gives
  410. us a nice looking string showing how much time we have left to wait before we respawn.
  411. We then change the :ref:`Label <class_Label>` in ``Death_Screen`` to show how much time we have left.
  412. Next we check to see if we've waited long enough and can respawn. We do this by checking to see if ``dead_time`` is ``0`` or less.
  413. If we have waited long enough to respawn, we set the player's position to a new respawn position provided by ``get_respawn_position``.
  414. We then enable both of our collision shapes so the player can collide with the environment.
  415. Next we make the ``Death_Screen`` invisible and make the rest of the UI, the ``Panel`` and ``Crosshair`` nodes, visible again.
  416. We then go through each weapon and call it's ``reset_weapon`` function. We'll add ``reset_weapon`` soon.
  417. Then we reset ``health`` to ``100``, ``grenade_amounts`` to it's default values, and change ``current_grenade`` to ``Grenade``.
  418. Finally, we set ``is_dead`` to ``false``.
  419. ______
  420. Before we leave ``Player.gd``, we need to add one quick thing to ``_input``. Add the following at the beginning of ``_input``:
  421. ::
  422. if is_dead:
  423. return
  424. Now when we're dead we cannot look around with the mouse.
  425. Finishing the respawn system
  426. ----------------------------
  427. First let's open ``Weapon_Pistol.gd`` and add the ``reset_weapon`` function. Add the following:
  428. ::
  429. func reset_weapon():
  430. ammo_in_weapon = 10
  431. spare_ammo = 20
  432. Now when we call ``reset_weapon``, the ammo in our weapon and the ammo in the spares will be reset to their default values.
  433. Now let's add ``reset_weapon`` in ``Weapon_Rifle.gd``:
  434. ::
  435. func reset_weapon():
  436. ammo_in_weapon = 50
  437. spare_ammo = 100
  438. And add the following to ``Weapon_Knife.gd``:
  439. ::
  440. func reset_weapon():
  441. ammo_in_weapon = 1
  442. spare_ammo = 1
  443. Now our weapons will reset when we die.
  444. ______
  445. Now we need to add a few things to ``Globals.gd``. First, add the following global variable:
  446. ::
  447. var respawn_points = null
  448. * ``respawn_points``: A variable to hold all of the respawn points in a level
  449. Because we're getting a random spawn point each time, we need to randomize the number generator. Add the following to ``_ready``:
  450. ::
  451. randomize()
  452. ``randomize`` will get us a new random seed so we get a (relatively) random string of numbers when we using any of the random functions.
  453. Now let's add ``get_respawn_position`` to ``Globals.gd``:
  454. ::
  455. func get_respawn_position():
  456. if respawn_points == null:
  457. return Vector3(0, 0, 0)
  458. else:
  459. var respawn_point = rand_range(0, respawn_points.size()-1)
  460. return respawn_points[respawn_point].global_transform.origin
  461. Let's go over what this function does.
  462. ______
  463. First we check to see if we have any ``respawn_points`` by checking to see if ``respawn_points`` is ``null`` or not.
  464. If ``respawn_points`` is ``null``, we return a position of empty :ref:`Vector 3 <class_Vector3>` with the position ``(0, 0, 0)``.
  465. If ``respawn_points`` is not ``null``, we then get a random number between ``0`` and the number of elements we have in ``respawn_points``, minus ``1`` since
  466. most programming languages (including ``GDScript``) start counting from ``0`` when you are accessing elements in a list.
  467. We then return the position of the :ref:`Spatial <class_Spatial>` node at ``respawn_point`` position in ``respawn_points``.
  468. ______
  469. Before we're done with ``Globals.gd``. We need to add the following to ``load_new_scene``:
  470. ::
  471. respawn_points = null
  472. We set ``respawn_points`` to ``null`` so when/if we get to a level with no respawn points, we do not respawn
  473. at the respawn points in the level prior.
  474. ______
  475. Now all we need is a way to set the respawn points. Open up ``Ruins_Level.tscn`` and select ``Spawn_Points``. Add a new script called
  476. ``Respawn_Point_Setter.gd`` and attach it to ``Spawn_Points``. Add the following to ``Respawn_Point_Setter.gd``:
  477. ::
  478. extends Spatial
  479. func _ready():
  480. var globals = get_node("/root/Globals")
  481. globals.respawn_points = get_children()
  482. Now when a node with ``Respawn_Point_Setter.gd`` has it's ``_ready`` function called, all of the children
  483. nodes of the node with ``Respawn_Point_Setter.gd``, ``Spawn_Points`` in the case of ``Ruins_Level.tscn``, will be added
  484. to ``respawn_points`` in ``Globals.gd``.
  485. .. warning:: Any node with ``Respawn_Point_Setter.gd`` has to be above the player in the :ref:`SceneTree <class_SceneTree>` so the respawn points are set
  486. before the player needs them in the player's ``_ready`` function.
  487. ______
  488. Now when you die you'll respawn after waiting ``4`` seconds!
  489. .. note:: No spawn points are already set up for any of the levels besides ``Ruins_Level.tscn``!
  490. Adding spawn points to ``Space_Level.tscn`` is left as an exercise for the reader.
  491. Writing a sound system we can use anywhere
  492. ------------------------------------------
  493. Finally, lets make a sound system so we can play sounds from anywhere, without having to use the player.
  494. First, open up ``SimpleAudioPlayer.gd`` and change it to the following:
  495. ::
  496. extends Spatial
  497. var audio_node = null
  498. var should_loop = false
  499. var globals = null
  500. func _ready():
  501. audio_node = $Audio_Stream_Player
  502. audio_node.connect("finished", self, "sound_finished")
  503. audio_node.stop()
  504. globals = get_node("/root/Globals")
  505. func play_sound(audio_stream, position=null):
  506. if audio_stream == null:
  507. print ("No audio stream passed, cannot play sound")
  508. globals.created_audio.remove(globals.created_audio.find(self))
  509. queue_free()
  510. return
  511. audio_node.stream = audio_stream
  512. # If you are using a AudioPlayer3D, then uncomment these lines to set the position.
  513. # if position != null:
  514. # audio_node.global_transform.origin = position
  515. audio_node.play(0.0)
  516. func sound_finished():
  517. if should_loop:
  518. audio_node.play(0.0)
  519. else:
  520. globals.created_audio.remove(globals.created_audio.find(self))
  521. audio_node.stop()
  522. queue_free()
  523. There's several changes from the old version, first and foremost being we're no longer storing the sound files in ``SimpleAudioPlayer.gd`` anymore.
  524. This is much better for performance since we're no longer loading each audio clip when we create a sound, but instead we're forcing a audio stream to be passed
  525. in to ``play_sound``.
  526. Another change is we have a new global variable called ``should_loop``. Instead of just destroying the audio player every time it's finished, we instead want check to
  527. see if we are set to loop or not. This allows us to have audio like looping background music without having to spawn a new audio player with the music
  528. when the old one is finished.
  529. Finally, instead of being instanced/spawned in ``Player.gd``, we're instead going to be spawned in ``Globals.gd`` so we can create sounds from any scene. We now need
  530. to store the ``Globals.gd`` singleton so when we destroy the audio player, we also remove it from a list in ``Globals.gd``.
  531. Let's go over the changes.
  532. ______
  533. For the global variables
  534. we removed all of the ``audio_[insert name here]`` variables since we will instead have these passed in to.
  535. We also added two new global variables, ``should_loop`` and ``globals``. We'll use ``should_loop`` to tell whether we want to loop when the sound has
  536. finished, and ``globals`` will hold the ``Globals.gd`` singleton.
  537. The only change in ``_ready`` is now we're getting the ``Globals.gd`` singleton and assigning it to ``globals``
  538. In ``play_sound`` we now expect a audio stream, named ``audio_stream``, to be passed in, instead of ``sound_name``. Instead of checking the
  539. sound name and setting the stream for the audio player, we instead check to make sure an audio stream was passed in. If a audio stream is not passed
  540. in, we print an error message, remove the audio player from a list in the ``Globals.gd`` singleton called ``created_audio``, and then free the audio player.
  541. Finally, in ``sound_finished`` we first check to see if we are supposed to loop or not using ``should_loop``. If we are supposed to loop, we play the sound
  542. again from the start of the audio, at position ``0.0``. If we are not supposed to loop, we remove the audio player from a list in the ``Globals.gd`` singleton
  543. called ``created_audio``, and then free the audio player.
  544. ______
  545. Now that we've finished our changes to ``SimpleAudioPlayer.gd``, we now need to turn our attention to ``Globals.gd``. First, add the following global variables:
  546. ::
  547. # ------------------------------------
  548. # All of the audio files.
  549. # You will need to provide your own sound files.
  550. var audio_clips = {
  551. "pistol_shot":null, #preload("res://path_to_your_audio_here!")
  552. "rifle_shot":null, #preload("res://path_to_your_audio_here!")
  553. "gun_cock":null, #preload("res://path_to_your_audio_here!")
  554. }
  555. const SIMPLE_AUDIO_PLAYER_SCENE = preload("res://Simple_Audio_Player.tscn")
  556. var created_audio = []
  557. # ------------------------------------
  558. Lets go over these global variables.
  559. * ``audio_clips``: A dictionary holding all of the audio clips we can play.
  560. * ``SIMPLE_AUDIO_PLAYER_SCENE``: The simple audio player scene.
  561. * ``created_audio``: A list to hold all of the simple audio players we create
  562. .. note:: If you want to add additional audio, you need to add it to ``audio_clips``. No audio files are provided in this tutorial,
  563. so you will have to provide your own.
  564. One site I'd recommend is **GameSounds.xyz**.
  565. I'm using the Gamemaster audio gun sound pack included in the Sonniss' GDC Game Audio bundle for 2017.
  566. The tracks I've used (with some minor editing) are as follows:
  567. * gun_revolver_pistol_shot_04,
  568. * gun_semi_auto_rifle_cock_02,
  569. * gun_submachine_auto_shot_00_automatic_preview_01
  570. ______
  571. Now we need to add a new function called ``play_sound`` to ``Globals.gd``:
  572. ::
  573. func play_sound(sound_name, loop_sound=false, sound_position=null):
  574. if audio_clips.has(sound_name):
  575. var new_audio = SIMPLE_AUDIO_PLAYER_SCENE.instance()
  576. new_audio.should_loop = loop_sound
  577. add_child(new_audio)
  578. created_audio.append(new_audio)
  579. new_audio.play_sound(audio_clips[sound_name], sound_position)
  580. else:
  581. print ("ERROR: cannot play sound that does not exist in audio_clips!")
  582. Let's go over what this script does.
  583. First we check to see if we have a audio clip with the name ``sound_name`` in ``audio_clips``. If we do not, we print an error message.
  584. If we do have a audio clip with the name ``sound_name``, we then instance/spawn a new ``SIMPLE_AUDIO_PLAYER_SCENE`` and assign it to ``new_audio``.
  585. We then set ``should_loop``, and add ``new_audio`` as a child of ``Globals.gd``.
  586. .. note:: Remember, we have to be careful adding nodes to a singleton, since these nodes will not be destroyed when changing scenes.
  587. We then call ``play_sound``, passing in the audio clip associated with ``sound_name``, and the sound position.
  588. ______
  589. Before we leave ``Globals.gd``, we need to add a few lines of code to ``load_new_scene`` so when we change scenes, we destroy all of the audio.
  590. Add the following to ``load_new_scene``:
  591. ::
  592. for sound in created_audio:
  593. if (sound != null):
  594. sound.queue_free()
  595. created_audio.clear()
  596. Now before we change scenes we go through each simple audio player in ``created_sounds`` and free/destroy them. Once we've gone through
  597. all of the sounds in ``created_audio``, we clear ``created_audio`` so it no longer holds any references to any of the previously created simple audio players.
  598. ______
  599. Let's change ``create_sound`` in ``Player.gd`` to use this new system. First, remove ``simple_audio_player`` from ``Player.gd``'s global variables, since we will
  600. no longer be directly instancing/spawning sounds from ``Player.gd``.
  601. Now, change ``create_sound`` to the following:
  602. ::
  603. func create_sound(sound_name, position=null):
  604. globals.play_sound(sound_name, false, position)
  605. Now whenever ``create_sound`` is called, we simply call ``play_sound`` in ``Globals.gd``, passing in all of the arguments we've revived.
  606. ______
  607. Now all of the sounds in our FPS can be played from anywhere. All we have to do is get the ``Globals.gd`` singleton, and call ``play_sound``, passing in the name of the sound
  608. we want to play, whether we want it to loop or not, and the position to play the sound from.
  609. For example, if you want to play an explosion sound when the grenades explode you'd need to add a new sound to ``audio_clips`` in ``Globals.gd``,
  610. get the ``Globals.gd`` singleton, and then you just need to add something like
  611. ``globals.play_sound("explosion", false, global_transform.origin)`` in the grenades
  612. ``_process`` function, right after the grenade damages all of the bodies within it's blast radius.
  613. Final notes
  614. -----------
  615. .. image:: img/FinishedTutorialPicture.png
  616. Now you have a fully working single player FPS!
  617. At this point you have a good base to build more complicated FPS games.
  618. .. warning:: If you ever get lost, be sure to read over the code again!
  619. You can download the finished project for the entire tutorial here: :download:`Godot_FPS_Part_6.zip <files/Godot_FPS_Finished.zip>`
  620. .. note:: The finished project source files contain the same exact code, just written in a different order.
  621. This is because the finished project source files are what the tutorial is based on.
  622. The finished project code was written in the order that features were created, not necessarily
  623. in a order that is ideal for learning.
  624. Other than that, the source is exactly the same, just with helpful comments explaining what
  625. each part does.
  626. .. tip:: The finished project source is hosted on Github as well: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial
  627. **Please note that the code in Github may or may not be in sync with the tutorial on the documentation**.
  628. The code in the documentation is likely better managed and/or more up to date.
  629. If you are unsure on which to use, use the project(s) provided in the documentation as they are maintained by the Godot community.
  630. You can download all of the ``.blend`` files used in this tutorial here: :download:`Godot_FPS_BlenderFiles.zip <files/Godot_FPS_BlenderFiles.zip>`
  631. All assets provided in the started assets (unless otherwise noted) were **originally created by TwistedTwigleg, with changes/additions by the Godot community.**
  632. All original assets provided for this tutorial are released under the ``MIT`` license.
  633. Feel free to use these assets however you want! All original assets belong to the Godot community, with the other assets belonging to those listed below:
  634. The skybox is created by **StumpyStrust** and can be found at OpenGameArt.org. https://opengameart.org/content/space-skyboxes-0
  635. . The skybox is licensed under the ``CC0`` license.
  636. The font used is **Titillium-Regular**, and is licensed under the ``SIL Open Font License, Version 1.1``.
  637. The skybox was convert to a 360 equirectangular image using this tool: https://www.360toolkit.co/convert-cubemap-to-spherical-equirectangular.html
  638. While no sounds are provided, you can find many game ready sounds at https://gamesounds.xyz/
  639. .. warning:: **OpenGameArt.org, 360toolkit.co, the creator(s) of Titillium-Regular, StumpyStrust, and GameSounds.xyz are in no way involved in this tutorial.**