part_two.rst 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. .. _doc_fps_tutorial_part_two:
  2. Part 2
  3. ======
  4. Part Overview
  5. -------------
  6. In this part we will be giving our player weapons to play with.
  7. .. image:: img/PartTwoFinished.png
  8. By the end of this part, you will have a player that can fire a pistol,
  9. rifle, and attack using a knife. The player will also now have animations with transitions,
  10. and the weapons can interact with objects in the environment.
  11. .. note:: You are assumed to have finished :ref:`doc_fps_tutorial_part_one` before moving on to this part of the tutorial.
  12. Let's get started!
  13. Making a system to handle animations
  14. ------------------------------------
  15. First we need a way to handle changing animations. Open up ``Player.tscn`` and select the :ref:`AnimationPlayer <class_AnimationPlayer>`
  16. Node (``Player``->``Rotation_helper``->``Model``->``AnimationPlayer``).
  17. Create a new script called ``AnimationPlayer_Manager.gd`` and attach that to the :ref:`AnimationPlayer <class_AnimationPlayer>`.
  18. Add the following code to ``AnimationPlayer_Manager.gd``:
  19. ::
  20. # Structure -> Animation name :[Connecting Animation states]
  21. var states = {
  22. "Idle_unarmed":["Knife_equip", "Pistol_equip", "Rifle_equip", "Idle_unarmed"],
  23. "Pistol_equip":["Pistol_idle"],
  24. "Pistol_fire":["Pistol_idle"],
  25. "Pistol_idle":["Pistol_fire", "Pistol_reload", "Pistol_unequip", "Pistol_idle"],
  26. "Pistol_reload":["Pistol_idle"],
  27. "Pistol_unequip":["Idle_unarmed"],
  28. "Rifle_equip":["Rifle_idle"],
  29. "Rifle_fire":["Rifle_idle"],
  30. "Rifle_idle":["Rifle_fire", "Rifle_reload", "Rifle_unequip", "Rifle_idle"],
  31. "Rifle_reload":["Rifle_idle"],
  32. "Rifle_unequip":["Idle_unarmed"],
  33. "Knife_equip":["Knife_idle"],
  34. "Knife_fire":["Knife_idle"],
  35. "Knife_idle":["Knife_fire", "Knife_unequip", "Knife_idle"],
  36. "Knife_unequip":["Idle_unarmed"],
  37. }
  38. var animation_speeds = {
  39. "Idle_unarmed":1,
  40. "Pistol_equip":1.4,
  41. "Pistol_fire":1.8,
  42. "Pistol_idle":1,
  43. "Pistol_reload":1,
  44. "Pistol_unequip":1.4,
  45. "Rifle_equip":2,
  46. "Rifle_fire":6,
  47. "Rifle_idle":1,
  48. "Rifle_reload":1.45,
  49. "Rifle_unequip":2,
  50. "Knife_equip":1,
  51. "Knife_fire":1.35,
  52. "Knife_idle":1,
  53. "Knife_unequip":1,
  54. }
  55. var current_state = null
  56. var callback_function = null
  57. func _ready():
  58. set_animation("Idle_unarmed")
  59. connect("animation_finished", self, "animation_ended")
  60. func set_animation(animation_name):
  61. if animation_name == current_state:
  62. print ("AnimationPlayer_Manager.gd -- WARNING: animation is already ", animation_name)
  63. return true
  64. if has_animation(animation_name) == true:
  65. if current_state != null:
  66. var possible_animations = states[current_state]
  67. if animation_name in possible_animations:
  68. current_state = animation_name
  69. play(animation_name, -1, animation_speeds[animation_name])
  70. return true
  71. else:
  72. print ("AnimationPlayer_Manager.gd -- WARNING: Cannot change to ", animation_name, " from ", current_state)
  73. return false
  74. else:
  75. current_state = animation_name
  76. play(animation_name, -1, animation_speeds[animation_name])
  77. return true
  78. return false
  79. func animation_ended(anim_name):
  80. # UNARMED transitions
  81. if current_state == "Idle_unarmed":
  82. pass
  83. # KNIFE transitions
  84. elif current_state == "Knife_equip":
  85. set_animation("Knife_idle")
  86. elif current_state == "Knife_idle":
  87. pass
  88. elif current_state == "Knife_fire":
  89. set_animation("Knife_idle")
  90. elif current_state == "Knife_unequip":
  91. set_animation("Idle_unarmed")
  92. # PISTOL transitions
  93. elif current_state == "Pistol_equip":
  94. set_animation("Pistol_idle")
  95. elif current_state == "Pistol_idle":
  96. pass
  97. elif current_state == "Pistol_fire":
  98. set_animation("Pistol_idle")
  99. elif current_state == "Pistol_unequip":
  100. set_animation("Idle_unarmed")
  101. elif current_state == "Pistol_reload":
  102. set_animation("Pistol_idle")
  103. # RIFLE transitions
  104. elif current_state == "Rifle_equip":
  105. set_animation("Rifle_idle")
  106. elif current_state == "Rifle_idle":
  107. pass;
  108. elif current_state == "Rifle_fire":
  109. set_animation("Rifle_idle")
  110. elif current_state == "Rifle_unequip":
  111. set_animation("Idle_unarmed")
  112. elif current_state == "Rifle_reload":
  113. set_animation("Rifle_idle")
  114. func animation_callback():
  115. if callback_function == null:
  116. print ("AnimationPlayer_Manager.gd -- WARNING: No callback function for the animation to call!")
  117. else:
  118. callback_function.call_func()
  119. Lets go over what this script is doing:
  120. _________
  121. Lets start with this script's global variables:
  122. - ``states``: A dictionary for holding our animation states. (Further explanation below)
  123. - ``animation_speeds``: A dictionary for holding all of the speeds we want to play our animations at.
  124. - ``current_state``: A variable for holding the name of the animation state we are currently in.
  125. - ``callback_function``: A variable for holding the callback function. (Further explanation below)
  126. If you are familiar with state machines, then you may have noticed that ``states`` is structured
  127. like a basic state machine. Here is roughly how ``states`` is set up:
  128. ``states`` is a dictionary with the key being the name of the current state, and the value being
  129. an array holding all of the states we can transition to. For example, if we are in currently in
  130. state ``Idle_unarmed``, we can only transition to ``Knife_equip``, ``Pistol_equip``, ``Rifle_equip``, and
  131. ``Idle_unarmed``.
  132. If we try to transition to a state that is not included in our possible transitions states,
  133. then we get a warning message and the animation does not change. We will also automatically
  134. transition from some states into others, as will be explained further below in ``animation_ended``
  135. .. note:: For the sake of keeping this tutorial simple we are not using a 'proper'
  136. state machine. If you are interested to know more about state machines,
  137. see the following articles:
  138. - (Python example) https://dev.to/karn/building-a-simple-state-machine-in-python
  139. - (C# example) https://www.codeproject.com/Articles/489136/UnderstandingplusandplusImplementingplusStateplusP
  140. - (Wiki article) https://en.wikipedia.org/wiki/Finite-state_machine
  141. In a future part of this tutorial series we may revise this script to include a proper state machine.
  142. ``animation_speeds`` is how fast each animation will play. Some of the animations are a little slow
  143. and in an effort to make everything smooth, we need to play them at faster speeds than some
  144. of the others.
  145. -- note:: Notice that all of the firing animations are faster than their normal speed. Remember this for later!
  146. ``current_state`` will hold the name of the animation state we are currently in.
  147. Finally, ``callback_function`` will be a :ref:`FuncRef <class_FuncRef>` passed in by our player for spawning bullets
  148. at the proper frame of animation. A :ref:`FuncRef <class_FuncRef>` allows us to pass in a function as an argument,
  149. effectively allowing us to call a function from another script, which is how we will use it later.
  150. _________
  151. Now lets look at ``_ready``. First we are setting our animation to ``Idle_unarmed``, using the ``set_animation`` function,
  152. so we for sure start in that animation. Next we connect the ``animation_finished`` signal to this script and assign
  153. it to call ``animation_ended``.
  154. _________
  155. Lets look at ``set_animation`` next.
  156. ``set_animation`` sets the animation to the that of the passed in
  157. animation state *if* we can transition to it. In other words, if the animation state we are currently in
  158. has the passed in animation state name in ``states``, then we will change to that animation.
  159. First we check if the passed in animation is the same as the animation state we are currently in.
  160. If it is, then we write a warning to the console and return ``true``.
  161. Next we see if :ref:`AnimationPlayer <class_AnimationPlayer>` has the passed in animation using ``has_animation``. If it does not, we return ``false``.
  162. Then we check if ``current_state`` is set or not. If ``current_state`` is *not* currently set, we
  163. set ``current_state`` to the passed in animation and tell :ref:`AnimationPlayer <class_AnimationPlayer>` to start playing the animation with
  164. a blend time of ``-1`` and at the speed set in ``animation_speeds`` and then we return ``true``.
  165. If we have a state in ``current_state``, then we get all of the possible states we can transition to.
  166. If the animation name is in the array of possible transitions, then we set ``current_state`` to the passed
  167. in animation, tell :ref:`AnimationPlayer <class_AnimationPlayer>` to play the animation with a blend time of ``-1`` at the speed set in ``animation_speeds``
  168. and then we return ``true``.
  169. _________
  170. Now lets look at ``animation_ended``.
  171. ``animation_ended`` is the function that will be called by :ref:`AnimationPlayer <class_AnimationPlayer>` when it's done playing a animation.
  172. For certain animation states, we may need to transition into another state when its finished. To handle this, we
  173. check for every possible animation state. If we need to, we transition into another state.
  174. .. warning:: If you are using your own animated models, make sure that none of the animations are set
  175. to loop. Looping animations do not send the ``animation_finished`` signal when they reach
  176. the end of the animation and are about to loop.
  177. .. note:: the transitions in ``animation_ended`` ideally would be part of the data in ``states``, but in
  178. an effort to make the tutorial easier to understand, we'll just hard code each state transition
  179. in ``animation_ended``.
  180. _________
  181. Finally we have ``animation_callback``. This function will be called by a function track in our animations.
  182. If we have a :ref:`FuncRef <class_FuncRef>` assigned to ``callback_function``, then we call that passed in function. If we do not
  183. have a :ref:`FuncRef <class_FuncRef>` assigned to ``callback_function``, we print out a warning to the console.
  184. .. tip:: Try running ``Testing_Area.tscn`` just to make sure there is no runtime issues. If the game runs but nothing
  185. seems to have changed, then everything is working correctly.
  186. Getting the animations ready
  187. ----------------------------
  188. Now that we have a working animation manager, we need to call it from our player script.
  189. Before that though, we need to set some animation callback tracks in our firing animations.
  190. Open up ``Player.tscn`` if you don't have it open and navigate to the :ref:`AnimationPlayer <class_AnimationPlayer>` node
  191. (``Player``->``Rotation_helper``->``Model``->``AnimationPlayer``).
  192. We need to attach a function track to three of our animations: The firing animation for the pistol, rifle, and knife.
  193. Let's start with the pistol. Click the animation drop down list and select "Pistol_fire".
  194. Now scroll down to the very bottom of the list of animation tracks. The final item in the list should read
  195. ``Armature/Skeleton:Left_UpperPointer``. Now at the bottom of the list, click the plus icon on the bottom
  196. bar of animation window, right plus right next to the loop button and the up arrow.
  197. .. image:: img/AnimationPlayerAddTrack.png
  198. This will bring up a window with three choices. We're wanting to add a function callback track, so click the
  199. option that reads "Add Call Func Track". This will open a window showing the entire node tree. Navigate to the
  200. :ref:`AnimationPlayer <class_AnimationPlayer>` node, select it, and press OK.
  201. .. image:: img/AnimationPlayerCallFuncTrack.png
  202. Now at the bottom of list of animation tracks you will have a green track that reads "AnimationPlayer".
  203. Now we need to add the point where we want to call our callback function. Scrub the timeline until you
  204. reach the point where the muzzle just starts to flash.
  205. .. note:: The timeline is the window where all of the points in our animation are stored. Each of the little
  206. points represents a point of animation data.
  207. Scrubbing the timeline means moving ourselves through the animation. So when we say "scrub the timeline
  208. until you reach a point", what we mean is move through the animation window until you reach the a point
  209. on the timeline.
  210. Also, the muzzle of a gun is the end point where the bullet comes out. The muzzle flash is the flash of
  211. light that escapes the muzzle when a bullet is fired. The muzzle is also sometimes referred to as the
  212. barrel of the gun.
  213. .. tip:: For finer control when scrubbing the timeline, press ``control`` and scroll forwards with the mouse wheel to zoom in.
  214. Scrolling backwards will zoom out.
  215. You can also change how the timeline scrubbing snaps by changing the value in ``Step (s)`` to a lower/higher value.
  216. Once you get to a point you like, press the little green plus symbol on the far right side of the
  217. ``AnimationPlayer`` track. This will place a little green point at the position you are currently
  218. at in the animation on your ``AnimationPlayer`` track.
  219. .. image:: img/AnimationPlayerAddPoint.png
  220. Now we have one more step before we are done with the pistol. Select the "enable editing of individual keys"
  221. button on the far right corner of the animation window. It looks like a pencil with a little point beside it.
  222. .. image:: img/AnimationPlayerEditPoints.png
  223. Once you've click that, a new window will open on the right side. Now click the green point on the ``AnimationPlayer``
  224. track. This will bring up the information associated with that point in the timeline. In the empty name field, enter
  225. "animation_callback" and press ``enter``.
  226. Now when we are playing this animation the callback function will be triggered at that specific point of the animation.
  227. .. warning:: Be sure to press the "enable editing of individual keys" button again to turn off the ability to edit individual keys
  228. so you cannot change one of the transform tracks by accident!
  229. _________
  230. Let's repeat the process for the rifle and knife firing animations!
  231. .. note:: Because the process is exactly the same as the pistol, the process is going to explained in a little less depth.
  232. Follow the steps in the above if you get lost! It is exactly the same, just on a different animation.
  233. Go to the "Rifle_fire" animation from the animation drop down. Add the function callback track once you reach the bottom of the
  234. animation track list by clicking the little plus icon at the bottom of the screen. Find the point where the muzzle just starts
  235. to flash and click the little green plus symbol to add a function callback point at that position on the track.
  236. Next, click the "enable editing of individual keys" button, the button with a plus at the bottom right side of the animation window.
  237. Select the newly created function callback point, put "animation_callback" into the name field and press ``enter``.
  238. Click the "enable editing of individual keys" button again to turn off individual key editing.
  239. so we cannot change one of the transform tracks by accident.
  240. Now we just need to apply the callback function track to the knife animation. Select the "Knife_fire" animation and scroll to the bottom of the
  241. animation tracks. Click the plus symbol at the bottom of the animation window and add a function callback track.
  242. Next find a point around the first third of the animation to place the animation callback function point at.
  243. .. note:: We will not actually be firing the knife, and the animation really is a stabbing animation rather than a firing one.
  244. For this tutorial we are just reusing the gun firing logic for our knife, so the animation has been named in a style that
  245. is consistent with the other animations.
  246. From there click the little green plus to add a function callback point at the current position. Then click the "enable editing of individual keys"
  247. button, the button with a plus at the bottom right side of the animation window.
  248. Select the newly created function callback point, put "animation_callback" into the name field and press ``enter``.
  249. Click the "enable editing of individual keys" button again to turn off individual key editing.
  250. so we cannot change one of the transform tracks by accident.
  251. .. tip:: Be sure to save your work!
  252. With that done, we are almost ready to start adding the ability to fire to our player script! We just need to setup one last scene:
  253. The scene for our bullet object.
  254. Creating the bullet scene
  255. -------------------------
  256. There are several ways to handle a gun's bullets in video games. In this tutorial series,
  257. we will be exploring two of the more common ways: Objects, and raycasts.
  258. _________
  259. One of the two ways is using a bullet object. This will be a object that travels through the world and handles
  260. its own collision code. This method we create/spawn a bullet object in the direction our gun is facing, and then
  261. it sends itself forward.
  262. There are several advantages to this method. The first being we do not have to store the bullets in our player. We can simply create the bullet
  263. and then move on, and the bullet itself with handle checking for collisions, sending the proper signal(s) to the object it collides with, and destroying itself.
  264. Another advantage is we can have more complex bullet movement. If we want to make the bullet fall ever so slightly as time goes on, we can make the bullet
  265. controlling script slowly push the bullet towards the ground. Using a object also makes the bullet take time to reach its target, it doesn't just instantly
  266. hit whatever its pointed at. This feels more realistic because nothing in real life really moves instantly from one point to another.
  267. One of the huge disadvantages performance. While having each bullet calculate their own paths and handle their own collision allows for a lot of flexibility,
  268. it comes at the cost of performance. With this method we are calculating every bullet's movement every step, and while this may not be a problem for a few dozen
  269. bullets, it can become a huge problem when you potentially have several hundred bullets.
  270. Despite the performance hit, many first person shooters include some form of object bullets. Rocket launchers are a prime example because in many
  271. first person shooters, Rockets do not just instantly explode at their target position. You can also find bullets as object many times with grenades
  272. because they generally bounce around the world before exploding.
  273. .. note:: While I cannot say for sure this is the case, these games *probably* use bullet objects in some form or another:
  274. (These are entirely from my observations. **They may be entirely wrong**. I have never worked on **any** of the following games)
  275. - Halo (Rocket launchers, fragment grenades, sniper rifles, brute shot, and more)
  276. - Destiny (Rocket launchers, grenades, fusion rifles, sniper rifles, super moves, and more)
  277. - Call of Duty (Rocket launchers, grenades, ballistic knives, crossbows, and more)
  278. - Battlefield (Rocket launchers, grenades, claymores, mortars, and more)
  279. Another disadvantage with bullet objects is networking. Bullet objects have to sync the positions (at least) with however many clients are connected
  280. to the server.
  281. While we are not implementing any form of networking (as that would be it's own entire tutorial series), it is a consideration
  282. to keep in mind when creating your first person shooter, especially if you plan on adding some form of networking in the future.
  283. _________
  284. The other way of handling bullet collisions we will be looking at, is raycasting.
  285. This method is extremely common in guns that have fast moving bullets that rarely change trajectory change over time.
  286. Instead of creating a bullet object and sending it through space, we instead send a ray starting from the barrel/muzzle of the gun forwards.
  287. We set the raycast's origin to the starting position of the bullet, and based on the length we can adjust how far the bullet 'travels' through space.
  288. .. note:: While I cannot say for sure this is the case, these games *probably* use raycasts in some form or another:
  289. (These are entirely from my observations. **They may be entirely wrong**. I have never worked on **any** of the following games)
  290. - Halo (Assault rifles, DMRs, battle rifles, covenant carbine, spartan laser, and more)
  291. - Destiny (Auto rifles, pulse rifles, scout rifles, hand cannons, machine guns, and more)
  292. - Call of Duty (Assault rifles, light machine guns, sub machine guns, pistols, and more)
  293. - Battlefield (Assault rifles, SMGs, carbines, pistols, and more)
  294. One huge advantage for this method is it's really light on performance.
  295. Sending a couple hundred rays through space is *way* easier for the computer to calculate than sending a couple hundred
  296. bullet objects.
  297. Another advantage is we can instantly know if we've hit something or not exactly when we call for it. For networking this is important because we do not need
  298. to sync the bullet movements over the Internet, we just need to send whether or not the raycast hit.
  299. Raycasting does have some disadvantages though. One major disadvantage is we cannot easily cast a ray in anything but a linear line.
  300. This means we can only fire in a straight line for however long our ray length is. You can create the illusion of bullet movement by casting
  301. multiple rays at different positions, but not only is this hard to implement in code, it is also is heavier on performance.
  302. Another disadvantage is we cannot see the bullet. With bullet objects we can actually see the bullet travel through space if we attach a mesh
  303. to it, but because raycasts happen instantly, we do not really have a decent way of showing the bullets. You could draw a line from the origin of the
  304. raycast to the point where the raycast collided, and that is one popular way of showing raycasts. Another way is simply not drawing the raycast
  305. at all, because theoretically the bullets move so fast our eyes could not see it anyway.
  306. _________
  307. Lets get the bullet object setup. This is what our pistol will create when the "Pistol_fire" animation callback function is called.
  308. Open up ``Bullet_Scene.tscn``. The scene contains :ref:`Spatial <class_Spatial>` node called bullet, with a :ref:`MeshInstance <class_MeshInstance>`
  309. and an :ref:`Area <class_Area>` with a :ref:`CollisionShape <class_CollisionShape>` childed to it.
  310. Create a new script called ``Bullet_script.gd`` and attach it to the ``Bullet`` :ref:`Spatial <class_Spatial>`.
  311. We are going to move the entire bullet object at the root (``Bullet``). We will be using the :ref:`Area <class_Area>` to check whether or not we've collided with something
  312. .. note:: Why are we using a :ref:`Area <class_Area>` and not a :ref:`RigidBody <class_RigidBody>`? The mean reason we're not using a :ref:`RigidBody <class_RigidBody>`
  313. is because we do not want the bullet to interact with other :ref:`RigidBody <class_RigidBody>` nodes.
  314. By using an :ref:`Area <class_Area>` we are assuring that none of the other :ref:`RigidBody <class_RigidBody>` nodes, including other bullets, will be effected.
  315. Another reason is simply because it is easier to detect collisions with a :ref:`Area <class_Area>`!
  316. Here's the script that will control our bullet:
  317. ::
  318. extends Spatial
  319. const BULLET_SPEED = 80
  320. const BULLET_DAMAGE = 15
  321. const KILL_TIMER = 4
  322. var timer = 0
  323. var hit_something = false
  324. func _ready():
  325. get_node("Area").connect("body_entered", self, "collided")
  326. set_physics_process(true)
  327. func _physics_process(delta):
  328. var forward_dir = global_transform.basis.z.normalized()
  329. global_translate(forward_dir * BULLET_SPEED * delta)
  330. timer += delta;
  331. if timer >= KILL_TIMER:
  332. queue_free()
  333. func collided(body):
  334. if hit_something == false:
  335. if body.has_method("bullet_hit"):
  336. body.bullet_hit(BULLET_DAMAGE, self.global_transform.origin)
  337. hit_something = true
  338. queue_free()
  339. Lets go through the script:
  340. _________
  341. First we define a few global variables:
  342. - ``BULLET_SPEED``: The speed the bullet travels at.
  343. - ``BULLET_DAMAGE``: The damage the bullet will cause to whatever it collides with.
  344. - ``KILL_TIMER``: How long the bullet can last without hitting anything.
  345. - ``timer``: A float for tracking how long we've been alive.
  346. - ``hit_something``: A boolean for tracking whether or not we've hit something.
  347. With the exception of ``timer`` and ``hit_something``, all of these variables
  348. change how the bullet interacts with the world.
  349. .. note:: The reason we are using a kill timer is so we do not have a case where we
  350. get a bullet traveling forever. By using a kill timer, we can assure that
  351. no bullets will just travel forever and consume resources.
  352. _________
  353. In ``_ready`` we set the area's ``body_entered`` signal to ourself so that it calls
  354. the ``collided`` function. Then we set ``_physics_process`` to ``true``.
  355. _________
  356. ``_physics_process`` gets the bullet's local ``Z`` axis. If you look in at the scene
  357. in local mode, you will find that the bullet faces the positive local ``Z`` axis.
  358. Next we translate the entire bullet by that forward direction, multiplying in our speed and delta time.
  359. After that we add delta time to our timer and check if the timer has as long or longer
  360. than our ``KILL_TIME`` constant. If it has, we use ``queue_free`` to free ourselves.
  361. _________
  362. In ``collided`` we check if we've hit something yet or not.
  363. Remember that ``collided`` is
  364. only called when a body has entered the :ref:`Area <class_Area>` node. If we have not already collided with
  365. something, we the proceed to check if the body we've collided with has a function/method
  366. called ``bullet_hit``. If it does, we call it and pass in our damage and our position.
  367. .. note:: in ``collided``, the passed in body can be a :ref:`StaticBody <class_StaticBody>`,
  368. :ref:`RigidBody <class_RigidBody>`, or :ref:`KinematicBody <class_KinematicBody>`
  369. Then we set ``hit_something`` to ``true`` because regardless of whether or not the body
  370. the bullet collided with has the ``bullet_hit`` function/method, it have hit something.
  371. Then we free the bullet using ``queue_free``.
  372. .. tip:: You may be wondering why we even have a ``hit_something`` variable if we
  373. free the bullet using ``queue_free`` as soon as it hits something.
  374. The reason we need to track whether we've hit something or not is because ``queue_free``
  375. does not immediately free the node, so the bullet could collide with another body
  376. before Godot has a chance to free it. By tracking if the bullet has hit something
  377. we can make sure that the bullet will only hit one object.
  378. _________
  379. Before we start programming the player again, let's take a quick look at ``Player.tscn``.
  380. Open up ``Player.tscn`` again.
  381. Expand ``Rotation_helper`` and notice how it has two nodes: ``Gun_fire_points`` and
  382. ``Gun_aim_point``.
  383. ``Gun_aim_point`` is the point that the bullets will be aiming at. Notice how it
  384. is lined up with the center of the screen and pulled a distance forward on the Z
  385. axis. ``Gun_aim_point`` will serve as the point where the bullets will for sure collied
  386. with as it goes along.
  387. .. note:: There is a invisible mesh instance for debugging purposes. The mesh is
  388. a small sphere that visually shows where the bullets will be aiming at.
  389. Open up ``Gun_fire_points`` and you'll find three more :ref:`Spatial <class_Spatial>` nodes, one for each
  390. weapon.
  391. Open up ``Rifle_point`` and you'll find a :ref:`Raycast <class_Raycast>` node. This is where
  392. we will be sending the raycasts for our rilfe's bullets.
  393. The length of the raycast will dictate how far our the bullets will travel.
  394. We are using a :ref:`Raycast <class_Raycast>` node to handle the rifle's bullet because
  395. we want to fire lots of bullets quickly. If we use bullet objects, it is quite possible
  396. we could run into performance issues on older machines.
  397. .. note:: If you are wondering where the positions of the points came from, they
  398. are the rough positions of the ends of each weapon. You can see this by
  399. going to ``AnimationPlayer``, selecting one of the firing animations
  400. and scrubbing through the timeline. The point for each weapon should mostly line
  401. up with the end of each weapon.
  402. Open up ``Knife_point`` and you'll find a :ref:`Area <class_Area>` node. We are using a :ref:`Area <class_Area>` for the knife
  403. because we only care for all of the bodies close to us, and because our knife does
  404. not fire into space. If we were making a throwing knife, we would likely spawn a bullet
  405. object that looks like a knife.
  406. Finally, we have ``Pistol point``. This is the point where we will be creating/instancing
  407. our bullet objects. We do not need any additional nodes here, as the bullet handles all
  408. of its own collision detection.
  409. Now that we've seen how we will handle our other weapons, and where we will spawn the bullets,
  410. let's start working on making them work.
  411. .. note:: You can also look at the HUD nodes if you want. There is nothing fancy there and other
  412. than using a single :ref:`Label <class_Label>`, we will not be touching any of those nodes.
  413. Check :ref:`doc_design_interfaces_with_the_control_nodes` for a tutorial on using GUI nodes.
  414. The GUI provided in this tutorial is *very* basic. Maybe in a later part we will
  415. revise the GUI, but for now we are going to just use this GUI as it will serve our needs for now.
  416. Making the weapons work
  417. -----------------------
  418. Lets start making the weapons work in ``Player.gd``.
  419. First lets start by adding some global variables we'll need for the weapons:
  420. ::
  421. # Place before _ready
  422. var animation_manager;
  423. var current_gun = "UNARMED"
  424. var changing_gun = false
  425. var bullet_scene = preload("Bullet_Scene.tscn")
  426. var health = 100
  427. const RIFLE_DAMAGE = 4
  428. const KNIFE_DAMAGE = 40
  429. var UI_status_label
  430. Let's go over what these new variables will do:
  431. - ``animation_manager``: This will hold the :ref:`AnimationPlayer <class_AnimationPlayer>` node and its script, which we wrote previously.
  432. - ``current_gun``: This is the name of the gun we are currently using. It has four possible values: ``UNARMED``, ``KNIFE``, ``PISTOL``, and ``RIFLE``.
  433. - ``changing_gun``: A boolean to track whether or not we are changing guns/weapons.
  434. - ``bullet_scene``: The bullet scene we worked on earlier, ``Bullet_Scene.tscn``. We need to load it here so we can create/spawn it when the pistol fires
  435. - ``health``: How much health our player has. In this part of the tutorial we will not really be using it.
  436. - ``RIFLE_DAMAGE``: How much damage a single rifle bullet causes.
  437. - ``KNIFE_DAMAGE``: How much damage a single knife stab/swipe causes.
  438. - ``UI_status_label``: A label to show how much health we have, and how much ammo we have both in our gun and in reserves.
  439. _________
  440. Next we need to add a few things in ``_ready``. Here's the new ``_ready`` function:
  441. ::
  442. func _ready():
  443. camera = get_node("Rotation_helper/Camera")
  444. rotation_helper = get_node("Rotation_helper")
  445. animation_manager = get_node("Rotation_helper/Model/AnimationPlayer")
  446. animation_manager.callback_function = funcref(self, "fire_bullet")
  447. set_physics_process(true)
  448. Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  449. set_process_input(true)
  450. # Make sure the bullet spawn point, the raycast, and the knife area are all aiming at the center of the screen
  451. var gun_aim_point_pos = get_node("Rotation_helper/Gun_aim_point").global_transform.origin
  452. get_node("Rotation_helper/Gun_fire_points/Pistol_point").look_at(gun_aim_point_pos, Vector3(0, 1, 0))
  453. get_node("Rotation_helper/Gun_fire_points/Rifle_point").look_at(gun_aim_point_pos, Vector3(0, 1, 0))
  454. get_node("Rotation_helper/Gun_fire_points/Knife_point").look_at(gun_aim_point_pos, Vector3(0, 1, 0))
  455. # Because we have the camera rotated by 180 degrees, we need to rotate the points around by 180
  456. # degrees on their local Y axis because otherwise the bullets will fire backwards
  457. get_node("Rotation_helper/Gun_fire_points/Pistol_point").rotate_object_local(Vector3(0, 1, 0), deg2rad(180))
  458. get_node("Rotation_helper/Gun_fire_points/Rifle_point").rotate_object_local(Vector3(0, 1, 0), deg2rad(180))
  459. get_node("Rotation_helper/Gun_fire_points/Knife_point").rotate_object_local(Vector3(0, 1, 0), deg2rad(180))
  460. UI_status_label = get_node("HUD/Panel/Gun_label")
  461. flashlight = get_node("Rotation_helper/Flashlight")
  462. Let's go over what's changed.
  463. First we get the :ref:`AnimationPlayer <class_AnimationPlayer>` node and assign it to our animation_manager variable. Then we set the callback function
  464. to a :ref:`FuncRef <class_FuncRef>` that will call the player's ``fire_bullet`` function. Right now we haven't written our fire_bullet function,
  465. but we'll get there soon.
  466. Then we get all of the weapon points and call each of their ``look_at``.
  467. This will make sure they all are facing the gun aim point, which is in the center of our camera at a certain distance back.
  468. Next we rotate all of those weapon points by ``180`` degrees on their ``Y`` axis. This is because our camera is pointing backwards.
  469. If we did not rotate all of these weapon points by ``180`` degrees, all of the weapons would fire backwards at ourselves.
  470. Finally, we get the UI :ref:`Label <class_Label>` from our HUD.
  471. _________
  472. Lets add a few things to ``_physics_process`` so we can fire our weapons. Here's the new code:
  473. ::
  474. func _physics_process(delta):
  475. var dir = Vector3()
  476. var cam_xform = camera.get_global_transform()
  477. if Input.is_action_pressed("movement_forward"):
  478. dir += -cam_xform.basis.z.normalized()
  479. if Input.is_action_pressed("movement_backward"):
  480. dir += cam_xform.basis.z.normalized()
  481. if Input.is_action_pressed("movement_left"):
  482. dir += -cam_xform.basis.x.normalized()
  483. if Input.is_action_pressed("movement_right"):
  484. dir += cam_xform.basis.x.normalized()
  485. if is_on_floor():
  486. if Input.is_action_just_pressed("movement_jump"):
  487. vel.y = JUMP_SPEED
  488. if Input.is_action_just_pressed("flashlight"):
  489. if flashlight.is_visible_in_tree():
  490. flashlight.hide()
  491. else:
  492. flashlight.show()
  493. if Input.is_action_pressed("movement_sprint"):
  494. is_sprinting = true;
  495. else:
  496. is_sprinting = false;
  497. dir.y = 0
  498. dir = dir.normalized()
  499. var grav = norm_grav
  500. vel.y += delta*grav
  501. var hvel = vel
  502. hvel.y = 0
  503. var target = dir
  504. if is_sprinting:
  505. target *= MAX_SPRINT_SPEED
  506. else:
  507. target *= MAX_SPEED
  508. var accel
  509. if dir.dot(hvel) > 0:
  510. if is_sprinting:
  511. accel = SPRINT_ACCEL
  512. else:
  513. accel = ACCEL
  514. else:
  515. accel = DEACCEL
  516. hvel = hvel.linear_interpolate(target, accel*delta)
  517. vel.x = hvel.x
  518. vel.z = hvel.z
  519. vel = move_and_slide(vel,Vector3(0,1,0), 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))
  520. if Input.is_action_just_pressed("ui_cancel"):
  521. if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
  522. Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  523. else:
  524. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  525. # NEW CODE
  526. if changing_gun == false:
  527. if Input.is_key_pressed(KEY_1):
  528. current_gun = "UNARMED"
  529. changing_gun = true
  530. elif Input.is_key_pressed(KEY_2):
  531. current_gun = "KNIFE"
  532. changing_gun = true
  533. elif Input.is_key_pressed(KEY_3):
  534. current_gun = "PISTOL"
  535. changing_gun = true
  536. elif Input.is_key_pressed(KEY_4):
  537. current_gun = "RIFLE"
  538. changing_gun = true
  539. if changing_gun == true:
  540. if current_gun != "PISTOL":
  541. if animation_manager.current_state == "Pistol_idle":
  542. animation_manager.set_animation("Pistol_unequip")
  543. if current_gun != "RIFLE":
  544. if animation_manager.current_state == "Rifle_idle":
  545. animation_manager.set_animation("Rifle_unequip")
  546. if current_gun != "KNIFE":
  547. if animation_manager.current_state == "Knife_idle":
  548. animation_manager.set_animation("Knife_unequip")
  549. if current_gun == "UNARMED":
  550. if animation_manager.current_state == "Idle_unarmed":
  551. changing_gun = false
  552. elif current_gun == "KNIFE":
  553. if animation_manager.current_state == "Knife_idle":
  554. changing_gun = false
  555. if animation_manager.current_state == "Idle_unarmed":
  556. animation_manager.set_animation("Knife_equip")
  557. elif current_gun == "PISTOL":
  558. if animation_manager.current_state == "Pistol_idle":
  559. changing_gun = false
  560. if animation_manager.current_state == "Idle_unarmed":
  561. animation_manager.set_animation("Pistol_equip")
  562. elif current_gun == "RIFLE":
  563. if animation_manager.current_state == "Rifle_idle":
  564. changing_gun = false
  565. if animation_manager.current_state == "Idle_unarmed":
  566. animation_manager.set_animation("Rifle_equip")
  567. # Firing the weapons
  568. if Input.is_action_pressed("fire"):
  569. if current_gun == "PISTOL":
  570. if animation_manager.current_state == "Pistol_idle":
  571. animation_manager.set_animation("Pistol_fire")
  572. elif current_gun == "RIFLE":
  573. if animation_manager.current_state == "Rifle_idle":
  574. animation_manager.set_animation("Rifle_fire")
  575. elif current_gun == "KNIFE":
  576. if animation_manager.current_state == "Knife_idle":
  577. animation_manager.set_animation("Knife_fire")
  578. # HUD (UI)
  579. UI_status_label.text = "HEALTH: " + str(health)
  580. Lets go over it one chunk at a time:
  581. _________
  582. First we have a if check to see if ``changing_gun`` is equal to ``false``. If it is, we
  583. then check to see if the number keys ``1`` through ``4`` are pressed. If one of the keys
  584. are pressed, we set current gun to the name of each weapon assigned to each key and set
  585. ``changing_gun`` to ``true``.
  586. Then we check if ``changing_gun`` is ``true``. If it is ``true``, we then go through a series of checks.
  587. The first set of checks is checking if we are in a idle animation that is not the weapon/gun we are trying
  588. to change to, as then we'd be stuck in a loop.
  589. Then we check if we are in an unarmed state. If we are and the newly selected 'weapon'
  590. is ``UNARMED``, then we set ``changing_gun`` to ``false``.
  591. If we are trying to change to any of the other weapons, we first check if we are in the
  592. desired weapon's idle state. If we are, then we've successfully changed weapons and set
  593. ``changing_gun`` to false.
  594. If we are not in the desired weapon's idle state, we then check if we are in the idle unarmed state.
  595. This is because all unequip animations transition to idle unarmed, and because we can transition to
  596. any equip animation from idle unarmed.
  597. If we are in the idle unarmed state, we set the animation to the equip animation for the
  598. desired weapon. Once the equip animation is finished, it will change to the idle state for that
  599. weapon, which will pass the ``if`` check above.
  600. _________
  601. For firing the weapons we first check if the ``fire`` action is pressed or not.
  602. If the fire action is pressed, we then check which weapon we are using.
  603. If we are in a weapon's idle state, we then call set the animation to the weapon's fire animation.
  604. _________
  605. Now, we just need to add one more function to the player, and then the player is ready to start shooting!
  606. We just need to add ``fire_bullet``, which will be called when by the :ref:`AnimationPlayer <class_AnimationPlayer>` at those
  607. points we set earlier in the :ref:`AnimationPlayer <class_AnimationPlayer>` function track:
  608. ::
  609. func fire_bullet():
  610. if changing_gun == true:
  611. return
  612. # Pistol bullet handling: Spawn a bullet object!
  613. if current_gun == "PISTOL":
  614. var clone = bullet_scene.instance()
  615. var scene_root = get_tree().root.get_children()[0]
  616. scene_root.add_child(clone)
  617. clone.global_transform = get_node("Rotation_helper/Gun_fire_points/Pistol_point").global_transform
  618. # The bullet is a little too small (by default), so let's make it bigger!
  619. clone.scale = Vector3(4, 4, 4)
  620. # Rifle bullet handeling: Send a raycast!
  621. elif current_gun == "RIFLE":
  622. var ray = get_node("Rotation_helper/Gun_fire_points/Rifle_point/RayCast")
  623. ray.force_raycast_update()
  624. if ray.is_colliding():
  625. var body = ray.get_collider()
  626. if body.has_method("bullet_hit"):
  627. body.bullet_hit(RIFLE_DAMAGE, ray.get_collision_point())
  628. # Knife bullet(?) handeling: Use an area!
  629. elif current_gun == "KNIFE":
  630. var area = get_node("Rotation_helper/Gun_fire_points/Knife_point/Area")
  631. var bodies = area.get_overlapping_bodies()
  632. for body in bodies:
  633. if body.has_method("bullet_hit"):
  634. body.bullet_hit(KNIFE_DAMAGE, area.global_transform.origin)
  635. Lets go over what this function is doing:
  636. _________
  637. First we check if we are changing weapons or not. If we are changing weapons, we do not want shoot so we just ``return``.
  638. .. tip:: Calling ``return`` stops the rest of the function from being called. In this case, we are not returning a variable
  639. because we are only interested in not running the rest of the code, and because we are not looking for a returned
  640. variable either when we call this function.
  641. Next we check which weapon we are using.
  642. If we are using a pistol, we first create a ``Bullet_Scene.tscn`` instance and assign it to
  643. a variable named ``clone``. Then we get the root node in the :ref:`SceneTree <class_SceneTree>`, which happens to be a :ref:`Viewport <class_Viewport>`.
  644. We then get the first child of the :ref:`Viewport <class_Viewport>` and assign it to the ``scene_root`` variable.
  645. We then add our newly instanced/created bullet as a child of ``scene_root``.
  646. .. warning:: As mentioned later below in the section on adding sounds, this method makes a assumption. This will be explained later
  647. in the section on adding sounds in :ref:`doc_fps_tutorial_part_three`
  648. Next we set the global :ref:`Transform <class_Transform>` of the bullet to that of the pistol bullet spawn point we
  649. talked about earlier.
  650. Finally, we set the scale a little bigger because the bullet normally is too small to see.
  651. _______
  652. For the rifle, we first get the :ref:`Raycast <class_Raycast>` node and assign it to a variable called ``ray``.
  653. Then we call :ref:`Raycast <class_Raycast>`'s ``force_raycast_update`` function.
  654. ``force_raycast_update`` sends the :ref:`Raycast <class_Raycast>` out and collects the collision data as soon as we call it,
  655. meaning we get frame perfect collision data and we do not need to worry about performance issues by having the
  656. :ref:`Raycast <class_Raycast>` enabled all the time.
  657. Next we check if the :ref:`Raycast <class_Raycast>` collided with anything. If it has, we then get the collision body
  658. it collided with. If the body has the ``bullet_hit`` method/function, we then call it and pass
  659. in ``RIFLE_DAMAGE`` and the position where the :ref:`Raycast <class_Raycast>` collided.
  660. .. tip:: Remember how we mentioned the speed of the animations for firing was faster than
  661. the other animations? By changing the firing animation speeds, you can change how
  662. fast the weapon fires bullets!
  663. _______
  664. For the knife we first get the :ref:`Area <class_Area>` node and assign it to a variable named ``area``.
  665. Then we get all of the collision bodies inside the :ref:`Area <class_Area>`. We loop through each one
  666. and check if they have the ``bullet_hit`` method/function. If they do, we call it and pass
  667. in ``KNIFE_DAMAGE`` and the global position of :ref:`Area <class_Area>`.
  668. .. note:: While we could attempt to calculate a rough location for where the knife hit, we
  669. do not bother because using the area's position works well enough and the extra time
  670. needed to calculate a rough position for each body is not really worth the effort.
  671. _______
  672. Before we are ready to test our new weapons, we still have just a little bit of work to do.
  673. Creating some test subjects
  674. ---------------------------
  675. Create a new script by going to the scripting window, clicking "file", and selecting new.
  676. Name this script "RigidBody_hit_test" and make sure it extends :ref:`RigidBody <class_RigidBody>`.
  677. Now we just need to add this code:
  678. ::
  679. extends RigidBody
  680. func _ready():
  681. pass
  682. func bullet_hit(damage, bullet_hit_pos):
  683. var direction_vect = self.global_transform.origin - bullet_hit_pos
  684. direction_vect = direction_vect.normalized()
  685. self.apply_impulse(bullet_hit_pos, direction_vect * damage)
  686. Lets go over how ``bullet_hit`` works:
  687. First we get the direction from the bullet pointing towards our global :ref:`Transform <class_Transform>`.
  688. We do this by subtracting the bullet's hit position from the :ref:`RigidBody <class_RigidBody>`'s position.
  689. This results in a :ref:`Vector3 <class_Vector3>` that we can use to tell the direction the bullet collided into the
  690. :ref:`RigidBody <class_RigidBody>` at.
  691. We then normalize it so we do not get crazy results from collisions on the extremes
  692. of the collision shape attached to the :ref:`RigidBody <class_RigidBody>`. Without normalizing shots farther
  693. away from the center of the :ref:`RigidBody <class_RigidBody>` would cause a more noticeable reaction than
  694. those closer to the center.
  695. Finally, we apply an impulse at the passed in bullet collision position. With the force
  696. being the directional vector times the damage the bullet is supposed to cause. This makes
  697. the :ref:`RigidBody <class_RigidBody>` seem to move in response to the bullet colliding into it.
  698. _______
  699. Now we just need to attach this script to all of the :ref:`RigidBody <class_RigidBody>` nodes we want to effect.
  700. Open up ``Testing_Area.tscn`` and select all of the cubes parented to the ``Cubes`` node.
  701. .. tip:: If you select the top cube, and then hold down ``shift`` and select the last cube, Godot will
  702. select all of the cubes in between!
  703. Once you have all of the cubes selected, scroll down in the inspector until you get to the
  704. the "scripts" section. Click the drop down and select "Load". Open your newly created ``RigidBody_hit_test.gd`` script.
  705. With that done, go give your guns a whirl! You should now be able to fire as many bullets as you want on the cubes and
  706. they will move in response to the bullets colliding into them.
  707. In :ref:`doc_fps_tutorial_part_three`, we will add ammo to the guns, as well as some sounds!