part_four.rst 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. .. _doc_fps_tutorial_part_four:
  2. Part 4
  3. ======
  4. Part Overview
  5. -------------
  6. In this part we will be adding health pick ups, ammo pick ups, targets we can destroy, add support for joypads, and add the ability to change weapons with the scroll wheel.
  7. .. image:: img/PartFourFinished.png
  8. .. note:: You are assumed to have finished :ref:`doc_fps_tutorial_part_three` before moving on to this part of the tutorial.
  9. The finished project from :ref:`doc_fps_tutorial_part_three` will be the starting project for part 4
  10. Let's get started!
  11. Adding joypad input
  12. -------------------
  13. .. note:: In Godot any game controller is referred to as a joypad. This includes:
  14. Console controllers, Joysticks (like for flight simulators), Wheels (like for driving simulators), VR Controllers, and more.
  15. First we need to change a few things in our project's input map. Open up the project settings and select the ``Input Map`` tab.
  16. Now we need to add some joypad buttons to our various actions. Click the plus icon and select ``Joy Button``.
  17. .. image:: img/ProjectSettingsAddKey.png
  18. Feel free to use whatever button layout you want. Make sure that the device selected is set to ``0``. In the finished project, we will be using the following:
  19. * movement_sprint: ``Device 0, Button 4 (L, L1)``
  20. * fire: ``Device 0, Button 0 (PS Cross, XBox A, Nintendo B)``
  21. * reload: ``Device 0, Button 0 (PS Square, XBox X, Nintendo Y)``
  22. * flashlight: ``Device 0, Button 12 (D-Pad Up)``
  23. * shift_weapon_positive: ``Device 0, Button 15 (D-Pad Right)``
  24. * shift_weapon_negative: ``Device 0, Button 14 (D-Pad Right)``
  25. * fire_grenade: ``Device 0, Button 1 (PS Circle, XBox B, Nintendo A).``
  26. .. note:: These are already set up for you if you downloaded the starter assets
  27. Once you are happy with the input, close the project settings and save.
  28. ______
  29. Now let's open up ``Player.gd`` and add joypad input.
  30. First, we need to define a few new global variables. Add the following global variables to ``Player.gd``:
  31. ::
  32. # You may need to adjust depending on the sensitivity of your joypad
  33. var JOYPAD_SENSITIVITY = 2
  34. const JOYPAD_DEADZONE = 0.15
  35. Let's go over what each of these do:
  36. * ``JOYPAD_SENSITIVITY``: This is how fast our joypad joysticks will move our camera.
  37. * ``JOYPAD_DEADZONE``: The dead zone for the joypad. You may need to adjust depending on your joypad.
  38. .. note:: Many joypads jitter around a certain point. To counter this, we ignore any movement in a
  39. with a radius of JOYPAD_DEADZONE. If we did not ignore said movement, the camera will jitter.
  40. Also, we are defining ``JOYPAD_SENSITIVITY`` as a variable instead of a constant because we'll later be changing it.
  41. Now we are ready to start handling joypad input!
  42. ______
  43. In ``process_input`` add the following code, just before ``input_movement_vector = input_movement_vector.normalized()``:
  44. ::
  45. # Add joypad input, if there is a joypad
  46. if Input.get_connected_joypads().size() > 0:
  47. var joypad_vec = Vector2(0, 0)
  48. if OS.get_name() == "Windows":
  49. joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
  50. elif OS.get_name() == "X11":
  51. joypad_vec = Vector2(Input.get_joy_axis(0, 1), Input.get_joy_axis(0, 2))
  52. elif OS.get_name() == "OSX":
  53. joypad_vec = Vector2(Input.get_joy_axis(0, 1), Input.get_joy_axis(0, 2))
  54. if joypad_vec.length() < JOYPAD_DEADZONE:
  55. joypad_vec = Vector2(0, 0)
  56. else:
  57. joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))
  58. input_movement_vector += joypad_vec
  59. Let's go over what we're doing.
  60. First we check to see if there is a connected joypad.
  61. If there is a joypad connected, we then get its left stick axes for right/left and up/down.
  62. Because a wired Xbox 360 controller has different joystick axis mapping based on OS, we use different axes based on
  63. the OS.
  64. .. warning:: This tutorial assumes you are using a XBox 360 wired controller.
  65. Also, I do not (currently) have access to a Mac computer, so the joystick axes may need changing.
  66. If they do, please open a GitHub issue on the Godot documentation repository!
  67. Next we check to see if the joypad vector length is within the ``JOYPAD_DEADZONE`` radius.
  68. If it is, we set ``joypad_vec`` to an empty Vector2. If it is not, we use a scaled Radial Dead zone for precise dead zone calculating.
  69. .. note:: You can find a great article explaining all about how to handle joypad/controller dead zones here:
  70. http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html
  71. We're using a translated version of the scaled radial dead zone code provided in that article.
  72. The article is a great read, and I highly suggest giving it a look!
  73. Finally, we add ``joypad_vec`` to ``input_movement_vector``.
  74. .. tip:: Remember how we normalize ``input_movement_vector``? This is why! If we did not normalize ``input_movement_vector`` players could
  75. move faster if they are pushing in the same direction with both their keyboard and their joypad!
  76. ______
  77. Make a new function called ``process_view_input`` and add the following:
  78. ::
  79. func process_view_input(delta):
  80. if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
  81. return
  82. # NOTE: Until some bugs relating to captured mouses are fixed, we cannot put the mouse view
  83. # rotation code here. Once the bug(s) are fixed, code for mouse view rotation code will go here!
  84. # ----------------------------------
  85. # Joypad rotation
  86. var joypad_vec = Vector2()
  87. if Input.get_connected_joypads().size() > 0:
  88. if OS.get_name() == "Windows":
  89. joypad_vec = Vector2(Input.get_joy_axis(0, 2), Input.get_joy_axis(0, 3))
  90. elif OS.get_name() == "X11":
  91. joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
  92. elif OS.get_name() == "OSX":
  93. joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
  94. if joypad_vec.length() < JOYPAD_DEADZONE:
  95. joypad_vec = Vector2(0, 0)
  96. else:
  97. joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))
  98. rotation_helper.rotate_x(deg2rad(joypad_vec.y * JOYPAD_SENSITIVITY))
  99. rotate_y(deg2rad(joypad_vec.x * JOYPAD_SENSITIVITY * -1))
  100. var camera_rot = rotation_helper.rotation_degrees
  101. camera_rot.x = clamp(camera_rot.x, -70, 70)
  102. rotation_helper.rotation_degrees = camera_rot
  103. # ----------------------------------
  104. Let's go over what's happening:
  105. First we check the mouse mode. If the mouse mode is not ``MOUSE_MODE_CAPTURED``, we want to return, which will skip the code below.
  106. Next we define a new :ref:`Vector2 <class_Vector2>` called ``joypad_vec``. This will hold the right joystick position. Based on the OS, we set its values so
  107. it is mapped to the proper axes for the right joystick.
  108. .. warning:: As stated above, I do not (currently) has access to a Mac computer, so the joystick axes may need changing. If they do,
  109. please open a GitHub issue on the Godot documentation repository!
  110. We then account for the joypad's dead zone, exactly like in ``process_input``.
  111. Then we rotate ``rotation_helper`` and our KinematicBody using ``joypad_vec``.
  112. Notice how the code that handles rotating ourselves and ``rotation_helper`` is exactly the same as the
  113. code in ``_input``. All we've done is change the values to use ``joypad_vec`` and ``JOYPAD_SENSITIVITY``.
  114. .. note:: Due to few mouse related bugs on Windows, we cannot put mouse rotation in ``process_view`` as well.
  115. Once these bugs are fixed, this will likely be updated to place the mouse rotation here as well.
  116. Finally, we clamp the camera's rotation so we cannot look upside down.
  117. ______
  118. The last thing you need to do is add ``process_view_input`` to ``_physics_process``.
  119. Once ``process_view_input`` is added to ``_physics_process``, you should be able to play using a joypad!
  120. .. note:: I decided not to use the joypad triggers for firing because we'd then have to do some more axis managing, and because I prefer to use a shoulder button to fire.
  121. If you want to use the triggers for firing, you will need to change how firing works in ``process_input``. You need to get the proper axis value for the trigger,
  122. and check if it's over a certain value, say ``0.8`` for example. If it is, you add the same code as when the ``fire`` action was pressed.
  123. Adding mouse scroll wheel input
  124. -------------------------------
  125. Let's add one input related feature before we start working on the pick ups and target. Let's add the ability to change weapons using the scroll wheel on the mouse.
  126. Open up ``Player.gd`` and add the following global variables:
  127. ::
  128. var mouse_scroll_value = 0
  129. const MOUSE_SENSITIVITY_SCROLL_WHEEL = 0.08
  130. Let's go over what each of these new variables will be doing:
  131. * ``mouse_scroll_value``: The value of the mouse scroll wheel.
  132. * ``MOUSE_SENSITIVITY_SCROLL_WHEEL``: How much a single scroll action increases mouse_scroll_value
  133. ______
  134. Now let's add the following to ``_input``:
  135. ::
  136. if event is InputEventMouseButton and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
  137. if event.button_index == BUTTON_WHEEL_UP or event.button_index == BUTTON_WHEEL_DOWN:
  138. if event.button_index == BUTTON_WHEEL_UP:
  139. mouse_scroll_value += MOUSE_SENSITIVITY_SCROLL_WHEEL
  140. elif event.button_index == BUTTON_WHEEL_DOWN:
  141. mouse_scroll_value -= MOUSE_SENSITIVITY_SCROLL_WHEEL
  142. mouse_scroll_value = clamp(mouse_scroll_value, 0, WEAPON_NUMBER_TO_NAME.size()-1)
  143. if changing_weapon == false:
  144. if reloading_weapon == false:
  145. var round_mouse_scroll_value = int(round(mouse_scroll_value))
  146. if WEAPON_NUMBER_TO_NAME[round_mouse_scroll_value] != current_weapon_name:
  147. changing_weapon_name = WEAPON_NUMBER_TO_NAME[round_mouse_scroll_value]
  148. changing_weapon = true
  149. mouse_scroll_value = round_mouse_scroll_value
  150. Let's go over what's happening here:
  151. First we check if the event is a ``InputEventMouseButton`` event and that our mouse mode is ``MOUSE_MODE_CAPTURED``.
  152. Then we check to see if the button index is either a ``BUTTON_WHEEL_UP`` or ``BUTTON_WHEEL_DOWN`` index.
  153. If the event's index is indeed a button wheel index, we then check to see if it is a ``BUTTON_WHEEL_UP`` or ``BUTTON_WHEEL_DOWN`` index.
  154. Based on whether it is up or down we add/remove ``MOUSE_SENSITIVITY_SCROLL_WHEEL`` to/from ``mouse_scroll_value``.
  155. Next we clamp mouse scroll value to assure it is inside the range of our weapons.
  156. We then check to see if we are changing weapons or reloading. If we are doing neither, we round ``mouse_scroll_value`` and cast it to a ``int``.
  157. .. note:: We are casting ``mouse_scroll_value`` to a ``int`` so we can use it as a key in our dictionary. If we left it as a float,
  158. we would get an error when we try to run the project.
  159. Next we check to see if the weapon name at ``round_mouse_scroll_value`` is not equal to the current weapon name using ``weapon_number_to_name``.
  160. If the weapon is different than our current weapon, we assign ``changing_weapon_name``, set ``changing_weapon`` to true so we will change weapons in
  161. ``process_changing_weapon``, and set ``mouse_scroll_value`` to ``round_mouse_scroll_value``.
  162. .. tip:: The reason we are setting ``mouse_scroll_value`` to the rounded scroll value is because we do not want the player to keep their
  163. mouse scroll wheel just in between values, giving them the ability to switch almost extremely fast. By assigning ``mouse_scroll_value``
  164. to ``round_mouse_scroll_value``, we assure that each weapon takes exactly the same amount of scrolling to change.
  165. ______
  166. One more thing we need to change is in ``process_input``. In the code for changing weapons, add the following right after the line ``changing_weapon = true``:
  167. ::
  168. mouse_scroll_value = weapon_change_number
  169. Now our scroll value we be changed with the keyboard input. If we did not change this, our scroll value will be out of sync. If the scroll wheel is out of
  170. sync, scrolling forwards or backwards would not transition to the next/last weapon, but rather the next/last weapon the scroll wheel changed to.
  171. ______
  172. Now you can change weapons using the scroll wheel! Go give it a whirl!
  173. Adding the health pick ups
  174. --------------------------
  175. Now that our player has health and ammo, we ideally need a way to replenish those resources.
  176. Open up ``Health_Pickup.tscn``.
  177. Expand ``Holder`` if it's not already expanded. Notice how we have two Spatial nodes, one called ``Health_Kit`` and another called ``Health_Kit_Small``.
  178. This is because we're actually going to be making two sizes of health pick ups, one small and one large/normal. ``Health_Kit`` and ``Health_Kit_Small`` only
  179. have a single :ref:`MeshInstance <class_MeshInstance>` as their children.
  180. Next expand ``Health_Pickup_Trigger``. This is an :ref:`Area <class_Area>` node we're going to use to check if the player has walked close enough to pick up
  181. the health kit. If you expand it you'll find two collision shapes, one for each size. We will be using a different collision shape size based on the size of the
  182. health pick up, so the smaller health pick up has a trigger collision shape closer to it's size.
  183. The last thing to note is how we have a :ref:`AnimationPlayer <class_AnimationPlayer>` node so the health kit spins around slowly and bobs up and down.
  184. Select ``Health_Pickup`` and add a new script called ``Health_Pickup.gd``. Add the following:
  185. ::
  186. extends Spatial
  187. export (int, "full size", "small") var kit_size = 0 setget kit_size_change
  188. # 0 = full size pickup, 1 = small pickup
  189. const HEALTH_AMOUNTS = [70, 30]
  190. const RESPAWN_TIME = 20
  191. var respawn_timer = 0
  192. var is_ready = false
  193. func _ready():
  194. $Holder/Health_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")
  195. is_ready = true
  196. kit_size_change_values(0, false)
  197. kit_size_change_values(1, false)
  198. kit_size_change_values(kit_size, true)
  199. func _physics_process(delta):
  200. if respawn_timer > 0:
  201. respawn_timer -= delta
  202. if respawn_timer <= 0:
  203. kit_size_change_values(kit_size, true)
  204. func kit_size_change(value):
  205. if is_ready:
  206. kit_size_change_values(kit_size, false)
  207. kit_size = value
  208. kit_size_change_values(kit_size, true)
  209. else:
  210. kit_size = value
  211. func kit_size_change_values(size, enable):
  212. if size == 0:
  213. $Holder/Health_Pickup_Trigger/Shape_Kit.disabled = !enable
  214. $Holder/Health_Kit.visible = enable
  215. elif size == 1:
  216. $Holder/Health_Pickup_Trigger/Shape_Kit_Small.disabled = !enable
  217. $Holder/Health_Kit_Small.visible = enable
  218. func trigger_body_entered(body):
  219. if body.has_method("add_health"):
  220. body.add_health(HEALTH_AMOUNTS[kit_size])
  221. respawn_timer = RESPAWN_TIME
  222. kit_size_change_values(kit_size, false)
  223. Let's go over what this script is doing, starting with its global variables:
  224. * ``kit_size``: The size of the health pick up. Notice how we're using a ``setget`` function to tell if it's changed.
  225. * ``HEALTH_AMMOUNTS``: The amount of health each pick up in each size contains.
  226. * ``RESPAWN_TIME``: The amount of time, in seconds, it takes for the health pick up to respawn
  227. * ``respawn_timer``: A variable used to track how long the health pick up has been waiting to respawn.
  228. * ``is_ready``: A variable to track whether the ``_ready`` function has been called or not.
  229. We're using ``is_ready`` because ``setget`` functions are called before ``_ready``, we need to ignore the
  230. first kit_size_change call, because we cannot access child nodes until ``_ready`` is called. If we did not ignore the
  231. first ``setget`` call, we would get several errors in the debugger.
  232. Also, notice how we're using a exported variable. This is so we can change the size of the health pick up in the editor, for each pick up. This makes it where
  233. we do not have to make two scenes for the two sizes, since we can easily change sizes in the editor using the exported variable.
  234. .. tip:: See :ref:`doc_GDScript` and scroll down to the Exports section for a list of of export hints you can use.
  235. ______
  236. Let's look at ``_ready``:
  237. First we connect the ``body_entered`` signal from our ``Health_Pickup_Trigger`` to the ``trigger_body_entered`` function. This makes is where any
  238. body that enters the :ref:`Area <class_Area>` triggers the ``trigger_body_entered`` function.
  239. Next we set ``is_ready`` to ``true`` so we can use our ``setget`` function.
  240. Then we hide all of the possible kits and their collision shapes using ``kit_size_change_values``. The first argument is the size of the kit, while the second argument
  241. is whether to enable or disable the collision shape and mesh at that size.
  242. Then we make only the kit size we selected visible, calling ``kit_size_change_values`` and passing in ``kit_size`` and ``true``, so the size at ``kit_size`` is enabled.
  243. ______
  244. Next let's look at ``kit_size_changed``.
  245. The first thing we do is check to see if ``is_ready`` is ``true``.
  246. If ``is_ready`` is ``true``, we then make whatever kit is currently assigned to ``kit_size`` disabled using ``kit_size_change_values``, passing in ``kit_size`` and ``false``.
  247. Then we assign ``kit_size`` to the new value passed in, ``value``. Then we call ``kit_size_change_values`` passing in ``kit_size`` again, but this time
  248. with the second argument as ``true`` so we enable it. Because we changed ``kit_size`` to the passed in value, this will make whatever kit size we passed in visible.
  249. If ``is_ready`` is not ``true``, we simply assign ``kit_size`` to the passed in ``value``.
  250. ______
  251. Now let's look at ``kit_size_change_values``.
  252. The first thing we do is check to see which size we're using. Based on which size we're wanting to enable/disable, we want to get different nodes.
  253. We get the collision shape for the node corresponding to ``size`` and disable it based on the ``enabled`` passed in argument/variable.
  254. .. note:: Why are we using ``!enable`` instead of ``enable``? This is so when we say we want to enable the node, we can pass in ``true``, but since
  255. :ref:`CollisionShape <class_CollisionShape>` uses disabled instead of enabled, we need to flip it. By flipping it, we can enable the collision shape
  256. and make the mesh visible when ``true`` is passed in.
  257. We then get the correct :ref:`Spatial <class_Spatial>` node holding the mesh and set its visibility to ``enable``.
  258. This function may be a little confusing, try to think of it like this: We're enabling/disabling the proper nodes for ``size`` using ``enabled``. This is so we cannot pick up
  259. health for a size that is not visible, and so only the mesh for the proper size will be visible.
  260. ______
  261. Finally, let's look at ``trigger_body_entered``.
  262. The first thing we do is see whether or not the body that just entered has a method/function called ``add_health``. If it does, we then
  263. call ``add_health`` and pass in the health provided by the current kit size.
  264. Then we set ``respawn_timer`` to ``RESPAWN_TIME`` so we have to wait before we can get health again. Finally, call ``kit_size_change_values``,
  265. passing in ``kit_size`` and ``false`` so the kit at ``kit_size`` is invisible until we've waited long enough to respawn.
  266. _______
  267. The last thing we need to do before we can use this health pick up is add a few things to our player.
  268. Open up ``Player.gd`` and add the following global variable:
  269. ::
  270. const MAX_HEALTH = 150
  271. * ``MAX_HEALTH``: The maximum amount of health a player can have.
  272. Now we need to add the ``add_health`` function to our player. Add the following to ``Player.gd``:
  273. ::
  274. func add_health(additional_health):
  275. health += additional_health
  276. health = clamp(health, 0, MAX_HEALTH)
  277. Let's quickly go over what this does.
  278. We first add ``additional_health`` to our current health. We then clamp the health so that it cannot exceed a value higher than ``MAX_HEALTH``, nor a value lower
  279. than ``0``.
  280. _______
  281. With that done, now we can collect health! Go place a few ``Health_Pickup`` scenes around and give it a try. You can change the size of the health pick up in the editor
  282. when a ``Health_Pickup`` instanced scene is selected, from a convenient drop down.
  283. Adding the ammo pick ups
  284. ------------------------
  285. While adding health is good and all, we can't reap the rewards from it since nothing can (currently) damage us.
  286. Let's add some ammo pick ups next!
  287. Open up ``Ammo_Pickup.tscn``. Notice how it's structured exactly the same as ``Health_Pickup.tscn``, but with the meshes and trigger collision shapes changed slightly to adjust
  288. for the difference in mesh sizes.
  289. Select ``Ammo_Pickup`` and add a new script called ``Ammo_Pickup.gd``. Add the following:
  290. ::
  291. extends Spatial
  292. export (int, "full size", "small") var kit_size = 0 setget kit_size_change
  293. # 0 = full size pickup, 1 = small pickup
  294. const AMMO_AMOUNTS = [4, 1]
  295. const RESPAWN_TIME = 20
  296. var respawn_timer = 0
  297. var is_ready = false
  298. func _ready():
  299. $Holder/Ammo_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")
  300. is_ready = true
  301. kit_size_change_values(0, false)
  302. kit_size_change_values(1, false)
  303. kit_size_change_values(kit_size, true)
  304. func _physics_process(delta):
  305. if respawn_timer > 0:
  306. respawn_timer -= delta
  307. if respawn_timer <= 0:
  308. kit_size_change_values(kit_size, true)
  309. func kit_size_change(value):
  310. if is_ready:
  311. kit_size_change_values(kit_size, false)
  312. kit_size = value
  313. kit_size_change_values(kit_size, true)
  314. else:
  315. kit_size = value
  316. func kit_size_change_values(size, enable):
  317. if size == 0:
  318. $Holder/Ammo_Pickup_Trigger/Shape_Kit.disabled = !enable
  319. $Holder/Ammo_Kit.visible = enable
  320. elif size == 1:
  321. $Holder/Ammo_Pickup_Trigger/Shape_Kit_Small.disabled = !enable
  322. $Holder/Ammo_Kit_Small.visible = enable
  323. func trigger_body_entered(body):
  324. if body.has_method("add_ammo"):
  325. body.add_ammo(AMMO_AMOUNTS[kit_size])
  326. respawn_timer = RESPAWN_TIME
  327. kit_size_change_values(kit_size, false)
  328. You may have noticed this code looks almost exactly the same as the health pick up. That's because it largely is the same! Only a few things
  329. have been changed, and that's what we're going to go over.
  330. First, notice how we have ``AMMO_AMOUNTS`` instead of ``HEALTH_AMMOUNTS``. ``AMMO_AMOUNTS`` will be how many ammo clips/magazines we add to the current weapon.
  331. (Unlike ``HEALTH_AMMOUNTS`` which was how many health points, we instead add an entire clip for the current weapon, instead of the raw ammo amount)
  332. The only other thing to notice is in ``trigger_body_entered`` we're checking and calling a function called ``add_ammo``, not ``add_health``.
  333. Other than those two small changes, everything else is exactly the same as the health pickup!
  334. _______
  335. All we need to do make the ammo pick ups work is add a new function to our player. Open ``Player.gd`` and add the following function:
  336. ::
  337. func add_ammo(additional_ammo):
  338. if (current_weapon_name != "UNARMED"):
  339. if (weapons[current_weapon_name].CAN_REFILL == true):
  340. weapons[current_weapon_name].spare_ammo += weapons[current_weapon_name].AMMO_IN_MAG * additional_ammo
  341. Let's go over what this function does.
  342. The first thing we check is to see whether we're using ``UNARMED`` or not. Because ``UNARMED`` does not have a node/script, we want to make sure we're not using
  343. ``UNARMED`` before trying to get the node/script attached to ``current_weapon_name``.
  344. Next we check to see if the current weapon can be refilled. If the current weapon can, we add a full clip/magazine worth of ammo to the weapon by
  345. multiplying the current weapon's ``AMMO_IN_MAG`` variable times however much ammo clips we're adding (``additional_ammo``).
  346. _______
  347. With that done, you should now be able to get additional ammo! Go place some ammo pick ups in one/both/all of the scenes and give it a try!
  348. .. note:: Notice how we're not limiting the amount of ammo you can carry. To limit the amount of ammo each weapon can carry, you need to add a additional variable to
  349. each weapon's script, and then clamp the weapon's ``spare_ammo`` variable after adding ammo in ``add_ammo``.
  350. Adding breakable targets
  351. ------------------------
  352. Before we end this part, let's add some targets.
  353. Open up ``Target.tscn`` and take a look at the scenes in the scene tree.
  354. First, notice how we're not using a :ref:`RigidBody <class_RigidBody>` node, but rather a :ref:`StaticBody <class_StaticBody>` node instead.
  355. The reason behind this is our non-broken targets will not be moving anywhere, using a :ref:`RigidBody <class_RigidBody>` would be more hassle then
  356. its worth, since all it has to do is stay still.
  357. .. tip:: We also save a tiny bit of performance using a :ref:`StaticBody <class_StaticBody>` over a :ref:`RigidBody <class_RigidBody>`
  358. The other thing to note is we have a node called ``Broken_Target_Holder``. This node is going to hold a spawned/instanced scene called
  359. ``Broken_Target.tscn``. Open up ``Broken_Target.tscn``.
  360. Notice how the target is broken up into five pieces, each a :ref:`RigidBody <class_RigidBody>` node. We're going to spawn/instance this scene when the target takes too much damage
  361. and needs to be destroyed. Then we're going to hide the non-broken target, so it looks like the target shattered rather than a shattered target was
  362. spawned/instanced.
  363. While you still have ``Broken_Target.tscn`` open, attach ``RigidBody_hit_test.gd`` to all of the :ref:`RigidBody <class_RigidBody>` nodes. This will make
  364. it where we can shoot at the broken pieces and they will react to the bullets.
  365. Alright, now switch back to ``Target.tscn``, select the ``Target`` :ref:`StaticBody <class_StaticBody>` node and created a new script called ``Target.gd``.
  366. Add the following code to ``Target.gd``:
  367. ::
  368. extends StaticBody
  369. const TARGET_HEALTH = 40
  370. var current_health = 40
  371. var broken_target_holder
  372. # The collision shape for the target.
  373. # NOTE: this is for the whole target, not the pieces of the target
  374. var target_collision_shape
  375. const TARGET_RESPAWN_TIME = 14
  376. var target_respawn_timer = 0
  377. export (PackedScene) var destroyed_target
  378. func _ready():
  379. broken_target_holder = get_parent().get_node("Broken_Target_Holder")
  380. target_collision_shape = $Collision_Shape
  381. func _physics_process(delta):
  382. if target_respawn_timer > 0:
  383. target_respawn_timer -= delta
  384. if target_respawn_timer <= 0:
  385. for child in broken_target_holder.get_children():
  386. child.queue_free()
  387. target_collision_shape.disabled = false
  388. visible = true
  389. current_health = TARGET_HEALTH
  390. func bullet_hit(damage, bullet_hit_pos):
  391. current_health -= damage
  392. if current_health <= 0:
  393. var clone = destroyed_target.instance()
  394. broken_target_holder.add_child(clone)
  395. for rigid in clone.get_children():
  396. if rigid is RigidBody:
  397. var center_in_rigid_space = broken_target_holder.global_transform.origin - rigid.global_transform.origin
  398. var direction = (rigid.transform.origin - center_in_rigid_space).normalized()
  399. # Apply the impulse with some additional force (I find 12 works nicely)
  400. rigid.apply_impulse(center_in_rigid_space, direction * 12 * damage)
  401. target_respawn_timer = TARGET_RESPAWN_TIME
  402. target_collision_shape.disabled = true
  403. visible = false
  404. Let's go over what this script does, starting with the global variables:
  405. * ``TARGET_HEALTH``: The amount of damage needed to break a fully healed target.
  406. * ``current_health``: The amount of health this target currently has.
  407. * ``broken_target_holder``: A variable to hold the ``Broken_Target_Holder`` node so we can use it easily.
  408. * ``target_collision_shape``: A variable to hold the :ref:`CollisionShape <class_CollisionShape>` for the non-broken target.
  409. * ``TARGET_RESPAWN_TIME``: The length of time, in seconds, it takes for a target to respawn.
  410. * ``target_respawn_timer``: A variable to track how long a target has been broken.
  411. * ``destroyed_target``: A :ref:`PackedScene <class_PackedScene>` to hold the broken target scene.
  412. Notice how we're using an exported variable (a :ref:`PackedScene <class_PackedScene>`) to get the broken target scene instead of
  413. using ``preload``. By using an exported variable, we can chose the scene from the editor, and when/if we need to use a different scene,
  414. it's as easy as selecting a different scene in the editor, we don't need to go to the code to change the scene we're using.
  415. ______
  416. Let's look at ``_ready``.
  417. The first thing we do is get the broken target holder and assign it to ``broken_target_holder``. Notice how we're using ``get_parent().get_node()`` here, instead
  418. of ``$``. If you want to use ``$``, then you'd need to change ``get_parent().get_node()`` to ``$"../Broken_Target_Holder"``.
  419. .. note:: At the time of when this was written, I did not realize you can use ``$"../NodeName"`` to get the parent nodes using ``$``, which is why ``get_parent().get_node()``
  420. is used instead.
  421. Next we get the collision shape and assign it to ``target_collision_shape``. The reason we need to collision shape is because even when the mesh is invisible, the
  422. collision shape will still exist in the physics world. This makes it where the player can interact with a non-broken target even though it's invisible, which is
  423. not what we want. To get around this, we will disable/enable the collision shape as we make the mesh visible/invisible.
  424. ______
  425. Next let's look at ``_physics_process``.
  426. We're only going to be using ``_physics_process`` for respawning, and so the first thing we do is check to see if ``target_respawn_timer`` is more than ``0``.
  427. If it is, we then remove ``delta`` from it.
  428. Then we check to see if ``target_respawn_timer`` is ``0`` or less. The reason behind this is since we just removed ``delta`` from ``target_respawn_timer``, if it's
  429. ``0`` or less then we've just got here, effectively allowing us to do whatever we need to do when the timer is finished.
  430. In this case, we want to respawn our target.
  431. The first thing we do is remove all children in the broken target holder. We do this by iterating over all of the children in ``broken_target_holder`` and free them.
  432. Next we enable our collision shape by setting its ``disabled`` boolean to ``false``.
  433. Then we make ourselves, and all of our children nodes, visible.
  434. Finally, we reset ``current_health`` to ``TARGET_HEALTH``.
  435. ______
  436. Finally, let's look at ``bullet_hit``.
  437. The first the we do is remove however much damage the bullet does from our health.
  438. Next we check to see if we're at ``0`` health or lower. If we are, then we've just died and need to spawn a broken target.
  439. We first instance a new destroyed target scene, and assign it to a new variable, ``clone``.
  440. Next we add ``clone`` as a child of our broken target holder.
  441. For an added bonus, we want to make all of the target pieces explode outwards. Do to this, we iterate over all of the children in ``clone``.
  442. For each child, we first check to see if it's a :ref:`RigidBody <class_RigidBody>` node. If it is, we then calculate the center position of the target relative
  443. to the child node. Then we figure out which direction we are relative to the center. Using those calculated variables, we push the child from the calculated center,
  444. in the direction away from the center, using the damage of the bullet as the force.
  445. .. note:: We multiply the damage by ``12`` so it has a more dramatic effect. You can change this to a higher or lower value depending on how explosive you want
  446. your targets to shatter.
  447. Next we set our respawn timer for our non-broken target. We set it to ``TARGET_RESPAWN_TIME``, so it takes ``TARGET_RESPAWN_TIME`` many seconds to respawn.
  448. Then we disable the non-broken target's collision shape, and set our visibility to ``false``.
  449. ______
  450. .. warning:: Make sure to set the exported ``destroyed_target`` value for ``Target.tscn`` in the editor! Otherwise the targets will not be destroyed
  451. and you will get an error!
  452. With that done, go place some ``Target.tscn`` instances around in one/both/all of the levels. You should find they explode into five pieces after they've taken enough
  453. damage. After a little while, they'll respawn into a whole target again.
  454. Final notes
  455. -----------
  456. .. image:: img/PartFourFinished.png
  457. Now you can use a joypad, change weapons with the mouse's scroll wheel, replenish your health and ammo, and break targets with your weapons.
  458. In the next part, :ref:`doc_fps_tutorial_part_five`, we're going to add grenades to our player, give our player the ability to grab and throw objects, and
  459. add turrets!
  460. .. warning:: If you ever get lost, be sure to read over the code again!
  461. You can download the finished project for this part here: :download:`Godot_FPS_Part_4.zip <files/Godot_FPS_Part_4.zip>`