part_five.rst 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. .. _doc_fps_tutorial_part_five:
  2. Part 5
  3. ======
  4. Part overview
  5. -------------
  6. In this part, we're going to add grenades to the player, give the player the ability to grab and throw objects, and add turrets!
  7. .. image:: img/PartFiveFinished.png
  8. .. note:: You are assumed to have finished :ref:`doc_fps_tutorial_part_four` before moving on to this part of the tutorial.
  9. The finished project from :ref:`doc_fps_tutorial_part_four` will be the starting project for part 5
  10. Let's get started!
  11. Adding grenades
  12. ---------------
  13. Firstly, let's give the player some grenades to play with. Open up ``Grenade.tscn``.
  14. There are a few things to note here, the first and foremost being that the grenades are going to use :ref:`RigidBody <class_RigidBody>` nodes.
  15. We're going to use :ref:`RigidBody <class_RigidBody>` nodes for our grenades so they bounce around the world in a (somewhat) realistic manner.
  16. The second thing to note is ``Blast_Area``. This is an :ref:`Area <class_Area>` node that will represent the blast radius of the grenade.
  17. Finally, the last thing to note is ``Explosion``. This is the :ref:`Particles <class_Particles>` node that will emit an explosion effect when
  18. the grenade explodes. One thing to note here is that we have ``One shot`` enabled. This is so we emit all the particles at once. The particles are also emitted using world
  19. coordinates instead of local coordinates, so we have ``Local Coords`` unchecked as well.
  20. .. note:: If you want, you can see how the particles are set up by looking through the particle's ``Process Material`` and ``Draw Passes``.
  21. Let's write the code needed for the grenade. Select ``Grenade`` and make a new script called ``Grenade.gd``. Add the following:
  22. ::
  23. extends RigidBody
  24. const GRENADE_DAMAGE = 60
  25. const GRENADE_TIME = 2
  26. var grenade_timer = 0
  27. const EXPLOSION_WAIT_TIME = 0.48
  28. var explosion_wait_timer = 0
  29. var rigid_shape
  30. var grenade_mesh
  31. var blast_area
  32. var explosion_particles
  33. func _ready():
  34. rigid_shape = $Collision_Shape
  35. grenade_mesh = $Grenade
  36. blast_area = $Blast_Area
  37. explosion_particles = $Explosion
  38. explosion_particles.emitting = false
  39. explosion_particles.one_shot = true
  40. func _process(delta):
  41. if grenade_timer < GRENADE_TIME:
  42. grenade_timer += delta
  43. return
  44. else:
  45. if explosion_wait_timer <= 0:
  46. explosion_particles.emitting = true
  47. grenade_mesh.visible = false
  48. rigid_shape.disabled = true
  49. mode = RigidBody.MODE_STATIC
  50. var bodies = blast_area.get_overlapping_bodies()
  51. for body in bodies:
  52. if body.has_method("bullet_hit"):
  53. body.bullet_hit(GRENADE_DAMAGE, body.global_transform.looking_at(global_transform.origin, Vector3(0, 1, 0)))
  54. # This would be the perfect place to play a sound!
  55. if explosion_wait_timer < EXPLOSION_WAIT_TIME:
  56. explosion_wait_timer += delta
  57. if explosion_wait_timer >= EXPLOSION_WAIT_TIME:
  58. queue_free()
  59. Let's go over what's happening, starting with the class variables:
  60. * ``GRENADE_DAMAGE``: The amount of damage the grenade causes when it explodes.
  61. * ``GRENADE_TIME``: The amount of time the grenade takes (in seconds) to explode once it's created/thrown.
  62. * ``grenade_timer``: A variable for tracking how long the grenade has been created/thrown.
  63. * ``EXPLOSION_WAIT_TIME``: The amount of time needed (in seconds) to wait before we destroy the grenade scene after the explosion
  64. * ``explosion_wait_timer``: A variable for tracking how much time has passed since the grenade exploded.
  65. * ``rigid_shape``: The :ref:`CollisionShape <class_CollisionShape>` for the grenade's :ref:`RigidBody <class_RigidBody>`.
  66. * ``grenade_mesh``: The :ref:`MeshInstance <class_MeshInstance>` for the grenade.
  67. * ``blast_area``: The blast :ref:`Area <class_Area>` used to damage things when the grenade explodes.
  68. * ``explosion_particles``: The :ref:`Particles <class_Particles>` that come out when the grenade explodes.
  69. Notice how ``EXPLOSION_WAIT_TIME`` is a rather strange number (``0.48``). This is because we want ``EXPLOSION_WAIT_TIME`` to be equal to the length of time
  70. the explosion particles are emitting, so when the particles are done we destroy/free the grenade. We calculate ``EXPLOSION_WAIT_TIME`` by taking the particle's life time
  71. and dividing it by the particle's speed scale. This gets us the exact time the explosion particles will last.
  72. ______
  73. Now let's turn our attention to ``_ready``.
  74. First we get all the nodes we'll need and assign them to the proper class variables.
  75. We need to get the :ref:`CollisionShape <class_CollisionShape>` and :ref:`MeshInstance <class_MeshInstance>` because similarly to the target in :ref:`doc_fps_tutorial_part_four`,
  76. we will be hiding the grenade's mesh and disabling the collision shape when the grenade explodes.
  77. The reason we need to get the blast :ref:`Area <class_Area>` is so we can damage everything inside it when the grenade explodes. We'll be using code similar to the knife
  78. code in the player. We need the :ref:`Particles <class_Particles>` so we can emit particles when the grenade explodes.
  79. After we get all the nodes and assign them to their class variables, we then make sure the explosion particles are not emitting, and that they are set to
  80. emit in one shot. This is to be extra sure the particles will behave the way we expect them to.
  81. ______
  82. Now let's look at ``_process``.
  83. Firstly, we check to see if the ``grenade_timer`` is less than ``GRENADE_TIME``. If it is, we add ``delta`` and return. This is so the grenade has to wait ``GRENADE_TIME`` seconds
  84. before exploding, allowing the :ref:`RigidBody <class_RigidBody>` to move around.
  85. If ``grenade_timer`` is at ``GRENADE_TIMER`` or higher, we then need to check if the grenade has waited long enough and needs to explode. We do this by checking to see
  86. if ``explosion_wait_timer`` is equal to ``0`` or less. Since we will be adding ``delta`` to ``explosion_wait_timer`` right after, whatever code under the check
  87. will only be called once, right when the grenade has waited long enough and needs to explode.
  88. If the grenade has waited long enough to explode, we first tell the ``explosion_particles`` to emit. Then we make ``grenade_mesh`` invisible, and disable ``rigid_shape``, effectively
  89. hiding the grenade.
  90. We then set the :ref:`RigidBody <class_RigidBody>`'s mode to ``MODE_STATIC`` so the grenade does not move.
  91. Then we get all the bodies in ``blast_area``, check to see if they have the ``bullet_hit`` method/function, and if they do, we call it and pass in ``GRENADE_DAMAGE`` and
  92. the transform from the body looking at the grenade. This makes it where the bodies exploded by the grenade will explode outwards from the grenade's position.
  93. We then check to see if ``explosion_wait_timer`` is less than ``EXPLOSION_WAIT_TIME``. If it is, we add ``delta`` to ``explosion_wait_timer``.
  94. Next, we check to see if ``explosion_wait_timer`` is greater than or equal to ``EXPLOSION_WAIT_TIME``. Because we added ``delta``, this will only be called once.
  95. If ``explosion_wait_timer`` is greater or equal to ``EXPLOSION_WAIT_TIME``, the grenade has waited long enough to let the :ref:`Particles <class_Particles>` play
  96. and we can free/destroy the grenade, as we no longer need it.
  97. ______
  98. Let's quickly get the sticky grenade set up too. Open up ``Sticky_Grenade.tscn``.
  99. ``Sticky_Grenade.tscn`` is almost identical to ``Grenade.tscn``, with one small addition. We now have a second
  100. :ref:`Area <class_Area>`, called ``Sticky_Area``. We will be using ``Stick_Area`` to detect when the sticky grenade has collided with
  101. the environment and needs to stick to something.
  102. Select ``Sticky_Grenade`` and make a new script called ``Sticky_Grenade.gd``. Add the following:
  103. ::
  104. extends RigidBody
  105. const GRENADE_DAMAGE = 40
  106. const GRENADE_TIME = 3
  107. var grenade_timer = 0
  108. const EXPLOSION_WAIT_TIME = 0.48
  109. var explosion_wait_timer = 0
  110. var attached = false
  111. var attach_point = null
  112. var rigid_shape
  113. var grenade_mesh
  114. var blast_area
  115. var explosion_particles
  116. var player_body
  117. func _ready():
  118. rigid_shape = $Collision_Shape
  119. grenade_mesh = $Sticky_Grenade
  120. blast_area = $Blast_Area
  121. explosion_particles = $Explosion
  122. explosion_particles.emitting = false
  123. explosion_particles.one_shot = true
  124. $Sticky_Area.connect("body_entered", self, "collided_with_body")
  125. func collided_with_body(body):
  126. if body == self:
  127. return
  128. if player_body != null:
  129. if body == player_body:
  130. return
  131. if attached == false:
  132. attached = true
  133. attach_point = Spatial.new()
  134. body.add_child(attach_point)
  135. attach_point.global_transform.origin = global_transform.origin
  136. rigid_shape.disabled = true
  137. mode = RigidBody.MODE_STATIC
  138. func _process(delta):
  139. if attached == true:
  140. if attach_point != null:
  141. global_transform.origin = attach_point.global_transform.origin
  142. if grenade_timer < GRENADE_TIME:
  143. grenade_timer += delta
  144. return
  145. else:
  146. if explosion_wait_timer <= 0:
  147. explosion_particles.emitting = true
  148. grenade_mesh.visible = false
  149. rigid_shape.disabled = true
  150. mode = RigidBody.MODE_STATIC
  151. var bodies = blast_area.get_overlapping_bodies()
  152. for body in bodies:
  153. if body.has_method("bullet_hit"):
  154. body.bullet_hit(GRENADE_DAMAGE, body.global_transform.looking_at(global_transform.origin, Vector3(0, 1, 0)))
  155. # This would be the perfect place to play a sound!
  156. if explosion_wait_timer < EXPLOSION_WAIT_TIME:
  157. explosion_wait_timer += delta
  158. if explosion_wait_timer >= EXPLOSION_WAIT_TIME:
  159. if attach_point != null:
  160. attach_point.queue_free()
  161. queue_free()
  162. The code above is almost identical to the code for ``Grenade.gd``, so let's just go over what's changed.
  163. Firstly, we have a few more class variables:
  164. * ``attached``: A variable for tracking whether or not the sticky grenade has attached to a :ref:`PhysicsBody <class_PhysicsBody>`.
  165. * ``attach_point``: A variable to hold a :ref:`Spatial <class_Spatial>` that will be at the position where the sticky grenade collided.
  166. * ``player_body``: The player's :ref:`KinematicBody <class_KinematicBody>`.
  167. They have been added to enable the sticky grenade to stick to any :ref:`PhysicsBody <class_PhysicsBody>` it might hit. We also now
  168. need the player's :ref:`KinematicBody <class_KinematicBody>` so the sticky grenade does not stick to the player when the player throws it.
  169. ______
  170. Now let's look at the small change in ``_ready``. In ``_ready`` we've added a line of code so when any body enters ``Stick_Area``,
  171. the ``collided_with_body`` function is called.
  172. ______
  173. Next let's take a look at ``collided_with_body``.
  174. Firstly, we make sure the sticky grenade is not colliding with itself.
  175. Because the sticky :ref:`Area <class_Area>` does not know it's attached to the grenade's :ref:`RigidBody <class_RigidBody>`,
  176. we need to make sure it's not going to stick to itself by checking to make sure the body it has collided with is not itself.
  177. If we have collided with ourself, we ignore it by returning.
  178. We then check to see if we have something assigned to ``player_body``, and if the body the sticky grenade has collided with is the player that threw it.
  179. If the body the sticky grenade has collided with is indeed ``player_body``, we ignore it by returning.
  180. Next, we check if the sticky grenade has attached to something already or not.
  181. If the sticky grenade is not attached, we then set ``attached`` to ``true`` so we know the sticky grenade has attached to something.
  182. We then make a new :ref:`Spatial <class_Spatial>` node, and make it a child of the body the sticky grenade collided with. We then set the :ref:`Spatial <class_Spatial>`'s position
  183. to the sticky grenade's current global position.
  184. .. note:: Because we've added the :ref:`Spatial <class_Spatial>` as a child of the body the sticky grenade has collided with, it will follow along with said body.
  185. We can then use this :ref:`Spatial <class_Spatial>` to set the sticky grenade's position, so it is always at the same position relative to the body it collided with.
  186. We then disable ``rigid_shape`` so the sticky grenade is not constantly moving whatever body it has collided with.
  187. Finally, we set our mode to ``MODE_STATIC`` so the grenade does not move.
  188. ______
  189. Finally, lets go over the few changes in ``_process``.
  190. Now we're checking to see if the sticky grenade is attached right at the top of ``_process``.
  191. If the sticky grenade is attached, we then make sure the attached point is not equal to ``null``.
  192. If the attached point is not equal to ``null``, we set the sticky grenade's global position (using its global :ref:`Transform <class_Transform>`'s origin) to the global position of
  193. the :ref:`Spatial <class_Spatial>` assigned to ``attach_point`` (using its global :ref:`Transform <class_Transform>`'s origin).
  194. The only other change is now before we free/destroy the sticky grenade is to check to see if the sticky grenade has an attached point.
  195. If it does, we also call ``queue_free`` on the attach point, so it's also freed/destroyed.
  196. Adding grenades to the player
  197. -----------------------------
  198. Now we need to add some code to ``Player.gd`` so we can use the grenades.
  199. Firstly, open up ``Player.tscn`` and expand the node tree until you get to ``Rotation_Helper``. Notice how in
  200. ``Rotation_Helper`` we have a node called ``Grenade_Toss_Pos``. This is where we will be spawning the grenades.
  201. Also notice how it's slightly rotated on the ``X`` axis, so it's not pointing straight, but rather slightly up. By changing
  202. the rotation of ``Grenade_Toss_Pos``, you can change the angle at which the grenades are tossed.
  203. Okay, now let's start making the grenades work with the player. Add the following class variables to ``Player.gd``:
  204. ::
  205. var grenade_amounts = {"Grenade":2, "Sticky Grenade":2}
  206. var current_grenade = "Grenade"
  207. var grenade_scene = preload("res://Grenade.tscn")
  208. var sticky_grenade_scene = preload("res://Sticky_Grenade.tscn")
  209. const GRENADE_THROW_FORCE = 50
  210. * ``grenade_amounts``: The amount of grenades the player is currently carrying (for each type of grenade).
  211. * ``current_grenade``: The name of the grenade the player is currently using.
  212. * ``grenade_scene``: The grenade scene we worked on earlier.
  213. * ``sticky_grenade_scene``: The sticky grenade scene we worked on earlier.
  214. * ``GRENADE_THROW_FORCE``: The force at which the player will throw the grenades.
  215. Most of these variables are similar to how we have our weapons set up.
  216. .. tip:: While it's possible to make a more modular grenade system, I found it was not worth the additional complexity for just two grenades.
  217. If you were going to make a more complex FPS with more grenades, you'd likely want to make a system for grenades similar to how we have the weapons set up.
  218. ______
  219. Now we need to add some code in ``_process_input`` Add the following to ``_process_input``:
  220. ::
  221. # ----------------------------------
  222. # Changing and throwing grenades
  223. if Input.is_action_just_pressed("change_grenade"):
  224. if current_grenade == "Grenade":
  225. current_grenade = "Sticky Grenade"
  226. elif current_grenade == "Sticky Grenade":
  227. current_grenade = "Grenade"
  228. if Input.is_action_just_pressed("fire_grenade"):
  229. if grenade_amounts[current_grenade] > 0:
  230. grenade_amounts[current_grenade] -= 1
  231. var grenade_clone
  232. if current_grenade == "Grenade":
  233. grenade_clone = grenade_scene.instance()
  234. elif current_grenade == "Sticky Grenade":
  235. grenade_clone = sticky_grenade_scene.instance()
  236. # Sticky grenades will stick to the player if we do not pass ourselves
  237. grenade_clone.player_body = self
  238. get_tree().root.add_child(grenade_clone)
  239. grenade_clone.global_transform = $Rotation_Helper/Grenade_Toss_Pos.global_transform
  240. grenade_clone.apply_impulse(Vector3(0, 0, 0), grenade_clone.global_transform.basis.z * GRENADE_THROW_FORCE)
  241. # ----------------------------------
  242. Let's go over what's happening here.
  243. Firstly, we check to see if the ``change_grenade`` action has just been pressed. If it has, we then check to see which grenade the player is
  244. currently using. Based on the name of the grenade the player is currently using, we change ``current_grenade`` to the opposite grenade name.
  245. Next we check to see if the ``fire_grenade`` action has just been pressed. If it has, we then check to see if the player has more than ``0`` grenades for the
  246. current grenade type selected.
  247. If the player has more than ``0`` grenades, we then remove one from the grenade amounts for the current grenade.
  248. Then, based on the grenade the player is currently using, we instance the proper grenade scene and assign it to ``grenade_clone``.
  249. Next we add ``grenade_clone`` as a child of the node at the root and set its global :ref:`Transform <class_Transform>` to
  250. ``Grenade_Toss_Pos``'s global :ref:`Transform <class_Transform>`. Finally, we apply an impulse to the grenade so that it is launched forward, relative
  251. to the ``Z`` directional vector of ``grenade_clone``'s.
  252. ______
  253. Now the player can use both types of grenades, but there are still a few things we should probably add before we move on to adding the other things.
  254. We still need a way to show the player how many grenades are left, and we should probably add a way to get more grenades when the player picks up ammo.
  255. Firstly, let's change some of the code in ``Player.gd`` to show how many grenades are left. Change ``process_UI`` to the following:
  256. ::
  257. func process_UI(delta):
  258. if current_weapon_name == "UNARMED" or current_weapon_name == "KNIFE":
  259. # First line: Health, second line: Grenades
  260. UI_status_label.text = "HEALTH: " + str(health) + \
  261. "\n" + current_grenade + ": " + str(grenade_amounts[current_grenade])
  262. else:
  263. var current_weapon = weapons[current_weapon_name]
  264. # First line: Health, second line: weapon and ammo, third line: grenades
  265. UI_status_label.text = "HEALTH: " + str(health) + \
  266. "\nAMMO: " + str(current_weapon.ammo_in_weapon) + "/" + str(current_weapon.spare_ammo) + \
  267. "\n" + current_grenade + ": " + str(grenade_amounts[current_grenade])
  268. Now we'll show how many grenades the player has left in the UI.
  269. While we're still in ``Player.gd``, let's add a function to add grenades to the player. Add the following function to ``Player.gd``:
  270. ::
  271. func add_grenade(additional_grenade):
  272. grenade_amounts[current_grenade] += additional_grenade
  273. grenade_amounts[current_grenade] = clamp(grenade_amounts[current_grenade], 0, 4)
  274. Now we can add a grenade using ``add_grenade``, and it will automatically be clamped to a maximum of ``4`` grenades.
  275. .. tip:: You can change the ``4`` to a constant if you want. You'd need to make a new global constant, something like ``MAX_GRENADES``, and
  276. then change the clamp from ``clamp(grenade_amounts[current_grenade], 0, 4)`` to ``clamp(grenade_amounts[current_grenade], 0, MAX_GRENADES)``
  277. If you do not want to limit how many grenades the player can carry, remove the line that clamps the grenades altogether!
  278. Now we have a function to add grenades, let's open up ``AmmoPickup.gd`` and use it!
  279. Open up ``AmmoPickup.gd`` and go to the ``trigger_body_entered`` function. Change it to the following:
  280. ::
  281. func trigger_body_entered(body):
  282. if body.has_method("add_ammo"):
  283. body.add_ammo(AMMO_AMOUNTS[kit_size])
  284. respawn_timer = RESPAWN_TIME
  285. kit_size_change_values(kit_size, false)
  286. if body.has_method("add_grenade"):
  287. body.add_grenade(GRENADE_AMOUNTS[kit_size])
  288. respawn_timer = RESPAWN_TIME
  289. kit_size_change_values(kit_size, false)
  290. Now we are also checking to see if the body has the ``add_grenade`` function. If it does, we call it like we call ``add_ammo``.
  291. You may have noticed we are using a new constant we have not defined yet, ``GRENADE_AMOUNTS``. Let's add it! Add the following class variable
  292. to ``AmmoPickup.gd`` with the other class variables:
  293. ::
  294. const GRENADE_AMOUNTS = [2, 0]
  295. * ``GRENADE_AMOUNTS``: The amount of grenades each pickup contains.
  296. Notice how the second element in ``GRENADE_AMOUNTS`` is ``0``. This is so the small ammo pickup does not give the player
  297. any additional grenades.
  298. ______
  299. Now you should be able to throw grenades! Go give it a try!
  300. Adding the ability to grab and throw RigidBody nodes to the player
  301. ------------------------------------------------------------------
  302. Next, let's give the player the ability to pick up and throw :ref:`RigidBody <class_RigidBody>` nodes.
  303. Open up ``Player.gd`` and add the following class variables:
  304. ::
  305. var grabbed_object = null
  306. const OBJECT_THROW_FORCE = 120
  307. const OBJECT_GRAB_DISTANCE = 7
  308. const OBJECT_GRAB_RAY_DISTANCE = 10
  309. * ``grabbed_object``: A variable to hold the grabbed :ref:`RigidBody <class_RigidBody>` node.
  310. * ``OBJECT_THROW_FORCE``: The force with which the player throws the grabbed object.
  311. * ``OBJECT_GRAB_DISTANCE``: The distance away from the camera at which the player holds the grabbed object.
  312. * ``OBJECT_GRAB_RAY_DISTANCE``: The distance the :ref:`Raycast <class_Raycast>` goes. This is the player's grab distance.
  313. With that done, all we need to do is add some code to ``process_input``:
  314. ::
  315. # ----------------------------------
  316. # Grabbing and throwing objects
  317. if Input.is_action_just_pressed("fire_grenade") and current_weapon_name == "UNARMED":
  318. if grabbed_object == null:
  319. var state = get_world().direct_space_state
  320. var center_position = get_viewport().size / 2
  321. var ray_from = camera.project_ray_origin(center_position)
  322. var ray_to = ray_from + camera.project_ray_normal(center_position) * OBJECT_GRAB_RAY_DISTANCE
  323. var ray_result = state.intersect_ray(ray_from, ray_to, [self, $Rotation_Helper/Gun_Fire_Points/Knife_Point/Area])
  324. if !ray_result.empty():
  325. if ray_result["collider"] is RigidBody:
  326. grabbed_object = ray_result["collider"]
  327. grabbed_object.mode = RigidBody.MODE_STATIC
  328. grabbed_object.collision_layer = 0
  329. grabbed_object.collision_mask = 0
  330. else:
  331. grabbed_object.mode = RigidBody.MODE_RIGID
  332. grabbed_object.apply_impulse(Vector3(0, 0, 0), -camera.global_transform.basis.z.normalized() * OBJECT_THROW_FORCE)
  333. grabbed_object.collision_layer = 1
  334. grabbed_object.collision_mask = 1
  335. grabbed_object = null
  336. if grabbed_object != null:
  337. grabbed_object.global_transform.origin = camera.global_transform.origin + (-camera.global_transform.basis.z.normalized() * OBJECT_GRAB_DISTANCE)
  338. # ----------------------------------
  339. Let's go over what's happening.
  340. Firstly, we check to see if the action pressed is the ``fire`` action, and that the player is using the ``UNARMED`` 'weapon'.
  341. This is because we only want the player to be able to pick up and throw objects when the player is not using any weapons. This is a design choice,
  342. but I feel it gives ``UNARMED`` a use.
  343. Next we check to see whether or not ``grabbed_object`` is ``null``.
  344. ______
  345. If ``grabbed_object`` is ``null``, we want to see if we can pick up a :ref:`RigidBody <class_RigidBody>`.
  346. We first get the direct space state from the current :ref:`World <class_World>`. This is so we can cast a ray entirely from code, instead of having to
  347. use a :ref:`Raycast <class_Raycast>` node.
  348. .. note:: See :ref:`Ray-casting <doc_ray-casting>` for more information on raycasting in Godot.
  349. Then we get the center of the screen by dividing the current :ref:`Viewport <class_Viewport>` size in half. We then get the ray's origin point and end point using
  350. ``project_ray_origin`` and ``project_ray_normal`` from the camera. If you want to know more about how these functions work, see :ref:`Ray-casting <doc_ray-casting>`.
  351. Next we send the ray into the space state and see if it gets a result. We add the player and the knife's :ref:`Area <class_Area>` as two exceptions so the player cannot carry
  352. themselves or the knife's collision :ref:`Area <class_Area>`.
  353. Then we check to see if we got a result back from the ray. If no object has collided with the ray, an empty Dictionary will be returned. If the Dictionary is not empty (i.e. at least one object has collided), we then see if the collider the ray collided with is a :ref:`RigidBody <class_RigidBody>`.
  354. If the ray collided with a :ref:`RigidBody <class_RigidBody>`, we set ``grabbed_object`` to the collider the ray collided with. We then set the mode on
  355. the :ref:`RigidBody <class_RigidBody>` we collided with to ``MODE_STATIC`` so it doesn't move in our hands.
  356. Finally, we set the grabbed :ref:`RigidBody <class_RigidBody>`'s collision layer and collision mask to ``0``.
  357. This will make the grabbed :ref:`RigidBody <class_RigidBody>` have no collision layer or mask, which means it will not be able to collide with anything as long as we are holding it.
  358. .. note::
  359. See :ref:`Physics introduction <doc_physics_introduction_collision_layer_code_example>`
  360. for more information on Godot collision masks.
  361. ______
  362. If ``grabbed_object`` is not ``null``, then we need to throw the :ref:`RigidBody <class_RigidBody>` the player is holding.
  363. We first set the mode of the :ref:`RigidBody <class_RigidBody>` we are holding to ``MODE_RIGID``.
  364. .. note:: This is making a rather large assumption that all the rigid bodies will be using ``MODE_RIGID``. While that is the case for this tutorial series,
  365. that may not be the case in other projects.
  366. If you have rigid bodies with different modes, you may need to store the mode of the :ref:`RigidBody <class_RigidBody>` you
  367. have picked up into a class variable so you can change it back to the mode it was in before you picked it up.
  368. Then we apply an impulse to send it flying forward. We send it flying in the direction the camera is facing, using the force we set in the ``OBJECT_THROW_FORCE`` variable.
  369. We then set the grabbed :ref:`RigidBody <class_RigidBody>`'s collision layer and mask to ``1``, so it can collide with anything on layer ``1`` again.
  370. .. note:: This is, once again, making a rather large assumption that all the rigid bodies will be only on collision layer ``1``, and all collision masks will be on layer ``1``.
  371. If you are using this script in other projects, you may need to store the collision layer/mask of the :ref:`RigidBody <class_RigidBody>` in a variable before you change them to ``0``, so you would have the original collision layer/mask to set for them when you are reversing the process.
  372. Finally, we set ``grabbed_object`` to ``null`` since the player has successfully thrown the held object.
  373. ______
  374. The last thing we do is check to see whether or not ``grabbed_object`` is equal to ``null``, outside all of the grabbing/throwing related code.
  375. .. note:: While technically not input related, it's easy enough to place the code moving the grabbed object here
  376. because it's only two lines, and then all of the grabbing/throwing code is in one place
  377. If the player is holding an object, we set its global position to the camera's position plus ``OBJECT_GRAB_DISTANCE`` in the direction the camera is facing.
  378. ______
  379. Before we test this, we need to change something in ``_physics_process``. While the player is holding an object, we do not
  380. want the player to be able to change weapons or reload, so change ``_physics_process`` to the following:
  381. ::
  382. func _physics_process(delta):
  383. process_input(delta)
  384. process_view_input(delta)
  385. process_movement(delta)
  386. if grabbed_object == null:
  387. process_changing_weapons(delta)
  388. process_reloading(delta)
  389. # Process the UI
  390. process_UI(delta)
  391. Now the player cannot change weapons or reload while holding an object.
  392. Now you can grab and throw RigidBody nodes while you're in the ``UNARMED`` state! Go give it a try!
  393. Adding a turret
  394. ---------------
  395. Next, let's make a turret to shoot the player!
  396. Open up ``Turret.tscn``. Expand ``Turret`` if it's not already expanded.
  397. Notice how the turret is broken up into several parts: ``Base``, ``Head``, ``Vision_Area``, and a ``Smoke`` :ref:`Particles <class_Particles>` node.
  398. Open up ``Base`` and you'll find it's a :ref:`StaticBody <class_StaticBody>` and a mesh. Open up ``Head`` and you'll find there are several meshes,
  399. a :ref:`StaticBody <class_StaticBody>` and a :ref:`Raycast <class_Raycast>` node.
  400. One thing to note with the ``Head`` is that the raycast will be where the turret's bullets will fire from if we are using raycasting. We also have two meshes called
  401. ``Flash`` and ``Flash_2``. These will be the muzzle flash that briefly shows when the turret fires.
  402. ``Vision_Area`` is an :ref:`Area <class_Area>` we'll use as the turret's ability to see. When something enters ``Vision_Area``, we'll assume the turret can see it.
  403. ``Smoke`` is a :ref:`Particles <class_Particles>` node that will play when the turret is destroyed and repairing.
  404. ______
  405. Now that we've looked at how the scene is set up, lets start writing the code for the turret. Select ``Turret`` and create a new script called ``Turret.gd``.
  406. Add the following to ``Turret.gd``:
  407. ::
  408. extends Spatial
  409. export (bool) var use_raycast = false
  410. const TURRET_DAMAGE_BULLET = 20
  411. const TURRET_DAMAGE_RAYCAST = 5
  412. const FLASH_TIME = 0.1
  413. var flash_timer = 0
  414. const FIRE_TIME = 0.8
  415. var fire_timer = 0
  416. var node_turret_head = null
  417. var node_raycast = null
  418. var node_flash_one = null
  419. var node_flash_two = null
  420. var ammo_in_turret = 20
  421. const AMMO_IN_FULL_TURRET = 20
  422. const AMMO_RELOAD_TIME = 4
  423. var ammo_reload_timer = 0
  424. var current_target = null
  425. var is_active = false
  426. const PLAYER_HEIGHT = 3
  427. var smoke_particles
  428. var turret_health = 60
  429. const MAX_TURRET_HEALTH = 60
  430. const DESTROYED_TIME = 20
  431. var destroyed_timer = 0
  432. var bullet_scene = preload("Bullet_Scene.tscn")
  433. func _ready():
  434. $Vision_Area.connect("body_entered", self, "body_entered_vision")
  435. $Vision_Area.connect("body_exited", self, "body_exited_vision")
  436. node_turret_head = $Head
  437. node_raycast = $Head/Ray_Cast
  438. node_flash_one = $Head/Flash
  439. node_flash_two = $Head/Flash_2
  440. node_raycast.add_exception(self)
  441. node_raycast.add_exception($Base/Static_Body)
  442. node_raycast.add_exception($Head/Static_Body)
  443. node_raycast.add_exception($Vision_Area)
  444. node_flash_one.visible = false
  445. node_flash_two.visible = false
  446. smoke_particles = $Smoke
  447. smoke_particles.emitting = false
  448. turret_health = MAX_TURRET_HEALTH
  449. func _physics_process(delta):
  450. if is_active == true:
  451. if flash_timer > 0:
  452. flash_timer -= delta
  453. if flash_timer <= 0:
  454. node_flash_one.visible = false
  455. node_flash_two.visible = false
  456. if current_target != null:
  457. node_turret_head.look_at(current_target.global_transform.origin + Vector3(0, PLAYER_HEIGHT, 0), Vector3(0, 1, 0))
  458. if turret_health > 0:
  459. if ammo_in_turret > 0:
  460. if fire_timer > 0:
  461. fire_timer -= delta
  462. else:
  463. fire_bullet()
  464. else:
  465. if ammo_reload_timer > 0:
  466. ammo_reload_timer -= delta
  467. else:
  468. ammo_in_turret = AMMO_IN_FULL_TURRET
  469. if turret_health <= 0:
  470. if destroyed_timer > 0:
  471. destroyed_timer -= delta
  472. else:
  473. turret_health = MAX_TURRET_HEALTH
  474. smoke_particles.emitting = false
  475. func fire_bullet():
  476. if use_raycast == true:
  477. node_raycast.look_at(current_target.global_transform.origin + Vector3(0, PLAYER_HEIGHT, 0), Vector3(0, 1, 0))
  478. node_raycast.force_raycast_update()
  479. if node_raycast.is_colliding():
  480. var body = node_raycast.get_collider()
  481. if body.has_method("bullet_hit"):
  482. body.bullet_hit(TURRET_DAMAGE_RAYCAST, node_raycast.get_collision_point())
  483. ammo_in_turret -= 1
  484. else:
  485. var clone = bullet_scene.instance()
  486. var scene_root = get_tree().root.get_children()[0]
  487. scene_root.add_child(clone)
  488. clone.global_transform = $Head/Barrel_End.global_transform
  489. clone.scale = Vector3(8, 8, 8)
  490. clone.BULLET_DAMAGE = TURRET_DAMAGE_BULLET
  491. clone.BULLET_SPEED = 60
  492. ammo_in_turret -= 1
  493. node_flash_one.visible = true
  494. node_flash_two.visible = true
  495. flash_timer = FLASH_TIME
  496. fire_timer = FIRE_TIME
  497. if ammo_in_turret <= 0:
  498. ammo_reload_timer = AMMO_RELOAD_TIME
  499. func body_entered_vision(body):
  500. if current_target == null:
  501. if body is KinematicBody:
  502. current_target = body
  503. is_active = true
  504. func body_exited_vision(body):
  505. if current_target != null:
  506. if body == current_target:
  507. current_target = null
  508. is_active = false
  509. flash_timer = 0
  510. fire_timer = 0
  511. node_flash_one.visible = false
  512. node_flash_two.visible = false
  513. func bullet_hit(damage, bullet_hit_pos):
  514. turret_health -= damage
  515. if turret_health <= 0:
  516. smoke_particles.emitting = true
  517. destroyed_timer = DESTROYED_TIME
  518. This is quite a bit of code, so let's break it down function by function. Let's first look at the class variables:
  519. * ``use_raycast``: An exported boolean so we can change whether the turret uses objects or raycasting for bullets.
  520. * ``TURRET_DAMAGE_BULLET``: The amount of damage a single bullet scene does.
  521. * ``TURRET_DAMAGE_RAYCAST``: The amount of damage a single :ref:`Raycast <class_Raycast>` bullet does.
  522. * ``FLASH_TIME``: The amount of time (in seconds) the muzzle flash meshes are visible.
  523. * ``flash_timer``: A variable for tracking how long the muzzle flash meshes have been visible.
  524. * ``FIRE_TIME``: The amount of time (in seconds) needed to fire a bullet.
  525. * ``fire_timer``: A variable for tracking how much time has passed since the turret last fired.
  526. * ``node_turret_head``: A variable to hold the ``Head`` node.
  527. * ``node_raycast``: A variable to hold the :ref:`Raycast <class_Raycast>` node attached to the turret's head.
  528. * ``node_flash_one``: A variable to hold the first muzzle flash :ref:`MeshInstance <class_MeshInstance>`.
  529. * ``node_flash_two``: A variable to hold the second muzzle flash :ref:`MeshInstance <class_MeshInstance>`.
  530. * ``ammo_in_turret``: The amount of ammo currently in the turret.
  531. * ``AMMO_IN_FULL_TURRET``: The amount of ammo in a full turret.
  532. * ``AMMO_RELOAD_TIME``: The amount of time it takes the turret to reload.
  533. * ``ammo_reload_timer``: A variable for tracking how long the turret has been reloading.
  534. * ``current_target``: The turret's current target.
  535. * ``is_active``: A variable for tracking whether the turret is able to fire at the target.
  536. * ``PLAYER_HEIGHT``: The amount of height we're adding to the target so we're not shooting at its feet.
  537. * ``smoke_particles``: A variable to hold the smoke particles node.
  538. * ``turret_health``: The amount of health the turret currently has.
  539. * ``MAX_TURRET_HEALTH``: The amount of health a fully healed turret has.
  540. * ``DESTROYED_TIME``: The amount of time (in seconds) it takes for a destroyed turret to repair itself.
  541. * ``destroyed_timer``: A variable for tracking the amount of time a turret has been destroyed.
  542. * ``bullet_scene``: The bullet scene the turret fires (same scene as the player's pistol)
  543. Whew, that's quite a few class variables!
  544. ______
  545. Let's go through ``_ready`` next.
  546. Firstly, we get the vision area and connect the ``body_entered`` and ``body_exited`` signals to ``body_entered_vision`` and ``body_exited_vision``, respectively.
  547. We then get all the nodes and assign them to their respective variables.
  548. Next, we add some exceptions to the :ref:`Raycast <class_Raycast>` so the turret cannot hurt itself.
  549. Then we make both flash meshes invisible at start, since we are not going to be firing during ``_ready``.
  550. We then get the smoke particles node and assign it to the ``smoke_particles`` variable. We also set ``emitting`` to ``false`` to ensure the particles are
  551. not emitting until the turret is broken.
  552. Finally, we set the turret's health to ``MAX_TURRET_HEALTH`` so it starts at full health.
  553. ______
  554. Now let's go through ``_physics_process``.
  555. Firstly, we check whether the turret is active. If the turret is active, we want to process the firing code.
  556. Next, if ``flash_timer`` is greater than zero, meaning the flash meshes are visible, we want to remove
  557. delta from ``flash_timer``. If ``flash_timer`` gets to zero or less after we've subtracted ``delta``, we want to hide
  558. both of the flash meshes.
  559. Next, we check whether the turret has a target. If the turret has a target, we make the turret head look at it, adding ``PLAYER_HEIGHT`` so it is not
  560. aiming at the player's feet.
  561. We then check whether the turret's health is greater than zero. If it is, we then check whether there is ammo in the turret.
  562. If there is, we then check whether ``fire_timer`` is greater than zero. If it is, the turret cannot fire and we need to
  563. remove ``delta`` from ``fire_timer``. If ``fire_timer`` is less than or equal to zero, the turret can fire a bullet, so we call the ``fire_bullet`` function.
  564. If there isn't any ammo in the turret, we check whether ``ammo_reload_timer`` is greater than zero. If it is,
  565. we subtract ``delta`` from ``ammo_reload_timer``. If ``ammo_reload_timer`` is less than or equal to zero, we set ``ammo_in_turret`` to ``AMMO_IN_FULL_TURRET`` because
  566. the turret has waited long enough to refill its ammo.
  567. Next, we check whether the turret's health is less than or equal to ``0`` outside of whether it is active or not. If the turret's health is zero or less, we then
  568. check whether ``destroyed_timer`` is greater than zero. If it is, we subtract ``delta`` from ``destroyed_timer``.
  569. If ``destroyed_timer`` is less than or equal to zero, we set ``turret_health`` to ``MAX_TURRET_HEALTH`` and stop emitting smoke particles by setting ``smoke_particles.emitting`` to
  570. ``false``.
  571. ______
  572. Next let's go through ``fire_bullet``.
  573. Firstly, we check whether the turret is using a raycast.
  574. The code for using a raycast is almost entirely the same as the code in the rifle from :ref:`doc_fps_tutorial_part_two`, so
  575. I'm only going to go over it briefly.
  576. We first make the raycast look at the target, ensuring the raycast will hit the target if nothing is in the way. We then force the raycast to update so we get a frame
  577. perfect collision check. We then check whether the raycast has collided with anything. If it has, we then check
  578. whether the collided body has the ``bullet_hit`` method. If it does, we call it and pass in the damage a single raycast bullet does along with the raycast's transform.
  579. We then subtract ``1`` from ``ammo_in_turret``.
  580. If the turret is not using a raycast, we spawn a bullet object instead. This code is almost entirely the same as the code in the pistol from :ref:`doc_fps_tutorial_part_two`, so
  581. like with the raycast code, I'm only going to go over it briefly.
  582. We first make a bullet clone and assign it to ``clone``. We then add that as a child of the root node. We set the bullet's global transform to
  583. the barrel end, scale it up since it's too small, and set its damage and speed using the turret's constant class variables. We then subtract ``1`` from
  584. ``ammo_in_turret``.
  585. Then, regardless of which bullet method we used, we make both of the muzzle flash meshes visible. We set ``flash_timer`` and ``fire_timer``
  586. to ``FLASH_TIME`` and ``FIRE_TIME``, respectively. We then check whether the turret has used the last bullet in its ammo. If it has,
  587. we set ``ammo_reload_timer`` to ``AMMO_RELOAD_TIME`` so the turret reloads.
  588. ______
  589. Let's look at ``body_entered_vision`` next, and thankfully it is rather short.
  590. We first check whether the turret currently has a target by checking if ``current_target`` is equal to ``null``.
  591. If the turret does not have a target, we then check whether the body that has just entered the vision :ref:`Area <class_Area>` is a :ref:`KinematicBody <class_KinematicBody>`.
  592. .. note:: We're assuming the turret should only fire at :ref:`KinematicBody <class_KinematicBody>` nodes since that is what the player is using.
  593. If the body that just entered the vision :ref:`Area <class_Area>` is a :ref:`KinematicBody <class_KinematicBody>`, we set ``current_target`` to the body, and set ``is_active`` to
  594. ``true``.
  595. ______
  596. Now let's look at ``body_exited_vision``.
  597. Firstly, we check whether the turret has a target. If it does, we then check whether the body that has just left the turret's vision :ref:`Area <class_Area>`
  598. is the turret's target.
  599. If the body that has just left the vision :ref:`Area <class_Area>` is the turret's current target, we set ``current_target`` to ``null``, set ``is_active`` to ``false``, and reset
  600. all the variables related to firing the turret since the turret no longer has a target to fire at.
  601. ______
  602. Finally, let's look at ``bullet_hit``.
  603. We first subtract however much damage the bullet causes from the turret's health.
  604. Then, we check whether the turret has been destroyed (health being zero or less).
  605. If the turret is destroyed, we start emitting the smoke particles and set ``destroyed_timer`` to ``DESTROYED_TIME`` so the turret has to wait before being repaired.
  606. ______
  607. Whew, with all of that done and coded, we only have one last thing to do before the turret is ready for use. Open up ``Turret.tscn`` if it's not already open and
  608. select one of the :ref:`StaticBody <class_StaticBody>` nodes from either ``Base`` or ``Head``. Create a new script called ``TurretBodies.gd`` and attach it to whichever
  609. :ref:`StaticBody <class_StaticBody>` you have selected.
  610. Add the following code to ``TurretBodies.gd``:
  611. ::
  612. extends StaticBody
  613. export (NodePath) var path_to_turret_root
  614. func _ready():
  615. pass
  616. func bullet_hit(damage, bullet_hit_pos):
  617. if path_to_turret_root != null:
  618. get_node(path_to_turret_root).bullet_hit(damage, bullet_hit_pos)
  619. All this code does is call ``bullet_hit`` on whatever node to which ``path_to_turret_root`` leads. Go back to the editor and assign the :ref:`NodePath <class_NodePath>`
  620. to the ``Turret`` node.
  621. Now select the other :ref:`StaticBody <class_StaticBody>` node (either in ``Body`` or ``Head``) and assign ``TurretBodies.gd`` script to it. Once the script is
  622. attached, assign again the :ref:`NodePath <class_NodePath>` to the ``Turret`` node.
  623. ______
  624. The last thing we need to do is add a way for the player to be hurt. Since all the bullets use the ``bullet_hit`` function, we need to add that function to the player.
  625. Open ``Player.gd`` and add the following:
  626. ::
  627. func bullet_hit(damage, bullet_hit_pos):
  628. health -= damage
  629. With all that done, you should have fully operational turrets! Go place a few in one/both/all of the scenes and give them a try!
  630. Final notes
  631. -----------
  632. .. image:: img/PartFiveFinished.png
  633. Now you can pick up :ref:`RigidBody <class_RigidBody>` nodes and throw grenades. We now also have turrets to fire at the player.
  634. In :ref:`doc_fps_tutorial_part_six`, we're going to add a main menu and a pause menu,
  635. add a respawn system for the player, and change/move the sound system so we can use it from any script.
  636. .. warning:: If you ever get lost, be sure to read over the code again!
  637. You can download the finished project for this part here: :download:`Godot_FPS_Part_5.zip <files/Godot_FPS_Part_5.zip>`