part_three.rst 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. .. _doc_fps_tutorial_part_three:
  2. Part 3
  3. ======
  4. Part Overview
  5. -------------
  6. In this part we will be limiting our guns by giving them ammo. We will also
  7. be giving the player the ability to reload, and we will be adding sounds when the
  8. guns fire.
  9. .. image:: img/PartThreeFinished.png
  10. By the end of this part, the player will have limited ammo, the ability to reload,
  11. and sounds will play when the player fires and changes weapons.
  12. .. note:: You are assumed to have finished :ref:`part two <doc_fps_tutorial_part_two>` before moving on to this part of the tutorial.
  13. Let's get started!
  14. Changing levels
  15. ---------------
  16. Now that we have a fully working FPS, let's move to a more FPS like level. Open up ``Test_Level.tscn``.
  17. ``Test_Level.tscn`` is a complete custom FPS level created for the purpose of this tutorial. Press ``F6`` to
  18. play the open scene, or press the "play current scene button", and give it a whirl.
  19. .. warning:: There will (likely) be the occasional random freeze as you go through the level. This is a known
  20. issue.
  21. If you find any way to solve it, please let me know on the Github repository, the Godot forums,
  22. or on Twitter! Be sure to include ``@TwistedTwigleg`` so I will have a greater chance of seeing it!
  23. You might have noticed there are several boxes and cylinders placed throughout the level. They are :ref:`RigidBody <class_RigidBody>`
  24. nodes we can place ``RigidBody_hit_test.gd`` on and then they will react to being hit with bullets, so lets do that!
  25. Select ``Center_room`` and open it up. From there select ``Physics_objects`` and open that up. You'll find there are
  26. ``6`` crates in a seemingly random order. Go select one of them and press the "Open in Editor" button. It's the one that
  27. looks like a little movie slide.
  28. .. note:: The reason the objects seem to be placed in a random order is because all of the objects were copied and pasted around
  29. in the Godot editor to save on time. If you want to move any of the nodes around, it is highly suggested to just
  30. left click inside the editor viewport to get the node you want, and then move it around with the :ref:`Spatial <class_Spatial>` gizmo.
  31. This will bring you to the crate's scene. From there, select the ``Crate`` :ref:`RigidBody <class_RigidBody>` (the one that is the root of the scene)
  32. and scroll down in the inspector until you get to the script section. From there, click the drop down and select "Load". Chose
  33. ``RigidBody_hit_test.gd`` and then return to ``Test_Level.tscn``.
  34. Now open ``Upper_room``, select ``Physics_objects``, and chose one of the cylinder :ref:`RigidBody <class_RigidBody>` nodes.
  35. Press the "Open in Editor" button beside one of the cylinders. This will bring you to the cylinder's scene.
  36. From there, select the ``Cylinder`` :ref:`RigidBody <class_RigidBody>` (the one that is the root of the scene)
  37. and scroll down in the inspector until you get to the script section. From there, click the drop down and select "Load". Chose
  38. ``RigidBody_hit_test.gd`` and then return to ``Test_Level.tscn``.
  39. Now you can fire at the boxes and cylinders and they will react to your bullets just like the cubes in ``Testing_Area.tscn``!
  40. Adding ammo
  41. -----------
  42. Now that we've got working guns, lets give them a limited amount of ammo.
  43. Lets define some more global variables in ``Player.gd``, ideally nearby the other gun related variables:
  44. ::
  45. var ammo_for_guns = {"PISTOL":60, "RIFLE":160, "KNIFE":1}
  46. var ammo_in_guns = {"PISTOL":20, "RIFLE":80, "KNIFE":1}
  47. const AMMO_IN_MAGS = {"PISTOL":20, "RIFLE":80, "KNIFE":1}
  48. Here is what these variables will be doing for us:
  49. - ``ammo_for_guns``: The amount of ammo we have in reserve for each weapon/gun.
  50. - ``ammo_in_guns``: The amount of ammo currently inside the weapon/gun.
  51. - ``AMMO_IN_MAGS``: How much ammo is in a fully filled weapon/gun.
  52. .. note:: There is no reason we've included ammo for the knife, so feel free to remove the knife's ammo
  53. if you desire.
  54. Depending on how you program melee weapons, you may need to define an ammo count even if the
  55. weapon does not use ammo. Some games use extremely short range 'guns' as their melee weapons,
  56. and in those cases you may need to define ammo for your melee weapons.
  57. _________
  58. Now we need to add a few ``if`` checks to ``_physics_process``.
  59. We need to make sure we have ammo in our gun before we try to fire a bullet.
  60. Go find the line that checks for the fire action being pressed and add the following new
  61. bits of code:
  62. ::
  63. # NOTE: You should have this if condition in your _physics_process function
  64. # Firing the weapons
  65. if Input.is_action_pressed("fire"):
  66. if current_gun == "PISTOL":
  67. if ammo_in_guns["PISTOL"] > 0: # NEW CODE
  68. if animation_manager.current_state == "Pistol_idle":
  69. animation_manager.set_animation("Pistol_fire")
  70. elif current_gun == "RIFLE":
  71. if ammo_in_guns["RIFLE"] > 0: # NEW CODE
  72. if animation_manager.current_state == "Rifle_idle":
  73. animation_manager.set_animation("Rifle_fire")
  74. elif current_gun == "KNIFE":
  75. if animation_manager.current_state == "Knife_idle":
  76. animation_manager.set_animation("Knife_fire")
  77. These two additional ``if`` checks make sure we have a bullet to fire before setting our firing animation.
  78. While we're still in ``_physics_process``, let's also add a way to track how much ammo we have. Find the line that
  79. has ``UI_status_label.text = "HEALTH: " + str(health)`` in ``_physics_process`` and replace it with the following:
  80. ::
  81. # HUD (UI)
  82. if current_gun == "UNARMED" or current_gun == "KNIFE":
  83. UI_status_label.text = "HEALTH: " + str(health)
  84. else:
  85. UI_status_label.text = "HEALTH: " + str(health) + "\nAMMO:" + \
  86. str(ammo_in_guns[current_gun]) + "/" + str(ammo_for_guns[current_gun])
  87. .. tip:: Did you now that you can combine two lines using ``\``? We're using it here
  88. so we do not have a extremely long line of code all on one line by splitting it
  89. into two lines!
  90. This will show the player how much ammo they currently have and how much ammo they currently have in reserve, only for
  91. the appropriate weapons (not unarmed or the knife). Regardless of the currently selected weapon/gun, we will always show
  92. how much health the player has
  93. .. note:: we cannot just add ``ammo_for_guns[current_gun]`` or ``ammo_in_guns[current_gun]`` to the ``string`` we
  94. are passing in to the :ref:`Label <class_Label>`. Instead we have to cast them from ``floats`` to ``strings``, which is what we are doing
  95. by using ``str()``.
  96. For more information on casting, see this page from wiki books:
  97. https://en.wikibooks.org/wiki/Computer_Programming/Type_conversion
  98. .. warning:: We are currently not using the player's health just yet in the tutorial. We will start
  99. using health for the player and objects when we include turrets and targets in later parts.
  100. Now we need to remove a bullet from the gun when we fire. To do that, we just need to add a few lines in
  101. ``fire_bullet``:
  102. ::
  103. func fire_bullet():
  104. if changing_gun == true:
  105. return
  106. # Pistol bullet handling: Spawn a bullet object!
  107. if current_gun == "PISTOL":
  108. var clone = bullet_scene.instance()
  109. var scene_root = get_tree().root.get_children()[0]
  110. scene_root.add_child(clone)
  111. clone.global_transform = $Rotation_helper/Gun_fire_points/Pistol_point.global_transform
  112. # The bullet is a little too small (by default), so let's make it bigger!
  113. clone.scale = Vector3(4, 4, 4)
  114. ammo_in_guns["PISTOL"] -= 1 # NEW CODE
  115. # Rifle bullet handeling: Send a raycast!
  116. elif current_gun == "RIFLE":
  117. var ray = $Rotation_helper/Gun_fire_points/Rifle_point/RayCast
  118. ray.force_raycast_update()
  119. if ray.is_colliding():
  120. var body = ray.get_collider()
  121. if body.has_method("bullet_hit"):
  122. body.bullet_hit(RIFLE_DAMAGE, ray.get_collision_point())
  123. ammo_in_guns["RIFLE"] -= 1 # NEW CODE
  124. # Knife bullet(?) handeling: Use an area!
  125. elif current_gun == "KNIFE":
  126. var area = $Rotation_helper/Gun_fire_points/Knife_point/Area
  127. var bodies = area.get_overlapping_bodies()
  128. for body in bodies:
  129. if body.has_method("bullet_hit"):
  130. body.bullet_hit(KNIFE_DAMAGE, area.global_transform.origin)
  131. Go play the project again! Now you'll lose ammo as you fire, until you reach zero and
  132. cannot fire anymore.
  133. Adding reloading
  134. ----------------
  135. Now that we can empty our gun, we need a way to refill it!
  136. First, let's start by
  137. adding another global variable. Add ``var reloading_gun = false`` somewhere along with your
  138. other global variables, preferably near the other gun related variables.
  139. _________
  140. Now we need to add several things to ``_physics_process``.
  141. First, let's make sure we cannot change guns while reloading.
  142. We need to change the weapon changing code to include the following:
  143. ::
  144. # Was "if changing_gun == false"
  145. if changing_gun == false and reloading_gun == false:
  146. if Input.is_key_pressed(KEY_1):
  147. current_gun = "UNARMED"
  148. changing_gun = true
  149. elif Input.is_key_pressed(KEY_2):
  150. current_gun = "KNIFE"
  151. changing_gun = true
  152. elif Input.is_key_pressed(KEY_3):
  153. current_gun = "PISTOL"
  154. changing_gun = true
  155. elif Input.is_key_pressed(KEY_4):
  156. current_gun = "RIFLE"
  157. changing_gun = true
  158. Now the player cannot change guns while reloading.
  159. _________
  160. Ideally we want the player to be able to reload when they chose, so lets given them
  161. the ability to reload when they press the ``reload`` action. Add the following
  162. somewhere in ``_physics_process``, ideally nearby your other input related code:
  163. ::
  164. # Reloading
  165. if reloading_gun == false:
  166. if Input.is_action_just_pressed("reload"):
  167. if current_gun == "PISTOL" or current_gun == "RIFLE"
  168. if animation_manager.current_state != "Pistol_reload" and animation_manager.current_state != "Rifle_reload":
  169. reloading_gun = true
  170. First we see if the player is already reloading. If they are not, then we check if they've pressed
  171. the reloading action. If they have pressed the ``reload`` action, we then check if they are using
  172. a weapon that has the ability to be reloaded. Finally, we make sure they are not already
  173. in a reloading animation. If they are not, we set ``reloading_gun`` to ``true``.
  174. We do not want to do our reloading processing here with the input in an effort to keep game logic
  175. separate from input logic. Keeping them separate makes the code easier to debug, and as a bonus it
  176. keeps the input logic from being overly bloated.
  177. _________
  178. Finally, we need to add the code that actually handles reloading. Add the following code to ``_physics_process``,
  179. ideally somewhere underneath the reloading input code you just inputted:
  180. ::
  181. # Reloading logic
  182. if reloading_gun == true:
  183. var can_reload = false
  184. if current_gun == "PISTOL":
  185. if animation_manager.current_state == "Pistol_idle":
  186. can_reload = true
  187. elif current_gun == "RIFLE":
  188. if animation_manager.current_state == "Rifle_idle":
  189. can_reload = true
  190. elif current_gun == "KNIFE":
  191. can_reload = false
  192. reloading_gun = false
  193. else:
  194. can_reload = false
  195. reloading_gun = false
  196. if ammo_for_guns[current_gun] <= 0 or ammo_in_guns[current_gun] == AMMO_IN_MAGS[current_gun]:
  197. can_reload = false
  198. reloading_gun = false
  199. if can_reload == true:
  200. var ammo_needed = AMMO_IN_MAGS[current_gun] - ammo_in_guns[current_gun]
  201. if ammo_for_guns[current_gun] >= ammo_needed:
  202. ammo_for_guns[current_gun] -= ammo_needed
  203. ammo_in_guns[current_gun] = AMMO_IN_MAGS[current_gun]
  204. else:
  205. ammo_in_guns[current_gun] += ammo_for_guns[current_gun]
  206. ammo_for_guns[current_gun] = 0
  207. if current_gun == "PISTOL":
  208. animation_manager.set_animation("Pistol_reload")
  209. elif current_gun == "RIFLE":
  210. animation_manager.set_animation("Rifle_reload")
  211. reloading_gun = false
  212. Lets go over what this code does.
  213. _________
  214. First we check if ``reloading_gun`` is ``true``. If it is we then go through a series of checks
  215. to see if we can reload or not. We use ``can_reload`` as a variable to track whether or not
  216. it is possible to reload.
  217. We go through series of checks for each weapon. For the pistol and the rifle we check if
  218. we're in an idle state or not. If we are, then we set ``can_reload`` to ``true``.
  219. For the knife we do not want to reload, because you cannot reload a knife, so we set ``can_reload`` and ``reloading_gun``
  220. to ``false``. If we are using a weapon that we do not have a ``if`` or ``elif`` check for, we set
  221. ``can_reload`` and ``reloading_gun`` to ``false``, as we do not want to be able to reload a weapon we are unaware of.
  222. Next we check if we have ammo in reserve for the gun in question. We also check to make sure the gun we are trying to reload
  223. is not already full of ammo. If the gun does not have ammo in reserve or the gun is already full, we set
  224. ``can_reload`` and ``reloading_gun`` to ``false``.
  225. If we've made it through all those checks and we can reload, then we have a few more steps to take.
  226. First we assign the ammo we are needing to fill the gun fully to the ``ammo_needed`` variable.
  227. We just subtract the amount of ammo we currently have in our gun by the amount of ammo in a full magazine.
  228. Then we check if have enough ammo in reserves to fill the gun fully. If we do, we subtract the amount of ammo
  229. we need to refill our gun from the reserves, and we set the amount of ammo in the gun to full.
  230. If we do not have enough ammo in reserves to fill the gun, we add all of the ammo left in reserves to our
  231. gun and then set the ammo in reserves to zero, making it empty.
  232. Regardless of how much ammo we've added to the gun, we set our animation to the reloading animation for the current gun.
  233. Finally, we set ``reloading_gun`` to false because we have finished reloading the gun.
  234. _________
  235. Go test the project again, and you'll find you can reload your gun when it is not
  236. full and when there is ammo left in the ammo reserves.
  237. _________
  238. Personally, I like the guns to automatically start reloading if we try to fire them
  239. when they have no ammo in them, so lets add that! Add the following code to the input code for
  240. firing the guns:
  241. ::
  242. # Firing the weapons
  243. if Input.is_action_pressed("fire"):
  244. if current_gun == "PISTOL":
  245. if ammo_in_guns["PISTOL"] > 0:
  246. if animation_manager.current_state == "Pistol_idle":
  247. animation_manager.set_animation("Pistol_fire")
  248. # NEW CODE!
  249. else:
  250. reloading_gun = true
  251. elif current_gun == "RIFLE":
  252. if ammo_in_guns["RIFLE"] > 0:
  253. if animation_manager.current_state == "Rifle_idle":
  254. animation_manager.set_animation("Rifle_fire")
  255. # NEW CODE!
  256. else:
  257. reloading_gun = true
  258. elif current_gun == "KNIFE":
  259. if animation_manager.current_state == "Knife_idle":
  260. animation_manager.set_animation("Knife_fire")
  261. Now whenever the player tries to fire the gun when it's empty, we automatically
  262. set ``reloading_gun`` to true, which will reload the gun if possible.
  263. Adding sounds
  264. -------------
  265. Finally, let's add some sounds that play when we are reloading, changing guns, and when we
  266. are firing them.
  267. .. tip:: There are no game sounds provided in this tutorial (for legal reasons).
  268. https://gamesounds.xyz/ is a collection of **"royalty free or public domain music and sounds suitable for games"**.
  269. I used Gamemaster's Gun Sound Pack, which can be found in the Sonniss.com GDC 2017 Game Audio Bundle.
  270. The video tutorial will briefly show how to edit the audio files for use in the tutorial.
  271. Open up ``SimpleAudioPlayer.tscn``. It is simply a :ref:`Spatial <class_Spatial>` with a :ref:'AudioStreamPlayer <class_AudioStreamPlayer>' as it's child.
  272. .. note:: The reason this is called a 'simple' audio player is because we are not taking performance into account
  273. and because the code is designed to provide sound in the simplest way possible. This will likely change
  274. in a future part.
  275. If you want to use 3D audio, so it sounds like it's coming from a location in 3D space, right click
  276. the :ref:'AudioStreamPlayer <class_AudioStreamPlayer>' and select "Change type".
  277. This will open the node browser. Navigate to :ref:'AudioStreamPlayer3D <class_AudioStreamPlayer3D>' and select "change".
  278. In the source for this tutorial, we will be using :ref:'AudioStreamPlayer <class_AudioStreamPlayer>', but you can optionally
  279. use :ref:'AudioStreamPlayer3D <class_AudioStreamPlayer3D>' if you desire, and the code provided below will work regardless of which
  280. one you chose.
  281. Create a new script and call it "SimpleAudioPlayer.gd". Attach it to the :ref:`Spatial <class_Spatial>` in ``SimpleAudioPlayer.tscn``
  282. and insert the following code:
  283. ::
  284. extends Spatial
  285. # All of the audio files.
  286. # You will need to provide your own sound files.
  287. var audio_pistol_shot = preload("res://path_to_your_audio_here")
  288. var audio_gun_cock = preload("res://path_to_your_audio_here")
  289. var audio_rifle_shot = preload("res://path_to_your_audio_here")
  290. var audio_node = null
  291. func _ready():
  292. audio_node = $AudioStreamPlayer
  293. audio_node.connect("finished", self, "destroy_self")
  294. audio_node.stop()
  295. func play_sound(sound_name, position=null):
  296. if sound_name == "Pistol_shot":
  297. audio_node.stream = audio_pistol_shot
  298. elif sound_name == "Rifle_shot":
  299. audio_node.stream = audio_rifle_shot
  300. elif sound_name == "Gun_cock":
  301. audio_node.stream = audio_gun_cock
  302. else:
  303. print ("UNKNOWN STREAM")
  304. queue_free()
  305. return
  306. # If you are using a AudioPlayer3D, then uncomment these lines to set the position.
  307. # if position != null:
  308. # audio_node.global_transform.origin = position
  309. audio_node.play()
  310. func destroy_self():
  311. audio_node.stop()
  312. queue_free()
  313. .. tip:: By setting ``position`` to ``null`` by default in ``play_sound``, we are making it an optional argument,
  314. meaning position doesn't necessarily have to be passed in to call the ``play_sound``.
  315. Let's go over what's happening here:
  316. _________
  317. In ``_ready`` we get the :ref:'AudioStreamPlayer <class_AudioStreamPlayer>' and connect it's ``finished`` signal to ourselves.
  318. It doesn't matter if it's :ref:'AudioStreamPlayer <class_AudioStreamPlayer>' or :ref:'AudioStreamPlayer3D <class_AudioStreamPlayer3D>' node,
  319. as they both have the finished signal. To make sure it is not playing any sounds, we call ``stop`` on the :ref:'AudioStreamPlayer <class_AudioStreamPlayer>'.
  320. .. warning:: Make sure your sound files are **not** set to loop! If it is set to loop
  321. the sounds will continue to play infinitely and the script will not work!
  322. The ``play_sound`` function is what we will be calling from ``Player.gd``. We check if the sound
  323. is one of the three possible sounds, and if it is we set the audio stream for our :ref:'AudioStreamPlayer <class_AudioStreamPlayer>'
  324. to the correct sound.
  325. If it is an unknown sound, we print an error message to the console and free ourselves.
  326. If you are using a :ref:'AudioStreamPlayer3D <class_AudioStreamPlayer3D>', remove the ``#`` to set the position of
  327. the audio player node so it plays at the correct position.
  328. Finally, we tell the :ref:'AudioStreamPlayer <class_AudioStreamPlayer>' to play.
  329. When the :ref:'AudioStreamPlayer <class_AudioStreamPlayer>' is finished playing the sound, it will call ``destroy_self`` because
  330. we connected the ``finished`` signal in ``_ready``. We stop the :ref:'AudioStreamPlayer <class_AudioStreamPlayer>' and free ourself
  331. to save on resources.
  332. .. note:: This system is extremely simple and has some major flaws:
  333. One flaw is we have to pass in a string value to play a sound. While it is relatively simple
  334. to remember the names of the three sounds, it can be increasingly complex when you have more sounds.
  335. Ideally we'd place these sounds in some sort of container with exposed variables so we do not have
  336. to remember the name(s) of each sound effect we want to play.
  337. Another flaw is we cannot play looping sounds effects, nor background music easily with this system.
  338. Because we cannot play looping sounds, certain effects like footstep sounds are harder to accomplish
  339. because we then have to keep track of whether or not there is a sound effect *and* whether or not we
  340. need to continue playing it.
  341. _________
  342. With that done, lets open up ``Player.gd`` again.
  343. First we need to load the ``SimpleAudioPlayer.tscn``. Place the following code in your global variables:
  344. ::
  345. var simple_audio_player = preload("res://SimpleAudioPlayer.tscn")
  346. Now we just need to instance the simple audio player when we need it, and then call it's
  347. ``play_sound`` function and pass the name of the sound we want to play. To make the process simpler,
  348. let's create a ``create_sound`` function:
  349. ::
  350. func create_sound(sound_name, position=null):
  351. var audio_clone = simple_audio_player.instance()
  352. var scene_root = get_tree().root.get_children()[0]
  353. scene_root.add_child(audio_clone)
  354. audio_clone.play_sound(sound_name, position)
  355. Lets walk through what this function does:
  356. _________
  357. The first line instances the ``simple_audio_player.tscn`` scene and assigns it to a variable,
  358. named ``audio_clone``.
  359. The second line gets the scene root, using one large assumption. We first get this node's :ref:`SceneTree <class_SceneTree>`,
  360. and then access the root node, which in this case is the :ref:`Viewport <class_Viewport>` this entire game is running under.
  361. Then we get the first child of the :ref:`Viewport <class_Viewport>`, which in our case happens to be the root node in
  362. ``Test_Area.tscn`` or ``Test_Level.tscn``. We are making a huge assumption that the first child of the root
  363. is the root node that our player is under, which could not always be the case.
  364. If this doesn't make sense to you, don't worry too much about it. The second line of code only doesn't work
  365. reliably if you have multiple scenes loaded as childs to the root node at a time, which will rarely happen for most projects. This is really
  366. only potentially a issue depending on how you handle scene loading.
  367. The third line adds our newly created ``SimpleAudioPlayer`` scene to be a child of the scene root. This
  368. works exactly the same as when we are spawning bullets.
  369. Finally, we call the ``play_sound`` function and pass in the arguments we're given. This will call
  370. ``SimpleAudioPlayer.gd``'s ``play_sound`` function with the passed in arguments.
  371. _________
  372. Now all that is left is playing the sounds when we want to. First, let's play the shooting sounds
  373. when a bullet is fired. Go to ``fire_bullet`` and add the following:
  374. ::
  375. func fire_bullet():
  376. if changing_gun == true:
  377. return
  378. # Pistol bullet handling: Spawn a bullet object!
  379. if current_gun == "PISTOL":
  380. var clone = bullet_scene.instance()
  381. var scene_root = get_tree().root.get_children()[0]
  382. scene_root.add_child(clone)
  383. clone.global_transform = $Rotation_helper/Gun_fire_points/Pistol_point.global_transform
  384. # The bullet is a little too small (by default), so let's make it bigger!
  385. clone.scale = Vector3(4, 4, 4)
  386. ammo_in_guns["PISTOL"] -= 1
  387. create_sound("Pistol_shot", clone.global_transform.origin); # NEW CODE
  388. # Rifle bullet handeling: Send a raycast!
  389. elif current_gun == "RIFLE":
  390. var ray = Rotation_helper/Gun_fire_points/Rifle_point/RayCast
  391. ray.force_raycast_update()
  392. if ray.is_colliding():
  393. var body = ray.get_collider()
  394. if body.has_method("bullet_hit"):
  395. body.bullet_hit(RIFLE_DAMAGE, ray.get_collision_point())
  396. ammo_in_guns["RIFLE"] -= 1
  397. create_sound("Rifle_shot", ray.global_transform.origin); # NEW CODE
  398. # Knife bullet(?) handeling: Use an area!
  399. elif current_gun == "KNIFE":
  400. var area = $Rotation_helper/Gun_fire_points/Knife_point/Area
  401. var bodies = area.get_overlapping_bodies()
  402. for body in bodies:
  403. if body.has_method("bullet_hit"):
  404. body.bullet_hit(KNIFE_DAMAGE, area.global_transform.origin)
  405. Now we will play the shooting noise for both the pistol and the rifle when a bullet is created.
  406. .. note:: We are passing in the positions of the ends of the guns using the bullet object's
  407. global :ref:`Transform <class_transform>` and the :ref:`Raycast <class_raycast>`'s global :ref:`Transform <class_transform>`.
  408. If you are not using a :ref:`AudioStreamPlayer3D <class_AudioStreamPlayer3D>` node, you can optionally leave the positions out and only
  409. pass in the name of the sound you want to play.
  410. Finally, lets play the sound of a gun being cocked when we reload and when we change weapons.
  411. Add the following to our reloading logic section of ``_physics_process``:
  412. ::
  413. # Reloading logic
  414. if reloading_gun == true:
  415. var can_reload = false
  416. if current_gun == "PISTOL":
  417. if animation_manager.current_state == "Pistol_idle":
  418. can_reload = true
  419. elif current_gun == "RIFLE":
  420. if animation_manager.current_state == "Rifle_idle":
  421. can_reload = true
  422. elif current_gun == "KNIFE":
  423. can_reload = false
  424. reloading_gun = false
  425. else:
  426. can_reload = false
  427. reloading_gun = false
  428. if ammo_for_guns[current_gun] <= 0 or ammo_in_guns[current_gun] == AMMO_IN_MAGS[current_gun]:
  429. can_reload = false
  430. reloading_gun = false
  431. if can_reload == true:
  432. var ammo_needed = AMMO_IN_MAGS[current_gun] - ammo_in_guns[current_gun]
  433. if ammo_for_guns[current_gun] >= ammo_needed:
  434. ammo_for_guns[current_gun] -= ammo_needed
  435. ammo_in_guns[current_gun] = AMMO_IN_MAGS[current_gun]
  436. else:
  437. ammo_in_guns[current_gun] += ammo_for_guns[current_gun]
  438. ammo_for_guns[current_gun] = 0
  439. if current_gun == "PISTOL":
  440. animation_manager.set_animation("Pistol_reload")
  441. elif current_gun == "RIFLE":
  442. animation_manager.set_animation("Rifle_reload")
  443. reloading_gun = false
  444. create_sound("Gun_cock", camera.global_transform.origin) # NEW CODE
  445. And add this code to the changing weapons section of ``_physics_process``:
  446. ::
  447. if changing_gun == true:
  448. if current_gun != "PISTOL":
  449. if animation_manager.current_state == "Pistol_idle":
  450. animation_manager.set_animation("Pistol_unequip")
  451. if current_gun != "RIFLE":
  452. if animation_manager.current_state == "Rifle_idle":
  453. animation_manager.set_animation("Rifle_unequip")
  454. if current_gun != "KNIFE":
  455. if animation_manager.current_state == "Knife_idle":
  456. animation_manager.set_animation("Knife_unequip")
  457. if current_gun == "UNARMED":
  458. if animation_manager.current_state == "Idle_unarmed":
  459. changing_gun = false
  460. elif current_gun == "KNIFE":
  461. if animation_manager.current_state == "Knife_idle":
  462. changing_gun = false
  463. if animation_manager.current_state == "Idle_unarmed":
  464. animation_manager.set_animation("Knife_equip")
  465. elif current_gun == "PISTOL":
  466. if animation_manager.current_state == "Pistol_idle":
  467. changing_gun = false
  468. if animation_manager.current_state == "Idle_unarmed":
  469. animation_manager.set_animation("Pistol_equip")
  470. create_sound("Gun_cock", camera.global_transform.origin) # NEW CODE
  471. elif current_gun == "RIFLE":
  472. if animation_manager.current_state == "Rifle_idle":
  473. changing_gun = false
  474. if animation_manager.current_state == "Idle_unarmed":
  475. animation_manager.set_animation("Rifle_equip")
  476. create_sound("Gun_cock", camera.global_transform.origin) # NEW CODE
  477. Now whatever sound you have assigned to "Gun_cock" will play when you reload and when you
  478. change to either the pistol or the rifle.
  479. Final notes
  480. -----------
  481. .. image:: img/FinishedTutorialPicture.png
  482. Now you have a fully working single player FPS!
  483. You can find the completed project here: :download:`Godot_FPS_Finished.zip <files/Godot_FPS_Finished.zip>`
  484. .. tip:: The finished project source is hosted on Github as well: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial
  485. You can also download all of the ``.blend`` files used here: :download:`Godot_FPS_BlenderFiles.zip <files/Godot_FPS_BlenderFiles.zip>`
  486. .. note:: The finished project source files contain the same exact code, just written in a different order.
  487. This is because the finished project source files are what the tutorial is based on.
  488. The finished project code was written in the order that features were created, not necessarily
  489. in a order that is ideal for learning.
  490. Other than that, the source is exactly the same, just with helpful comments explaining what
  491. each part does.
  492. The skybox is created by **StumpyStrust** and can be found at OpenGameArt.org. https://opengameart.org/content/space-skyboxes-0
  493. The font used is **Titillium-Regular**, and is licensed under the SIL Open Font License, Version 1.1.
  494. The skybox was convert to a 360 equirectangular image using this tool: https://www.360toolkit.co/convert-cubemap-to-spherical-equirectangular.html
  495. While no sounds are provided, you can find many game ready sounds at https://gamesounds.xyz/
  496. .. warning:: OpenGameArt.org, 360toolkit.co, the creator(s) of Titillium-Regular, and GameSounds.xyz are in no way involved in this tutorial.
  497. __________
  498. In :ref:`part four <doc_fps_tutorial_part_four>` we will be refactoring/rewriting ``Player.gd`` to a more modular format, as well as adding joypad support!