part_four.rst 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. .. _doc_fps_tutorial_part_four:
  2. Part 4
  3. ======
  4. Part Overview
  5. -------------
  6. In this part we will be refactoring ``Player.gd`` to use a more modular format, add support for joypads, and add the ability to change weapons with the scroll wheel.
  7. .. image:: img/FinishedTutorialPicture.png
  8. While this part may not be the most interesting, it is very important. Having a clean and modular code base allows us to build
  9. more complex behaviour in the future.
  10. .. note:: You are assumed to have finished :ref:`part three <doc_fps_tutorial_part_three>` before moving on to this part of the tutorial.
  11. .. tip:: You can find the completed code for part three here: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial/tree/part_3
  12. .. image:: img/GithubDownloadZip.png
  13. Just click the green "Clone or download" button and choose "Download Zip" to get the finished project for part 3.
  14. Let's get started!
  15. A quick note
  16. ------------
  17. Before we dig into refactoring the code, let's quickly talk about *why* we want to refactor the code.
  18. First, what is refactoring? According to wikipedia:
  19. **"Code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behaviour."**
  20. Basically, refactoring is taking code we've already written, and rewriting/restructuring it without changing what it does.
  21. Second, why refactor? There are plenty of reasons why you may want to refactor your code base, but for this tutorial there is really only three
  22. major reasons:
  23. 1: By refactoring the code base we can take out certain elements from the various functions in ``player.gd`` and separate them into their own functions/scripts.
  24. ``_physics_process`` benefits greatly from this, because while it does work right now, it is very confusing to navigate.
  25. 2: With some careful refactoring, we can take out most of the gun logic from ``Player.gd`` and put them into their own scripts. This is key because it easily allows
  26. us to make/edit weapons and their behaviours without having to change much in ``Player.gd``.
  27. 3: Currently performance in ``Player.gd`` is okay, but with some work we can make it even better! Performance was not a primary concern for the first three parts
  28. of this tutorial series, and while it still is not a major concern, we ideally want to write code with good performance when possible.
  29. All of these reasons are why we are going to refactor ``Player.gd``.
  30. What we plan on doing in this part is taking our very linear ``Player.gd`` script and make it more modular and extendible. This will allow us
  31. to more easily add features later, as well as make it easier to work with in later parts.
  32. .. note:: Even though part 4 is dedicated to refactoring ``Player.gd``, it is likely we will need to do more refactoring in later parts as we continue to add features!
  33. Breaking it down
  34. ----------------
  35. Current a majority of the code in ``Player.gd`` is located in ``_physics_process``. Right now ``_physics_process`` is a huge function with several works parts.
  36. With some refactoring, we can break ``_physics_process`` into several smaller functions.
  37. Ideally we want to make these smaller functions focused on doing a small set of tasks.
  38. This makes it much easier to know where we need to add code to when we are working on new features.
  39. Another benefit of using smaller functions is they are generally easier to debug!
  40. Breaking down input processing
  41. ______________________________
  42. First, lets make a function for handling all of the :ref:`Input <class_Input>` related code.
  43. This allows us to more clearly see all of our player input.
  44. Create new function called `process_input` and add the following code:
  45. ::
  46. func process_input(delta):
  47. # ----------------------------------
  48. # Walking
  49. dir = Vector3()
  50. var cam_xform = camera.get_global_transform()
  51. var input_movement_vector = Vector2()
  52. # Add keyboard input
  53. if (Input.is_action_pressed("movement_forward")):
  54. input_movement_vector.y += 1
  55. if (Input.is_action_pressed("movement_backward")):
  56. input_movement_vector.y -= 1
  57. if (Input.is_action_pressed("movement_left")):
  58. input_movement_vector.x -= 1
  59. if (Input.is_action_pressed("movement_right")):
  60. input_movement_vector.x = 1
  61. input_movement_vector = input_movement_vector.normalized()
  62. dir += -cam_xform.basis.z.normalized() * input_movement_vector.y
  63. dir += cam_xform.basis.x.normalized() * input_movement_vector.x
  64. # ----------------------------------
  65. # ----------------------------------
  66. # Sprinting
  67. if Input.is_action_pressed("movement_sprint"):
  68. is_spriting = true
  69. else:
  70. is_spriting = false
  71. # ----------------------------------
  72. # ----------------------------------
  73. # Jumping
  74. if is_on_floor():
  75. if Input.is_action_just_pressed("movement_jump"):
  76. vel.y = JUMP_SPEED
  77. # ----------------------------------
  78. # ----------------------------------
  79. # Changing weapons.
  80. if changing_gun == false and reloading_gun == false:
  81. if Input.is_key_pressed(KEY_1):
  82. current_gun = "UNARMED"
  83. changing_gun = true
  84. elif Input.is_key_pressed(KEY_2):
  85. current_gun = "KNIFE"
  86. changing_gun = true
  87. elif Input.is_key_pressed(KEY_3):
  88. current_gun = "PISTOL"
  89. changing_gun = true
  90. elif Input.is_key_pressed(KEY_4):
  91. current_gun = "RIFLE"
  92. changing_gun = true
  93. # ----------------------------------
  94. # ----------------------------------
  95. # Reloading
  96. if reloading_gun == false:
  97. if Input.is_action_just_pressed("reload"):
  98. if current_gun == "PISTOL" or current_gun == "RIFLE":
  99. if animation_manager.current_state != "Pistol_reload" and animation_manager.current_state != "Rifle_reload":
  100. reloading_gun = true
  101. # ----------------------------------
  102. # ----------------------------------
  103. # Firing the weapons
  104. if Input.is_action_pressed("fire"):
  105. if current_gun == "PISTOL":
  106. if ammo_in_guns["PISTOL"] > 0:
  107. if animation_manager.current_state == "Pistol_idle":
  108. animation_manager.set_animation("Pistol_fire")
  109. else:
  110. reloading_gun = true
  111. elif current_gun == "RIFLE":
  112. if ammo_in_guns["RIFLE"] > 0:
  113. if animation_manager.current_state == "Rifle_idle":
  114. animation_manager.set_animation("Rifle_fire")
  115. else:
  116. reloading_gun = true
  117. elif current_gun == "KNIFE":
  118. if animation_manager.current_state == "Knife_idle":
  119. animation_manager.set_animation("Knife_fire")
  120. # ----------------------------------
  121. # ----------------------------------
  122. # Turning the flashlight on/off
  123. if Input.is_action_just_pressed("flashlight"):
  124. if flashlight.is_visible_in_tree():
  125. flashlight.hide()
  126. else:
  127. flashlight.show()
  128. # ----------------------------------
  129. # ----------------------------------
  130. # Capturing/Freeing the cursor
  131. if Input.is_action_just_pressed("ui_cancel"):
  132. if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
  133. Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  134. else:
  135. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  136. # ----------------------------------
  137. You may have noticed that all of the code so far is exactly the same as the :ref:`Input <class_Input>` relate code already written in ``_physics_process``,
  138. but is now all placed in one function.
  139. There are a few changes though:
  140. Because we are now calling our input code outside of ``_physics_process`` we need to change ``dir`` from a local variable to a global variable.
  141. Add ``var dir = Vector3()`` with the rest of the global variables, ideally nearby the movement code for organization.
  142. .. warning:: Do not forget to change ``dir`` to a global variable!
  143. Another change is we're not directly effecting ``dir`` any more. Before we were changing ``dir`` when a movement action was pressed. Now we are changing a new local variable,
  144. ``input_movement_vector``, instead. This will later allow us to have more than one form of directional input. By multiplying ``input_movement_vector`` by the camera's
  145. directional vectors, we get the same result as when we were effecting ``dir`` directly.
  146. Notice how we are normalizing ``input_movement_vector`` as well. This is important because later when we add additional forms of directional input, we do not
  147. want to move faster if two forms of input are moving at the same time. For example, we do not want to move faster if we are pressing the ``UP`` key on the keyboard and also
  148. are pushing forward on a controller. If we did not normalize, then we'd move twice as fast! By normalizing, we make everyone move at the same speed, regardless of how many
  149. input devices they are using.
  150. Breaking down ``KinematicBody`` movement
  151. ________________________________________
  152. Next we want to move all of the code relating to moving using the :ref:`KinematicBody <class_KinematicBody>` into its own function.
  153. This allows us to more clearly see what code we are sending :ref:`KinematicBody <class_KinematicBody>` and what it does.
  154. Create a new function and call it ``process_movement``. Lets add the following code:
  155. ::
  156. func process_movement(delta):
  157. var grav = norm_grav
  158. dir.y = 0
  159. dir = dir.normalized()
  160. vel.y += delta*grav
  161. var hvel = vel
  162. hvel.y = 0
  163. var target = dir
  164. if is_spriting:
  165. target *= MAX_SPRINT_SPEED
  166. else:
  167. target *= MAX_SPEED
  168. var accel
  169. if dir.dot(hvel) > 0:
  170. if is_spriting:
  171. accel = SPRINT_ACCEL
  172. else:
  173. accel = ACCEL
  174. else:
  175. accel = DEACCEL
  176. hvel = hvel.linear_interpolate(target, accel*delta)
  177. vel.x = hvel.x
  178. vel.z = hvel.z
  179. vel = move_and_slide(vel,Vector3(0,1,0), 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))
  180. Thankfully nothing is has changed here, all we've done is moved the code out of ``_physics_process``.
  181. .. warning:: If you are using Godot ``master`` branch (or Godot 3.1), you will need to change ``vel = move_and_slide(vel,Vector3(0,1,0), 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))``
  182. to ``vel = move_and_slide(vel,Vector3(0,1,0), true, 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))``.
  183. Now when we are ready to have the :ref:`KinematicBody <class_KinematicBody>` process our movement and send us through space, all we need to do is call ``process_movement``.
  184. Changing the weapon code structure
  185. ----------------------------------
  186. So far, we have not really changed the structure of the code, we've just shuffled it around, so lets change that.
  187. One of the major things we ideally want to change is how the weapon code is handled. Currently all of the weapon realted code is all in ``Player.gd``, everything
  188. from how much ammo a weapon carries, to firing bullets. While this has the advantage of having all of your code in one place, it would be much
  189. nicer if we make a weapon interface so we can create/change weapons easily without having to scroll through ``Player.gd`` to look for the bit of code we want to add/change.
  190. Open up ``Player.tscn`` and navigate to the ``Gun_fire_points`` node. Lets make the pistol first. Select ``Pistol_point`` and attach a node node and call it
  191. ``Weapon_Pistol.gd``.
  192. Our weapon scripts are going to do four things: They're going to handle *firing*, *reloading*, *equipping*, and *unequipping*.
  193. Add the following code to ``Weapon_Pistol.gd``:
  194. ::
  195. extends Spatial
  196. var ammo_in_weapon = 20;
  197. var spare_ammo = 60;
  198. const AMMO_IN_MAG = 20;
  199. const DAMAGE = 15;
  200. const CAN_RELOAD = true;
  201. const RELOADING_ANIM_NAME = "Pistol_reload"
  202. const IDLE_ANIM_NAME = "Pistol_idle"
  203. const FIRE_ANIM_NAME = "Pistol_fire"
  204. var is_weapon_enabled = false;
  205. var bullet_scene = preload("Bullet_Scene.tscn")
  206. var player_node = null;
  207. func _ready():
  208. pass;
  209. func fire_weapon():
  210. var clone = bullet_scene.instance()
  211. var scene_root = get_tree().root.get_children()[0]
  212. scene_root.add_child(clone)
  213. clone.global_transform = self.global_transform
  214. clone.scale = Vector3(4, 4, 4)
  215. clone.BULLET_DAMAGE = DAMAGE;
  216. ammo_in_weapon -= 1
  217. player_node.create_sound("Pistol_shot", self.global_transform.origin)
  218. func reload_weapon():
  219. var can_reload = false;
  220. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  221. can_reload = true
  222. if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
  223. can_reload = false
  224. if can_reload == true:
  225. var ammo_needed = AMMO_IN_MAG - ammo_in_weapon;
  226. if spare_ammo >= ammo_needed:
  227. spare_ammo -= ammo_needed
  228. ammo_in_weapon = AMMO_IN_MAG;
  229. else:
  230. ammo_in_weapon += spare_ammo
  231. spare_ammo = 0
  232. player_node.animation_manager.set_animation("Pistol_reload")
  233. player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
  234. return true;
  235. return false;
  236. func equip_weapon():
  237. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  238. is_weapon_enabled = true;
  239. return true
  240. if player_node.animation_manager.current_state == "Idle_unarmed":
  241. player_node.animation_manager.set_animation("Pistol_equip")
  242. player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
  243. return false
  244. func unequip_weapon():
  245. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  246. if (player_node.animation_manager.current_state != "Pistol_unequip"):
  247. player_node.animation_manager.set_animation("Pistol_unequip")
  248. if player_node.animation_manager.current_state == "Idle_unarmed":
  249. is_weapon_enabled = false;
  250. return true
  251. else:
  252. return false
  253. Lets go over what is happening in this script:
  254. ______
  255. First lets look at the constants and go over what each will do:
  256. * ``ammo_in_weapon``: How much ammo is *currently* in this weapon.
  257. * ``spare_ammo``: How much spare ammo we have in reserve for this weapon. ``spare_ammo + ammo_in_weapon = total ammo for this weapon``.
  258. * ``AMMO_IN_MAG``: The amount ammo needed to fill the weapon. To put it another way, the amount of ammo in each magazine.
  259. * ``DAMAGE``: The amount of damage a single bullet does.
  260. * ``CAN_RELOAD``: A boolean for tracking whether this weapon has the ability to reload.
  261. * ``RELOADING_ANIM_NAME``: The name of the reloading animation for this weapon.
  262. * ``IDLE_ANIM_NAME``: The name of the idle animation for this weapon.
  263. * ``FIRE_ANIM_NAME``: The name of the firing animation for this weapon.
  264. * ``is_weapon_enabled``: A boolean for tracking whether or not this weapon is the currently used/enabled weapon.
  265. * ``bullet_scene``: The bullet scene we created in part 2 of this tutorial.
  266. * ``player_node``: The player node and script (``Player.gd``).
  267. ______
  268. Notice how we do not do anything in ``_ready``.
  269. We could try and grab the player node here, but it makes a messy ``get_node`` call, and because we already
  270. have to aim these points in ``Player.gd`` anyway, we will just pass the player node then.
  271. .. note:: This is just a design choice. Depending on your project, it may be better to use ``get_node`` in the
  272. weapon scripts.
  273. ______
  274. Lets look at ``fire_weapon``.
  275. First we make a clone of the bullet scene and add it as a child of the scene root.
  276. Next we set its global transform to ``self.global_transform``.
  277. .. note:: before we were using a ``get_node`` call to
  278. get here because we were calling this from ``Player.gd``. Now that we are firing from the fire point itself, we do not
  279. need to use ``get_node`` any more.
  280. Then we set its scale. As before, the bullet object is too small by default, so we scale it up so it's easier to see.
  281. Next we set its damage. This is new, but nothing crazy. To make this work, we just need to go into
  282. ``Bullet_script.gd`` and change ``const BULLET_DAMAGE`` to ``var BULLET_DAMAGE``. The reason behind changing ``BULLET_DAMAGE`` from
  283. a constant to a normal variable is because we may reuse the bullet object later (for a different weapon)
  284. .. warning:: Do not forgot to change ``const BULLET_DAMAGE`` to ``var BULLET_DAMAGE`` in ``Bullet_script.gd``!
  285. Then we remove one from the ammo in our weapon and play a sound (if we have sounds).
  286. .. note:: With the exception of how we are no longer using ``get_node``, everything in ``fire_weapon`` is the same as the code
  287. as ``Player.gd``'s ``fire_bullet`` function.
  288. ______
  289. In ``reload_weapon`` we are doing things a little differently.
  290. First we define a variable to track whether or not we can reload. We then do a couple checks. The first check is checking whether
  291. or not we are in this weapon's idle animation. We do not want to reload while we are playing any other animation, so this check ensures
  292. that does not happen.
  293. The next thing we check is whether or not we have any ammo in reserve and/or if our weapon is full. We cannot reload with no spare ammo, and
  294. we do not want the player to be able to reload if the weapon is already full.
  295. .. tip:: In some games you can reload while full. Many times in these cases you lose whatever ammo was in the weapon when you reload.
  296. For this tutorial though, we will only allow the player to reload if they do not have a full weapon.
  297. Then we check ``can_reload`` to see if it is true.
  298. If it is, we then calculate how much ammo we need to fill the weapon.
  299. If we have enough ammo in spares to fill the weapon, we remove the ammo we are taking from spares and set ``ammo_in_weapon`` to however much ammo is in a full weapon.
  300. If we do not have enough ammo in spares, we instead add all of the ammo left in spares and then set our spare ammo to zero.
  301. We then play the reloading animation and play a sound. We return ``true`` to signal we have successfully reloaded.
  302. If we cannot reload because ``reload_weapon`` is ``false``, we return ``false`` to signal we did not successfully reload.
  303. ______
  304. For ``equip_weapon`` we first check if the player is in the pistol's idle state.
  305. If we are in the pistol's idle state we've successfully equipped the pistol.
  306. We set ``is_weapon_enabled`` to ``true`` because we are now using this weapon, and return ``true``.
  307. .. note:: We need ``is_weapon_enabled`` so we do not keep trying to equip/unequip the weapons over and over again. If we relied only on using
  308. the ``equip_weapon``/``unequip_weapon`` functions, we could possibility get cases where we are stuck in a loop where we are equipping/unequipping
  309. the same weapon over and over again.
  310. Next we check if we are in the idle unarmed state, a state where we can transition to our equip animation. If we are, then we change the animation
  311. to ``Pistol_equip`` and play a sound. Finally, we return ``false``.
  312. The reason behind returning ``false`` unless we are in our idle animation is because we will be calling this function more than once, checking to see if we
  313. have successfully equipped the pistol.
  314. ______
  315. ``unequip_weapon`` is extremely similar to ``equip_weapon``, but the checks are in reverse.
  316. We just check if we are in our idle state. If we are, and we are not already unequipping we set our animation to ``Pistol_unequip``.
  317. Then we check if we are in the idle animation. If we are, we set ``is_weapon_enabled`` to ``false`` because we are no longer using this weapon, and return ``true``.
  318. Finally, if we did not return ``true``, we return false.
  319. As with ``equip_weapon``, we want to return false by default because we will be calling this function until it returns true.
  320. ______
  321. Now we just need to do the same thing for the knife and the rifle.
  322. There is only one minor difference with the knife and the rifle. We still define a reload function for the knife, but instead of doing
  323. anything we automatically return false.
  324. Select ``Knife_point``, created a new script called ``Weapon_Knife.gd``, and add the following:
  325. ::
  326. extends Spatial
  327. var ammo_in_weapon = 1;
  328. var spare_ammo = 1;
  329. const AMMO_IN_MAG = 1;
  330. const DAMAGE = 40;
  331. const CAN_RELOAD = false;
  332. const RELOADING_ANIM_NAME = ""
  333. const IDLE_ANIM_NAME = "Knife_idle"
  334. const FIRE_ANIM_NAME = "Knife_fire"
  335. var is_weapon_enabled = false;
  336. var player_node = null;
  337. func _ready():
  338. pass;
  339. func fire_weapon():
  340. var area = get_node("Area")
  341. var bodies = area.get_overlapping_bodies()
  342. for body in bodies:
  343. if body.has_method("bullet_hit"):
  344. body.bullet_hit(DAMAGE, area.global_transform.origin)
  345. func reload_weapon():
  346. return false;
  347. func equip_weapon():
  348. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  349. is_weapon_enabled = true;
  350. return true
  351. if player_node.animation_manager.current_state == "Idle_unarmed":
  352. player_node.animation_manager.set_animation("Knife_equip")
  353. return false
  354. func unequip_weapon():
  355. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  356. player_node.animation_manager.set_animation("Knife_unequip")
  357. if player_node.animation_manager.current_state == "Idle_unarmed":
  358. is_weapon_enabled = false;
  359. return true
  360. return false
  361. There are only a few things to note here.
  362. The first is we still are defining ``ammo_in_weapon``, ``spare_ammo`` and ``AMMO_IN_MAG``. The reason behind this is so our code has a consistent
  363. interface. We may later need to access these variables in all weapons, so we are adding them for the knife as a way assure all weapons have these variables.
  364. The second thing of note is in ``reload_weapon``. Because we cannot reload a knife (or at least, not this one), we just always return ``false``.
  365. The last thing to note is how ``fire_weapon``'s code is exactly the same as the code from ``Player.gd``. The firing code for all three weapons,
  366. the pistol, rifle, and knife, are exactly the same as the code in ``Player.gd``. The only differences is how we are accessing the spawn point nodes
  367. and their children.
  368. ______
  369. Finally, select ``Rifle_point``, create a new script called ``Weapon_Rifle.gd``, and add the following code:
  370. ::
  371. extends Spatial
  372. var ammo_in_weapon = 80;
  373. var spare_ammo = 160;
  374. const AMMO_IN_MAG = 80;
  375. const DAMAGE = 4;
  376. const CAN_RELOAD = true;
  377. const RELOADING_ANIM_NAME = "Rifle_reload"
  378. const IDLE_ANIM_NAME = "Rifle_idle"
  379. const FIRE_ANIM_NAME = "Rifle_fire"
  380. var is_weapon_enabled = false;
  381. var player_node = null;
  382. func _ready():
  383. pass;
  384. func fire_weapon():
  385. var ray = get_node("RayCast")
  386. ray.force_raycast_update()
  387. if ray.is_colliding():
  388. var body = ray.get_collider()
  389. if body.has_method("bullet_hit"):
  390. body.bullet_hit(DAMAGE, ray.get_collision_point())
  391. ammo_in_weapon -= 1;
  392. player_node.create_sound("Rifle_shot", ray.global_transform.origin)
  393. func reload_weapon():
  394. var can_reload = false;
  395. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  396. can_reload = true
  397. if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
  398. can_reload = false
  399. if can_reload == true:
  400. var ammo_needed = AMMO_IN_MAG - ammo_in_weapon;
  401. if spare_ammo >= ammo_needed:
  402. spare_ammo -= ammo_needed
  403. ammo_in_weapon = AMMO_IN_MAG;
  404. else:
  405. ammo_in_weapon += spare_ammo
  406. spare_ammo = 0
  407. player_node.animation_manager.set_animation("Rifle_reload")
  408. player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
  409. return true;
  410. return false;
  411. func equip_weapon():
  412. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  413. is_weapon_enabled = true;
  414. return true
  415. if player_node.animation_manager.current_state == "Idle_unarmed":
  416. player_node.animation_manager.set_animation("Rifle_equip")
  417. player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
  418. return false
  419. func unequip_weapon():
  420. if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
  421. if (player_node.animation_manager.current_state != "Rifle_unequip"):
  422. player_node.animation_manager.set_animation("Rifle_unequip")
  423. if player_node.animation_manager.current_state == "Idle_unarmed":
  424. is_weapon_enabled = false;
  425. return true
  426. return false
  427. Thankfully the code for the rifle is exactly the same as the pistol, with ``fire_weapon`` changed to use the rifle's firing code. Other than that, everything is exactly the same,
  428. just adjusted for the rifle.
  429. Finishing refactoring ``Player.gd``
  430. -----------------------------------
  431. Now we are ready to use our newly refactored weapons in ``Player.gd``. First, we need to change some of the global variables.
  432. Find all of the constants relating to the weapons, delete them, and add the following:
  433. ::
  434. var current_weapon_name = "UNARMED"
  435. var weapons = {"UNARMED":null, "KNIFE":null, "PISTOL":null, "RIFLE":null}
  436. const weapon_number_to_name = {0:"UNARMED", 1:"KNIFE", 2:"PISTOL", 3:"RIFLE"}
  437. const weapon_name_to_number = {"UNARMED":0, "KNIFE":1, "PISTOL":2, "RIFLE":3}
  438. var changing_weapon = false
  439. var changing_weapon_name = "UNARMED"
  440. var reloading_weapon = false
  441. Lets go over each of these new global variables:
  442. * ``current_weapon_name``: The name of the weapon currently in use.
  443. * ``weapons``: A dictionary holding all of the weapon nodes, allowing us to access them by name instead of using ``get_node``.
  444. * ``weapon_number_to_name``: A dictionary holding all of the weapons and which number they represent.
  445. * ``weapon_name_to_number``: A dictionary holding all of the weapons numbers and which names they represent. Combined with ``weapon_number_to_name``, we can change from number to name and back.
  446. * ``changing_weapon``: A boolean to track whether we are trying to change weapons or not.
  447. * ``changing_weapon_name``: The name of the weapon we are trying to change to.
  448. * ``reloading_weapon``: A boolean to track whether we are reloading or not.
  449. We need to change ``_ready`` to the following:
  450. ::
  451. func _ready():
  452. camera = get_node("Rotation_helper/Camera")
  453. rotation_helper = get_node("Rotation_helper")
  454. animation_manager = get_node("Rotation_helper/Model/AnimationPlayer")
  455. animation_manager.callback_function = funcref(self, "fire_bullet")
  456. set_physics_process(true)
  457. Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  458. set_process_input(true)
  459. weapons["KNIFE"] = get_node("Rotation_helper/Gun_fire_points/Knife_point")
  460. weapons["PISTOL"] = get_node("Rotation_helper/Gun_fire_points/Pistol_point")
  461. weapons["RIFLE"] = get_node("Rotation_helper/Gun_fire_points/Rifle_point")
  462. var gun_aim_point_pos = get_node("Rotation_helper/Gun_aim_point").global_transform.origin
  463. for weapon in weapons:
  464. var weapon_node = weapons[weapon]
  465. if weapon_node != null:
  466. weapon_node.player_node = self
  467. weapon_node.look_at(gun_aim_point_pos, Vector3(0, 1, 0))
  468. weapon_node.rotate_object_local(Vector3(0, 1, 0), deg2rad(180))
  469. current_weapon_name = "UNARMED"
  470. changing_weapon_name = "UNARMED"
  471. UI_status_label = get_node("HUD/Panel/Gun_label")
  472. flashlight = get_node("Rotation_helper/Flashlight")
  473. Lets quickly go over the new stuff.
  474. Notice how most of the code is exactly the same as before. The only code that's changed is how
  475. we are handling the gun aim points, so let's look at those changes.
  476. First, we get all of the weapon nodes using ``get_node`` and assign them to the ``weapons`` dictionary.
  477. Then we loop through all of the weapons in the ``weapons`` dictionary. For each weapon node, we get the value assigned to that key.
  478. .. tip:: When we are using ``for X in Y`` where ``Y`` is a dictionary, ``X`` is assigned to the each **key** in the dictionary, not the value. To get the value, we
  479. have to retrieve it using ``Y[X]``.
  480. If the weapon node is not ``null``, we set it's ``player_node`` variable to ``self``, and we make the point look at the gun aim position.
  481. .. note:: The reason we check for ``null`` is because our ``UNARMED`` weapon is ``null``. This is just a design choice, not a requirement for FPS games.
  482. You could define a "weapon" for the UNARMED state, but in this series we are just going to use ``null``.
  483. Next we flip the aim point by ``180`` degrees so it doesn't fire backwards.
  484. .. warning:: The reason behind rotating the gun aim point is explained in :ref:`part 2 <doc_fps_tutorial_part_two>`
  485. Finally, we set ``current_weapon_name`` and ``changing_weapon_name`` to ``UNARMED`` so our starting weapon is ``UNARMED``.
  486. ______
  487. Now we need to change ``_physics_process``. Delete everything in ``_physics_process`` and add the following:
  488. ::
  489. func _physics_process(delta):
  490. process_input(delta)
  491. #process_view_input(delta)
  492. process_movement(delta)
  493. process_changing_weapons(delta)
  494. process_reloading(delta)
  495. process_UI(delta)
  496. .. note:: You may have noticed how we have a commented out function, ``process_view_input``. We will be using this later!
  497. For now just leave it commented out!
  498. Now we are calling each of our modular functions in order. Notice how we are still missing
  499. ``process_changing_weapons``, ``process_reloading``, and ``process_UI``. Before we add those functions, lets quickly return to
  500. ``process_input``.
  501. Finishing ``process_input``
  502. ___________________________
  503. First, lets change ``process_input`` so our weapon related code works with the new weapon system.
  504. First, delete all of the weapon related code in `process_input`. This is the includes:
  505. Changing weapons, Reloading, and Firing.
  506. Now at the bottom of ``process_input``, add the following code:
  507. ::
  508. func process_input(delta):
  509. # Other input code (like movement, jumping, etc) above!
  510. # ----------------------------------
  511. # Changing weapons.
  512. var weapon_change_number = weapon_name_to_number[current_weapon_name]
  513. if Input.is_key_pressed(KEY_1):
  514. weapon_change_number = 0
  515. if Input.is_key_pressed(KEY_2):
  516. weapon_change_number = 1
  517. if Input.is_key_pressed(KEY_3):
  518. weapon_change_number = 2
  519. if Input.is_key_pressed(KEY_4):
  520. weapon_change_number = 3
  521. if Input.is_action_just_pressed("shift_weapon_positive"):
  522. weapon_change_number += 1
  523. if Input.is_action_just_pressed("shift_weapon_negative"):
  524. weapon_change_number -= 1
  525. weapon_change_number = clamp(weapon_change_number, 0, weapon_number_to_name.size()-1)
  526. if changing_weapon == false:
  527. if reloading_weapon == false:
  528. if weapon_number_to_name[weapon_change_number] != current_weapon_name:
  529. changing_weapon_name = weapon_number_to_name[weapon_change_number]
  530. changing_weapon = true
  531. # ----------------------------------
  532. # ----------------------------------
  533. # Reloading
  534. if reloading_weapon == false:
  535. if changing_weapon == false:
  536. if Input.is_action_just_pressed("reload"):
  537. var current_weapon = weapons[current_weapon_name]
  538. if current_weapon != null:
  539. if current_weapon.CAN_RELOAD == true:
  540. var current_anim_state = animation_manager.current_state
  541. var is_reloading = false
  542. for weapon in weapons:
  543. var weapon_node = weapons[weapon]
  544. if weapon_node != null:
  545. if current_anim_state == weapon_node.RELOADING_ANIM_NAME:
  546. is_reloading = true
  547. if is_reloading == false:
  548. reloading_weapon = true
  549. # ----------------------------------
  550. # ----------------------------------
  551. # Firing the weapons
  552. if Input.is_action_pressed("fire"):
  553. if reloading_weapon == false:
  554. if changing_weapon == false:
  555. var current_weapon = weapons[current_weapon_name]
  556. if current_weapon != null:
  557. if current_weapon.ammo_in_weapon > 0:
  558. if animation_manager.current_state == current_weapon.IDLE_ANIM_NAME:
  559. animation_manager.set_animation(current_weapon.FIRE_ANIM_NAME)
  560. else:
  561. reloading_weapon = true
  562. # ----------------------------------
  563. Lets go through what each of these sections are doing.
  564. ______
  565. Lets look at the weapon changing section first.
  566. The first thing we do is get the current weapon number and assign it to ``weapon_change_number``.
  567. Next we check each of the four number keys and we assign ``weapon_change_number`` to their value if they are pressed.
  568. .. note:: Most keyboards go in the order of ``1234567890``, so we when we set ``weapon_change_number``, we offset the value by ``-1`` so the first key (``1``)
  569. is actually ``0``, which is our first weapon.
  570. Then we check if two new actions are pressed: ``shift_weapon_positive`` and ``shift_weapon_negative``. We will add these actions once we've finished
  571. going over ``process_input``.
  572. Next we clamp ``weapon_change_number`` so it cannot be higher or lower than the amount of weapons we have.
  573. .. tip:: We are making a small assumption here: We are assuming our weapons are defined in a linear pattern, where we do not have any jumps in number.
  574. Another thing to note is we are getting the maximum value using ``weapon_to_number.size()-1``. We remove ``1`` because ``size`` returns the number
  575. of elements in the dictionary, starting from ``1``, while GDScript accesses values starting from ``0``.
  576. We do not want to suddenly change weapons while already changing weapons or reload, so we check to make sure both variables are ``false``.
  577. Then we convert ``weapon_change_number`` to a weapon name using ``weapon_number_to_name`` and check to make sure we not trying to change to the weapon we
  578. are already using. If we are indeed changing weapons, we set ``changing_weapon_name`` to the name of the weapon at ``weapon_change_name`` using ``weapon_number_to_name``.
  579. Finally, we set ``changing_weapon`` to true so we can process the actual weapon changing logic in ``process_changing_weapons``.
  580. ______
  581. For reloading we first check to make sure we are not already reload, or changing weapons.
  582. Then we check to see if the reloading action has been pressed.
  583. Next we get the current weapon and assign it to ``current_weapon``.
  584. If the current weapon is not ``null`` we then make sure this weapon can reload using the weapon's ``CAN_RELOAD`` constant.
  585. .. tip:: We check for ``null`` because we do not want to reload ``UNARMED``!
  586. Next we check get the current animation state from our animation manager, and we set ``is_reloading`` to ``false``.
  587. The reason we need ``is_reloading`` is because we need to go through each weapon and make sure we are not in it's reloading state already,
  588. because we do not want to allow the player to (potentially) reload if they are already in a reloading animation.
  589. We then go through each weapon in our ``weapons`` dictionary. We then get the weapon node, assign it to ``weapon_node`` and check to make sure it
  590. is not ``null``. If it is not ``null``, we then make sure it's ``RELOADING_ANIM_NAME`` constant to see if it is equal to the animation we are currently in. If it is,
  591. we set ``is_reloading`` to ``true``.
  592. If ``is_reloading`` is still ``false``, we then set ``reloading_weapon`` to true so we can process the reloading weapon logic in ``process_reloading``.
  593. ______
  594. Finally, we have the firing section.
  595. The first thing we do is check to see if the ``fire`` action has been pressed. If it has, we then make sure we are not reloading or changing weapons.
  596. Next we get the current weapon and assign it to ``current_weapon``. We then check to make sure it is not equal to ``null``.
  597. If the current weapon is not equal to ``null``, we then make sure the weapon actually has ammo. If it does, we then check to see if we are in the weapon's idle state.
  598. If we are indeed in the weapon's idle state, we set our animation to the weapon's fire animation.
  599. If the current weapon does not have any ammo, we set ``reloading_weapon`` to true.
  600. Adding our new input map actions
  601. ________________________________
  602. As mentioned above, we've defined a couple new input actions: ``shift_weapon_positive`` and ``shift_weapon_negative``.
  603. Currently these input actions do not exist in our project, so let's add them!
  604. .. image:: img/ProjectSettingsAddAction.png
  605. Open up your project settings and go to the ``Input Map`` tab. In the ``Action`` text field, type ``shift_weapon_positive`` and press enter or press the
  606. button on the side that reads ``Add``. Next write ``shift_weapon_negative`` and press enter or press the ``Add`` button.
  607. Scroll down to the bottom of the list and click the little plus sign next to one of the newly created actions.
  608. .. image:: img/ProjectSettingsAddKey.png
  609. You can assign whatever key you want to either
  610. of these actions. The finished project has the ``Equal`` and ``Kp Add`` keys assigned to ``shift_weapon_positive``. ``shift_weapon_negative`` has ``Minus`` and
  611. ``Kp Subtract`` keys assigned in the finished project.
  612. Once you've assigned whatever keys you want to both actions, close the project settings and save.
  613. Adding ``process_changing_weapons``
  614. ___________________________________
  615. Lets make the weapon changing logic next. Open up ``Player.gd`` and add the following function:
  616. ::
  617. func process_changing_weapons(delta):
  618. if changing_weapon == true:
  619. var weapon_unequipped = false
  620. var current_weapon = weapons[current_weapon_name]
  621. if current_weapon == null:
  622. weapon_unequipped = true
  623. else:
  624. if current_weapon.is_weapon_enabled == true:
  625. weapon_unequipped = current_weapon.unequip_weapon()
  626. else:
  627. weapon_unequipped = true
  628. if weapon_unequipped == true:
  629. var weapon_equiped = false
  630. var weapon_to_equip = weapons[changing_weapon_name]
  631. if weapon_to_equip == null:
  632. weapon_equiped = true
  633. else:
  634. if weapon_to_equip.is_weapon_enabled == false:
  635. weapon_equiped = weapon_to_equip.equip_weapon()
  636. else:
  637. weapon_equiped = true
  638. if weapon_equiped == true:
  639. changing_weapon = false
  640. current_weapon_name = changing_weapon_name
  641. changing_weapon_name = ""
  642. Lets go over what's happening here.
  643. First we check to make sure ``changing_weapon`` is ``true``.
  644. Next we make a new variable, ``weapon_unequipped``, and set it to ``false``. We will use ``weapon_unequipped`` to check whether or not the current weapon is unequipped.
  645. We then get the current weapon and assign it to ``current_weapon``.
  646. If the current weapon is ``null``, if we are ``UNARMED``, we can conclude the weapon has been successfully unequipped and set ``weapon_unequipped`` to ``true``.
  647. If the weapon is not ``null``, we check if the weapon is enabled. If the weapon is enabled, we call it's ``unequip_weapon`` function. If it is not enabled, we set ``weapon_unequipped`` to ``true``.
  648. Next we check if ``weapon_unequipped`` is ``true`` or not. Remember, ``weapon_unequipped`` will only be true if the current weapon's ``is_weapon_enabled`` variable is ``false`` (or the weapon
  649. is ``null``).
  650. If the current weapon is successfully unequipped, we then make a variable, ``weapon_equipped``. ``weapon_equipped`` will serve the same function as ``weapon_unequipped``, but instead of
  651. tracking if we've successfully unequipped the current weapon, we instead are tracking to see if the weapon we are wanting to change to has been successfully equipped.
  652. We then get the weapon we want to change to and assign it to ``weapon_to_equip``.
  653. Next we check to see if ``weapon_to_equip`` is ``null``. If it is, we set ``weapon_equipped`` to ``true`` because ``UNARMED`` does not need any additional processing.
  654. If ``weapon_to_equip`` is not null, we then check to see if the weapon is not enabled by checking it's ``is_weapon_enabled`` variable. If it is not enabled, we call ``equip_weapon``
  655. on the weapon we are wanting to equip.
  656. If the weapon we are wanting to equip is enabled, we set ``weapon_equipped`` to true.
  657. Finally, we check to see if ``weapon_equipped`` is ``true``. If it is, we set ``changing_weapon`` to ``false``, set ``current_weapon_name`` to the weapon we have changed to (``changing_weapon_name``),
  658. and we set ``changing_weapon_name`` to a empty string.
  659. Adding ``process_reloading``
  660. ____________________________
  661. Let's finish up our new modular weapon system and add ``process_reloading``. Make a new function called ``process_reloading`` and add the following:
  662. ::
  663. func process_reloading(delta):
  664. if reloading_weapon == true:
  665. var current_weapon = weapons[current_weapon_name]
  666. if current_weapon != null:
  667. current_weapon.reload_weapon()
  668. reloading_weapon = false
  669. Let's go over what's this function does.
  670. First we check to make sure we are wanting to reload. If we are, we then get the current weapon and assign it to ``current_weapon``.
  671. If ``current_weapon`` is not equal to ``null``, we call it's ``reload_weapon`` function.
  672. Finally, we set ``reloading_weapon`` to ``false`` because regardless of whether we've successfully reloaded, we have tried and no longer
  673. need to process weapon reloading.
  674. Changing ``fire_bullet``
  675. ________________________
  676. Next we need to change ``fire_bullet`` because we are no longer actually firing the bullets in ``Player.gd``. Change ``fire_bullet`` to the following:
  677. ::
  678. func fire_bullet():
  679. if changing_weapon == true:
  680. return
  681. weapons[current_weapon_name].fire_weapon()
  682. Now in ``fire_bullet`` we make sure we are not changing weapons, and if we are not we call the current weapon's ``fire_weapon`` function.
  683. Adding ``process_UI``
  684. _____________________
  685. Because we've changed how weapons work, we need to change how we update the UI.
  686. Make a new function called ``process_UI`` and add the following:
  687. ::
  688. func process_UI(delta):
  689. if current_weapon_name == "UNARMED" or current_weapon_name == "KNIFE":
  690. UI_status_label.text = "HEALTH: " + str(health)
  691. else:
  692. var current_weapon = weapons[current_weapon_name]
  693. UI_status_label.text = "HEALTH: " + str(health) + "\nAMMO:" + \
  694. str(current_weapon.ammo_in_weapon) + "/" + str(current_weapon.spare_ammo)
  695. Nothing much has changed from the code that was in ``_physics_process``, we've mainly just moved the UI processing code to
  696. its own function.
  697. The only major change is how we get the amount counts in the current weapon.
  698. ______
  699. Now we have successfully refactored ``Player.gd`` to use a more modular approach and the weapons now are (mainly) processed in their own scripts!
  700. Go give the game a test. If everything is written correctly you should be able to run around and shoot things just like before.
  701. Now that we've refactored ``Player.gd``, lets add something new: Let's allow our plays to play using a joypad!
  702. Adding joypad input
  703. -------------------
  704. .. note:: In Godot any game controller is referred to as a joypad. This includes:
  705. Console controllers, Joysticks (like for flight simulators), Wheels (like for driving simulators), VR Controllers, and more.
  706. 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.
  707. Now we need to add some joypad buttons to our various actions. Click the plus icon and select ``Joy Button``.
  708. .. image:: img/ProjectSettingsAddKey.png
  709. 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:
  710. * movement_sprint: ``Device 0, Button 4 (L, L1)``
  711. * fire: ``Device 0, Button 0 (PS Cross, XBox A, Nintendo B)``
  712. * reload: ``Device 0, Button 0 (PS Square, XBox X, Nintendo Y)``
  713. * flashlight: ``Device 0, Button 12 (D-Pad Up)``
  714. * shift_weapon_positive: ``Device 0, Button 15 (D-Pad Right)``
  715. * shift_weapon_negative: ``Device 0, Button 14 (D-Pad Right)``
  716. Once you are happy with the input, close the project settings and save.
  717. ______
  718. Now let's open up ``Player.gd`` and add joypad input.
  719. First, we need to define a few new global variables. Add the following global variables to ``Player.gd``:
  720. ::
  721. # You may need to adjust depending on the sensitivity of your joypad
  722. const JOYPAD_SENSITIVITY = 2
  723. const JOYPAD_DEADZONE = 0.15
  724. Lets go over what each of these do:
  725. * ``JOYPAD_SENSITIVITY``: This is how fast our joypad joysticks will move our camera.
  726. * ``JOYPAD_DEADZONE``: The dead zone for the joypad. You may need to adjust depending on your joypad.
  727. .. note:: Many joypads jitter around a certain point. To counter this, we ignore any movement in a
  728. with a radius of JOYPAD_DEADZONE. If we did not ignore said movement, the camera will jitter.
  729. Now we are ready to start handling joypad input!
  730. ______
  731. In ``process_input`` add the following code, just before ``input_movement_vector = input_movement_vector.normalized()``:
  732. ::
  733. # Add joypad input, if there is a joypad
  734. if Input.get_connected_joypads().size() > 0:
  735. var joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
  736. if (abs(joypad_vec.x) <= JOYPAD_DEADZONE):
  737. joypad_vec.x = 0
  738. if (abs(joypad_vec.y) <= JOYPAD_DEADZONE):
  739. joypad_vec.y = 0
  740. input_movement_vector += joypad_vec
  741. Lets go over what we're doing.
  742. First we check to see if there is a connected joypad.
  743. If there is a joypad connected, we then get it's left stick axes for right/left and up/down.
  744. .. warning:: This tutorial assumes you are using a XBox 360 wired controller
  745. on Windows. The axes needed may be different on different operating systems and/or controllers.
  746. Next we check to see if the joypad vector is within the ``JOYPAD_DEADZONE`` radius. If the ``x`` or ``y`` coordinates
  747. are within the ``JOYPAD_DEADZONE`` radius, we set it to zero.
  748. Finally, we add ``joypad_vec`` to ``input_movement_vector``.
  749. .. tip:: Remember how we normalize ``input_movement_vector``? This is why! If we did not normalize ``input_movement_vector`` players could
  750. move faster if they are pushing in the same direction with both their keyboard and their joypad!
  751. ______
  752. Remember that commented out function in ``_physics_process``? Lets add it! Remove the ``#`` in ``_physics_process`` and make a new function called ``process_view_input``.
  753. Add the following to ``process_view_input``:
  754. ::
  755. func process_view_input(delta):
  756. if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
  757. return
  758. # ----------------------------------
  759. # Joypad rotation
  760. var joypad_vec = Vector2()
  761. if Input.get_connected_joypads().size() > 0:
  762. # For windows (XBOX 360)
  763. joypad_vec = Vector2(Input.get_joy_axis(0, 2), Input.get_joy_axis(0, 3))
  764. # For Linux (XBOX 360)
  765. #joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
  766. # For Mac (XBOX 360) Unknown, but likely:
  767. #joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
  768. if abs(joypad_vec.x) <= JOYPAD_DEADZONE:
  769. joypad_vec.x = 0
  770. if abs(joypad_vec.y) <= JOYPAD_DEADZONE:
  771. joypad_vec.y = 0
  772. rotation_helper.rotate_x(deg2rad(joypad_vec.y * JOYPAD_SENSITIVITY))
  773. self.rotate_y(deg2rad(joypad_vec.x * JOYPAD_SENSITIVITY * -1))
  774. # ----------------------------------
  775. var camera_rot = rotation_helper.rotation_degrees
  776. camera_rot.x = clamp(camera_rot.x, -70, 70)
  777. rotation_helper.rotation_degrees = camera_rot
  778. Let's go over what's happening:
  779. 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.
  780. .. note:: The reason we are checking to see if the mouse mode is captured or not is because we may want to add a pause menu later. If we do,
  781. we do not want players to move around while the game is paused if they are using a joypad!
  782. Next we define a new :ref:`Vector2 <class_Vector2>` called ``joypad_vec``. This will hold the right joystick position if there is one, and if there is not one it will
  783. default to ``(0, 0)``, which will do nothing.
  784. We then check to see if we have a joypad connected. If we do, we then assign ``joypad_vec`` to the proper axes values.
  785. .. warning:: Depending on our OS, you may need to change the axis order. The axis values proved are confirmed to work
  786. on Linux and Windows 10 using a XBox 360 wired controller.
  787. We then account for the joypad's dead zone, just like in ``process_input``.
  788. Regardless of whehter or not there is a joypad connected, we rotate ``rotation_helper`` and ourselves using ``joypad_vec``. If we do not have a joypad connected,
  789. ``joypad_vec`` will be equal to zero, which will do nothing.
  790. Notice how the code that handles rotating ourselves and ``rotation_helper`` is exactly the same as the
  791. code in ``_input``. All we've done is change the values to use ``joypad_vec`` and ``JOYPAD_SENSITIVITY``.
  792. .. note:: Due to few mouse related bugs on Windows, we cannot put mouse rotation in ``process_view`` as well. The tutorial will be updated once the bugs are fixed!
  793. Finally, we clamp the camera's rotation so we cannot look upside down.
  794. ______
  795. If everything is setup correctly, you can now play around using a joypad!
  796. .. 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.
  797. 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,
  798. and check if it's over a certain value, say ``0.8`` for example. If it is, you just add the same code as when the ``fire`` action was pressed.
  799. Adding mouse scroll wheel input
  800. -------------------------------
  801. Let's add one more feature before we close this part off. Let's add the ability to change weapons using the scroll wheel on the mouse.
  802. Open up ``Player.gd`` and add the following global variables:
  803. ::
  804. var mouse_scroll_value = 0
  805. const MOUSE_SENSITIVITY_SCROLL_WHEEL = 0.08
  806. Lets go over what each of these new varibles will be doing:
  807. * ``mouse_scroll_value``: The value of the mouse scroll wheel.
  808. * ``MOUSE_SENSITIVITY_SCROLL_WHEEL``: How much a single scroll action increases mouse_scroll_value
  809. ______
  810. Now lets add the following to ``_input``:
  811. ::
  812. if event is InputEventMouseButton && Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
  813. if event.button_index == BUTTON_WHEEL_UP or event.button_index == BUTTON_WHEEL_DOWN:
  814. if event.button_index == BUTTON_WHEEL_UP:
  815. mouse_scroll_value += MOUSE_SENSITIVITY_SCROLL_WHEEL
  816. elif event.button_index == BUTTON_WHEEL_DOWN:
  817. mouse_scroll_value -= MOUSE_SENSITIVITY_SCROLL_WHEEL
  818. mouse_scroll_value = clamp(mouse_scroll_value, 0, weapon_number_to_name.size()-1)
  819. if changing_weapon == false:
  820. if reloading_weapon == false:
  821. var round_mouse_scroll_value = int(round(mouse_scroll_value))
  822. if weapon_number_to_name[round_mouse_scroll_value] != current_weapon_name:
  823. changing_weapon_name = weapon_number_to_name[round_mouse_scroll_value]
  824. changing_weapon = true
  825. mouse_scroll_value = round_mouse_scroll_value
  826. Let's go over what's happening here:
  827. First we check if the event is a ``InputEventMouseButton`` event and that our mouse mode is ``MOUSE_MODE_CAPTURED``.
  828. Then we check to see if the button index is either a ``BUTTON_WHEEL_UP`` or ``BUTTON_WHEEL_DOWN`` index.
  829. 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.
  830. Based on whether it is up or down we add/remove ``MOUSE_SENSITIVITY_SCROLL_WHEEL`` to/from ``mouse_scroll_value``.
  831. Next we clamp mouse scroll value to assure it is inside the range of our weapons.
  832. 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``.
  833. .. 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,
  834. we would get an error when we try to run the project.
  835. 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``.
  836. 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
  837. ``process_changing_weapon``, and set ``mouse_scroll_value`` to ``round_mouse_scroll_value``.
  838. .. 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
  839. mouse scroll wheel just in between values, giving them the ability to switch almost extremely fast. By assigning ``mouse_scroll_value``
  840. to ``round_mouse_scroll_value``, we assure that each weapon takes exactly the same amount of scrolling to change.
  841. ______
  842. Now you can change weapons using the scroll wheel! Go give it a whirl!
  843. Final notes
  844. -----------
  845. Now ``Player.gd`` is laid out much better, is easier to extend, we've added joypad input, and now the player can change weapons with the scroll wheel!
  846. .. tip:: You can find the finished project for part 4 here: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial/tree/part_4
  847. The completed project has helpful comments every step of the way for almost every line of code!
  848. (Remember, you can download the completed project as a ZIP file if you want)
  849. .. image:: img/GithubDownloadZip.png
  850. If you want to see what is coming next, and what could be coming in the future, check out this issue on the repository: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial/issues/6
  851. How to make ``Test_Level.tscn`` look cool!
  852. __________________________________________
  853. One quick thing! As noted by **MagicLord** from the Godot forums, you can make ``Test_Level.tscn`` look really cool with a little tweaking!
  854. If you change the roughness values down in the Spatial materials for the provided starter assets, you get this:
  855. .. image:: img/PartFourFinished.png
  856. .. note:: Huge thanks to **MagicLord** for sharing! (Credit for the picture goes to **MagicLord** as well!)
  857. All you have to do is lower the roughness (I found a value of ``0.1`` looks nice) in ``LevelAssets_SpatialMaterial.tres`` and ``LevelAssets_Transparent_SpatialMaterial.tres``,
  858. which you can find at ``assets/Level_assets``.
  859. .. note:: Remember, you have to hit the save button or your changes to ``LevelAssets_SpatialMaterial.tres`` and/or ``LevelAssets_Transparent_SpatialMaterial.tres``
  860. will not be saved! The save icon looks like a little floppy disk!
  861. You can also turn on SSR (Screen Space Reflections) and/or use :ref:`reflection probes <class_ReflectionProbe>`
  862. as well! Turning up the metallic value a little can also give a more realistic look.
  863. In a later part we will likely change ``Test_Level.tscn`` a bit so the sky texture does not leak through the tiles before setting
  864. the material roughness down in the finished project.