03.player_movement_code.rst 16 KB

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