Explorar o código

Added part 4 of the FPS tutorial

Noah Beard %!s(int64=7) %!d(string=hai) anos
pai
achega
b586ffccf8

BIN=BIN
tutorials/3d/fps_tutorial/img/GithubDownloadZip.png


BIN=BIN
tutorials/3d/fps_tutorial/img/PartFourFinished.png


BIN=BIN
tutorials/3d/fps_tutorial/img/ProjectSettingsAddAction.png


BIN=BIN
tutorials/3d/fps_tutorial/img/ProjectSettingsAddKey.png


+ 1 - 0
tutorials/3d/fps_tutorial/index.rst

@@ -8,3 +8,4 @@ FPS tutorial
    part_one
    part_two
    part_three
+   part_four

+ 1300 - 0
tutorials/3d/fps_tutorial/part_four.rst

@@ -0,0 +1,1300 @@
+.. _doc_fps_tutorial_part_four:
+
+Part 4
+======
+
+Part Overview
+-------------
+
+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.
+
+.. image:: img/FinishedTutorialPicture.png
+
+While this part may not be the most interesting, it is very important. Having a clean and modular code base allows us to build
+more complex behaviour in the future.
+
+.. note:: You are assumed to have finished :ref:`part three <doc_fps_tutorial_part_three>` before moving on to this part of the tutorial.
+
+.. tip:: You can find the completed code for part three here: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial/tree/part_3
+         
+         .. image:: img/GithubDownloadZip.png
+         
+         Just click the green "Clone or download" button and choose "Download Zip" to get the finished project for part 3.
+
+Let's get started!
+
+
+A quick note
+------------
+
+Before we dig into refactoring the code, let's quickly talk about *why* we want to refactor the code.
+
+First, what is refactoring? According to wikipedia:
+
+**"Code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behaviour."**
+
+Basically, refactoring is taking code we've already written, and rewriting/restructuring it without changing what it does.
+
+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
+major reasons:
+
+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.
+``_physics_process`` benefits greatly from this, because while it does work right now, it is very confusing to navigate.
+
+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
+us to make/edit weapons and their behaviours without having to change much in ``Player.gd``.
+
+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
+of this tutorial series, and while it still is not a major concern, we ideally want to write code with good performance when possible.
+
+All of these reasons are why we are going to refactor ``Player.gd``.
+
+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
+to more easily add features later, as well as make it easier to work with in later parts.
+
+.. 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!
+
+
+Breaking it down
+----------------
+
+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.
+With some refactoring, we can break ``_physics_process`` into several smaller functions.
+
+Ideally we want to make these smaller functions focused on doing a small set of tasks.
+This makes it much easier to know where we need to add code to when we are working on new features.
+
+Another benefit of using smaller functions is they are generally easier to debug!
+
+Breaking down input processing
+______________________________
+
+First, lets make a function for handling all of the :ref:`Input <class_Input>` related code.
+This allows us to more clearly see all of our player input.
+
+Create new function called `process_input` and add the following code:
+
+::
+    
+    func process_input(delta):
+        # ----------------------------------
+        # Walking
+        
+        dir = Vector3()
+        var cam_xform = camera.get_global_transform()
+        
+        var input_movement_vector = Vector2()
+        
+        # Add keyboard input
+        if (Input.is_action_pressed("movement_forward")):
+            input_movement_vector.y += 1
+        if (Input.is_action_pressed("movement_backward")):
+            input_movement_vector.y -= 1
+        if (Input.is_action_pressed("movement_left")):
+            input_movement_vector.x -= 1
+        if (Input.is_action_pressed("movement_right")):
+            input_movement_vector.x = 1
+        
+        input_movement_vector = input_movement_vector.normalized()
+        
+        dir += -cam_xform.basis.z.normalized() * input_movement_vector.y
+        dir += cam_xform.basis.x.normalized() * input_movement_vector.x
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Sprinting
+        
+        if Input.is_action_pressed("movement_sprint"):
+            is_spriting = true
+        else:
+            is_spriting = false
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Jumping
+        
+        if is_on_floor():
+            if Input.is_action_just_pressed("movement_jump"):
+                vel.y = JUMP_SPEED
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Changing weapons.
+        
+        if changing_gun == false and reloading_gun == false:
+            if Input.is_key_pressed(KEY_1):
+                current_gun = "UNARMED"
+                changing_gun = true
+            elif Input.is_key_pressed(KEY_2):
+                current_gun = "KNIFE"
+                changing_gun = true
+            elif Input.is_key_pressed(KEY_3):
+                current_gun = "PISTOL"
+                changing_gun = true
+            elif Input.is_key_pressed(KEY_4):
+                current_gun = "RIFLE"
+                changing_gun = true
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Reloading
+        
+        if reloading_gun == false:
+            if Input.is_action_just_pressed("reload"):
+                if current_gun == "PISTOL" or current_gun == "RIFLE":
+                    if animation_manager.current_state != "Pistol_reload" and animation_manager.current_state != "Rifle_reload":
+                        reloading_gun = true
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Firing the weapons
+        
+        if Input.is_action_pressed("fire"):
+            
+            if current_gun == "PISTOL":
+                if ammo_in_guns["PISTOL"] > 0:
+                    if animation_manager.current_state == "Pistol_idle":
+                        animation_manager.set_animation("Pistol_fire")
+                else:
+                    reloading_gun = true
+            
+            elif current_gun == "RIFLE":
+                if ammo_in_guns["RIFLE"] > 0:
+                    if animation_manager.current_state == "Rifle_idle":
+                        animation_manager.set_animation("Rifle_fire")
+                else:
+                    reloading_gun = true
+            
+            elif current_gun == "KNIFE":
+                if animation_manager.current_state == "Knife_idle":
+                    animation_manager.set_animation("Knife_fire")
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Turning the flashlight on/off
+        
+        if Input.is_action_just_pressed("flashlight"):
+            if flashlight.is_visible_in_tree():
+                flashlight.hide()
+            else:
+                flashlight.show()
+        # ----------------------------------
+        
+        # ----------------------------------
+        
+        # Capturing/Freeing the cursor
+        if Input.is_action_just_pressed("ui_cancel"):
+            if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
+                Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+            else:
+                Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
+        # ----------------------------------
+
+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``,
+but is now all placed in one function.
+
+There are a few changes though:
+
+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.
+Add ``var dir = Vector3()`` with the rest of the global variables, ideally nearby the movement code for organization.
+
+.. warning:: Do not forget to change ``dir`` to a global variable!
+
+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,
+``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
+directional vectors, we get the same result as when we were effecting ``dir`` directly.
+
+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
+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
+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
+input devices they are using.
+
+Breaking down ``KinematicBody`` movement
+________________________________________
+
+Next we want to move all of the code relating to moving using the :ref:`KinematicBody <class_KinematicBody>` into its own function.
+This allows us to more clearly see what code we are sending :ref:`KinematicBody <class_KinematicBody>` and what it does.
+
+Create a new function and call it ``process_movement``. Lets add the following code:
+
+::
+    
+    func process_movement(delta):
+        var grav = norm_grav
+        
+        dir.y = 0
+        dir = dir.normalized()
+        
+        vel.y += delta*grav
+        
+        var hvel = vel
+        hvel.y = 0
+        
+        var target = dir
+        if is_spriting:
+            target *= MAX_SPRINT_SPEED
+        else:
+            target *= MAX_SPEED
+        
+        var accel
+        if dir.dot(hvel) > 0:
+            if is_spriting:
+                accel = SPRINT_ACCEL
+            else:
+                accel = ACCEL
+        else:
+            accel = DEACCEL
+        
+        hvel = hvel.linear_interpolate(target, accel*delta)
+        vel.x = hvel.x
+        vel.z = hvel.z
+        vel = move_and_slide(vel,Vector3(0,1,0), 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))
+
+Thankfully nothing is has changed here, all we've done is moved the code out of ``_physics_process``.
+
+.. 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))``
+             to ``vel = move_and_slide(vel,Vector3(0,1,0), true, 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))``.
+
+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``.
+
+
+Changing the weapon code structure
+----------------------------------
+
+So far, we have not really changed the structure of the code, we've just shuffled it around, so lets change that.
+
+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
+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
+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.
+
+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
+``Weapon_Pistol.gd``.
+
+Our weapon scripts are going to do four things: They're going to handle *firing*, *reloading*, *equipping*, and *unequipping*.
+
+Add the following code to ``Weapon_Pistol.gd``:
+
+::
+    
+    extends Spatial
+
+    var ammo_in_weapon = 20;
+    var spare_ammo = 60;
+    const AMMO_IN_MAG = 20;
+    const DAMAGE = 15;
+
+    const CAN_RELOAD = true;
+
+    const RELOADING_ANIM_NAME = "Pistol_reload"
+    const IDLE_ANIM_NAME = "Pistol_idle"
+    const FIRE_ANIM_NAME = "Pistol_fire"
+
+    var is_weapon_enabled = false;
+
+    var bullet_scene = preload("Bullet_Scene.tscn")
+
+    var player_node = null;
+
+    func _ready():
+        pass;
+
+    func fire_weapon():
+        var clone = bullet_scene.instance()
+        var scene_root = get_tree().root.get_children()[0]
+        scene_root.add_child(clone)
+        
+        clone.global_transform = self.global_transform
+        clone.scale = Vector3(4, 4, 4)
+        clone.BULLET_DAMAGE = DAMAGE;
+        ammo_in_weapon -= 1
+        
+        player_node.create_sound("Pistol_shot", self.global_transform.origin)
+
+
+    func reload_weapon():
+        var can_reload = false;
+        
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            can_reload = true
+        
+        if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
+            can_reload = false
+        
+        if can_reload == true:
+            var ammo_needed = AMMO_IN_MAG - ammo_in_weapon;
+            
+            if spare_ammo >= ammo_needed:
+                spare_ammo -= ammo_needed
+                ammo_in_weapon = AMMO_IN_MAG;
+            else:
+                ammo_in_weapon += spare_ammo
+                spare_ammo = 0
+            
+            player_node.animation_manager.set_animation("Pistol_reload")
+            
+            player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
+            
+            return true;
+        
+        return false;
+
+    func equip_weapon():
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            is_weapon_enabled = true;
+            return true
+        
+        if player_node.animation_manager.current_state == "Idle_unarmed":
+            player_node.animation_manager.set_animation("Pistol_equip")
+            
+            player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
+        
+        return false
+
+    func unequip_weapon():
+        
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            if (player_node.animation_manager.current_state != "Pistol_unequip"):
+                player_node.animation_manager.set_animation("Pistol_unequip")
+        
+        if player_node.animation_manager.current_state == "Idle_unarmed":
+            is_weapon_enabled = false;
+            return true
+        else:
+            return false
+
+            
+Lets go over what is happening in this script:
+
+______
+
+First lets look at the constants and go over what each will do:
+
+* ``ammo_in_weapon``: How much ammo is *currently* in this weapon.
+* ``spare_ammo``: How much spare ammo we have in reserve for this weapon. ``spare_ammo + ammo_in_weapon = total ammo for this weapon``.
+* ``AMMO_IN_MAG``: The amount ammo needed to fill the weapon. To put it another way, the amount of ammo in each magazine.
+* ``DAMAGE``: The amount of damage a single bullet does.
+* ``CAN_RELOAD``: A boolean for tracking whether this weapon has the ability to reload.
+* ``RELOADING_ANIM_NAME``: The name of the reloading animation for this weapon.
+* ``IDLE_ANIM_NAME``: The name of the idle animation for this weapon.
+* ``FIRE_ANIM_NAME``: The name of the firing animation for this weapon.
+* ``is_weapon_enabled``: A boolean for tracking whether or not this weapon is the currently used/enabled weapon.
+* ``bullet_scene``: The bullet scene we created in part 2 of this tutorial.
+* ``player_node``: The player node and script (``Player.gd``).
+
+______
+
+Notice how we do not do anything in ``_ready``.
+
+We could try and grab the player node here, but it makes a messy ``get_node`` call, and because we already
+have to aim these points in ``Player.gd`` anyway, we will just pass the player node then.
+
+.. note:: This is just a design choice. Depending on your project, it may be better to use ``get_node`` in the
+          weapon scripts.
+
+______
+
+Lets look at ``fire_weapon``.
+
+First we make a clone of the bullet scene and add it as a child of the scene root.
+Next we set its global transform to ``self.global_transform``.
+
+.. note:: before we were using a ``get_node`` call to
+          get here because we were calling this from ``Player.gd``. Now that we are firing from the fire point itself, we do not
+          need to use ``get_node`` any more.
+
+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.
+
+Next we set its damage. This is new, but nothing crazy. To make this work, we just need to go into
+``Bullet_script.gd`` and change ``const BULLET_DAMAGE`` to ``var BULLET_DAMAGE``. The reason behind changing ``BULLET_DAMAGE`` from
+a constant to a normal variable is because we may reuse the bullet object later (for a different weapon)
+
+.. warning:: Do not forgot to change ``const BULLET_DAMAGE`` to ``var BULLET_DAMAGE`` in ``Bullet_script.gd``!
+
+Then we remove one from the ammo in our weapon and play a sound (if we have sounds).
+
+.. note:: With the exception of how we are no longer using ``get_node``, everything in ``fire_weapon`` is the same as the code
+          as ``Player.gd``'s ``fire_bullet`` function.
+
+______
+
+In ``reload_weapon`` we are doing things a little differently.
+
+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
+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
+that does not happen.
+
+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
+we do not want the player to be able to reload if the weapon is already full.
+
+.. 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.
+         For this tutorial though, we will only allow the player to reload if they do not have a full weapon.
+
+Then we check ``can_reload`` to see if it is true.
+
+If it is, we then calculate how much ammo we need to fill the weapon.
+
+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.
+
+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.
+
+We then play the reloading animation and play a sound. We return ``true`` to signal we have successfully reloaded.
+
+If we cannot reload because ``reload_weapon`` is ``false``, we return ``false`` to signal we did not successfully reload.
+
+______
+
+For ``equip_weapon`` we first check if the player is in the pistol's idle state.
+
+If we are in the pistol's idle state we've successfully equipped the pistol.
+We set ``is_weapon_enabled`` to ``true`` because we are now using this weapon, and return ``true``.
+
+.. 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
+          the ``equip_weapon``/``unequip_weapon`` functions, we could possibility get cases where we are stuck in a loop where we are equipping/unequipping
+          the same weapon over and over again.
+
+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
+to ``Pistol_equip`` and play a sound. Finally, we return ``false``.
+
+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
+have successfully equipped the pistol.
+
+______
+
+``unequip_weapon`` is extremely similar to ``equip_weapon``, but the checks are in reverse.
+
+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``.
+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``.
+
+Finally, if we did not return ``true``, we return false.
+
+As with ``equip_weapon``, we want to return false by default because we will be calling this function until it returns true.
+
+______
+
+Now we just need to do the same thing for the knife and the rifle.
+
+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
+anything we automatically return false.
+
+Select ``Knife_point``, created a new script called ``Weapon_Knife.gd``, and add the following:
+
+::
+    
+    extends Spatial
+
+    var ammo_in_weapon = 1;
+    var spare_ammo = 1;
+    const AMMO_IN_MAG = 1;
+
+    const DAMAGE = 40;
+
+    const CAN_RELOAD = false;
+    const RELOADING_ANIM_NAME = ""
+    const IDLE_ANIM_NAME = "Knife_idle"
+    const FIRE_ANIM_NAME = "Knife_fire"
+
+    var is_weapon_enabled = false;
+
+    var player_node = null;
+
+    func _ready():
+        pass;
+
+    func fire_weapon():
+        var area = get_node("Area")
+        var bodies = area.get_overlapping_bodies()
+        
+        for body in bodies:
+            if body.has_method("bullet_hit"):
+                body.bullet_hit(DAMAGE, area.global_transform.origin)
+
+
+    func reload_weapon():
+        return false;
+
+    func equip_weapon():
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            is_weapon_enabled = true;
+            return true
+        
+        if player_node.animation_manager.current_state == "Idle_unarmed":
+            player_node.animation_manager.set_animation("Knife_equip")
+        
+        return false
+
+    func unequip_weapon():
+        
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            player_node.animation_manager.set_animation("Knife_unequip")
+        
+        if player_node.animation_manager.current_state == "Idle_unarmed":
+            is_weapon_enabled = false;
+            return true
+        
+        return false
+
+There are only a few things to note here.
+
+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
+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.
+
+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``.
+
+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,
+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
+and their children.
+
+______
+
+Finally, select ``Rifle_point``, create a new script called ``Weapon_Rifle.gd``, and add the following code:
+
+::
+    
+    extends Spatial
+
+    var ammo_in_weapon = 80;
+    var spare_ammo = 160;
+    const AMMO_IN_MAG = 80;
+    const DAMAGE = 4;
+
+    const CAN_RELOAD = true;
+    const RELOADING_ANIM_NAME = "Rifle_reload"
+    const IDLE_ANIM_NAME = "Rifle_idle"
+    const FIRE_ANIM_NAME = "Rifle_fire"
+
+    var is_weapon_enabled = false;
+
+    var player_node = null;
+
+    func _ready():
+        pass;
+
+    func fire_weapon():
+        var ray = get_node("RayCast")
+        ray.force_raycast_update()
+        
+        if ray.is_colliding():
+            var body = ray.get_collider()
+            if body.has_method("bullet_hit"):
+                body.bullet_hit(DAMAGE, ray.get_collision_point())
+        
+        ammo_in_weapon -= 1;
+        
+        player_node.create_sound("Rifle_shot", ray.global_transform.origin)
+
+
+    func reload_weapon():
+        var can_reload = false;
+        
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            can_reload = true
+        
+        if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
+            can_reload = false
+        
+        if can_reload == true:
+            var ammo_needed = AMMO_IN_MAG - ammo_in_weapon;
+            
+            if spare_ammo >= ammo_needed:
+                spare_ammo -= ammo_needed
+                ammo_in_weapon = AMMO_IN_MAG;
+            else:
+                ammo_in_weapon += spare_ammo
+                spare_ammo = 0
+            
+            player_node.animation_manager.set_animation("Rifle_reload")
+            
+            player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
+            
+            return true;
+        
+        return false;
+
+    func equip_weapon():
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            is_weapon_enabled = true;
+            return true
+        
+        if player_node.animation_manager.current_state == "Idle_unarmed":
+            player_node.animation_manager.set_animation("Rifle_equip")
+            
+            player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
+        
+        return false
+
+    func unequip_weapon():
+        
+        if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
+            if (player_node.animation_manager.current_state != "Rifle_unequip"):
+                player_node.animation_manager.set_animation("Rifle_unequip")
+        
+        if player_node.animation_manager.current_state == "Idle_unarmed":
+            is_weapon_enabled = false;
+            return true
+        
+        return false
+
+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,
+just adjusted for the rifle.
+        
+Finishing refactoring ``Player.gd``
+-----------------------------------
+
+Now we are ready to use our newly refactored weapons in ``Player.gd``. First, we need to change some of the global variables.
+Find all of the constants relating to the weapons, delete them, and add the following:
+
+::
+    
+    var current_weapon_name = "UNARMED"
+    var weapons = {"UNARMED":null, "KNIFE":null, "PISTOL":null, "RIFLE":null}
+    const weapon_number_to_name = {0:"UNARMED", 1:"KNIFE", 2:"PISTOL", 3:"RIFLE"}
+    const weapon_name_to_number = {"UNARMED":0, "KNIFE":1, "PISTOL":2, "RIFLE":3}
+    var changing_weapon = false
+    var changing_weapon_name = "UNARMED"
+    var reloading_weapon = false
+
+Lets go over each of these new global variables:
+
+* ``current_weapon_name``: The name of the weapon currently in use.
+* ``weapons``: A dictionary holding all of the weapon nodes, allowing us to access them by name instead of using ``get_node``.
+* ``weapon_number_to_name``: A dictionary holding all of the weapons and which number they represent.
+* ``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.
+* ``changing_weapon``: A boolean to track whether we are trying to change weapons or not.
+* ``changing_weapon_name``: The name of the weapon we are trying to change to.
+* ``reloading_weapon``: A boolean to track whether we are reloading or not.
+
+We need to change ``_ready`` to the following:
+
+::
+    
+    func _ready():
+        camera = get_node("Rotation_helper/Camera")
+        rotation_helper = get_node("Rotation_helper")
+        
+        animation_manager = get_node("Rotation_helper/Model/AnimationPlayer")
+        animation_manager.callback_function = funcref(self, "fire_bullet")
+        
+        set_physics_process(true)
+        
+        Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+        set_process_input(true)
+        
+        weapons["KNIFE"] = get_node("Rotation_helper/Gun_fire_points/Knife_point")
+        weapons["PISTOL"] = get_node("Rotation_helper/Gun_fire_points/Pistol_point")
+        weapons["RIFLE"] = get_node("Rotation_helper/Gun_fire_points/Rifle_point")
+        
+        var gun_aim_point_pos = get_node("Rotation_helper/Gun_aim_point").global_transform.origin
+        
+        for weapon in weapons:
+            var weapon_node = weapons[weapon]
+            if weapon_node != null:
+                weapon_node.player_node = self
+                weapon_node.look_at(gun_aim_point_pos, Vector3(0, 1, 0))
+                weapon_node.rotate_object_local(Vector3(0, 1, 0), deg2rad(180))
+        
+        current_weapon_name = "UNARMED"
+        changing_weapon_name = "UNARMED"
+        
+        UI_status_label = get_node("HUD/Panel/Gun_label")
+        flashlight = get_node("Rotation_helper/Flashlight")
+
+Lets quickly go over the new stuff.
+
+Notice how most of the code is exactly the same as before. The only code that's changed is how
+we are handling the gun aim points, so let's look at those changes.
+
+First, we get all of the weapon nodes using ``get_node`` and assign them to the ``weapons`` dictionary.
+
+Then we loop through all of the weapons in the ``weapons`` dictionary. For each weapon node, we get the value assigned to that key.
+
+.. 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
+          have to retrieve it using ``Y[X]``.
+
+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.
+
+.. 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.
+          You could define a "weapon" for the UNARMED state, but in this series we are just going to use ``null``.
+
+Next we flip the aim point by ``180`` degrees so it doesn't fire backwards.
+
+.. warning:: The reason behind rotating the gun aim point is explained in :ref:`part 2 <doc_fps_tutorial_part_two>`
+
+Finally, we set ``current_weapon_name`` and ``changing_weapon_name`` to ``UNARMED`` so our starting weapon is ``UNARMED``.
+
+______
+
+Now we need to change ``_physics_process``. Delete everything in ``_physics_process`` and add the following:
+
+::
+    
+    func _physics_process(delta):
+        process_input(delta)
+        #process_view_input(delta)
+        process_movement(delta)
+        process_changing_weapons(delta)
+        process_reloading(delta)
+        process_UI(delta)
+
+.. note:: You may have noticed how we have a commented out function, ``process_view_input``. We will be using this later!
+          For now just leave it commented out!
+
+Now we are calling each of our modular functions in order. Notice how we are still missing
+``process_changing_weapons``, ``process_reloading``, and ``process_UI``. Before we add those functions, lets quickly return to
+``process_input``.
+
+Finishing ``process_input``
+___________________________
+
+First, lets change ``process_input`` so our weapon related code works with the new weapon system.
+
+First, delete all of the weapon related code in `process_input`. This is the includes:
+Changing weapons, Reloading, and Firing.
+
+Now at the bottom of ``process_input``, add the following code:
+
+::
+    
+    func process_input(delta):
+        
+        # Other input code (like movement, jumping, etc) above!
+        
+        # ----------------------------------
+        # Changing weapons.
+        var weapon_change_number = weapon_name_to_number[current_weapon_name]
+        
+        if Input.is_key_pressed(KEY_1):
+            weapon_change_number = 0
+        if Input.is_key_pressed(KEY_2):
+            weapon_change_number = 1
+        if Input.is_key_pressed(KEY_3):
+            weapon_change_number = 2
+        if Input.is_key_pressed(KEY_4):
+            weapon_change_number = 3
+        
+        if Input.is_action_just_pressed("shift_weapon_positive"):
+            weapon_change_number += 1
+        if Input.is_action_just_pressed("shift_weapon_negative"):
+            weapon_change_number -= 1
+        
+        weapon_change_number = clamp(weapon_change_number, 0, weapon_number_to_name.size()-1)
+        
+        if changing_weapon == false:
+            if reloading_weapon == false:
+                if weapon_number_to_name[weapon_change_number] != current_weapon_name:
+                    changing_weapon_name = weapon_number_to_name[weapon_change_number]
+                    changing_weapon = true
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Reloading
+        if reloading_weapon == false:
+            if changing_weapon == false:
+                if Input.is_action_just_pressed("reload"):
+                    var current_weapon = weapons[current_weapon_name]
+                    if current_weapon != null:
+                        if current_weapon.CAN_RELOAD == true:
+                            var current_anim_state = animation_manager.current_state
+                            var is_reloading = false
+                            for weapon in weapons:
+                                var weapon_node = weapons[weapon]
+                                if weapon_node != null:
+                                    if current_anim_state == weapon_node.RELOADING_ANIM_NAME:
+                                        is_reloading = true
+                            if is_reloading == false:
+                                reloading_weapon = true
+        # ----------------------------------
+        
+        # ----------------------------------
+        # Firing the weapons
+        if Input.is_action_pressed("fire"):
+            if reloading_weapon == false:
+                if changing_weapon == false:
+                    var current_weapon = weapons[current_weapon_name]
+                    if current_weapon != null:
+                        if current_weapon.ammo_in_weapon > 0:
+                            if animation_manager.current_state == current_weapon.IDLE_ANIM_NAME:
+                                animation_manager.set_animation(current_weapon.FIRE_ANIM_NAME)
+                        else:
+                            reloading_weapon = true
+        # ----------------------------------
+
+Lets go through what each of these sections are doing.
+
+______
+
+Lets look at the weapon changing section first.
+
+The first thing we do is get the current weapon number and assign it to ``weapon_change_number``.
+
+Next we check each of the four number keys and we assign ``weapon_change_number`` to their value if they are pressed.
+
+.. 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``)
+          is actually ``0``, which is our first weapon.
+
+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
+going over ``process_input``.
+
+Next we clamp ``weapon_change_number`` so it cannot be higher or lower than the amount of weapons we have.
+
+.. 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.
+         
+         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
+         of elements in the dictionary, starting from ``1``, while GDScript accesses values starting from ``0``.
+
+We do not want to suddenly change weapons while already changing weapons or reload, so we check to make sure both variables are ``false``.
+
+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
+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``.
+Finally, we set ``changing_weapon`` to true so we can process the actual weapon changing logic in ``process_changing_weapons``.
+
+______
+
+For reloading we first check to make sure we are not already reload, or changing weapons.
+
+Then we check to see if the reloading action has been pressed.
+Next we get the current weapon and assign it to ``current_weapon``.
+If the current weapon is not ``null`` we then make sure this weapon can reload using the weapon's ``CAN_RELOAD`` constant.
+
+.. tip:: We check for ``null`` because we do not want to reload ``UNARMED``!
+
+Next we check get the current animation state from our animation manager, and we set ``is_reloading`` to ``false``.
+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,
+because we do not want to allow the player to (potentially) reload if they are already in a reloading animation.
+
+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
+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,
+we set ``is_reloading`` to ``true``.
+
+If ``is_reloading`` is still ``false``, we then set ``reloading_weapon`` to true so we can process the reloading weapon logic in ``process_reloading``.
+
+______
+
+Finally, we have the firing section.
+
+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.
+
+Next we get the current weapon and assign it to ``current_weapon``. We then check to make sure it is not equal to ``null``.
+
+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.
+If we are indeed in the weapon's idle state, we set our animation to the weapon's fire animation.
+
+If the current weapon does not have any ammo, we set ``reloading_weapon`` to true.
+
+Adding our new input map actions
+________________________________
+
+As mentioned above, we've defined a couple new input actions: ``shift_weapon_positive`` and ``shift_weapon_negative``.
+Currently these input actions do not exist in our project, so let's add them!
+
+.. image:: img/ProjectSettingsAddAction.png
+
+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
+button on the side that reads ``Add``. Next write ``shift_weapon_negative`` and press enter or press the ``Add`` button.
+
+Scroll down to the bottom of the list and click the little plus sign next to one of the newly created actions.
+
+.. image:: img/ProjectSettingsAddKey.png
+
+You can assign whatever key you want to either
+of these actions. The finished project has the ``Equal`` and ``Kp Add`` keys assigned to ``shift_weapon_positive``. ``shift_weapon_negative`` has ``Minus`` and
+``Kp Subtract`` keys assigned in the finished project.
+
+Once you've assigned whatever keys you want to both actions, close the project settings and save.
+
+Adding ``process_changing_weapons``
+___________________________________
+
+Lets make the weapon changing logic next. Open up ``Player.gd`` and add the following function:
+
+::
+    
+    func process_changing_weapons(delta):
+        if changing_weapon == true:
+            
+            var weapon_unequipped = false
+            var current_weapon = weapons[current_weapon_name]
+            
+            if current_weapon == null:
+                weapon_unequipped = true
+            else:
+                if current_weapon.is_weapon_enabled == true:
+                    weapon_unequipped = current_weapon.unequip_weapon()
+                else:
+                    weapon_unequipped = true
+            
+            if weapon_unequipped == true:
+                
+                var weapon_equiped = false
+                var weapon_to_equip = weapons[changing_weapon_name]
+                
+                if weapon_to_equip == null:
+                    weapon_equiped = true
+                else:
+                    if weapon_to_equip.is_weapon_enabled == false:
+                        weapon_equiped = weapon_to_equip.equip_weapon()
+                    else:
+                        weapon_equiped = true
+                
+                if weapon_equiped == true:
+                    changing_weapon = false
+                    current_weapon_name = changing_weapon_name
+                    changing_weapon_name = ""
+
+Lets go over what's happening here.
+
+First we check to make sure ``changing_weapon`` is ``true``.
+
+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.
+We then get the current weapon and assign it to ``current_weapon``.
+
+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``.
+
+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``.
+
+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
+is ``null``).
+
+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
+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.
+
+We then get the weapon we want to change to and assign it to ``weapon_to_equip``.
+
+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.
+
+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``
+on the weapon we are wanting to equip.
+
+If the weapon we are wanting to equip is enabled, we set ``weapon_equipped`` to true.
+
+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``),
+and we set ``changing_weapon_name`` to a empty string.
+
+Adding ``process_reloading``
+____________________________
+
+Let's finish up our new modular weapon system and add ``process_reloading``. Make a new funciton called ``process_reloading`` and add the following:
+
+::
+    
+    
+    func process_reloading(delta):
+        if reloading_weapon == true:
+            var current_weapon = weapons[current_weapon_name]
+            if current_weapon != null:
+                current_weapon.reload_weapon()
+            reloading_weapon = false
+
+Let's go over what's this function does.
+
+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``.
+If ``current_weapon`` is not equal to ``null``, we call it's ``reload_weapon`` function.
+
+Finally, we set ``reloading_weapon`` to ``false`` because regardless of whether we've successfully reloaded, we have tried and no longer
+need to process weapon reloading.
+
+Changing ``fire_bullet``
+________________________
+
+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:
+
+::
+    
+    func fire_bullet():
+        if changing_weapon == true:
+            return
+        weapons[current_weapon_name].fire_weapon()
+
+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.
+        
+
+Adding ``process_UI``
+_____________________
+
+
+Because we've changed how weapons work, we need to change how we update the UI.
+Make a new function called ``process_UI`` and add the following:
+
+::
+    
+    func process_UI(delta):
+        if current_weapon_name == "UNARMED" or current_weapon_name == "KNIFE":
+            UI_status_label.text = "HEALTH: " + str(health)
+        else:
+            var current_weapon = weapons[current_weapon_name]
+            UI_status_label.text = "HEALTH: " + str(health) + "\nAMMO:" + \
+            str(current_weapon.ammo_in_weapon) + "/" + str(current_weapon.spare_ammo)
+
+        
+Nothing much has changed from the code that was in ``_physics_process``, we've mainly just moved the UI processing code to
+its own function.
+
+The only major change is how we get the amount counts in the current weapon.
+
+______
+
+Now we have successfully refactored ``Player.gd`` to use a more modular approach and the weapons now are (mainly) processed in their own scripts!
+Go give the game a test. If everything is written correctly you should be able to run around and shoot things just like before.
+
+Now that we've refactored ``Player.gd``, lets add something new: Let's allow our plays to play using a joypad!
+
+Adding joypad input
+-------------------
+
+.. note:: In Godot any game controller is referred to as a joypad. This includes:
+          Console controllers, Joysticks (like for flight simulators), Wheels (like for driving simulators), VR Controllers, and more.
+
+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.
+
+Now we need to add some joypad buttons to our various actions. Click the plus icon and select ``Joy Button``.
+
+.. image:: img/ProjectSettingsAddKey.png
+
+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:
+
+* movement_sprint: ``Device 0, Button 4 (L, L1)``
+* fire: ``Device 0, Button 0 (PS Cross, XBox A, Nintendo B)``
+* reload: ``Device 0, Button 0 (PS Square, XBox X, Nintendo Y)``
+* flashlight: ``Device 0, Button 12 (D-Pad Up)``
+* shift_weapon_positive: ``Device 0, Button 15 (D-Pad Right)``
+* shift_weapon_negative: ``Device 0, Button 14 (D-Pad Right)``
+
+Once you are happy with the input, close the project settings and save.
+
+______
+
+Now let's open up ``Player.gd`` and add joypad input.
+
+First, we need to define a few new global variables. Add the following global variables to ``Player.gd``:
+
+::
+    
+    # You may need to adjust depending on the sensitivity of your joypad
+    const JOYPAD_SENSITIVITY = 2
+    const JOYPAD_DEADZONE = 0.15
+
+Lets go over what each of these do:
+
+* ``JOYPAD_SENSITIVITY``: This is how fast our joypad joysticks will move our camera.
+* ``JOYPAD_DEADZONE``: The dead zone for the joypad. You may need to adjust depending on your joypad.
+
+.. note::  Many joypads jitter around a certain point. To counter this, we ignore any movement in a
+           with a radius of JOYPAD_DEADZONE. If we did not ignore said movement, the camera will jitter.
+
+Now we are ready to start handling joypad input!           
+
+______
+           
+In ``process_input`` add the following code, just before ``input_movement_vector = input_movement_vector.normalized()``:
+
+::
+    
+    # Add joypad input, if there is a joypad
+	if Input.get_connected_joypads().size() > 0:
+		var joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
+		
+		if (abs(joypad_vec.x) <= JOYPAD_DEADZONE):
+			joypad_vec.x = 0
+		if (abs(joypad_vec.y) <= JOYPAD_DEADZONE):
+			joypad_vec.y = 0
+		
+		input_movement_vector += joypad_vec
+
+Lets go over what we're doing.
+
+First we check to see if there is a connected joypad.
+
+If there is a joypad connected, we then get it's left stick axes for right/left and up/down.
+
+.. warning:: This tutorial assumes you are using a XBox 360 wired controller
+             on Windows. The axes needed may be different on different operating systems and/or controllers.
+
+Next we check to see if the joypad vector is within the ``JOYPAD_DEADZONE`` radius. If the ``x`` or ``y`` coordinates
+are within the ``JOYPAD_DEADZONE`` radius, we set it to zero.
+
+Finally, we add ``joypad_vec`` to ``input_movement_vector``.
+
+.. tip:: Remember how we normalize ``input_movement_vector``? This is why! If we did not normalize ``input_movement_vector`` players could
+         move faster if they are pushing in the same direction with both their keyboard and their joypad!
+         
+______
+
+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``.
+Add the following to ``process_view_input``:
+
+::
+    
+    func process_view_input(delta):
+	
+        if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
+            return
+        
+        # ----------------------------------
+        # Joypad rotation
+        
+        var joypad_vec = Vector2()
+        if Input.get_connected_joypads().size() > 0:
+            
+            # For windows (XBOX 360)
+            joypad_vec = Vector2(Input.get_joy_axis(0, 2), Input.get_joy_axis(0, 3))
+            # For Linux (XBOX 360)
+            #joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
+            # For Mac (XBOX 360) Unknown, but likely:
+            #joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
+            
+            if abs(joypad_vec.x) <= JOYPAD_DEADZONE:
+                joypad_vec.x = 0
+            if abs(joypad_vec.y) <= JOYPAD_DEADZONE:
+                joypad_vec.y = 0
+        
+        rotation_helper.rotate_x(deg2rad(joypad_vec.y * JOYPAD_SENSITIVITY))
+        self.rotate_y(deg2rad(joypad_vec.x * JOYPAD_SENSITIVITY * -1))
+        # ----------------------------------
+        
+        var camera_rot = rotation_helper.rotation_degrees
+        camera_rot.x = clamp(camera_rot.x, -70, 70)
+        rotation_helper.rotation_degrees = camera_rot
+        
+Let's go over what's happening:
+
+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.
+
+.. 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,
+          we do not want players to move around while the game is paused if they are using a joypad!
+
+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
+default to ``(0, 0)``, which will do nothing.
+
+We then check to see if we have a joypad connected. If we do, we then assign ``joypad_vec`` to the proper axes values.
+
+.. warning:: Depending on our OS, you may need to change the axis order. The axis values proved are confirmed to work
+             on Linux and Windows 10 using a XBox 360 wired controller.
+
+We then account for the joypad's dead zone, just like in ``process_input``.
+
+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,
+``joypad_vec`` will be equal to zero, which will do nothing.
+
+Notice how the code that handles rotating ourselves and ``rotation_helper`` is exactly the same as the
+code in ``_input``. All we've done is change the values to use ``joypad_vec`` and ``JOYPAD_SENSITIVITY``.
+
+.. 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!
+
+Finally, we clamp the camera's rotation so we cannot look upside down.
+
+______
+
+If everything is setup correctly, you can now play around using a joypad!
+
+.. 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.
+          
+          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,
+          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.
+         
+Adding mouse scroll wheel input
+-------------------------------
+
+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.
+
+Open up ``Player.gd`` and add the following global variables:
+
+::
+    
+    var mouse_scroll_value = 0
+    const MOUSE_SENSITIVITY_SCROLL_WHEEL = 0.08
+
+Lets go over what each of these new varibles will be doing:
+
+* ``mouse_scroll_value``: The value of the mouse scroll wheel.
+* ``MOUSE_SENSITIVITY_SCROLL_WHEEL``: How much a single scroll action increases mouse_scroll_value
+
+______
+
+Now lets add the following to ``_input``:
+
+::
+    
+    if event is InputEventMouseButton && Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
+        if event.button_index == BUTTON_WHEEL_UP or event.button_index == BUTTON_WHEEL_DOWN:
+            if event.button_index == BUTTON_WHEEL_UP:
+                mouse_scroll_value += MOUSE_SENSITIVITY_SCROLL_WHEEL
+            elif event.button_index == BUTTON_WHEEL_DOWN:
+                mouse_scroll_value -= MOUSE_SENSITIVITY_SCROLL_WHEEL
+            
+            mouse_scroll_value = clamp(mouse_scroll_value, 0, weapon_number_to_name.size()-1)
+            
+            if changing_weapon == false:
+                if reloading_weapon == false:
+                    var round_mouse_scroll_value = int(round(mouse_scroll_value))
+                    if weapon_number_to_name[round_mouse_scroll_value] != current_weapon_name:
+                        changing_weapon_name = weapon_number_to_name[round_mouse_scroll_value]
+                        changing_weapon = true
+                        mouse_scroll_value = round_mouse_scroll_value
+
+                        
+Let's go over what's happening here:
+
+First we check if the event is a ``InputEventMouseButton`` event and that our mouse mode is ``MOUSE_MODE_CAPTURED``.
+Then we check to see if the button index is either a ``BUTTON_WHEEL_UP`` or ``BUTTON_WHEEL_DOWN`` index.
+
+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.
+Based on whether it is up or down we add/remove ``MOUSE_SENSITIVITY_SCROLL_WHEEL`` to/from ``mouse_scroll_value``.
+
+Next we clamp mouse scroll value to assure it is inside the range of our weapons.
+
+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``.
+
+.. 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,
+          we would get an error when we try to run the project.
+
+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``.
+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
+``process_changing_weapon``, and set ``mouse_scroll_value`` to ``round_mouse_scroll_value``.
+
+.. 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
+         mouse scroll wheel just in between values, giving them the ability to switch almost extremely fast. By assigning ``mouse_scroll_value``
+         to ``round_mouse_scroll_value``, we assure that each weapon takes exactly the same amount of scrolling to change.
+
+______
+
+Now you can change weapons using the scroll wheel! Go give it a whirl!
+
+Final notes
+-----------
+
+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!
+
+.. tip:: You can find the finished project for part 4 here: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial/tree/part_4
+         
+         The completed project has helpful comments every step of the way for almost every line of code!
+         
+         (Remember, you can download the completed project as a ZIP file if you want)
+         
+         .. image:: img/GithubDownloadZip.png
+
+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
+
+
+How to make ``Test_Level.tscn`` look cool!
+__________________________________________
+
+One quick thing! As noted by **MagicLord** from the Godot forums, you can make ``Test_Level.tscn`` look really cool with a little tweaking!
+
+If you change the roughness values down in the Spatial materials for the provided starter assets, you get this:
+
+.. image:: img/PartFourFinished.png
+
+.. note:: Huge thanks to **MagicLord** for sharing! (Credit for the picture goes to **MagicLord** as well!)
+
+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``,
+which you can find at ``assets/Level_assets``.
+
+.. note:: Remember, you have to hit the save button or your changes to ``LevelAssets_SpatialMaterial.tres`` and/or ``LevelAssets_Transparent_SpatialMaterial.tres``
+          will not be saved! The save icon looks like a little floppy disk!
+
+You can also turn on SSR (Screen Space Reflections) and/or use :ref:`reflection probes <class_ReflectionProbe>`
+as well! Turning up the metallic value a little can also give a more realistic look.
+
+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
+the material roughness down in the finished project.
+
+

+ 2 - 11
tutorials/3d/fps_tutorial/part_three.rst

@@ -15,7 +15,7 @@ guns fire.
 By the end of this part, the player will have limited ammo, the ability to reload,
 and sounds will play when the player fires and changes weapons.
 
-.. note:: You are assumed to have finished :ref:`doc_fps_tutorial_part_two` before moving on to this part of the tutorial.
+.. note:: You are assumed to have finished :ref:`part two <doc_fps_tutorial_part_two>` before moving on to this part of the tutorial.
 
 Let's get started!
 
@@ -701,13 +701,4 @@ While no sounds are provided, you can find many game ready sounds at https://gam
 
 __________
 
-In future parts we will be adding the following:
-
-- Adding a spawning system
-- Adding grenades
-- Adding turrets and targets
-- Adding a sound manager
-- Adding ammo and health pickups
-- Refining and cleaning up the code
-
-.. warning:: All plans are subject to change without warning!
+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!