03.player_movement_code.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. .. _doc_first_3d_game_player_movement:
  2. Moving the player with code
  3. ===========================
  4. It's time to code! We're going to use the input actions we created in the last
  5. part to move the character.
  6. Right-click the *Player* node and select *Attach Script* to add a new script to
  7. it. In the popup, set the *Template* to *Empty* before pressing the *Create*
  8. button.
  9. |image0|
  10. Let's start with the class's properties. We're going to define a movement speed,
  11. a fall acceleration representing gravity, and a velocity we'll use to move the
  12. character.
  13. .. tabs::
  14. .. code-tab:: gdscript GDScript
  15. extends KinematicBody
  16. # How fast the player moves in meters per second.
  17. export var speed = 14
  18. # The downward acceleration when in the air, in meters per second squared.
  19. export var fall_acceleration = 75
  20. var velocity = Vector3.ZERO
  21. .. code-tab:: csharp
  22. public class Player : KinematicBody
  23. {
  24. // Don't forget to rebuild the project so the editor knows about the new export variable.
  25. // How fast the player moves in meters per second.
  26. [Export]
  27. public int Speed = 14;
  28. // The downward acceleration when in the air, in meters per second squared.
  29. [Export]
  30. public int FallAcceleration = 75;
  31. private Vector3 _velocity = Vector3.Zero;
  32. }
  33. These are common properties for a moving body. The ``velocity`` is a 3D vector
  34. combining a speed with a direction. Here, we define it as a property because
  35. we want to update and reuse its value across frames.
  36. .. note::
  37. The values are quite different from 2D code because distances are in meters.
  38. While in 2D, a thousand units (pixels) may only correspond to half of your
  39. screen's width, in 3D, it's a kilometer.
  40. Let's code the movement now. We start by calculating the input direction vector
  41. using the global ``Input`` object, in ``_physics_process()``.
  42. .. tabs::
  43. .. code-tab:: gdscript GDScript
  44. func _physics_process(delta):
  45. # We create a local variable to store the input direction.
  46. var direction = Vector3.ZERO
  47. # We check for each move input and update the direction accordingly.
  48. if Input.is_action_pressed("move_right"):
  49. direction.x += 1
  50. if Input.is_action_pressed("move_left"):
  51. direction.x -= 1
  52. if Input.is_action_pressed("move_back"):
  53. # Notice how we are working with the vector's x and z axes.
  54. # In 3D, the XZ plane is the ground plane.
  55. direction.z += 1
  56. if Input.is_action_pressed("move_forward"):
  57. direction.z -= 1
  58. .. code-tab:: csharp
  59. public override void _PhysicsProcess(float delta)
  60. {
  61. // We create a local variable to store the input direction.
  62. var direction = Vector3.Zero;
  63. // We check for each move input and update the direction accordingly
  64. if (Input.IsActionPressed("move_right"))
  65. {
  66. direction.x += 1f;
  67. }
  68. if (Input.IsActionPressed("move_left"))
  69. {
  70. direction.x -= 1f;
  71. }
  72. if (Input.IsActionPressed("move_back"))
  73. {
  74. // Notice how we are working with the vector's x and z axes.
  75. // In 3D, the XZ plane is the ground plane.
  76. direction.z += 1f;
  77. }
  78. if (Input.IsActionPressed("move_forward"))
  79. {
  80. direction.z -= 1f;
  81. }
  82. }
  83. Here, we're going to make all calculations using the ``_physics_process()``
  84. virtual function. Like ``_process()``, it allows you to update the node every
  85. frame, but it's designed specifically for physics-related code like moving a
  86. kinematic or rigid body.
  87. .. seealso::
  88. To learn more about the difference between ``_process()`` and
  89. ``_physics_process()``, see :ref:`doc_idle_and_physics_processing`.
  90. We start by initializing a ``direction`` variable to ``Vector3.ZERO``. Then, we
  91. check if the player is pressing one or more of the ``move_*`` inputs and update
  92. the vector's ``x`` and ``z`` components accordingly. These correspond to the
  93. ground plane's axes.
  94. These four conditions give us eight possibilities and eight possible directions.
  95. In case the player presses, say, both W and D simultaneously, the vector will
  96. have a length of about ``1.4``. But if they press a single key, it will have a
  97. length of ``1``. We want the vector's length to be consistent. To do so, we can
  98. call its ``normalize()`` method.
  99. .. tabs::
  100. .. code-tab:: gdscript GDScript
  101. #func _physics_process(delta):
  102. #...
  103. if direction != Vector3.ZERO:
  104. direction = direction.normalized()
  105. $Pivot.look_at(translation + direction, Vector3.UP)
  106. .. code-tab:: csharp
  107. public override void _PhysicsProcess(float delta)
  108. {
  109. // ...
  110. if (direction != Vector3.Zero)
  111. {
  112. direction = direction.Normalized();
  113. GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
  114. }
  115. }
  116. Here, we only normalize the vector if the direction has a length greater than
  117. zero, which means the player is pressing a direction key.
  118. In this case, we also get the *Pivot* node and call its ``look_at()`` method.
  119. This method takes a position in space to look at in global coordinates and the
  120. up direction. In this case, we can use the ``Vector3.UP`` constant.
  121. .. note::
  122. A node's local coordinates, like ``translation``, are relative to their
  123. parent. Global coordinates are relative to the world's main axes you can see
  124. in the viewport instead.
  125. In 3D, the property that contains a node's position is ``translation``. By
  126. adding the ``direction`` to it, we get a position to look at that's one meter
  127. away from the *Player*.
  128. Then, we update the velocity. We have to calculate the ground velocity and the
  129. fall speed separately. Be sure to go back one tab so the lines are inside the
  130. ``_physics_process()`` function but outside the condition we just wrote.
  131. .. tabs::
  132. .. code-tab:: gdscript GDScript
  133. func _physics_process(delta):
  134. #...
  135. if direction != Vector3.ZERO:
  136. #...
  137. # Ground velocity
  138. velocity.x = direction.x * speed
  139. velocity.z = direction.z * speed
  140. # Vertical velocity
  141. velocity.y -= fall_acceleration * delta
  142. # Moving the character
  143. velocity = move_and_slide(velocity, Vector3.UP)
  144. .. code-tab:: csharp
  145. public override void _PhysicsProcess(float delta)
  146. {
  147. // ...
  148. // Ground velocity
  149. _velocity.x = direction.x * Speed;
  150. _velocity.z = direction.z * Speed;
  151. // Vertical velocity
  152. _velocity.y -= FallAcceleration * delta;
  153. // Moving the character
  154. _velocity = MoveAndSlide(_velocity, Vector3.Up);
  155. }
  156. For the vertical velocity, we subtract the fall acceleration multiplied by the
  157. delta time every frame. Notice the use of the ``-=`` operator, which is a
  158. shorthand for ``variable = variable - ...``.
  159. This line of code will cause our character to fall in every frame. This may seem
  160. strange if it's already on the floor. But we have to do this for the character
  161. to collide with the ground every frame.
  162. The physics engine can only detect interactions with walls, the floor, or other
  163. bodies during a given frame if movement and collisions happen. We will use this
  164. property later to code the jump.
  165. On the last line, we call ``KinematicBody.move_and_slide()``. It's a powerful
  166. method of the ``KinematicBody`` class that allows you to move a character
  167. smoothly. If it hits a wall midway through a motion, the engine will try to
  168. smooth it out for you.
  169. The function takes two parameters: our velocity and the up direction. It moves
  170. the character and returns a leftover velocity after applying collisions. When
  171. hitting the floor or a wall, the function will reduce or reset the speed in that
  172. direction from you. In our case, storing the function's returned value prevents
  173. the character from accumulating vertical momentum, which could otherwise get so
  174. big the character would move through the ground slab after a while.
  175. And that's all the code you need to move the character on the floor.
  176. Here is the complete ``Player.gd`` code for reference.
  177. .. tabs::
  178. .. code-tab:: gdscript GDScript
  179. extends KinematicBody
  180. # How fast the player moves in meters per second.
  181. export var speed = 14
  182. # The downward acceleration when in the air, in meters per second squared.
  183. export var fall_acceleration = 75
  184. var velocity = Vector3.ZERO
  185. func _physics_process(delta):
  186. var direction = Vector3.ZERO
  187. if Input.is_action_pressed("move_right"):
  188. direction.x += 1
  189. if Input.is_action_pressed("move_left"):
  190. direction.x -= 1
  191. if Input.is_action_pressed("move_back"):
  192. direction.z += 1
  193. if Input.is_action_pressed("move_forward"):
  194. direction.z -= 1
  195. if direction != Vector3.ZERO:
  196. direction = direction.normalized()
  197. $Pivot.look_at(translation + direction, Vector3.UP)
  198. velocity.x = direction.x * speed
  199. velocity.z = direction.z * speed
  200. velocity.y -= fall_acceleration * delta
  201. velocity = move_and_slide(velocity, Vector3.UP)
  202. .. code-tab:: csharp
  203. public class Player : KinematicBody
  204. {
  205. // How fast the player moves in meters per second.
  206. [Export]
  207. public int Speed = 14;
  208. // The downward acceleration when in the air, in meters per second squared.
  209. [Export]
  210. public int FallAcceleration = 75;
  211. private Vector3 _velocity = Vector3.Zero;
  212. public override void _PhysicsProcess(float delta)
  213. {
  214. // We create a local variable to store the input direction.
  215. var direction = Vector3.Zero;
  216. // We check for each move input and update the direction accordingly
  217. if (Input.IsActionPressed("move_right"))
  218. {
  219. direction.x += 1f;
  220. }
  221. if (Input.IsActionPressed("move_left"))
  222. {
  223. direction.x -= 1f;
  224. }
  225. if (Input.IsActionPressed("move_back"))
  226. {
  227. // Notice how we are working with the vector's x and z axes.
  228. // In 3D, the XZ plane is the ground plane.
  229. direction.z += 1f;
  230. }
  231. if (Input.IsActionPressed("move_forward"))
  232. {
  233. direction.z -= 1f;
  234. }
  235. if (direction != Vector3.Zero)
  236. {
  237. direction = direction.Normalized();
  238. GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
  239. }
  240. // Ground velocity
  241. _velocity.x = direction.x * Speed;
  242. _velocity.z = direction.z * Speed;
  243. // Vertical velocity
  244. _velocity.y -= FallAcceleration * delta;
  245. // Moving the character
  246. _velocity = MoveAndSlide(_velocity, Vector3.Up);
  247. }
  248. }
  249. Testing our player's movement
  250. -----------------------------
  251. We're going to put our player in the *Main* scene to test it. To do so, we need
  252. to instantiate the player and then add a camera. Unlike in 2D, in 3D, you won't
  253. see anything if your viewport doesn't have a camera pointing at something.
  254. Save your *Player* scene and open the *Main* scene. You can click on the *Main*
  255. tab at the top of the editor to do so.
  256. |image1|
  257. If you closed the scene before, head to the *FileSystem* dock and double-click
  258. ``Main.tscn`` to re-open it.
  259. To instantiate the *Player*, right-click on the *Main* node and select *Instance
  260. Child Scene*.
  261. |image2|
  262. In the popup, double-click *Player.tscn*. The character should appear in the
  263. center of the viewport.
  264. Adding a camera
  265. ~~~~~~~~~~~~~~~
  266. Let's add the camera next. Like we did with our *Player*\ 's *Pivot*, we're
  267. going to create a basic rig. Right-click on the *Main* node again and select
  268. *Add Child Node* this time. Create a new *Position3D*, name it *CameraPivot*,
  269. and add a *Camera* node as a child of it. Your scene tree should look like this.
  270. |image3|
  271. Notice the *Preview* checkbox that appears in the top-left when you have the
  272. *Camera* selected. You can click it to preview the in-game camera projection.
  273. |image4|
  274. We're going to use the *Pivot* to rotate the camera as if it was on a crane.
  275. Let's first split the 3D view to be able to freely navigate the scene and see
  276. what the camera sees.
  277. In the toolbar right above the viewport, click on *View*, then *2 Viewports*.
  278. You can also press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS).
  279. |image5|
  280. On the bottom view, select the *Camera* and turn on camera preview by clicking
  281. the checkbox.
  282. |image6|
  283. In the top view, move the camera about ``19`` units on the Z axis (the blue
  284. one).
  285. |image7|
  286. Here's where the magic happens. Select the *CameraPivot* and rotate it ``45``
  287. degrees around the X axis (using the red circle). You'll see the camera move as
  288. if it was attached to a crane.
  289. |image8|
  290. You can run the scene by pressing :kbd:`F6` and press the arrow keys to move the
  291. character.
  292. |image9|
  293. We can see some empty space around the character due to the perspective
  294. projection. In this game, we're going to use an orthographic projection instead
  295. to better frame the gameplay area and make it easier for the player to read
  296. distances.
  297. Select the *Camera* again and in the *Inspector*, set the *Projection* to
  298. *Orthogonal* and the *Size* to ``19``. The character should now look flatter and
  299. the ground should fill the background.
  300. |image10|
  301. With that, we have both player movement and the view in place. Next, we will
  302. work on the monsters.
  303. .. |image0| image:: img/03.player_movement_code/01.attach_script_to_player.png
  304. .. |image1| image:: img/03.player_movement_code/02.clicking_main_tab.png
  305. .. |image2| image:: img/03.player_movement_code/03.instance_child_scene.png
  306. .. |image3| image:: img/03.player_movement_code/04.scene_tree_with_camera.png
  307. .. |image4| image:: img/03.player_movement_code/05.camera_preview_checkbox.png
  308. .. |image5| image:: img/03.player_movement_code/06.two_viewports.png
  309. .. |image6| image:: img/03.player_movement_code/07.camera_preview_checkbox.png
  310. .. |image7| image:: img/03.player_movement_code/08.camera_moved.png
  311. .. |image8| image:: img/03.player_movement_code/09.camera_rotated.png
  312. .. |image9| image:: img/03.player_movement_code/10.camera_perspective.png
  313. .. |image10| image:: img/03.player_movement_code/11.camera_orthographic.png