Browse Source

Update "Your first 3D game" tutorial for Godot 4 (#6243)

* FULLY REMADE FOR 4.0

Co-authored-by: Johannes Loepelmann <[email protected]>
TheYellowArchitect 2 years ago
parent
commit
41252827ca
100 changed files with 733 additions and 642 deletions
  1. 48 55
      getting_started/first_3d_game/01.game_setup.rst
  2. 47 45
      getting_started/first_3d_game/02.player_input.rst
  3. 67 55
      getting_started/first_3d_game/03.player_movement_code.rst
  4. 81 81
      getting_started/first_3d_game/04.mob_scene.rst
  5. 62 49
      getting_started/first_3d_game/05.spawning_mobs.rst
  6. 44 39
      getting_started/first_3d_game/06.jump_and_squash.rst
  7. 155 129
      getting_started/first_3d_game/07.killing_player.rst
  8. 79 72
      getting_started/first_3d_game/08.score_and_replay.rst
  9. 150 117
      getting_started/first_3d_game/09.adding_animations.rst
  10. BIN
      getting_started/first_3d_game/img/01.game_setup/08.create_box_shape.png
  11. BIN
      getting_started/first_3d_game/img/01.game_setup/08.create_box_shape3D.jpg
  12. BIN
      getting_started/first_3d_game/img/01.game_setup/09.box_extents.png
  13. BIN
      getting_started/first_3d_game/img/01.game_setup/09.box_extents.webp
  14. BIN
      getting_started/first_3d_game/img/01.game_setup/10.mesh_instance.png
  15. BIN
      getting_started/first_3d_game/img/01.game_setup/10.mesh_instance3d.png
  16. BIN
      getting_started/first_3d_game/img/01.game_setup/11.box_mesh.webp
  17. BIN
      getting_started/first_3d_game/img/01.game_setup/11.cube_mesh.png
  18. BIN
      getting_started/first_3d_game/img/01.game_setup/16.turn_on_shadows.png
  19. BIN
      getting_started/first_3d_game/img/01.game_setup/16.turn_on_shadows.webp
  20. BIN
      getting_started/first_3d_game/img/01.game_setup/17.project_with_light.png
  21. BIN
      getting_started/first_3d_game/img/01.game_setup/17.project_with_light.webp
  22. BIN
      getting_started/first_3d_game/img/01.game_setup/adding_static_body3D.webp
  23. BIN
      getting_started/first_3d_game/img/01.game_setup/create_directional_light3d.webp
  24. BIN
      getting_started/first_3d_game/img/01.game_setup/ground_down1meter.webp
  25. BIN
      getting_started/first_3d_game/img/02.player_input/02.instantiating_the_model.png
  26. BIN
      getting_started/first_3d_game/img/02.player_input/02.instantiating_the_model.webp
  27. BIN
      getting_started/first_3d_game/img/02.player_input/06.toggling_visibility.png
  28. BIN
      getting_started/first_3d_game/img/02.player_input/06.toggling_visibility.webp
  29. BIN
      getting_started/first_3d_game/img/02.player_input/08.create_key_action.png
  30. BIN
      getting_started/first_3d_game/img/02.player_input/12.move_inputs_mapped.webp
  31. BIN
      getting_started/first_3d_game/img/02.player_input/13.joy_button_option.png
  32. BIN
      getting_started/first_3d_game/img/02.player_input/13.joy_button_option.webp
  33. BIN
      getting_started/first_3d_game/img/02.player_input/14.jump_input_action.webp
  34. BIN
      getting_started/first_3d_game/img/02.player_input/add_capsuleshape3d.webp
  35. BIN
      getting_started/first_3d_game/img/02.player_input/add_character_body3D.webp
  36. BIN
      getting_started/first_3d_game/img/02.player_input/adding_node3D.webp
  37. BIN
      getting_started/first_3d_game/img/02.player_input/joystick_axis_input.webp
  38. BIN
      getting_started/first_3d_game/img/02.player_input/left_inputmap.webp
  39. BIN
      getting_started/first_3d_game/img/02.player_input/left_joystick_select.webp
  40. BIN
      getting_started/first_3d_game/img/03.player_movement_code/01.attach_script_to_player.png
  41. BIN
      getting_started/first_3d_game/img/03.player_movement_code/01.attach_script_to_player.webp
  42. BIN
      getting_started/first_3d_game/img/03.player_movement_code/12.viewport_change.webp
  43. BIN
      getting_started/first_3d_game/img/03.player_movement_code/13.camera3d_values.webp
  44. BIN
      getting_started/first_3d_game/img/04.mob_scene/04.create_box_shape.png
  45. BIN
      getting_started/first_3d_game/img/04.mob_scene/10.node_dock.png
  46. BIN
      getting_started/first_3d_game/img/04.mob_scene/10.node_dock.webp
  47. BIN
      getting_started/first_3d_game/img/04.mob_scene/11.connect_signal.png
  48. BIN
      getting_started/first_3d_game/img/04.mob_scene/11.connect_signal.webp
  49. BIN
      getting_started/first_3d_game/img/04.mob_scene/drag_drop_mob.webp
  50. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/03.window_settings.png
  51. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/03.window_settings.webp
  52. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/14.multi_material_selection.webp
  53. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/14.spatial_material.png
  54. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/24.connect_timer_to_main.png
  55. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/24.connect_timer_to_main.webp
  56. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/albedo_section.webp
  57. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/standard_material.webp
  58. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/03.physics_layers.png
  59. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/03.physics_layers.webp
  60. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/04.default_physics_properties.png
  61. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/04.default_physics_properties.webp
  62. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/05.toggle_layer_and_mask.png
  63. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/05.toggle_layer_and_mask.webp
  64. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/07.player_physics_mask.webp
  65. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/08.mob_physics_mask.png
  66. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/08.mob_physics_mask.webp
  67. BIN
      getting_started/first_3d_game/img/07.killing_player/04.mob_detector_properties.png
  68. BIN
      getting_started/first_3d_game/img/07.killing_player/04.mob_detector_properties.webp
  69. BIN
      getting_started/first_3d_game/img/08.score_and_replay/02.score_custom_color.png
  70. BIN
      getting_started/first_3d_game/img/08.score_and_replay/02.score_custom_color.webp
  71. BIN
      getting_started/first_3d_game/img/08.score_and_replay/05.dynamic_font.png
  72. BIN
      getting_started/first_3d_game/img/08.score_and_replay/05.dynamic_font.webp
  73. BIN
      getting_started/first_3d_game/img/08.score_and_replay/06.font_data.png
  74. BIN
      getting_started/first_3d_game/img/08.score_and_replay/06.font_data.webp
  75. BIN
      getting_started/first_3d_game/img/08.score_and_replay/07.font_size.png
  76. BIN
      getting_started/first_3d_game/img/08.score_and_replay/07.font_size.webp
  77. BIN
      getting_started/first_3d_game/img/09.adding_animations/02.new_animation.png
  78. BIN
      getting_started/first_3d_game/img/09.adding_animations/02.new_animation.webp
  79. BIN
      getting_started/first_3d_game/img/09.adding_animations/06.animation_duration.png
  80. BIN
      getting_started/first_3d_game/img/09.adding_animations/06.animation_duration.webp
  81. BIN
      getting_started/first_3d_game/img/09.adding_animations/07.editable_timeline.png
  82. BIN
      getting_started/first_3d_game/img/09.adding_animations/07.editable_timeline.webp
  83. BIN
      getting_started/first_3d_game/img/09.adding_animations/08.zoom_slider.png
  84. BIN
      getting_started/first_3d_game/img/09.adding_animations/08.zoom_slider.webp
  85. BIN
      getting_started/first_3d_game/img/09.adding_animations/09.creating_first_keyframe.png
  86. BIN
      getting_started/first_3d_game/img/09.adding_animations/09.creating_first_keyframe.webp
  87. BIN
      getting_started/first_3d_game/img/09.adding_animations/10.initial_keys.png
  88. BIN
      getting_started/first_3d_game/img/09.adding_animations/10.initial_keys.webp
  89. BIN
      getting_started/first_3d_game/img/09.adding_animations/11.moving_keys.png
  90. BIN
      getting_started/first_3d_game/img/09.adding_animations/11.moving_keys.webp
  91. BIN
      getting_started/first_3d_game/img/09.adding_animations/12.second_keys_values.png
  92. BIN
      getting_started/first_3d_game/img/09.adding_animations/12.second_keys_values.webp
  93. BIN
      getting_started/first_3d_game/img/09.adding_animations/13.second_keys.png
  94. BIN
      getting_started/first_3d_game/img/09.adding_animations/13.second_keys.webp
  95. BIN
      getting_started/first_3d_game/img/09.adding_animations/15.box_select.png
  96. BIN
      getting_started/first_3d_game/img/09.adding_animations/15.box_select.webp
  97. BIN
      getting_started/first_3d_game/img/09.adding_animations/animation_final_keyframes.webp
  98. BIN
      getting_started/first_3d_game/img/09.adding_animations/curves.webp
  99. BIN
      getting_started/first_3d_game/img/09.adding_animations/second_keys_both.webp
  100. BIN
      getting_started/first_3d_game/img/09.adding_animations/timeline_05_click.webp

+ 48 - 55
getting_started/first_3d_game/01.game_setup.rst

@@ -14,23 +14,23 @@ the archive here: `Squash the Creeps assets
 Once you downloaded it, extract the .zip archive on your computer. Open the
 Once you downloaded it, extract the .zip archive on your computer. Open the
 Godot project manager and click the *Import* button.
 Godot project manager and click the *Import* button.
 
 
-|image1|
+.. image:: img/01.game_setup/01.import_button.png
 
 
 In the import popup, enter the full path to the freshly created directory
 In the import popup, enter the full path to the freshly created directory
 ``squash_the_creeps_start/``. You can click the *Browse* button on the right to
 ``squash_the_creeps_start/``. You can click the *Browse* button on the right to
 open a file browser and navigate to the ``project.godot`` file the folder
 open a file browser and navigate to the ``project.godot`` file the folder
 contains.
 contains.
 
 
-|image2|
+.. image:: img/01.game_setup/02.browse_to_project_folder.png
 
 
 Click *Import & Edit* to open the project in the editor.
 Click *Import & Edit* to open the project in the editor.
 
 
-|image3|
+.. image:: img/01.game_setup/03.import_and_edit.png
 
 
 The start project contains an icon and two folders: ``art/`` and ``fonts/``.
 The start project contains an icon and two folders: ``art/`` and ``fonts/``.
 There, you will find the art assets and music we'll use in the game.
 There, you will find the art assets and music we'll use in the game.
 
 
-|image4|
+.. image:: img/01.game_setup/04.start_assets.png
 
 
 There are two 3D models, ``player.glb`` and ``mob.glb``, some materials that
 There are two 3D models, ``player.glb`` and ``mob.glb``, some materials that
 belong to these models, and a music track.
 belong to these models, and a music track.
@@ -38,44 +38,47 @@ belong to these models, and a music track.
 Setting up the playable area
 Setting up the playable area
 ----------------------------
 ----------------------------
 
 
-We're going to create our main scene with a plain *Node* as its root. In the
+We're going to create our main scene with a plain :ref:`Node <class_Node>` as its root. In the
 *Scene* dock, click the *Add Node* button represented by a "+" icon in the
 *Scene* dock, click the *Add Node* button represented by a "+" icon in the
-top-left and double-click on *Node*. Name the node "Main". Alternatively, to add
+top-left and double-click on *Node*. Name the node ``Main``. Alternatively, to add
 a node to the scene, you can press :kbd:`Ctrl + a` (or :kbd:`Cmd + a` on macOS).
 a node to the scene, you can press :kbd:`Ctrl + a` (or :kbd:`Cmd + a` on macOS).
 
 
-|image5|
+.. image:: img/01.game_setup/05.main_node.png
 
 
 Save the scene as ``Main.tscn`` by pressing :kbd:`Ctrl + s` (:kbd:`Cmd + s` on macOS).
 Save the scene as ``Main.tscn`` by pressing :kbd:`Ctrl + s` (:kbd:`Cmd + s` on macOS).
 
 
 We'll start by adding a floor that'll prevent the characters from falling. To
 We'll start by adding a floor that'll prevent the characters from falling. To
-create static colliders like the floor, walls, or ceilings, you can use
-*StaticBody* nodes. They require *CollisionShape* child nodes to
-define the collision area. With the *Main* node selected, add a *StaticBody*
-node, then a *CollisionShape*. Rename the *StaticBody* as *Ground*.
+create static colliders like the floor, walls, or ceilings, you can use :ref:`StaticBody3D <class_StaticBody3D>` nodes. They require :ref:`CollisionShape3D <class_CollisionShape3D>` child nodes to
+define the collision area. With the ``Main`` node selected, add a :ref:`StaticBody3D <class_StaticBody3D>`
+node, then a :ref:`CollisionShape3D <class_CollisionShape3D>`. Rename the :ref:`StaticBody3D <class_StaticBody3D>` to ``Ground``.
 
 
-|image6|
+.. image:: img/01.game_setup/adding_static_body3D.webp
 
 
-A warning sign next to the *CollisionShape* appears because we haven't defined
+Your scene tree should look like this
+
+.. image:: img/01.game_setup/06.staticbody_node.png
+
+A warning sign next to the :ref:`CollisionShape3D <class_CollisionShape3D>` appears because we haven't defined
 its shape. If you click the icon, a popup appears to give you more information.
 its shape. If you click the icon, a popup appears to give you more information.
 
 
-|image7|
+.. image:: img/01.game_setup/07.collision_shape_warning.png
 
 
-To create a shape, with the *CollisionShape* selected, head to the *Inspector*
-and click the *[empty]* field next to the *Shape* property. Create a new *Box
-Shape*.
+To create a shape, select the :ref:`CollisionShape3D <class_CollisionShape3D>` node, head to the *Inspector*
+and click the *<empty>* field next to the *Shape* property. Create a new *Box
+Shape3D*.
 
 
-|image8|
+.. image:: img/01.game_setup/08.create_box_shape3D.jpg
 
 
 The box shape is perfect for flat ground and walls. Its thickness makes it
 The box shape is perfect for flat ground and walls. Its thickness makes it
 reliable to block even fast-moving objects.
 reliable to block even fast-moving objects.
 
 
 A box's wireframe appears in the viewport with three orange dots. You can click
 A box's wireframe appears in the viewport with three orange dots. You can click
 and drag these to edit the shape's extents interactively. We can also precisely
 and drag these to edit the shape's extents interactively. We can also precisely
-set the size in the inspector. Click on the *BoxShape* to expand the resource.
+set the size in the inspector. Click on the *BoxShape3D* to expand the resource.
 Set its *Extents* to ``30`` on the X axis, ``1`` for the Y axis, and ``30`` for
 Set its *Extents* to ``30`` on the X axis, ``1`` for the Y axis, and ``30`` for
 the Z axis.
 the Z axis.
 
 
-|image9|
+.. image:: img/01.game_setup/09.box_extents.webp
 
 
 .. note::
 .. note::
 
 
@@ -85,80 +88,70 @@ the Z axis.
     axis represents the height.
     axis represents the height.
 
 
 Collision shapes are invisible. We need to add a visual floor that goes along
 Collision shapes are invisible. We need to add a visual floor that goes along
-with it. Select the *Ground* node and add a *MeshInstance* as its child.
+with it. Select the ``Ground`` node and add a :ref:`MeshInstance <class_MeshInstance3D>` as its child.
 
 
-|image10|
+.. image:: img/01.game_setup/10.mesh_instance3d.png
 
 
-In the *Inspector*, click on the field next to *Mesh* and create a *CubeMesh*
+In the *Inspector*, click on the field next to *Mesh* and create a *BoxMesh*
 resource to create a visible cube.
 resource to create a visible cube.
 
 
-|image11|
+.. image:: img/01.game_setup/11.box_mesh.webp
 
 
 Once again, it's too small by default. Click the cube icon to expand the
 Once again, it's too small by default. Click the cube icon to expand the
 resource and set its *Size* to ``60``, ``2``, and ``60``. As the cube
 resource and set its *Size* to ``60``, ``2``, and ``60``. As the cube
 resource works with a size rather than extents, we need to use these values so
 resource works with a size rather than extents, we need to use these values so
 it matches our collision shape.
 it matches our collision shape.
 
 
-|image12|
+.. image:: img/01.game_setup/12.cube_resized.png
 
 
 You should see a wide grey slab that covers the grid and blue and red axes in
 You should see a wide grey slab that covers the grid and blue and red axes in
 the viewport.
 the viewport.
 
 
 We're going to move the ground down so we can see the floor grid. Select the
 We're going to move the ground down so we can see the floor grid. Select the
-*Ground* node, hold the :kbd:`Ctrl` key down to turn on grid snapping (:kbd:`Cmd` on macOS),
+``Ground`` node, hold the :kbd:`Ctrl` key down to turn on grid snapping (:kbd:`Cmd` on macOS),
 and click and drag down on the Y axis. It's the green arrow in the move gizmo.
 and click and drag down on the Y axis. It's the green arrow in the move gizmo.
 
 
-|image13|
+.. image:: img/01.game_setup/13.move_gizmo_y_axis.png
 
 
 .. note::
 .. note::
 
 
     If you can't see the 3D object manipulator like on the image above, ensure
     If you can't see the 3D object manipulator like on the image above, ensure
     the *Select Mode* is active in the toolbar above the view.
     the *Select Mode* is active in the toolbar above the view.
 
 
-|image14|
+.. image:: img/01.game_setup/14.select_mode_icon.png
 
 
-Move the ground down ``1`` meter. A label in the bottom-left corner of the
+Move the ground down ``1`` meter, in order to have a visible editor grid. A label in the bottom-left corner of the
 viewport tells you how much you're translating the node.
 viewport tells you how much you're translating the node.
 
 
-|image15|
+.. image:: img/01.game_setup/15.translation_amount.png
 
 
 .. note::
 .. note::
 
 
     Moving the *Ground* node down moves both children along with it.
     Moving the *Ground* node down moves both children along with it.
-    Ensure you move the *Ground* node, **not** the *MeshInstance* or the
-    *CollisionShape*.
+    Ensure you move the *Ground* node, **not** the *MeshInstance3D* or the
+    *CollisionShape3D*.
+
+Ultimately, ``Ground``'s transform.position.y should be -1
+
+.. image:: img/01.game_setup/ground_down1meter.webp
 
 
-Let's add a directional light so our scene isn't all grey. Select the *Main*
-node and add a *DirectionalLight* as a child of it. We need to move it and
-rotate it. Move it up by clicking and dragging on the manipulator's green arrow
+Let's add a directional light so our scene isn't all grey. Select the ``Main``
+node and add a child node :ref:`DirectionalLight <class_DirectionalLight3D>`.
+
+.. image:: img/01.game_setup/create_directional_light3d.webp
+
+We need to move and rotate the :ref:`DirectionalLight <class_DirectionalLight3D>` node.
+Move it up by clicking and dragging on the manipulator's green arrow
 and click and drag on the red arc to rotate it around the X axis, until the
 and click and drag on the red arc to rotate it around the X axis, until the
 ground is lit.
 ground is lit.
 
 
 In the *Inspector*, turn on *Shadow -> Enabled* by clicking the checkbox.
 In the *Inspector*, turn on *Shadow -> Enabled* by clicking the checkbox.
 
 
-|image16|
+.. image:: img/01.game_setup/16.turn_on_shadows.webp
 
 
 At this point, your project should look like this.
 At this point, your project should look like this.
 
 
-|image17|
+.. image:: img/01.game_setup/17.project_with_light.webp
 
 
 That's our starting point. In the next part, we will work on the player scene
 That's our starting point. In the next part, we will work on the player scene
 and base movement.
 and base movement.
-
-.. |image1| image:: img/01.game_setup/01.import_button.png
-.. |image2| image:: img/01.game_setup/02.browse_to_project_folder.png
-.. |image3| image:: img/01.game_setup/03.import_and_edit.png
-.. |image4| image:: img/01.game_setup/04.start_assets.png
-.. |image5| image:: img/01.game_setup/05.main_node.png
-.. |image6| image:: img/01.game_setup/06.staticbody_node.png
-.. |image7| image:: img/01.game_setup/07.collision_shape_warning.png
-.. |image8| image:: img/01.game_setup/08.create_box_shape.png
-.. |image9| image:: img/01.game_setup/09.box_extents.png
-.. |image10| image:: img/01.game_setup/10.mesh_instance.png
-.. |image11| image:: img/01.game_setup/11.cube_mesh.png
-.. |image12| image:: img/01.game_setup/12.cube_resized.png
-.. |image13| image:: img/01.game_setup/13.move_gizmo_y_axis.png
-.. |image14| image:: img/01.game_setup/14.select_mode_icon.png
-.. |image15| image:: img/01.game_setup/15.translation_amount.png
-.. |image16| image:: img/01.game_setup/16.turn_on_shadows.png
-.. |image17| image:: img/01.game_setup/17.project_with_light.png

+ 47 - 45
getting_started/first_3d_game/02.player_input.rst

@@ -11,13 +11,18 @@ that moves in eight directions.
 .. player_movement.gif
 .. player_movement.gif
 
 
 Create a new scene by going to the Scene menu in the top-left and clicking *New
 Create a new scene by going to the Scene menu in the top-left and clicking *New
-Scene*. Create a *CharacterBody3D* node as the root and name it *Player*.
+Scene*.
 
 
 |image0|
 |image0|
 
 
+ Create a :ref:`CharacterBody3D <class_CharacterBody3D>` node as the root
+
+.. image:: img/02.player_input/add_character_body3D.webp
+
+Name the :ref:`CharacterBody3D <class_CharacterBody3D>` to ``Player``.
 Character bodies are complementary to the area and rigid bodies used in the 2D
 Character bodies are complementary to the area and rigid bodies used in the 2D
 game tutorial. Like rigid bodies, they can move and collide with the
 game tutorial. Like rigid bodies, they can move and collide with the
-environment, but instead of being controlled by the physics engine, you dictate
+environment, but instead of being controlled by the physics engine, **you** dictate
 their movement. You will see how we use the node's unique features when we code
 their movement. You will see how we use the node's unique features when we code
 the jump and squash mechanics.
 the jump and squash mechanics.
 
 
@@ -29,14 +34,18 @@ the jump and squash mechanics.
 For now, we're going to create a basic rig for our character's 3D model. This
 For now, we're going to create a basic rig for our character's 3D model. This
 will allow us to rotate the model later via code while it plays an animation.
 will allow us to rotate the model later via code while it plays an animation.
 
 
-Add a *Node3D* node as a child of *Player* and name it *Pivot*. Then, in the
-FileSystem dock, expand the ``art/`` folder by double-clicking it and drag and
-drop ``player.glb`` onto the *Pivot* node.
+Add a :ref:`Node3D <class_Node3D>` node as a child of ``Player`` and name it ``Pivot``
+
+.. image:: img/02.player_input/adding_node3D.webp
+
+Then, in the FileSystem dock, expand the ``art/`` folder
+by double-clicking it and drag and
+drop ``player.glb`` onto ``Pivot``.
 
 
 |image1|
 |image1|
 
 
-This should instantiate the model as a child of *Pivot*. You can rename it to
-*Character*.
+This should instantiate the model as a child of ``Pivot``.
+You can rename it to ``Character``.
 
 
 |image2|
 |image2|
 
 
@@ -48,9 +57,12 @@ This should instantiate the model as a child of *Pivot*. You can rename it to
     model in `Blender 3D <https://www.blender.org/>`__ and exported it to GLTF.
     model in `Blender 3D <https://www.blender.org/>`__ and exported it to GLTF.
 
 
 As with all kinds of physics nodes, we need a collision shape for our character
 As with all kinds of physics nodes, we need a collision shape for our character
-to collide with the environment. Select the *Player* node again and add a
-*CollisionShape*. In the *Inspector*, assign a *SphereShape* to the *Shape*
-property. The sphere's wireframe appears below the character.
+to collide with the environment. Select the ``Player`` node again and add a child node
+:ref:`CollisionShape3D <class_CollisionShape3D>`. In the *Inspector*, on the *Shape* property, add a new :ref:`SphereShape3D <class_SphereShape3D>`.
+
+.. image:: img/02.player_input/add_capsuleshape3d.webp
+
+The sphere's wireframe appears below the character.
 
 
 |image3|
 |image3|
 
 
@@ -63,11 +75,11 @@ Then, move the shape up so its bottom roughly aligns with the grid's plane.
 |image4|
 |image4|
 
 
 You can toggle the model's visibility by clicking the eye icon next to the
 You can toggle the model's visibility by clicking the eye icon next to the
-*Character* or the *Pivot* nodes.
+``Character`` or the ``Pivot`` nodes.
 
 
 |image5|
 |image5|
 
 
-Save the scene as ``Player.tscn``.
+Save the scene as ``Player.tscn``
 
 
 With the nodes ready, we can almost get coding. But first, we need to define
 With the nodes ready, we can almost get coding. But first, we need to define
 some input actions.
 some input actions.
@@ -81,7 +93,7 @@ a powerful system that allows you to assign a label to a set of keys and
 buttons. This simplifies our scripts and makes them more readable.
 buttons. This simplifies our scripts and makes them more readable.
 
 
 This system is the Input Map. To access its editor, head to the *Project* menu
 This system is the Input Map. To access its editor, head to the *Project* menu
-and select *Project Settings*.
+and select *Project Settings*.
 
 
 |image6|
 |image6|
 
 
@@ -101,52 +113,45 @@ To add an action, write its name in the bar at the top and press Enter.
 
 
 |image8|
 |image8|
 
 
-Create the five actions. Your window should have them all listed at the bottom.
+Create the following five actions:
 
 
 |image9|
 |image9|
 
 
 To bind a key or button to an action, click the "+" button to its right. Do this
 To bind a key or button to an action, click the "+" button to its right. Do this
-for ``move_left`` and in the drop-down menu, click *Key*.
-
-|image10|
+for ``move_left``. Press the left arrow key and click *OK*.
 
 
-This option allows you to add a keyboard input. A popup appears and waits for
-you to press a key. Press the left arrow key and click *OK*.
+.. image:: img/02.player_input/left_inputmap.webp
 
 
-|image11|
-
-Do the same for the A key.
+Bind also the :kbd:`A` key, onto the action ``move_left``.
 
 
 |image12|
 |image12|
 
 
 Let's now add support for a gamepad's left joystick. Click the "+" button again
 Let's now add support for a gamepad's left joystick. Click the "+" button again
-but this time, select *Joy Axis*.
+but this time, select *Manual Selection -> Joypad Axes*.
+
+.. image:: img/02.player_input/left_inputmap.webp
 
 
-|image13|
+Select the negative X axis of the left joystick.
 
 
-The popup gives you two drop-down menus. On the left, you can select a gamepad
-by index. *Device 0* corresponds to the first plugged gamepad, *Device 1*
-corresponds to the second, and so on. You can select the joystick and direction
-you want to bind to the input action on the right. Leave the default values and
-press the *Add* button.
+.. image:: img/02.player_input/left_joystick_select.webp
 
 
-|image14|
+Leave the other values as default and press *OK*
+
+.. note::
+
+    If you want controllers to have different input actions, you should use the Devices option in Additional Options. Device 0 corresponds to the first plugged gamepad, Device 1 corresponds to the second plugged gamepad, and so on.
 
 
 Do the same for the other input actions. For example, bind the right arrow, D,
 Do the same for the other input actions. For example, bind the right arrow, D,
-and the left joystick's right axis to ``move_right``. After binding all keys,
+and the left joystick's positive axis to ``move_right``. After binding all keys,
 your interface should look like this.
 your interface should look like this.
 
 
 |image15|
 |image15|
 
 
-We have the ``jump`` action left to set up. Bind the Space key and the gamepad's
-A button. To bind a gamepad's button, select the *Joy Button* option in the menu.
+The final action to set up is the ``jump`` action. Bind the Space key and the gamepad's
+A button.
 
 
 |image16|
 |image16|
 
 
-Leave the default values and click the *Add* button.
-
-|image17|
-
 Your jump input action should look like this.
 Your jump input action should look like this.
 
 
 |image18|
 |image18|
@@ -157,21 +162,18 @@ groups of keys and buttons in your projects.
 In the next part, we'll code and test the player's movement.
 In the next part, we'll code and test the player's movement.
 
 
 .. |image0| image:: img/02.player_input/01.new_scene.png
 .. |image0| image:: img/02.player_input/01.new_scene.png
-.. |image1| image:: img/02.player_input/02.instantiating_the_model.png
+.. |image1| image:: img/02.player_input/02.instantiating_the_model.webp
 .. |image2| image:: img/02.player_input/03.scene_structure.png
 .. |image2| image:: img/02.player_input/03.scene_structure.png
 .. |image3| image:: img/02.player_input/04.sphere_shape.png
 .. |image3| image:: img/02.player_input/04.sphere_shape.png
 .. |image4| image:: img/02.player_input/05.moving_the_sphere_up.png
 .. |image4| image:: img/02.player_input/05.moving_the_sphere_up.png
-.. |image5| image:: img/02.player_input/06.toggling_visibility.png
+.. |image5| image:: img/02.player_input/06.toggling_visibility.webp
 .. |image6| image:: img/02.player_input/07.project_settings.png
 .. |image6| image:: img/02.player_input/07.project_settings.png
 .. |image7| image:: img/02.player_input/07.input_map_tab.png
 .. |image7| image:: img/02.player_input/07.input_map_tab.png
 .. |image8| image:: img/02.player_input/07.adding_action.png
 .. |image8| image:: img/02.player_input/07.adding_action.png
 .. |image9| image:: img/02.player_input/08.actions_list_empty.png
 .. |image9| image:: img/02.player_input/08.actions_list_empty.png
-.. |image10| image:: img/02.player_input/08.create_key_action.png
 .. |image11| image:: img/02.player_input/09.keyboard_key_popup.png
 .. |image11| image:: img/02.player_input/09.keyboard_key_popup.png
 .. |image12| image:: img/02.player_input/09.keyboard_keys.png
 .. |image12| image:: img/02.player_input/09.keyboard_keys.png
-.. |image13| image:: img/02.player_input/10.joy_axis_option.png
-.. |image14| image:: img/02.player_input/11.joy_axis_popup.png
-.. |image15| image:: img/02.player_input/12.move_inputs_mapped.png
-.. |image16| image:: img/02.player_input/13.joy_button_option.png
+.. |image15| image:: img/02.player_input/12.move_inputs_mapped.webp
+.. |image16| image:: img/02.player_input/13.joy_button_option.webp
 .. |image17| image:: img/02.player_input/14.add_jump_button.png
 .. |image17| image:: img/02.player_input/14.add_jump_button.png
-.. |image18| image:: img/02.player_input/14.jump_input_action.png
+.. |image18| image:: img/02.player_input/14.jump_input_action.webp

+ 67 - 55
getting_started/first_3d_game/03.player_movement_code.rst

@@ -6,7 +6,7 @@ Moving the player with code
 It's time to code! We're going to use the input actions we created in the last
 It's time to code! We're going to use the input actions we created in the last
 part to move the character.
 part to move the character.
 
 
-Right-click the *Player* node and select *Attach Script* to add a new script to
+Right-click the ``Player`` node and select *Attach Script* to add a new script to
 it. In the popup, set the *Template* to *Empty* before pressing the *Create*
 it. In the popup, set the *Template* to *Empty* before pressing the *Create*
 button.
 button.
 
 
@@ -23,10 +23,10 @@ character.
 
 
    # How fast the player moves in meters per second.
    # How fast the player moves in meters per second.
    @export var speed = 14
    @export var speed = 14
-   # The downward acceleration when in the air, in meters per second squared.
+   # The downward acceleration while in the air, in meters per second squared.
    @export var fall_acceleration = 75
    @export var fall_acceleration = 75
 
 
-   var velocity = Vector3.ZERO
+   var target_velocity = Vector3.ZERO
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
@@ -37,7 +37,7 @@ character.
         // How fast the player moves in meters per second.
         // How fast the player moves in meters per second.
         [Export]
         [Export]
         public int Speed = 14;
         public int Speed = 14;
-        // The downward acceleration when in the air, in meters per second squared.
+        // The downward acceleration while in the air, in meters per second squared.
         [Export]
         [Export]
         public int FallAcceleration = 75;
         public int FallAcceleration = 75;
 
 
@@ -45,7 +45,7 @@ character.
     }
     }
 
 
 
 
-These are common properties for a moving body. The ``velocity`` is a 3D vector
+These are common properties for a moving body. The ``velocity`` is a :ref:`3D vector <class_Vector3>`
 combining a speed with a direction. Here, we define it as a property because
 combining a speed with a direction. Here, we define it as a property because
 we want to update and reuse its value across frames.
 we want to update and reuse its value across frames.
 
 
@@ -55,7 +55,7 @@ we want to update and reuse its value across frames.
     While in 2D, a thousand units (pixels) may only correspond to half of your
     While in 2D, a thousand units (pixels) may only correspond to half of your
     screen's width, in 3D, it's a kilometer.
     screen's width, in 3D, it's a kilometer.
 
 
-Let's code the movement now. We start by calculating the input direction vector
+Let's code the movement. We start by calculating the input direction vector
 using the global ``Input`` object, in ``_physics_process()``.
 using the global ``Input`` object, in ``_physics_process()``.
 
 
 .. tabs::
 .. tabs::
@@ -124,7 +124,7 @@ These four conditions give us eight possibilities and eight possible directions.
 
 
 In case the player presses, say, both W and D simultaneously, the vector will
 In case the player presses, say, both W and D simultaneously, the vector will
 have a length of about ``1.4``. But if they press a single key, it will have a
 have a length of about ``1.4``. But if they press a single key, it will have a
-length of ``1``. We want the vector's length to be consistent. To do so, we can
+length of ``1``. We want the vector's length to be consistent, and not move faster diagonally. To do so, we can
 call its ``normalize()`` method.
 call its ``normalize()`` method.
 
 
 .. tabs::
 .. tabs::
@@ -135,7 +135,7 @@ call its ``normalize()`` method.
 
 
        if direction != Vector3.ZERO:
        if direction != Vector3.ZERO:
            direction = direction.normalized()
            direction = direction.normalized()
-           $Pivot.look_at(translation + direction, Vector3.UP)
+           $Pivot.look_at(position + direction, Vector3.UP)
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
@@ -146,30 +146,30 @@ call its ``normalize()`` method.
         if (direction != Vector3.Zero)
         if (direction != Vector3.Zero)
         {
         {
             direction = direction.Normalized();
             direction = direction.Normalized();
-            GetNode<Node3D>("Pivot").LookAt(Translation + direction, Vector3.Up);
+            GetNode<Node3D>("Pivot").LookAt(position + direction, Vector3.Up);
         }
         }
     }
     }
 
 
 Here, we only normalize the vector if the direction has a length greater than
 Here, we only normalize the vector if the direction has a length greater than
 zero, which means the player is pressing a direction key.
 zero, which means the player is pressing a direction key.
 
 
-In this case, we also get the *Pivot* node and call its ``look_at()`` method.
+In this case, we also get the ``Pivot`` node and call its ``look_at()`` method.
 This method takes a position in space to look at in global coordinates and the
 This method takes a position in space to look at in global coordinates and the
 up direction. In this case, we can use the ``Vector3.UP`` constant.
 up direction. In this case, we can use the ``Vector3.UP`` constant.
 
 
 .. note::
 .. note::
 
 
-    A node's local coordinates, like ``translation``, are relative to their
-    parent. Global coordinates are relative to the world's main axes you can see
+    A node's local coordinates, like ``position``, are relative to their
+    parent. Global coordinates, like ``global_position`` are relative to the world's main axes you can see
     in the viewport instead.
     in the viewport instead.
 
 
-In 3D, the property that contains a node's position is ``translation``. By
+In 3D, the property that contains a node's position is ``position``. By
 adding the ``direction`` to it, we get a position to look at that's one meter
 adding the ``direction`` to it, we get a position to look at that's one meter
-away from the *Player*.
+away from the ``Player``.
 
 
 Then, we update the velocity. We have to calculate the ground velocity and the
 Then, we update the velocity. We have to calculate the ground velocity and the
 fall speed separately. Be sure to go back one tab so the lines are inside the
 fall speed separately. Be sure to go back one tab so the lines are inside the
-``_physics_process()`` function but outside the condition we just wrote.
+``_physics_process()`` function but outside the condition we just wrote above.
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -179,14 +179,17 @@ fall speed separately. Be sure to go back one tab so the lines are inside the
         if direction != Vector3.ZERO:
         if direction != Vector3.ZERO:
             #...
             #...
 
 
-        # Ground velocity
-        velocity.x = direction.x * speed
-        velocity.z = direction.z * speed
-        # Vertical velocity
-        velocity.y -= fall_acceleration * delta
-        # Moving the character
-        velocity = move_and_slide(velocity, Vector3.UP)
+        # Ground Velocity
+        target_velocity.x = direction.x * speed
+        target_velocity.z = direction.z * speed
+
+        # Vertical Velocity
+    	if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
+	        target_velocity.y = target_velocity.y - (fall_acceleration * delta)
 
 
+        # Moving the Character
+        velocity = target_velocity
+        move_and_slide()
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public override void _PhysicsProcess(float delta)
     public override void _PhysicsProcess(float delta)
@@ -202,29 +205,28 @@ fall speed separately. Be sure to go back one tab so the lines are inside the
         _velocity = MoveAndSlide(_velocity, Vector3.Up);
         _velocity = MoveAndSlide(_velocity, Vector3.Up);
     }
     }
 
 
-For the vertical velocity, we subtract the fall acceleration multiplied by the
-delta time every frame. Notice the use of the ``-=`` operator, which is a
-shorthand for ``variable = variable - ...``.
+The ``CharacterBody3D.is_on_floor()`` function returns ``true`` if the body collided with the floor in this frame. That's why
+we apply gravity to the ``Player`` only while he is in the air.
 
 
-This line of code will cause our character to fall in every frame. This may seem
-strange if it's already on the floor. But we have to do this for the character
-to collide with the ground every frame.
+For the vertical velocity, we subtract the fall acceleration multiplied by the
+delta time every frame.
+This line of code will cause our character to fall in every frame, as long he is not on the floor, or collides with it.
 
 
 The physics engine can only detect interactions with walls, the floor, or other
 The physics engine can only detect interactions with walls, the floor, or other
 bodies during a given frame if movement and collisions happen. We will use this
 bodies during a given frame if movement and collisions happen. We will use this
 property later to code the jump.
 property later to code the jump.
 
 
-On the last line, we call ``CharacterBody3D.move_and_slide()``. It's a powerful
+On the last line, we call ``CharacterBody3D.move_and_slide()`` which is a powerful
 method of the ``CharacterBody3D`` class that allows you to move a character
 method of the ``CharacterBody3D`` class that allows you to move a character
 smoothly. If it hits a wall midway through a motion, the engine will try to
 smoothly. If it hits a wall midway through a motion, the engine will try to
-smooth it out for you.
+smooth it out for you. It uses the *velocity* value native to the :ref:`CharacterBody3D <class_CharacterBody3D>`
 
 
-The function takes two parameters: our velocity and the up direction. It moves
-the character and returns a leftover velocity after applying collisions. When
-hitting the floor or a wall, the function will reduce or reset the speed in that
-direction from you. In our case, storing the function's returned value prevents
-the character from accumulating vertical momentum, which could otherwise get so
-big the character would move through the ground slab after a while.
+.. OLD TEXT: The function takes two parameters: our velocity and the up direction. It moves
+.. the character and returns a leftover velocity after applying collisions. When
+.. hitting the floor or a wall, the function will reduce or reset the speed in that
+.. direction from you. In our case, storing the function's returned value prevents
+.. the character from accumulating vertical momentum, which could otherwise get so
+.. big the character would move through the ground slab after a while.
 
 
 And that's all the code you need to move the character on the floor.
 And that's all the code you need to move the character on the floor.
 
 
@@ -240,7 +242,7 @@ Here is the complete ``Player.gd`` code for reference.
    # The downward acceleration when in the air, in meters per second squared.
    # The downward acceleration when in the air, in meters per second squared.
    @export var fall_acceleration = 75
    @export var fall_acceleration = 75
 
 
-   var velocity = Vector3.ZERO
+   var target_velocity = Vector3.ZERO
 
 
 
 
    func _physics_process(delta):
    func _physics_process(delta):
@@ -255,15 +257,21 @@ Here is the complete ``Player.gd`` code for reference.
        if Input.is_action_pressed("move_forward"):
        if Input.is_action_pressed("move_forward"):
            direction.z -= 1
            direction.z -= 1
 
 
-       if direction != Vector3.ZERO:
-           direction = direction.normalized()
-           $Pivot.look_at(translation + direction, Vector3.UP)
+        if direction != Vector3.ZERO:
+            direction = direction.normalized()
+            $Pivot.look_at(position + direction, Vector3.UP)
+
+        # Ground Velocity
+        target_velocity.x = direction.x * speed
+        target_velocity.z = direction.z * speed
 
 
-       velocity.x = direction.x * speed
-       velocity.z = direction.z * speed
-       velocity.y -= fall_acceleration * delta
-       velocity = move_and_slide(velocity, Vector3.UP)
+        # Vertical Velocity
+    	if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
+	    	target_velocity.y = target_velocity.y - (fall_acceleration * delta)
 
 
+        # Moving the Character
+        velocity = target_velocity
+        move_and_slide()
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Player : CharacterBody3D
     public class Player : CharacterBody3D
@@ -321,11 +329,11 @@ Here is the complete ``Player.gd`` code for reference.
 Testing our player's movement
 Testing our player's movement
 -----------------------------
 -----------------------------
 
 
-We're going to put our player in the *Main* scene to test it. To do so, we need
+We're going to put our player in the ``Main`` scene to test it. To do so, we need
 to instantiate the player and then add a camera. Unlike in 2D, in 3D, you won't
 to instantiate the player and then add a camera. Unlike in 2D, in 3D, you won't
 see anything if your viewport doesn't have a camera pointing at something.
 see anything if your viewport doesn't have a camera pointing at something.
 
 
-Save your *Player* scene and open the *Main* scene. You can click on the *Main*
+Save your ``Player`` scene and open the ``Main`` scene. You can click on the *Main*
 tab at the top of the editor to do so.
 tab at the top of the editor to do so.
 
 
 |image1|
 |image1|
@@ -333,21 +341,20 @@ tab at the top of the editor to do so.
 If you closed the scene before, head to the *FileSystem* dock and double-click
 If you closed the scene before, head to the *FileSystem* dock and double-click
 ``Main.tscn`` to re-open it.
 ``Main.tscn`` to re-open it.
 
 
-To instantiate the *Player*, right-click on the *Main* node and select *Instance
+To instantiate the ``Player``, right-click on the ``Main`` node and select *Instance
 Child Scene*.
 Child Scene*.
 
 
 |image2|
 |image2|
 
 
-In the popup, double-click *Player.tscn*. The character should appear in the
+In the popup, double-click ``Player.tscn``. The character should appear in the
 center of the viewport.
 center of the viewport.
 
 
 Adding a camera
 Adding a camera
 ~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~
 
 
 Let's add the camera next. Like we did with our *Player*\ 's *Pivot*, we're
 Let's add the camera next. Like we did with our *Player*\ 's *Pivot*, we're
-going to create a basic rig. Right-click on the *Main* node again and select
-*Add Child Node* this time. Create a new *Marker3D*, name it *CameraPivot*,
-and add a *Camera* node as a child of it. Your scene tree should look like this.
+going to create a basic rig. Right-click on the ``Main`` node again and select
+*Add Child Node*. Create a new :ref:`Marker3D <class_Marker3D>`, and name it ``CameraPivot``. Select ``CameraPivot`` and add a child node :ref:`Camera3D <class_Camera3D>` to it. Your scene tree should look like this.
 
 
 |image3|
 |image3|
 
 
@@ -363,9 +370,11 @@ what the camera sees.
 In the toolbar right above the viewport, click on *View*, then *2 Viewports*.
 In the toolbar right above the viewport, click on *View*, then *2 Viewports*.
 You can also press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS).
 You can also press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS).
 
 
+|image11|
+
 |image5|
 |image5|
 
 
-On the bottom view, select the *Camera* and turn on camera preview by clicking
+On the bottom view, select your :ref:`Camera3D <class_Camera3D>` and turn on camera Preview by clicking
 the checkbox.
 the checkbox.
 
 
 |image6|
 |image6|
@@ -397,10 +406,12 @@ the ground should fill the background.
 
 
 |image10|
 |image10|
 
 
-With that, we have both player movement and the view in place. Next, we will
+Test your scene and you should be able to move in all 8 directions and not glitch through the floor!
+
+Ultimately, we have both player movement and the view in place. Next, we will
 work on the monsters.
 work on the monsters.
 
 
-.. |image0| image:: img/03.player_movement_code/01.attach_script_to_player.png
+.. |image0| image:: img/03.player_movement_code/01.attach_script_to_player.webp
 .. |image1| image:: img/03.player_movement_code/02.clicking_main_tab.png
 .. |image1| image:: img/03.player_movement_code/02.clicking_main_tab.png
 .. |image2| image:: img/03.player_movement_code/03.instance_child_scene.png
 .. |image2| image:: img/03.player_movement_code/03.instance_child_scene.png
 .. |image3| image:: img/03.player_movement_code/04.scene_tree_with_camera.png
 .. |image3| image:: img/03.player_movement_code/04.scene_tree_with_camera.png
@@ -410,4 +421,5 @@ work on the monsters.
 .. |image7| image:: img/03.player_movement_code/08.camera_moved.png
 .. |image7| image:: img/03.player_movement_code/08.camera_moved.png
 .. |image8| image:: img/03.player_movement_code/09.camera_rotated.png
 .. |image8| image:: img/03.player_movement_code/09.camera_rotated.png
 .. |image9| image:: img/03.player_movement_code/10.camera_perspective.png
 .. |image9| image:: img/03.player_movement_code/10.camera_perspective.png
-.. |image10| image:: img/03.player_movement_code/11.camera_orthographic.png
+.. |image10| image:: img/03.player_movement_code/13.camera3d_values.webp
+.. |image11| image:: img/03.player_movement_code/12.viewport_change.webp

+ 81 - 81
getting_started/first_3d_game/04.mob_scene.rst

@@ -7,28 +7,33 @@ In this part, you're going to code the monsters, which we'll call mobs. In the
 next lesson, we'll spawn them randomly around the playable area.
 next lesson, we'll spawn them randomly around the playable area.
 
 
 Let's design the monsters themselves in a new scene. The node structure is going
 Let's design the monsters themselves in a new scene. The node structure is going
-to be similar to the *Player* scene.
+to be similar to the ``Player.tscn`` scene.
 
 
-Create a scene with, once again, a *CharacterBody3D* node as its root. Name it
-*Mob*. Add a *Node3D* node as a child of it, name it *Pivot*. And drag and drop
-the file ``mob.glb`` from the *FileSystem* dock onto the *Pivot* to add the
-monster's 3D model to the scene. You can rename the newly created *mob* node
-into *Character*.
+Create a scene with, once again, a :ref:`CharacterBody3D <class_CharacterBody3D>` node as its root. Name it
+``Mob``. Add a child node :ref:`Node3D <class_Node3D>`, name it ``Pivot``. And drag and drop
+the file ``mob.glb`` from the *FileSystem* dock onto the ``Pivot`` to add the
+monster's 3D model to the scene.
+
+.. image:: img/04.mob_scene/drag_drop_mob.webp
+
+You can rename the newly created ``mob`` node
+into ``Character``.
 
 
 |image0|
 |image0|
 
 
-We need a collision shape for our body to work. Right-click on the *Mob* node,
+We need a collision shape for our body to work. Right-click on the ``Mob`` node,
 the scene's root, and click *Add Child Node*.
 the scene's root, and click *Add Child Node*.
 
 
 |image1|
 |image1|
 
 
-Add a *CollisionShape*.
+Add a :ref:`CollisionShape3D <class_CollisionShape3D>`.
 
 
 |image2|
 |image2|
 
 
-In the *Inspector*, assign a *BoxShape* to the *Shape* property.
 
 
-|image3|
+In the *Inspector*, assign a *BoxShape3D* to the *Shape* property.
+
+.. image:: img/01.game_setup/08.create_box_shape3D.jpg
 
 
 We should change its size to fit the 3D model better. You can do so
 We should change its size to fit the 3D model better. You can do so
 interactively by clicking and dragging on the orange dots.
 interactively by clicking and dragging on the orange dots.
@@ -52,15 +57,15 @@ Removing monsters off-screen
 We're going to spawn monsters at regular time intervals in the game level. If
 We're going to spawn monsters at regular time intervals in the game level. If
 we're not careful, their count could increase to infinity, and we don't want
 we're not careful, their count could increase to infinity, and we don't want
 that. Each mob instance has both a memory and a processing cost, and we don't
 that. Each mob instance has both a memory and a processing cost, and we don't
-want to pay for it when the mob's outside the screen.
+want to pay for it when the mob is outside the screen.
 
 
-Once a monster leaves the screen, we don't need it anymore, so we can delete it.
+Once a monster leaves the screen, we don't need it anymore, so we should delete it.
 Godot has a node that detects when objects leave the screen,
 Godot has a node that detects when objects leave the screen,
-*VisibilityNotifier*, and we're going to use it to destroy our mobs.
+:ref:`VisibleOnScreenNotifier3D <class_VisibileOnScreenNotifier3D>`, and we're going to use it to destroy our mobs.
 
 
 .. note::
 .. note::
 
 
-    When you keep instancing an object in games, there's a technique you can
+    When you keep instancing an object, there's a technique you can
     use to avoid the cost of creating and destroying instances all the time
     use to avoid the cost of creating and destroying instances all the time
     called pooling. It consists of pre-creating an array of objects and reusing
     called pooling. It consists of pre-creating an array of objects and reusing
     them over and over.
     them over and over.
@@ -69,9 +74,9 @@ Godot has a node that detects when objects leave the screen,
     reason to use pools is to avoid freezes with garbage-collected languages
     reason to use pools is to avoid freezes with garbage-collected languages
     like C# or Lua. GDScript uses a different technique to manage memory,
     like C# or Lua. GDScript uses a different technique to manage memory,
     reference counting, which doesn't have that caveat. You can learn more
     reference counting, which doesn't have that caveat. You can learn more
-    about that here :ref:`doc_gdscript_basics_memory_management`.
+    about that here: :ref:`doc_gdscript_basics_memory_management`.
 
 
-Select the *Mob* node and add a *VisibilityNotifier* as a child of it. Another
+Select the ``Mob`` node and add a child node :ref:`VisibleOnScreenNotifier3D <class_VisibileOnScreenNotifier3D>`. Another
 box, pink this time, appears. When this box completely leaves the screen, the
 box, pink this time, appears. When this box completely leaves the screen, the
 node will emit a signal.
 node will emit a signal.
 
 
@@ -85,17 +90,16 @@ Coding the mob's movement
 -------------------------
 -------------------------
 
 
 Let's implement the monster's motion. We're going to do this in two steps.
 Let's implement the monster's motion. We're going to do this in two steps.
-First, we'll write a script on the *Mob* that defines a function to initialize
-the monster. We'll then code the randomized spawn mechanism in the *Main* scene
+First, we'll write a script on the ``Mob`` that defines a function to initialize
+the monster. We'll then code the randomized spawn mechanism in the ``Main.tscn`` scene
 and call the function from there.
 and call the function from there.
 
 
-Attach a script to the *Mob*.
+Attach a script to the ``Mob``.
 
 
 |image7|
 |image7|
 
 
 Here's the movement code to start with. We define two properties, ``min_speed``
 Here's the movement code to start with. We define two properties, ``min_speed``
-and ``max_speed``, to define a random speed range. We then define and initialize
-the ``velocity``.
+and ``max_speed``, to define a random speed range, which we will later use to define ``CharacterBody3D.velocity``.
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -107,11 +111,9 @@ the ``velocity``.
    # Maximum speed of the mob in meters per second.
    # Maximum speed of the mob in meters per second.
    @export var max_speed = 18
    @export var max_speed = 18
 
 
-   var velocity = Vector3.ZERO
-
 
 
    func _physics_process(_delta):
    func _physics_process(_delta):
-       move_and_slide(velocity)
+        move_and_slide()
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
@@ -126,24 +128,22 @@ the ``velocity``.
         [Export]
         [Export]
         public int MaxSpeed = 18;
         public int MaxSpeed = 18;
 
 
-        private Vector3 _velocity = Vector3.Zero;
-
         public override void _PhysicsProcess(float delta)
         public override void _PhysicsProcess(float delta)
         {
         {
-            MoveAndSlide(_velocity);
+            MoveAndSlide();
         }
         }
     }
     }
 
 
-Similarly to the player, we move the mob every frame by calling
-``CharacterBody3D``\ 's ``move_and_slide()`` method. This time, we don't update
-the ``velocity`` every frame: we want the monster to move at a constant speed
+Similarly to the player, we move the mob every frame by calling the function
+``CharacterBody3D.move_and_slide()``. This time, we don't update
+the ``velocity`` every frame; we want the monster to move at a constant speed
 and leave the screen, even if it were to hit an obstacle.
 and leave the screen, even if it were to hit an obstacle.
 
 
-We need to define another function to calculate the start velocity. This
+We need to define another function to calculate the ``CharacterBody3D.velocity``. This
 function will turn the monster towards the player and randomize both its angle
 function will turn the monster towards the player and randomize both its angle
 of motion and its velocity.
 of motion and its velocity.
 
 
-The function will take a ``start_position``, the mob's spawn position, and the
+The function will take a ``start_position``,the mob's spawn position, and the
 ``player_position`` as its arguments.
 ``player_position`` as its arguments.
 
 
 We position the mob at ``start_position`` and turn it towards the player using
 We position the mob at ``start_position`` and turn it towards the player using
@@ -154,13 +154,14 @@ between ``-PI / 4`` radians and ``PI / 4`` radians.
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   # We will call this function from the Main scene.
-   func initialize(start_position, player_position):
-       # We position the mob and turn it so that it looks at the player.
-       look_at_from_position(start_position, player_position, Vector3.UP)
-       # And rotate it randomly so it doesn't move exactly toward the player.
-       rotate_y(rand_range(-PI / 4, PI / 4))
-
+    # This function will be called from the Main scene.
+    func initialize(start_position, player_position):
+        # We position the mob by placing it at start_position
+        # and rotate it towards player_position, so it looks at the player.
+        look_at_from_position(start_position, player_position, Vector3.UP)
+        # In this rotation^, the mob will move directly towards the player
+        # so we rotate it randomly within range of -90 and +90 degrees.
+        rotate_y(randf_range(-PI / 4, PI / 4))
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     // We will call this function from the Main scene
     // We will call this function from the Main scene
@@ -172,12 +173,8 @@ between ``-PI / 4`` radians and ``PI / 4`` radians.
         RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
         RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
     }
     }
 
 
-We then calculate a random speed using ``rand_range()`` once again and we use it
-to calculate the velocity.
-
-We start by creating a 3D vector pointing forward, multiply it by our
-``random_speed``, and finally rotate it using the ``Vector3`` class's
-``rotated()`` method.
+We got a random position, now we need a ``random_speed``. ``randi_range()`` will be useful as it gives random int values, and we will use ``min_speed`` and ``max_speed``.
+``random_speed`` is just an integer, and we just use it to multiply our ``CharacterBody3D.velocity``. After ``random_speed`` is applied, we rotate ``CharacterBody3D.velocity`` Vector3 towards the player.
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -185,11 +182,12 @@ We start by creating a 3D vector pointing forward, multiply it by our
    func initialize(start_position, player_position):
    func initialize(start_position, player_position):
        # ...
        # ...
 
 
-       # We calculate a random speed.
-       var random_speed = rand_range(min_speed, max_speed)
+       # We calculate a random speed (integer)
+       var random_speed = randi_range(min_speed, max_speed)
        # We calculate a forward velocity that represents the speed.
        # We calculate a forward velocity that represents the speed.
        velocity = Vector3.FORWARD * random_speed
        velocity = Vector3.FORWARD * random_speed
-       # We then rotate the vector based on the mob's Y rotation to move in the direction it's looking.
+       # We then rotate the velocity vector based on the mob's Y rotation
+       # in order to move in the direction the mob is looking.
        velocity = velocity.rotated(Vector3.UP, rotation.y)
        velocity = velocity.rotated(Vector3.UP, rotation.y)
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
@@ -210,26 +208,25 @@ Leaving the screen
 ------------------
 ------------------
 
 
 We still have to destroy the mobs when they leave the screen. To do so, we'll
 We still have to destroy the mobs when they leave the screen. To do so, we'll
-connect our *VisibilityNotifier* node's ``screen_exited`` signal to the *Mob*.
+connect our :ref:`VisibleOnScreenNotifier3D <class_VisibleOnScreenNotifier3D>` node's ``screen_exited`` signal to the ``Mob``.
 
 
 Head back to the 3D viewport by clicking on the *3D* label at the top of the
 Head back to the 3D viewport by clicking on the *3D* label at the top of the
 editor. You can also press :kbd:`Ctrl + F2` (:kbd:`Alt + 2` on macOS).
 editor. You can also press :kbd:`Ctrl + F2` (:kbd:`Alt + 2` on macOS).
 
 
 |image8|
 |image8|
 
 
-Select the *VisibilityNotifier* node and on the right side of the interface,
-navigate to the *Node* dock. Double-click the *screen_exited()* signal.
+Select the :ref:`VisibleOnScreenNotifier3D <class_VisibleOnScreenNotifier3D>` node and on the right side of the interface,
+navigate to the *Node* dock. Double-click the ``screen_exited()`` signal.
 
 
 |image9|
 |image9|
 
 
-Connect the signal to the *Mob*.
+Connect the signal to the ``Mob``
 
 
 |image10|
 |image10|
 
 
 This will take you back to the script editor and add a new function for you,
 This will take you back to the script editor and add a new function for you,
-``_on_VisibilityNotifier_screen_exited()``. From it, call the ``queue_free()``
-method. This will destroy the mob instance when the *VisibilityNotifier* \'s box
-leaves the screen.
+``_on_visible_on_screen_notifier_3d_screen_exited()``. From it, call the ``queue_free()``
+method. This function destroy the instance it's called on.
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -256,29 +253,33 @@ Here is the complete ``Mob.gd`` script for reference.
 
 
    extends CharacterBody3D
    extends CharacterBody3D
 
 
-   # Minimum speed of the mob in meters per second.
-   @export var min_speed = 10
-   # Maximum speed of the mob in meters per second.
-   @export var max_speed = 18
-
-   var velocity = Vector3.ZERO
-
-
-   func _physics_process(_delta):
-       move_and_slide(velocity)
-
-   func initialize(start_position, player_position):
-       look_at_from_position(start_position, player_position, Vector3.UP)
-       rotate_y(rand_range(-PI / 4, PI / 4))
-
-       var random_speed = rand_range(min_speed, max_speed)
-       velocity = Vector3.FORWARD * random_speed
-       velocity = velocity.rotated(Vector3.UP, rotation.y)
-
-
-   func _on_VisibilityNotifier_screen_exited():
-       queue_free()
-
+    # Minimum speed of the mob in meters per second.
+    @export var min_speed = 10
+    # Maximum speed of the mob in meters per second.
+    @export var max_speed = 18
+
+    func _physics_process(_delta):
+        move_and_slide()
+
+    # This function will be called from the Main scene.
+    func initialize(start_position, player_position):
+        # We position the mob by placing it at start_position
+        # and rotate it towards player_position, so it looks at the player.
+        look_at_from_position(start_position, player_position, Vector3.UP)
+        # In this rotation^, the mob will move directly towards the player
+        # so we rotate it randomly within range of -90 and +90 degrees.
+        rotate_y(randf_range(-PI / 4, PI / 4))
+
+        # We calculate a random speed (integer)
+        var random_speed = randi_range(min_speed, max_speed)
+        # We calculate a forward velocity that represents the speed.
+        velocity = Vector3.FORWARD * random_speed
+        # We then rotate the velocity vector based on the mob's Y rotation
+        # in order to move in the direction the mob is looking.
+        velocity = velocity.rotated(Vector3.UP, rotation.y)
+
+    func _on_visible_on_screen_notifier_3d_screen_exited():
+        queue_free()
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Mob : CharacterBody3D
     public class Mob : CharacterBody3D
@@ -318,11 +319,10 @@ Here is the complete ``Mob.gd`` script for reference.
 .. |image0| image:: img/04.mob_scene/01.initial_three_nodes.png
 .. |image0| image:: img/04.mob_scene/01.initial_three_nodes.png
 .. |image1| image:: img/04.mob_scene/02.add_child_node.png
 .. |image1| image:: img/04.mob_scene/02.add_child_node.png
 .. |image2| image:: img/04.mob_scene/03.scene_with_collision_shape.png
 .. |image2| image:: img/04.mob_scene/03.scene_with_collision_shape.png
-.. |image3| image:: img/04.mob_scene/04.create_box_shape.png
 .. |image4| image:: img/04.mob_scene/05.box_final_size.png
 .. |image4| image:: img/04.mob_scene/05.box_final_size.png
 .. |image5| image:: img/04.mob_scene/06.visibility_notifier.png
 .. |image5| image:: img/04.mob_scene/06.visibility_notifier.png
 .. |image6| image:: img/04.mob_scene/07.visibility_notifier_bbox_resized.png
 .. |image6| image:: img/04.mob_scene/07.visibility_notifier_bbox_resized.png
 .. |image7| image:: img/04.mob_scene/08.mob_attach_script.png
 .. |image7| image:: img/04.mob_scene/08.mob_attach_script.png
 .. |image8| image:: img/04.mob_scene/09.switch_to_3d_workspace.png
 .. |image8| image:: img/04.mob_scene/09.switch_to_3d_workspace.png
-.. |image9| image:: img/04.mob_scene/10.node_dock.png
-.. |image10| image:: img/04.mob_scene/11.connect_signal.png
+.. |image9| image:: img/04.mob_scene/10.node_dock.webp
+.. |image10| image:: img/04.mob_scene/11.connect_signal.webp

+ 62 - 49
getting_started/first_3d_game/05.spawning_mobs.rst

@@ -8,7 +8,7 @@ you will have monsters roaming the game board.
 
 
 |image0|
 |image0|
 
 
-Double-click on ``Main.tscn`` in the *FileSystem* dock to open the *Main* scene.
+Double-click on ``Main.tscn`` in the *FileSystem* dock to open the ``Main`` scene.
 
 
 Before drawing the path, we're going to change the game resolution. Our game has
 Before drawing the path, we're going to change the game resolution. Our game has
 a default window size of ``1024x600``. We're going to set it to ``720x540``, a
 a default window size of ``1024x600``. We're going to set it to ``720x540``, a
@@ -27,7 +27,7 @@ Creating the spawn path
 -----------------------
 -----------------------
 
 
 Like you did in the 2D game tutorial, you're going to design a path and use a
 Like you did in the 2D game tutorial, you're going to design a path and use a
-*PathFollow* node to sample random locations on it.
+:ref:`PathFollow3D <class_PathFollow3D>` node to sample random locations on it.
 
 
 In 3D though, it's a bit more complicated to draw the path. We want it to be
 In 3D though, it's a bit more complicated to draw the path. We want it to be
 around the game view so monsters appear right outside the screen. But if we draw
 around the game view so monsters appear right outside the screen. But if we draw
@@ -36,7 +36,7 @@ a path, we won't see it from the camera preview.
 To find the view's limits, we can use some placeholder meshes. Your viewport
 To find the view's limits, we can use some placeholder meshes. Your viewport
 should still be split into two parts, with the camera preview at the bottom. If
 should still be split into two parts, with the camera preview at the bottom. If
 that isn't the case, press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS) to split the view into two.
 that isn't the case, press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS) to split the view into two.
-Select the *Camera* node and click the *Preview* checkbox in the bottom
+Select the :ref:`Camera3D <class_Camera3D>` node and click the *Preview* checkbox in the bottom
 viewport.
 viewport.
 
 
 |image3|
 |image3|
@@ -44,9 +44,8 @@ viewport.
 Adding placeholder cylinders
 Adding placeholder cylinders
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
-Let's add the placeholder meshes. Add a new *Node3D* node as a child of the
-*Main* node and name it *Cylinders*. We'll use it to group the cylinders. As a
-child of it, add a *MeshInstance* node.
+Let's add the placeholder meshes. Add a new :ref:`Node3D <class_Node3D>` as a child of the
+``Main`` node and name it ``Cylinders``. We'll use it to group the cylinders. Select ``Cylinders`` and add a child node :ref:`MeshInstance3D <class_MeshInstance3D>`
 
 
 |image4|
 |image4|
 
 
@@ -59,7 +58,7 @@ top-left corner. Alternatively, you can press the keypad's 7 key.
 
 
 |image6|
 |image6|
 
 
-The grid is a bit distracting for me. You can toggle it by going to the *View*
+The grid may be distracting. You can toggle it by going to the *View*
 menu in the toolbar and clicking *View Grid*.
 menu in the toolbar and clicking *View Grid*.
 
 
 |image7|
 |image7|
@@ -70,7 +69,7 @@ toggle it by clicking the magnet icon in the toolbar or pressing Y.
 
 
 |image8|
 |image8|
 
 
-Place the cylinder so it's right outside the camera's view in the top-left
+Move the cylinder so it's right outside the camera's view in the top-left
 corner.
 corner.
 
 
 |image9|
 |image9|
@@ -101,16 +100,21 @@ last one.
 
 
 |image12|
 |image12|
 
 
-In the *Inspector*, expand the *Material* section and assign a *StandardMaterial3D*
-to slot *0*.
+In the *Inspector*, expand the *Material* section and assign a :ref:`StandardMaterial3D <class_StandardMaterial3D>` to slot *0*.
 
 
 |image13|
 |image13|
 
 
+.. image:: img/05.spawning_mobs/standard_material.webp
+
 Click the sphere icon to open the material resource. You get a preview of the
 Click the sphere icon to open the material resource. You get a preview of the
 material and a long list of sections filled with properties. You can use these
 material and a long list of sections filled with properties. You can use these
 to create all sorts of surfaces, from metal to rock or water.
 to create all sorts of surfaces, from metal to rock or water.
 
 
-Expand the *Albedo* section and set the color to something that contrasts with
+Expand the *Albedo* section.
+
+.. image:: img/05.spawning_mobs/albedo_section.webp
+
+Set the color to something that contrasts with
 the background, like a bright orange.
 the background, like a bright orange.
 
 
 |image14|
 |image14|
@@ -121,7 +125,7 @@ visibility by clicking the eye icon next to *Cylinders*.
 
 
 |image15|
 |image15|
 
 
-Add a *Path* node as a child of *Main*. In the toolbar, four icons appear. Click
+Add a child node :ref:`Path3D <class_Path3D>` to ``Main`` node. In the toolbar, four icons appear. Click
 the *Add Point* tool, the icon with the green "+" sign.
 the *Add Point* tool, the icon with the green "+" sign.
 
 
 |image16|
 |image16|
@@ -138,9 +142,9 @@ Your path should look like this.
 
 
 |image18|
 |image18|
 
 
-To sample random positions on it, we need a *PathFollow* node. Add a
-*PathFollow* as a child of the *Path*. Rename the two nodes to *SpawnPath* and
-*SpawnLocation*, respectively. It's more descriptive of what we'll use them for.
+To sample random positions on it, we need a :ref:`PathFollow3D <class_PathFollow3D>` node. Add a
+:ref:`PathFollow3D <class_PathFollow3D>` as a child of the ``Path3d``. Rename the two nodes to ``SpawnPath`` and
+``SpawnLocation``, respectively. It's more descriptive of what we'll use them for.
 
 
 |image19|
 |image19|
 
 
@@ -149,7 +153,7 @@ With that, we're ready to code the spawn mechanism.
 Spawning monsters randomly
 Spawning monsters randomly
 --------------------------
 --------------------------
 
 
-Right-click on the *Main* node and attach a new script to it.
+Right-click on the ``Main`` node and attach a new script to it.
 
 
 We first export a variable to the *Inspector* so that we can assign ``Mob.tscn``
 We first export a variable to the *Inspector* so that we can assign ``Mob.tscn``
 or any other monster to it.
 or any other monster to it.
@@ -189,14 +193,14 @@ always spawn following the same sequence.
 
 
 We want to spawn mobs at regular time intervals. To do this, we need to go back
 We want to spawn mobs at regular time intervals. To do this, we need to go back
 to the scene and add a timer. Before that, though, we need to assign the
 to the scene and add a timer. Before that, though, we need to assign the
-``Mob.tscn`` file to the ``mob_scene`` property.
+``Mob.tscn`` file to the ``mob_scene`` property above (otherwise it's null!)
 
 
-Head back to the 3D screen and select the *Main* node. Drag ``Mob.tscn`` from
+Head back to the 3D screen and select the ``Main`` node. Drag ``Mob.tscn`` from
 the *FileSystem* dock to the *Mob Scene* slot in the *Inspector*.
 the *FileSystem* dock to the *Mob Scene* slot in the *Inspector*.
 
 
 |image20|
 |image20|
 
 
-Add a new *Timer* node as a child of *Main*. Name it *MobTimer*.
+Add a new :ref:`Timer <class_Timer>` node as a child of ``Main``. Name it ``MobTimer``.
 
 
 |image21|
 |image21|
 
 
@@ -210,7 +214,7 @@ Time*. By default, they restart automatically, emitting the signal in a cycle.
 We can connect to this signal from the *Main* node to spawn monsters every
 We can connect to this signal from the *Main* node to spawn monsters every
 ``0.5`` seconds.
 ``0.5`` seconds.
 
 
-With the *MobTimer* still selected, head to the *Node* dock on the right and
+With the *MobTimer* still selected, head to the *Node* dock on the right, and
 double-click the ``timeout`` signal.
 double-click the ``timeout`` signal.
 
 
 |image23|
 |image23|
@@ -220,7 +224,7 @@ Connect it to the *Main* node.
 |image24|
 |image24|
 
 
 This will take you back to the script, with a new empty
 This will take you back to the script, with a new empty
-``_on_MobTimer_timeout()`` function.
+``_on_mob_timer_timeout()`` function.
 
 
 Let's code the mob spawning logic. We're going to:
 Let's code the mob spawning logic. We're going to:
 
 
@@ -234,20 +238,21 @@ Let's code the mob spawning logic. We're going to:
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   func _on_MobTimer_timeout():
-       # Create a new instance of the Mob scene.
-       var mob = mob_scene.instantiate()
+    func _on_mob_timer_timeout():
+        # Create a new instance of the Mob scene.
+        var mob = mob_scene.instantiate()
 
 
-       # Choose a random location on the SpawnPath.
-       # We store the reference to the SpawnLocation node.
-       var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
-       # And give it a random offset.
-       mob_spawn_location.unit_offset = randf()
+        # Choose a random location on the SpawnPath.
+        # We store the reference to the SpawnLocation node.
+        var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
+        # And give it a random offset.
+        mob_spawn_location.progress_ratio = randf()
 
 
-       var player_position = $Player.transform.origin
-       mob.initialize(mob_spawn_location.translation, player_position)
+        var player_position = $Player.position
+        mob.initialize(mob_spawn_location.position, player_position)
 
 
-       add_child(mob)
+        # Spawn the mob by adding it to the Main scene.
+        add_child(mob)
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
@@ -263,7 +268,7 @@ Let's code the mob spawning logic. We're going to:
         // And give it a random offset.
         // And give it a random offset.
         mobSpawnLocation.UnitOffset = GD.Randf();
         mobSpawnLocation.UnitOffset = GD.Randf();
 
 
-        Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
+        Vector3 playerPosition = GetNode<Player>("Player").position;
         mob.Initialize(mobSpawnLocation.Translation, playerPosition);
         mob.Initialize(mobSpawnLocation.Translation, playerPosition);
 
 
         AddChild(mob);
         AddChild(mob);
@@ -271,31 +276,39 @@ Let's code the mob spawning logic. We're going to:
     }
     }
 
 
 Above, ``randf()`` produces a random value between ``0`` and ``1``, which is
 Above, ``randf()`` produces a random value between ``0`` and ``1``, which is
-what the *PathFollow* node's ``unit_offset`` expects.
+what the *PathFollow* node's ``progress_ratio`` expects:
+0 is the start of the path, 1 is the end of the path.
+The path we have set is around the camera's viewport, so any random value between 0 and 1
+is a random position alongside the edges of the viewport!
 
 
 Here is the complete ``Main.gd`` script so far, for reference.
 Here is the complete ``Main.gd`` script so far, for reference.
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   extends Node
+    extends Node
 
 
-   @export var mob_scene: PackedScene
+    @export var mob_scene: PackedScene
 
 
+    func _ready():
+        randomize()
 
 
-   func _ready():
-       randomize()
 
 
+    func _on_mob_timer_timeout():
+        # Create a new instance of the Mob scene.
+        var mob = mob_scene.instantiate()
 
 
-   func _on_MobTimer_timeout():
-       var mob = mob_scene.instantiate()
+        # Choose a random location on the SpawnPath.
+        # We store the reference to the SpawnLocation node.
+        var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
+        # And give it a random offset.
+        mob_spawn_location.progress_ratio = randf()
 
 
-       var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
-       mob_spawn_location.unit_offset = randf()
-       var player_position = $Player.transform.origin
-       mob.initialize(mob_spawn_location.translation, player_position)
+        var player_position = $Player.position
+        mob.initialize(mob_spawn_location.position, player_position)
 
 
-       add_child(mob)
+        # Spawn the mob by adding it to the Main scene.
+        add_child(mob)
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
@@ -318,7 +331,7 @@ Here is the complete ``Main.gd`` script so far, for reference.
             var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
             var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
             mobSpawnLocation.UnitOffset = GD.Randf();
             mobSpawnLocation.UnitOffset = GD.Randf();
 
 
-            Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
+            Vector3 playerPosition = GetNode<Player>("Player").position;
             mob.Initialize(mobSpawnLocation.Translation, playerPosition);
             mob.Initialize(mobSpawnLocation.Translation, playerPosition);
 
 
             AddChild(mob);
             AddChild(mob);
@@ -335,7 +348,7 @@ address this in the next part.
 
 
 .. |image0| image:: img/05.spawning_mobs/01.monsters_path_preview.png
 .. |image0| image:: img/05.spawning_mobs/01.monsters_path_preview.png
 .. |image1| image:: img/05.spawning_mobs/02.project_settings.png
 .. |image1| image:: img/05.spawning_mobs/02.project_settings.png
-.. |image2| image:: img/05.spawning_mobs/03.window_settings.png
+.. |image2| image:: img/05.spawning_mobs/03.window_settings.webp
 .. |image3| image:: img/05.spawning_mobs/04.camera_preview.png
 .. |image3| image:: img/05.spawning_mobs/04.camera_preview.png
 .. |image4| image:: img/05.spawning_mobs/05.cylinders_node.png
 .. |image4| image:: img/05.spawning_mobs/05.cylinders_node.png
 .. |image5| image:: img/05.spawning_mobs/06.cylinder_mesh.png
 .. |image5| image:: img/05.spawning_mobs/06.cylinder_mesh.png
@@ -346,7 +359,7 @@ address this in the next part.
 .. |image10| image:: img/05.spawning_mobs/11.both_cylinders_selected.png
 .. |image10| image:: img/05.spawning_mobs/11.both_cylinders_selected.png
 .. |image11| image:: img/05.spawning_mobs/12.four_cylinders.png
 .. |image11| image:: img/05.spawning_mobs/12.four_cylinders.png
 .. |image12| image:: img/05.spawning_mobs/13.selecting_all_cylinders.png
 .. |image12| image:: img/05.spawning_mobs/13.selecting_all_cylinders.png
-.. |image13| image:: img/05.spawning_mobs/14.spatial_material.png
+.. |image13| image:: img/05.spawning_mobs/14.multi_material_selection.webp
 .. |image14| image:: img/05.spawning_mobs/15.bright-cylinders.png
 .. |image14| image:: img/05.spawning_mobs/15.bright-cylinders.png
 .. |image15| image:: img/05.spawning_mobs/16.cylinders_fold.png
 .. |image15| image:: img/05.spawning_mobs/16.cylinders_fold.png
 .. |image16| image:: img/05.spawning_mobs/17.points_options.png
 .. |image16| image:: img/05.spawning_mobs/17.points_options.png
@@ -357,5 +370,5 @@ address this in the next part.
 .. |image21| image:: img/05.spawning_mobs/21.mob_timer.png
 .. |image21| image:: img/05.spawning_mobs/21.mob_timer.png
 .. |image22| image:: img/05.spawning_mobs/22.mob_timer_properties.png
 .. |image22| image:: img/05.spawning_mobs/22.mob_timer_properties.png
 .. |image23| image:: img/05.spawning_mobs/23.timeout_signal.png
 .. |image23| image:: img/05.spawning_mobs/23.timeout_signal.png
-.. |image24| image:: img/05.spawning_mobs/24.connect_timer_to_main.png
+.. |image24| image:: img/05.spawning_mobs/24.connect_timer_to_main.webp
 .. |image25| image:: img/05.spawning_mobs/25.spawn_result.png
 .. |image25| image:: img/05.spawning_mobs/25.spawn_result.png

+ 44 - 39
getting_started/first_3d_game/06.jump_and_squash.rst

@@ -3,7 +3,7 @@
 Jumping and squashing monsters
 Jumping and squashing monsters
 ==============================
 ==============================
 
 
-In this part, we'll add the ability to jump, to squash the monsters. In the next
+In this part, we'll add the ability to jump and squash the monsters. In the next
 lesson, we'll make the player die when a monster hits them on the ground.
 lesson, we'll make the player die when a monster hits them on the ground.
 
 
 First, we have to change a few settings related to physics interactions. Enter
 First, we have to change a few settings related to physics interactions. Enter
@@ -51,21 +51,20 @@ Now, we can assign them to our physics nodes.
 Assigning layers and masks
 Assigning layers and masks
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
-In the *Main* scene, select the *Ground* node. In the *Inspector*, expand the
+In the *Main* scene, select the ``Ground`` node. In the *Inspector*, expand the
 *Collision* section. There, you can see the node's layers and masks as a grid of
 *Collision* section. There, you can see the node's layers and masks as a grid of
 buttons.
 buttons.
 
 
 |image2|
 |image2|
 
 
 The ground is part of the world, so we want it to be part of the third layer.
 The ground is part of the world, so we want it to be part of the third layer.
-Click the lit button to toggle off the first *Layer* and toggle on the third
-one. Then, toggle off the *Mask* by clicking on it.
+Click the lit button to toggle **off** the first *Layer* and toggle **on** the third
+one. Then, toggle **off** the *Mask* by clicking on it.
 
 
 |image3|
 |image3|
 
 
-As I mentioned above, the *Mask* property allows a node to listen to interaction
-with other physics objects, but we don't need it to have collisions. The
-*Ground* doesn't need to listen to anything; it's just there to prevent
+As mentioned before, the *Mask* property allows a node to listen to interaction
+with other physics objects, but we don't need it to have collisions. ``Ground`` doesn't need to listen to anything; it's just there to prevent
 creatures from falling.
 creatures from falling.
 
 
 Note that you can click the "..." button on the right side of the properties to
 Note that you can click the "..." button on the right side of the properties to
@@ -73,17 +72,17 @@ see a list of named checkboxes.
 
 
 |image4|
 |image4|
 
 
-Next up are the *Player* and the *Mob*. Open ``Player.tscn`` by double-clicking
+Next up are the ``Player`` and the ``Mob``. Open ``Player.tscn`` by double-clicking
 the file in the *FileSystem* dock.
 the file in the *FileSystem* dock.
 
 
 Select the *Player* node and set its *Collision -> Mask* to both "enemies" and
 Select the *Player* node and set its *Collision -> Mask* to both "enemies" and
-"world". You can leave the default *Layer* property as the first layer is the
-"player" one.
+"world". You can leave the default *Layer* property as it is, because the first layer is the
+"player" layer.
 
 
 |image5|
 |image5|
 
 
 Then, open the *Mob* scene by double-clicking on ``Mob.tscn`` and select the
 Then, open the *Mob* scene by double-clicking on ``Mob.tscn`` and select the
-*Mob* node.
+``Mob`` node.
 
 
 Set its *Collision -> Layer* to "enemies" and unset its *Collision -> Mask*,
 Set its *Collision -> Layer* to "enemies" and unset its *Collision -> Mask*,
 leaving the mask empty.
 leaving the mask empty.
@@ -91,7 +90,7 @@ leaving the mask empty.
 |image6|
 |image6|
 
 
 These settings mean the monsters will move through one another. If you want the
 These settings mean the monsters will move through one another. If you want the
-monsters to collide with and slide against each other, turn on the "enemies"
+monsters to collide with and slide against each other, turn **on** the "enemies"
 mask.
 mask.
 
 
 .. note::
 .. note::
@@ -125,8 +124,7 @@ the ``jump_impulse``.
     [Export]
     [Export]
     public int JumpImpulse = 20;
     public int JumpImpulse = 20;
 
 
-Inside ``_physics_process()``, add the following code before the line where we
-called ``move_and_slide()``.
+Inside ``_physics_process()``, add the following code before the ``move_and_slide()`` codeblock.
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -136,7 +134,7 @@ called ``move_and_slide()``.
 
 
        # Jumping.
        # Jumping.
        if is_on_floor() and Input.is_action_just_pressed("jump"):
        if is_on_floor() and Input.is_action_just_pressed("jump"):
-           velocity.y += jump_impulse
+           target_velocity.y = jump_impulse
 
 
        #...
        #...
 
 
@@ -149,7 +147,7 @@ called ``move_and_slide()``.
         // Jumping.
         // Jumping.
         if (IsOnFloor() && Input.IsActionJustPressed("jump"))
         if (IsOnFloor() && Input.IsActionJustPressed("jump"))
         {
         {
-            _velocity.y += JumpImpulse;
+            _velocity.y = JumpImpulse;
         }
         }
 
 
         // ...
         // ...
@@ -222,7 +220,7 @@ when jumping.
     [Export]
     [Export]
     public int BounceImpulse = 16;
     public int BounceImpulse = 16;
 
 
-Then, at the bottom of ``_physics_process()``, add the following loop. With
+Then, after the **Jumping** codeblock we added above in ``_physics_process()``, add the following loop. With
 ``move_and_slide()``, Godot makes the body move sometimes multiple times in a
 ``move_and_slide()``, Godot makes the body move sometimes multiple times in a
 row to smooth out the character's motion. So we have to loop over all collisions
 row to smooth out the character's motion. So we have to loop over all collisions
 that may have happened.
 that may have happened.
@@ -237,17 +235,24 @@ With this code, if no collisions occurred on a given frame, the loop won't run.
 
 
    func _physics_process(delta):
    func _physics_process(delta):
        #...
        #...
-       for index in range(get_slide_count()):
-           # We check every collision that occurred this frame.
-           var collision = get_slide_collision(index)
-           # If we collide with a monster...
-           if collision.collider.is_in_group("mob"):
-               var mob = collision.collider
-               # ...we check that we are hitting it from above.
-               if Vector3.UP.dot(collision.normal) > 0.1:
-                   # If so, we squash it and bounce.
-                   mob.squash()
-                   velocity.y = bounce_impulse
+       # Iterate through all collisions that occurred this frame
+        # in C this would be for(int i = 0; i < collisions.Count; i++)
+        for index in range(get_slide_collision_count()):
+            # We get one of the collisions with the player
+            var collision = get_slide_collision(index)
+
+            # If the collision is with ground
+            if (collision.get_collider() == null):
+                continue
+
+            # If the collider is with a mob
+            if collision.get_collider().is_in_group("mob"):
+                var mob = collision.get_collider()
+                # we check that we are hitting it from above.
+                if Vector3.UP.dot(collision.get_normal()) > 0.1:
+                    # If so, we squash it and bounce.
+                    mob.squash()
+                    target_velocity.y = bounce_impulse
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
@@ -275,22 +280,22 @@ With this code, if no collisions occurred on a given frame, the loop won't run.
 
 
  That's a lot of new functions. Here's some more information about them.
  That's a lot of new functions. Here's some more information about them.
 
 
-The functions ``get_slide_count()`` and ``get_slide_collision()`` both come from
-the :ref:`CharacterBody3D<class_CharacterBody3D>` class and are related to
+The functions ``get_slide_collision_count()`` and ``get_slide_collision()`` both come from
+the :ref:`CharacterBody3D <class_CharacterBody3D>` class and are related to
 ``move_and_slide()``.
 ``move_and_slide()``.
 
 
 ``get_slide_collision()`` returns a
 ``get_slide_collision()`` returns a
 :ref:`KinematicCollision3D<class_KinematicCollision3D>` object that holds
 :ref:`KinematicCollision3D<class_KinematicCollision3D>` object that holds
 information about where and how the collision occurred. For example, we use its
 information about where and how the collision occurred. For example, we use its
-``collider`` property to check if we collided with a "mob" by calling
-``is_in_group()`` on it: ``collision.collider.is_in_group("mob")``.
+``get_collider`` property to check if we collided with a "mob" by calling
+``is_in_group()`` on it: ``collision.get_collider(index).is_in_group("mob")``.
 
 
 .. note::
 .. note::
 
 
     The method ``is_in_group()`` is available on every :ref:`Node<class_Node>`.
     The method ``is_in_group()`` is available on every :ref:`Node<class_Node>`.
 
 
 To check that we are landing on the monster, we use the vector dot product:
 To check that we are landing on the monster, we use the vector dot product:
-``Vector3.UP.dot(collision.normal) > 0.1``. The collision normal is a 3D vector
+``Vector3.UP.dot(collision.get_normal()) > 0.1``. The collision normal is a 3D vector
 that is perpendicular to the plane where the collision occurred. The dot product
 that is perpendicular to the plane where the collision occurred. The dot product
 allows us to compare it to the up direction.
 allows us to compare it to the up direction.
 
 
@@ -298,7 +303,7 @@ With dot products, when the result is greater than ``0``, the two vectors are at
 an angle of fewer than 90 degrees. A value higher than ``0.1`` tells us that we
 an angle of fewer than 90 degrees. A value higher than ``0.1`` tells us that we
 are roughly above the monster.
 are roughly above the monster.
 
 
-We are calling one undefined function, ``mob.squash()``. We have to add it to
+We are calling one undefined function, ``mob.squash()``, so we have to add it to
 the Mob class.
 the Mob class.
 
 
 Open the script ``Mob.gd`` by double-clicking on it in the *FileSystem* dock. At
 Open the script ``Mob.gd`` by double-clicking on it in the *FileSystem* dock. At
@@ -343,11 +348,11 @@ With that, you should be able to kill monsters by jumping on them. You can press
 However, the player won't die yet. We'll work on that in the next part.
 However, the player won't die yet. We'll work on that in the next part.
 
 
 .. |image0| image:: img/06.jump_and_squash/02.project_settings.png
 .. |image0| image:: img/06.jump_and_squash/02.project_settings.png
-.. |image1| image:: img/06.jump_and_squash/03.physics_layers.png
-.. |image2| image:: img/06.jump_and_squash/04.default_physics_properties.png
-.. |image3| image:: img/06.jump_and_squash/05.toggle_layer_and_mask.png
+.. |image1| image:: img/06.jump_and_squash/03.physics_layers.webp
+.. |image2| image:: img/06.jump_and_squash/04.default_physics_properties.webp
+.. |image3| image:: img/06.jump_and_squash/05.toggle_layer_and_mask.webp
 .. |image4| image:: img/06.jump_and_squash/06.named_checkboxes.png
 .. |image4| image:: img/06.jump_and_squash/06.named_checkboxes.png
-.. |image5| image:: img/06.jump_and_squash/07.player_physics_mask.png
-.. |image6| image:: img/06.jump_and_squash/08.mob_physics_mask.png
+.. |image5| image:: img/06.jump_and_squash/07.player_physics_mask.webp
+.. |image6| image:: img/06.jump_and_squash/08.mob_physics_mask.webp
 .. |image7| image:: img/06.jump_and_squash/09.groups_tab.png
 .. |image7| image:: img/06.jump_and_squash/09.groups_tab.png
 .. |image8| image:: img/06.jump_and_squash/10.group_scene_icon.png
 .. |image8| image:: img/06.jump_and_squash/10.group_scene_icon.png

+ 155 - 129
getting_started/first_3d_game/07.killing_player.rst

@@ -9,14 +9,15 @@ Let's fix this.
 We want to detect being hit by an enemy differently from squashing them.
 We want to detect being hit by an enemy differently from squashing them.
 We want the player to die when they're moving on the floor, but not if
 We want the player to die when they're moving on the floor, but not if
 they're in the air. We could use vector math to distinguish the two
 they're in the air. We could use vector math to distinguish the two
-kinds of collisions. Instead, though, we will use an *Area* node, which
+kinds of collisions. Instead, though, we will use an :ref:`Area3D <class_Area3D>` node, which
 works well for hitboxes.
 works well for hitboxes.
 
 
 Hitbox with the Area node
 Hitbox with the Area node
 -------------------------
 -------------------------
 
 
-Head back to the *Player* scene and add a new *Area* node. Name it
-*MobDetector*. Add a *CollisionShape* node as a child of it.
+Head back to the ``Player.tscn`` scene and add a new child node :ref:`Area3D <class_Area3D>`. Name it
+``MobDetector``
+Add a :ref:`CollisionShape3D <class_CollisionShape3D>` node as a child of it.
 
 
 |image0|
 |image0|
 
 
@@ -38,8 +39,8 @@ monster's collision box.
 
 
 The wider the cylinder, the more easily the player will get killed.
 The wider the cylinder, the more easily the player will get killed.
 
 
-Next, select the *MobDetector* node again, and in the *Inspector*, turn
-off its *Monitorable* property. This makes it so other physics nodes
+Next, select the ``MobDetector`` node again, and in the *Inspector*, turn
+**off** its *Monitorable* property. This makes it so other physics nodes
 cannot detect the area. The complementary *Monitoring* property allows
 cannot detect the area. The complementary *Monitoring* property allows
 it to detect collisions. Then, remove the *Collision -> Layer* and set
 it to detect collisions. Then, remove the *Collision -> Layer* and set
 the mask to the "enemies" layer.
 the mask to the "enemies" layer.
@@ -47,14 +48,14 @@ the mask to the "enemies" layer.
 |image3|
 |image3|
 
 
 When areas detect a collision, they emit signals. We're going to connect
 When areas detect a collision, they emit signals. We're going to connect
-one to the *Player* node. In the *Node* tab, double-click the
-``body_entered`` signal and connect it to the *Player*.
+one to the ``Player`` node. Select ``MobDetector`` and go to *Inspector*'s *Node* tab, double-click the
+``body_entered`` signal and connect it to the ``Player``
 
 
 |image4|
 |image4|
 
 
-The *MobDetector* will emit ``body_entered`` when a *CharacterBody3D* or a
-*RigidBody* node enters it. As it only masks the "enemies" physics
-layers, it will only detect the *Mob* nodes.
+The *MobDetector* will emit ``body_entered`` when a :ref:`CharacterBody3D <class_CharacterBody3D>` or a
+:ref:`RigidBody3D <class_RigidBody3D>` node enters it. As it only masks the "enemies" physics
+layers, it will only detect the ``Mob`` nodes.
 
 
 Code-wise, we're going to do two things: emit a signal we'll later use
 Code-wise, we're going to do two things: emit a signal we'll later use
 to end the game and destroy the player. We can wrap these operations in
 to end the game and destroy the player. We can wrap these operations in
@@ -74,7 +75,7 @@ a ``die()`` function that helps us put a descriptive label on the code.
        queue_free()
        queue_free()
 
 
 
 
-   func _on_MobDetector_body_entered(_body):
+   func _on_mob_detector_body_entered(_body):
        die()
        die()
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
@@ -100,30 +101,37 @@ a ``die()`` function that helps us put a descriptive label on the code.
     }
     }
 
 
 Try the game again by pressing :kbd:`F5`. If everything is set up correctly,
 Try the game again by pressing :kbd:`F5`. If everything is set up correctly,
-the character should die when an enemy runs into it.
+the character should die when an enemy runs into the collider. Note that without a ``Player``, the following line
 
 
-However, note that this depends entirely on the size and position of the
-*Player* and the *Mob*\ 's collision shapes. You may need to move them
+.. tabs::
+   .. code-tab:: gdscript GDScript
+
+    var player_position = $Player.transform.origin
+
+gives error because there is no $Player!
+
+Also note that the enemy colliding with the player and dying depends on the size and position of the
+``Player`` and the ``Mob``\ 's collision shapes. You may need to move them
 and resize them to achieve a tight game feel.
 and resize them to achieve a tight game feel.
 
 
 Ending the game
 Ending the game
 ---------------
 ---------------
 
 
-We can use the *Player*\ 's ``hit`` signal to end the game. All we need
-to do is connect it to the *Main* node and stop the *MobTimer* in
+We can use the ``Player``\ 's ``hit`` signal to end the game. All we need
+to do is connect it to the ``Main`` node and stop the ``MobTimer`` in
 reaction.
 reaction.
 
 
-Open ``Main.tscn``, select the *Player* node, and in the *Node* dock,
-connect its ``hit`` signal to the *Main* node.
+Open ``Main.tscn``, select the ``Player`` node, and in the *Node* dock,
+connect its ``hit`` signal to the ``Main`` node.
 
 
 |image5|
 |image5|
 
 
-Get and stop the timer in the ``_on_Player_hit()`` function.
+Get the timer, and stop it, in the ``_on_player_hit()`` function.
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   func _on_Player_hit():
+   func _on_player_hit():
        $MobTimer.stop()
        $MobTimer.stop()
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
@@ -147,7 +155,7 @@ animations.
 Code checkpoint
 Code checkpoint
 ---------------
 ---------------
 
 
-Here are the complete scripts for the *Main*, *Mob*, and *Player* nodes,
+Here are the complete scripts for the ``Main``, ``Mob``, and ``Player`` nodes,
 for reference. You can use them to compare and check your code.
 for reference. You can use them to compare and check your code.
 
 
 Starting with ``Main.gd``.
 Starting with ``Main.gd``.
@@ -155,35 +163,32 @@ Starting with ``Main.gd``.
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   extends Node
-
-   export(PackedScene) var mob_scene
+    extends Node
 
 
+    @export var mob_scene: PackedScene
 
 
-   func _ready():
-       randomize()
+    func _ready():
+        randomize()
 
 
 
 
-   func _on_MobTimer_timeout():
-       # Create a new instance of the Mob scene.
-       var mob = mob_scene.instantiate()
+    func _on_mob_timer_timeout():
+        # Create a new instance of the Mob scene.
+        var mob = mob_scene.instantiate()
 
 
-       # Choose a random location on the SpawnPath.
-       var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
-       # And give it a random offset.
-       mob_spawn_location.unit_offset = randf()
+        # Choose a random location on the SpawnPath.
+        # We store the reference to the SpawnLocation node.
+        var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
+        # And give it a random offset.
+        mob_spawn_location.progress_ratio = randf()
 
 
-       # Communicate the spawn location and the player's location to the mob.
-       var player_position = $Player.transform.origin
-       mob.initialize(mob_spawn_location.translation, player_position)
+        var player_position = $Player.position
+        mob.initialize(mob_spawn_location.position, player_position)
 
 
-       # Spawn the mob by adding it to the Main scene.
-       add_child(mob)
-
-
-   func _on_Player_hit():
-       $MobTimer.stop()
+        # Spawn the mob by adding it to the Main scene.
+        add_child(mob)
 
 
+    func _on_player_hit():
+        $MobTimer.stop()
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Main : Node
     public class Main : Node
@@ -210,7 +215,7 @@ Starting with ``Main.gd``.
             mobSpawnLocation.UnitOffset = GD.Randf();
             mobSpawnLocation.UnitOffset = GD.Randf();
 
 
             // Communicate the spawn location and the player's location to the mob.
             // Communicate the spawn location and the player's location to the mob.
-            Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
+            Vector3 playerPosition = GetNode<Player>("Player").position;
             mob.Initialize(mobSpawnLocation.Translation, playerPosition);
             mob.Initialize(mobSpawnLocation.Translation, playerPosition);
 
 
             // Spawn the mob by adding it to the Main scene.
             // Spawn the mob by adding it to the Main scene.
@@ -228,40 +233,42 @@ Next is ``Mob.gd``.
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   extends CharacterBody3D
-
-   # Emitted when the player jumped on the mob.
-   signal squashed
+    extends CharacterBody3D
 
 
-   # Minimum speed of the mob in meters per second.
-   @export var min_speed = 10
-   # Maximum speed of the mob in meters per second.
-   @export var max_speed = 18
+    # Minimum speed of the mob in meters per second.
+    @export var min_speed = 10
+    # Maximum speed of the mob in meters per second.
+    @export var max_speed = 18
 
 
-   var velocity = Vector3.ZERO
+    # Emitted when the player jumped on the mob
+    signal squashed
 
 
+    func _physics_process(_delta):
+        move_and_slide()
 
 
-   func _physics_process(_delta):
-       move_and_slide(velocity)
+    # This function will be called from the Main scene.
+    func initialize(start_position, player_position):
+        # We position the mob by placing it at start_position
+        # and rotate it towards player_position, so it looks at the player.
+        look_at_from_position(start_position, player_position, Vector3.UP)
+        # In this rotation^, the mob will move directly towards the player
+        # so we rotate it randomly within range of -90 and +90 degrees.
+        rotate_y(randf_range(-PI / 4, PI / 4))
 
 
+        # We calculate a random speed (integer)
+        var random_speed = randi_range(min_speed, max_speed)
+        # We calculate a forward velocity that represents the speed.
+        velocity = Vector3.FORWARD * random_speed
+        # We then rotate the velocity vector based on the mob's Y rotation
+        # in order to move in the direction the mob is looking.
+        velocity = velocity.rotated(Vector3.UP, rotation.y)
 
 
-   func initialize(start_position, player_position):
-       look_at_from_position(start_position, player_position, Vector3.UP)
-       rotate_y(rand_range(-PI / 4, PI / 4))
-
-       var random_speed = rand_range(min_speed, max_speed)
-       velocity = Vector3.FORWARD * random_speed
-       velocity = velocity.rotated(Vector3.UP, rotation.y)
-
+    func _on_visible_on_screen_notifier_3d_screen_exited():
+        queue_free()
 
 
     func squash():
     func squash():
-       emit_signal("squashed")
-       queue_free()
-
-
-   func _on_VisibilityNotifier_screen_exited():
-       queue_free()
-
+        emit_signal("squashed")
+        queue_free() # Destroy this node
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Mob : CharacterBody3D
     public class Mob : CharacterBody3D
@@ -306,71 +313,90 @@ Next is ``Mob.gd``.
         }
         }
     }
     }
 
 
-Finally, the longest script, ``Player.gd``.
+Finally, the longest script, ``Player.gd``:
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
    extends CharacterBody3D
    extends CharacterBody3D
 
 
-   # Emitted when a mob hit the player.
-   signal hit
-
-   # How fast the player moves in meters per second.
-   @export var speed = 14
-   # The downward acceleration when in the air, in meters per second squared.
-   @export var fall_acceleration = 75
-   # Vertical impulse applied to the character upon jumping in meters per second.
-   @export var jump_impulse = 20
-   # Vertical impulse applied to the character upon bouncing over a mob in meters per second.
-   @export var bounce_impulse = 16
-
-   var velocity = Vector3.ZERO
-
-
-   func _physics_process(delta):
-       var direction = Vector3.ZERO
-
-       if Input.is_action_pressed("move_right"):
-           direction.x += 1
-       if Input.is_action_pressed("move_left"):
-           direction.x -= 1
-       if Input.is_action_pressed("move_back"):
-           direction.z += 1
-       if Input.is_action_pressed("move_forward"):
-           direction.z -= 1
-
-       if direction != Vector3.ZERO:
-           direction = direction.normalized()
-           $Pivot.look_at(translation + direction, Vector3.UP)
-
-       velocity.x = direction.x * speed
-       velocity.z = direction.z * speed
-
-       # Jumping.
-       if is_on_floor() and Input.is_action_just_pressed("jump"):
-           velocity.y += jump_impulse
-
-       velocity.y -= fall_acceleration * delta
-       velocity = move_and_slide(velocity, Vector3.UP)
-
-       for index in range(get_slide_count()):
-           var collision = get_slide_collision(index)
-           if collision.collider.is_in_group("mob"):
-               var mob = collision.collider
-               if Vector3.UP.dot(collision.normal) > 0.1:
-                   mob.squash()
-                   velocity.y = bounce_impulse
-
-
-   func die():
-       emit_signal("hit")
-       queue_free()
-
-
-   func _on_MobDetector_body_entered(_body):
-       die()
-
+    signal hit
+
+    # How fast the player moves in meters per second
+    @export var speed = 14
+    # The downward acceleration while in the air, in meters per second squared.
+    @export var fall_acceleration = 75
+    @export var jump_impulse = 20
+    # Vertical impulse applied to the character upon bouncing over a mob
+    # in meters per second.
+    @export var bounce_impulse = 16
+
+    var target_velocity = Vector3.ZERO
+
+
+    func _physics_process(delta):
+        # We create a local variable to store the input direction
+        var direction = Vector3.ZERO
+
+        # We check for each move input and update the direction accordingly
+        if Input.is_action_pressed("move_right"):
+            direction.x = direction.x + 1
+        if Input.is_action_pressed("move_left"):
+            direction.x = direction.x - 1
+        if Input.is_action_pressed("move_back"):
+            # Notice how we are working with the vector's x and z axes.
+            # In 3D, the XZ plane is the ground plane.
+            direction.z = direction.z + 1
+        if Input.is_action_pressed("move_forward"):
+            direction.z = direction.z - 1
+
+        # Prevent diagonal moving fast af
+        if direction != Vector3.ZERO:
+            direction = direction.normalized()
+            $Pivot.look_at(position + direction,Vector3.UP)
+
+        # Ground Velocity
+        target_velocity.x = direction.x * speed
+        target_velocity.z = direction.z * speed
+
+        # Vertical Velocity
+        if not is_on_floor(): # If in the air, fall towards the floor
+            target_velocity.y = target_velocity.y - (fall_acceleration * delta)
+
+        # Jumping.
+        if is_on_floor() and Input.is_action_just_pressed("jump"):
+            target_velocity.y = jump_impulse
+
+        # Iterate through all collisions that occurred this frame
+        # in C this would be for(int i = 0; i < collisions.Count; i++)
+        for index in range(get_slide_collision_count()):
+            # We get one of the collisions with the player
+            var collision = get_slide_collision(index)
+
+            # If the collision is with ground
+            if (collision.get_collider() == null):
+                continue
+
+            # If the collider is with a mob
+            if collision.get_collider().is_in_group("mob"):
+                var mob = collision.get_collider()
+                # we check that we are hitting it from above.
+                if Vector3.UP.dot(collision.get_normal()) > 0.1:
+                    # If so, we squash it and bounce.
+                    mob.squash()
+                    target_velocity.y = bounce_impulse
+
+        # Moving the Character
+        velocity = target_velocity
+        move_and_slide()
+
+    # And this function at the bottom.
+    func die():
+        emit_signal("hit")
+        queue_free()
+
+    func _on_mob_detector_body_entered(body):
+        die()
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Player : CharacterBody3D
     public class Player : CharacterBody3D
@@ -464,6 +490,6 @@ See you in the next lesson to add the score and the retry option.
 .. |image0| image:: img/07.killing_player/01.adding_area_node.png
 .. |image0| image:: img/07.killing_player/01.adding_area_node.png
 .. |image1| image:: img/07.killing_player/02.cylinder_shape.png
 .. |image1| image:: img/07.killing_player/02.cylinder_shape.png
 .. |image2| image:: img/07.killing_player/03.cylinder_in_editor.png
 .. |image2| image:: img/07.killing_player/03.cylinder_in_editor.png
-.. |image3| image:: img/07.killing_player/04.mob_detector_properties.png
+.. |image3| image:: img/07.killing_player/04.mob_detector_properties.webp
 .. |image4| image:: img/07.killing_player/05.body_entered_signal.png
 .. |image4| image:: img/07.killing_player/05.body_entered_signal.png
 .. |image5| image:: img/07.killing_player/06.player_hit_signal.png
 .. |image5| image:: img/07.killing_player/06.player_hit_signal.png

+ 79 - 72
getting_started/first_3d_game/08.score_and_replay.rst

@@ -9,11 +9,11 @@ the game.
 We have to keep track of the current score in a variable and display it on
 We have to keep track of the current score in a variable and display it on
 screen using a minimal interface. We will use a text label to do that.
 screen using a minimal interface. We will use a text label to do that.
 
 
-In the main scene, add a new *Control* node as a child of *Main* and name it
-*UserInterface*. You will automatically be taken to the 2D screen, where you can
+In the main scene, add a new child node :ref:`Control <class_Control>` to ``Main`` and name it
+``UserInterface``. You will automatically be taken to the 2D screen, where you can
 edit your User Interface (UI).
 edit your User Interface (UI).
 
 
-Add a *Label* node and rename it to *ScoreLabel*.
+Add a :ref:`Label <class_Label>` node and name it ``ScoreLabel``
 
 
 |image0|
 |image0|
 
 
@@ -24,28 +24,25 @@ In the *Inspector*, set the *Label*'s *Text* to a placeholder like "Score: 0".
 Also, the text is white by default, like our game's background. We need to
 Also, the text is white by default, like our game's background. We need to
 change its color to see it at runtime.
 change its color to see it at runtime.
 
 
-Scroll down to *Theme Overrides*, and expand *Colors* and click the black box next to *Font Color* to
-tint the text.
+Scroll down to *Theme Overrides*, and expand *Colors*
+and enable *Font Color* in order to tint the text to black
+(which contrasts well with the white 3D scene)
 
 
 |image2|
 |image2|
 
 
-Pick a dark tone so it contrasts well with the 3D scene.
-
-|image3|
-
 Finally, click and drag on the text in the viewport to move it away from the
 Finally, click and drag on the text in the viewport to move it away from the
 top-left corner.
 top-left corner.
 
 
 |image4|
 |image4|
 
 
-The *UserInterface* node allows us to group our UI in a branch of the scene tree
+The ``UserInterface`` node allows us to group our UI in a branch of the scene tree
 and use a theme resource that will propagate to all its children. We'll use it
 and use a theme resource that will propagate to all its children. We'll use it
 to set our game's font.
 to set our game's font.
 
 
 Creating a UI theme
 Creating a UI theme
 -------------------
 -------------------
 
 
-Once again, select the *UserInterface* node. In the *Inspector*, create a new
+Once again, select the ``UserInterface`` node. In the *Inspector*, create a new
 theme resource in *Theme -> Theme*.
 theme resource in *Theme -> Theme*.
 
 
 |image5|
 |image5|
@@ -63,11 +60,11 @@ By default, a theme only has one property, the *Default Font*.
     interfaces, but that is beyond the scope of this series. To learn more about
     interfaces, but that is beyond the scope of this series. To learn more about
     creating and editing themes, see :ref:`doc_gui_skinning`.
     creating and editing themes, see :ref:`doc_gui_skinning`.
 
 
-Click the *Default Font* property and create a new *DynamicFont*.
+Click the *Default Font* property and create a new :ref:`FontVariation <class_FontVariation>`
 
 
 |image7|
 |image7|
 
 
-Expand the *DynamicFont* by clicking on it and expand its *Font* section. There,
+Expand the :ref:`FontVariation <class_FontVariation>` by clicking on it and expand its *Font* section. There,
 you will see an empty *Font Data* field.
 you will see an empty *Font Data* field.
 
 
 |image8|
 |image8|
@@ -75,7 +72,7 @@ you will see an empty *Font Data* field.
 This one expects a font file like the ones you have on your computer. Two common
 This one expects a font file like the ones you have on your computer. Two common
 font file formats are TrueType Font (TTF) and OpenType Font (OTF).
 font file formats are TrueType Font (TTF) and OpenType Font (OTF).
 
 
-In the *FileSystem* dock, Expand the ``fonts`` directory and click and drag the
+In the *FileSystem* dock, expand the ``fonts`` directory and click and drag the
 ``Montserrat-Medium.ttf`` file we included in the project onto the *Font Data*.
 ``Montserrat-Medium.ttf`` file we included in the project onto the *Font Data*.
 The text will reappear in the theme preview.
 The text will reappear in the theme preview.
 
 
@@ -87,7 +84,7 @@ the text's size.
 Keeping track of the score
 Keeping track of the score
 --------------------------
 --------------------------
 
 
-Let's work on the score next. Attach a new script to the *ScoreLabel* and define
+Let's work on the score next. Attach a new script to the ``ScoreLabel`` and define
 the ``score`` variable.
 the ``score`` variable.
 
 
 .. tabs::
 .. tabs::
@@ -105,8 +102,8 @@ the ``score`` variable.
     }
     }
 
 
 The score should increase by ``1`` every time we squash a monster. We can use
 The score should increase by ``1`` every time we squash a monster. We can use
-their ``squashed`` signal to know when that happens. However, as we instantiate
-monsters from the code, we cannot do the connection in the editor.
+their ``squashed`` signal to know when that happens. However, because we instantiate
+monsters from the code, we cannot connect the mob signal to the ``ScoreLabel`` via the editor.
 
 
 Instead, we have to make the connection from the code every time we spawn a
 Instead, we have to make the connection from the code every time we spawn a
 monster.
 monster.
@@ -119,16 +116,16 @@ the script editor's left column.
 Alternatively, you can double-click the ``Main.gd`` file in the *FileSystem*
 Alternatively, you can double-click the ``Main.gd`` file in the *FileSystem*
 dock.
 dock.
 
 
-At the bottom of the ``_on_MobTimer_timeout()`` function, add the following
-line.
+At the bottom of the ``_on_mob_timer_timeout()`` function, add the following
+line:
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   func _on_MobTimer_timeout():
+   func _on_mob_timer_timeout():
        #...
        #...
-       # We connect the mob to the score label to update the score upon squashing one.
-       mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
+        # We connect the mob to the score label to update the score upon squashing one.
+        mob.squashed.connect($UserInterface/ScoreLabel._on_Mob_squashed.bind())
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
@@ -140,7 +137,7 @@ line.
     }
     }
 
 
 This line means that when the mob emits the ``squashed`` signal, the
 This line means that when the mob emits the ``squashed`` signal, the
-*ScoreLabel* node will receive it and call the function ``_on_Mob_squashed()``.
+``ScoreLabel`` node will receive it and call the function ``_on_Mob_squashed()``.
 
 
 Head back to the ``ScoreLabel.gd`` script to define the ``_on_Mob_squashed()``
 Head back to the ``ScoreLabel.gd`` script to define the ``_on_Mob_squashed()``
 callback function.
 callback function.
@@ -164,13 +161,19 @@ There, we increment the score and update the displayed text.
 
 
 The second line uses the value of the ``score`` variable to replace the
 The second line uses the value of the ``score`` variable to replace the
 placeholder ``%s``. When using this feature, Godot automatically converts values
 placeholder ``%s``. When using this feature, Godot automatically converts values
-to text, which is convenient to output text in labels or using the ``print()``
+to string text, which is convenient to output text in labels or using the ``print()``
 function.
 function.
 
 
 .. seealso::
 .. seealso::
 
 
     You can learn more about string formatting here: :ref:`doc_gdscript_printf`.
     You can learn more about string formatting here: :ref:`doc_gdscript_printf`.
 
 
+
+.. note::
+
+   If you get an error when you squash a mob
+   check your capital letters in the signal "_on_Mob_squashed"
+
 You can now play the game and squash a few enemies to see the score
 You can now play the game and squash a few enemies to see the score
 increase.
 increase.
 
 
@@ -190,8 +193,8 @@ Retrying the game
 We'll now add the ability to play again after dying. When the player dies, we'll
 We'll now add the ability to play again after dying. When the player dies, we'll
 display a message on the screen and wait for input.
 display a message on the screen and wait for input.
 
 
-Head back to the *Main* scene, select the *UserInterface* node, add a
-*ColorRect* node as a child of it and name it *Retry*. This node fills a
+Head back to the ``Main.tscn`` scene, select the ``UserInterface`` node, add a
+child node *ColorRect*, and name it ``Retry``. This node fills a
 rectangle with a uniform color and will serve as an overlay to darken the
 rectangle with a uniform color and will serve as an overlay to darken the
 screen.
 screen.
 
 
@@ -204,27 +207,27 @@ Open it and apply the *Full Rect* command.
 
 
 |image13|
 |image13|
 
 
-Nothing happens. Well, almost nothing: only the four green pins move to the
+Nothing happens. Well, almost nothing; only the four green pins move to the
 corners of the selection box.
 corners of the selection box.
 
 
 |image14|
 |image14|
 
 
 This is because UI nodes (all the ones with a green icon) work with anchors and
 This is because UI nodes (all the ones with a green icon) work with anchors and
-margins relative to their parent's bounding box. Here, the *UserInterface* node
-has a small size and the *Retry* one is limited by it.
+margins relative to their parent's bounding box. Here, the ``UserInterface`` node
+has a small size and the ``Retry`` one is limited by it.
 
 
-Select the *UserInterface* and apply *Layout -> Full Rect* to it as well. The
-*Retry* node should now span the whole viewport.
+Select the ``UserInterface`` and apply *Layout -> Full Rect* to it as well. The
+``Retry`` node should now span the whole viewport.
 
 
-Let's change its color so it darkens the game area. Select *Retry* and in the
+Let's change its color so it darkens the game area. Select ``Retry`` and in the
 *Inspector*, set its *Color* to something both dark and transparent. To do so,
 *Inspector*, set its *Color* to something both dark and transparent. To do so,
 in the color picker, drag the *A* slider to the left. It controls the color's
 in the color picker, drag the *A* slider to the left. It controls the color's
-alpha channel, that is to say, its opacity.
+Alpha channel, that is to say, its opacity/transparency.
 
 
 |image15|
 |image15|
 
 
-Next, add a *Label* as a child of *Retry* and give it the *Text* "Press Enter to
-retry."
+Next, add a :ref:`Label <class_Label>` as a child of ``Retry`` and give it the *Text*
+"Press Enter to retry."
 
 
 |image16|
 |image16|
 
 
@@ -236,7 +239,7 @@ to it.
 Coding the retry option
 Coding the retry option
 ~~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~~
 
 
-We can now head to the code to show and hide the *Retry* node when the player
+We can now head to the code to show and hide the ``Retry`` node when the player
 dies and plays again.
 dies and plays again.
 
 
 Open the script ``Main.gd``. First, we want to hide the overlay at the start of
 Open the script ``Main.gd``. First, we want to hide the overlay at the start of
@@ -274,11 +277,11 @@ Then, when the player gets hit, we show the overlay.
         GetNode<Control>("UserInterface/Retry").Show();
         GetNode<Control>("UserInterface/Retry").Show();
     }
     }
 
 
-Finally, when the *Retry* node is visible, we need to listen to the player's
+Finally, when the ``Retry`` node is visible, we need to listen to the player's
 input and restart the game if they press enter. To do this, we use the built-in
 input and restart the game if they press enter. To do this, we use the built-in
-``_unhandled_input()`` callback.
+``_unhandled_input()`` callback, which is triggered on any input.
 
 
-If the player pressed the predefined ``ui_accept`` input action and *Retry* is
+If the player pressed the predefined ``ui_accept`` input action and ``Retry`` is
 visible, we reload the current scene.
 visible, we reload the current scene.
 
 
 .. tabs::
 .. tabs::
@@ -310,7 +313,7 @@ Adding music
 To add music that plays continuously in the background, we're going to use
 To add music that plays continuously in the background, we're going to use
 another feature in Godot: :ref:`autoloads <doc_singletons_autoload>`.
 another feature in Godot: :ref:`autoloads <doc_singletons_autoload>`.
 
 
-To play audio, all you need to do is add an *AudioStreamPlayer* node to your
+To play audio, all you need to do is add an :ref:`AudioStreamPlayer <class_AudioStreamPlayer>` node to your
 scene and attach an audio file to it. When you start the scene, it can play
 scene and attach an audio file to it. When you start the scene, it can play
 automatically. However, when you reload the scene, like we do to play again, the
 automatically. However, when you reload the scene, like we do to play again, the
 audio nodes are also reset, and the music starts back from the beginning.
 audio nodes are also reset, and the music starts back from the beginning.
@@ -323,8 +326,8 @@ Create a new scene by going to the *Scene* menu and clicking *New Scene*.
 
 
 |image18|
 |image18|
 
 
-Click the *Other Node* button to create an *AudioStreamPlayer* and rename it to
-*MusicPlayer*.
+Click the *Other Node* button to create an :ref:`AudioStreamPlayer2D <class_AudioStreamPlayer2D>` and rename it to
+``MusicPlayer``.
 
 
 |image19|
 |image19|
 
 
@@ -346,8 +349,8 @@ click the *Add* button on the right to register the node.
 
 
 |image21|
 |image21|
 
 
-If you run the game now, the music will play automatically. And even when you
-lose and retry, it keeps going.
+``MusicPlayer.tscn`` now loads into any scene you open or play.
+So if you run the game now, the music will play automatically in any scene.
 
 
 Before we wrap up this lesson, here's a quick look at how it works under the
 Before we wrap up this lesson, here's a quick look at how it works under the
 hood. When you run the game, your *Scene* dock changes to give you two tabs:
 hood. When you run the game, your *Scene* dock changes to give you two tabs:
@@ -361,7 +364,7 @@ instantiated mobs at the bottom.
 
 
 |image23|
 |image23|
 
 
-At the top are the autoloaded *MusicPlayer* and a *root* node, which is your
+At the top are the autoloaded ``MusicPlayer`` and a *root* node, which is your
 game's viewport.
 game's viewport.
 
 
 And that does it for this lesson. In the next part, we'll add an animation to
 And that does it for this lesson. In the next part, we'll add an animation to
@@ -372,38 +375,42 @@ Here is the complete ``Main.gd`` script for reference.
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   extends Node
-
-   @export var mob_scene: PackedScene
-
+    extends Node
 
 
-   func _ready():
-       randomize()
-       $UserInterface/Retry.hide()
+    @export var mob_scene: PackedScene
 
 
-
-   func _unhandled_input(event):
-       if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
-           get_tree().reload_current_scene()
+    func _ready():
+        randomize()
+        $UserInterface/Retry.hide()
 
 
 
 
-   func _on_MobTimer_timeout():
-       var mob = mob_scene.instantiate()
+    func _on_mob_timer_timeout():
+        # Create a new instance of the Mob scene.
+        var mob = mob_scene.instantiate()
 
 
-       var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
-       mob_spawn_location.unit_offset = randf()
+        # Choose a random location on the SpawnPath.
+        # We store the reference to the SpawnLocation node.
+        var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
+        # And give it a random offset.
+        mob_spawn_location.progress_ratio = randf()
 
 
-       var player_position = $Player.transform.origin
-       mob.initialize(mob_spawn_location.translation, player_position)
+        var player_position = $Player.position
+        mob.initialize(mob_spawn_location.position, player_position)
 
 
-       add_child(mob)
-       mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
+        # Spawn the mob by adding it to the Main scene.
+        add_child(mob)
 
 
+        # We connect the mob to the score label to update the score upon squashing one.
+        mob.squashed.connect($UserInterface/ScoreLabel._on_Mob_squashed.bind())
 
 
-   func _on_Player_hit():
-       $MobTimer.stop()
-       $UserInterface/Retry.show()
+    func _on_player_hit():
+        $MobTimer.stop()
+        $UserInterface/Retry.show()
 
 
+    func _unhandled_input(event):
+        if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
+            # This restarts the current scene.
+            get_tree().reload_current_scene()
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Main : Node
     public class Main : Node
@@ -434,7 +441,7 @@ Here is the complete ``Main.gd`` script for reference.
             var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
             var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
             mobSpawnLocation.UnitOffset = GD.Randf();
             mobSpawnLocation.UnitOffset = GD.Randf();
 
 
-            Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
+            Vector3 playerPosition = GetNode<Player>("Player").position;
             mob.Initialize(mobSpawnLocation.Translation, playerPosition);
             mob.Initialize(mobSpawnLocation.Translation, playerPosition);
 
 
             AddChild(mob);
             AddChild(mob);
@@ -451,14 +458,14 @@ Here is the complete ``Main.gd`` script for reference.
 
 
 .. |image0| image:: img/08.score_and_replay/01.label_node.png
 .. |image0| image:: img/08.score_and_replay/01.label_node.png
 .. |image1| image:: img/08.score_and_replay/02.score_placeholder.png
 .. |image1| image:: img/08.score_and_replay/02.score_placeholder.png
-.. |image2| image:: img/08.score_and_replay/02.score_custom_color.png
+.. |image2| image:: img/08.score_and_replay/02.score_custom_color.webp
 .. |image3| image:: img/08.score_and_replay/02.score_color_picker.png
 .. |image3| image:: img/08.score_and_replay/02.score_color_picker.png
 .. |image4| image:: img/08.score_and_replay/02.score_label_moved.png
 .. |image4| image:: img/08.score_and_replay/02.score_label_moved.png
 .. |image5| image:: img/08.score_and_replay/03.creating_theme.png
 .. |image5| image:: img/08.score_and_replay/03.creating_theme.png
 .. |image6| image:: img/08.score_and_replay/04.theme_preview.png
 .. |image6| image:: img/08.score_and_replay/04.theme_preview.png
-.. |image7| image:: img/08.score_and_replay/05.dynamic_font.png
-.. |image8| image:: img/08.score_and_replay/06.font_data.png
-.. |image9| image:: img/08.score_and_replay/07.font_size.png
+.. |image7| image:: img/08.score_and_replay/05.dynamic_font.webp
+.. |image8| image:: img/08.score_and_replay/06.font_data.webp
+.. |image9| image:: img/08.score_and_replay/07.font_size.webp
 .. |image10| image:: img/08.score_and_replay/08.open_main_script.png
 .. |image10| image:: img/08.score_and_replay/08.open_main_script.png
 .. |image11| image:: img/08.score_and_replay/09.score_in_game.png
 .. |image11| image:: img/08.score_and_replay/09.score_in_game.png
 .. |image12| image:: img/08.score_and_replay/10.layout_icon.png
 .. |image12| image:: img/08.score_and_replay/10.layout_icon.png

+ 150 - 117
getting_started/first_3d_game/09.adding_animations.rst

@@ -17,7 +17,7 @@ Using the animation editor
 The engine comes with tools to author animations in the editor. You can then use
 The engine comes with tools to author animations in the editor. You can then use
 the code to play and control them at runtime.
 the code to play and control them at runtime.
 
 
-Open the player scene, select the player node, and add an *AnimationPlayer* node.
+Open the player scene, select the ``Player`` node, and add an :ref:`AnimationPlayer <class_AnimationPlayer>` node.
 
 
 The *Animation* dock appears in the bottom panel.
 The *Animation* dock appears in the bottom panel.
 
 
@@ -78,13 +78,16 @@ corresponding property. The keyframe gets inserted where your time cursor is in
 the timeline.
 the timeline.
 
 
 Let's insert our first keys. Here, we will animate both the translation and the
 Let's insert our first keys. Here, we will animate both the translation and the
-rotation of the *Character* node.
+rotation of the ``Character`` node.
 
 
-Select the *Character* and click the key icon next to *Translation* in the
-*Inspector*. Do the same for *Rotation Degrees*.
+Select the ``Character`` and in the *Inspector* expand the *Transform* section. Click the key icon next to *Position*, and *Rotation*.
 
 
 |image10|
 |image10|
 
 
+.. image:: img/09.adding_animations/curves.webp
+
+For this tutorial, just create RESET Track(s) which is the default choice
+
 Two tracks appear in the editor with a diamond icon representing each keyframe.
 Two tracks appear in the editor with a diamond icon representing each keyframe.
 
 
 |image11|
 |image11|
@@ -95,12 +98,19 @@ translation key to ``0.2`` seconds and the rotation key to ``0.1`` seconds.
 |image12|
 |image12|
 
 
 Move the time cursor to ``0.5`` seconds by clicking and dragging on the gray
 Move the time cursor to ``0.5`` seconds by clicking and dragging on the gray
-timeline. In the *Inspector*, set the *Translation*'s *Y* axis to about
-``0.65`` meters and the *Rotation Degrees*' *X* axis to ``8``.
+timeline.
+
+.. image:: img/09.adding_animations/timeline_05_click.webp
+
+In the *Inspector*, set the *Position*'s *Y* axis to ``0.65`` meters and the *Rotation*' *X* axis to ``8``.
 
 
 |image13|
 |image13|
 
 
-Create a keyframe for both properties and shift the translation key to ``0.7``
+Create a keyframe for both properties
+
+.. image:: img/09.adding_animations/second_keys_both.webp
+
+Now, move the position keyframe to ``0.7``
 seconds by dragging it on the timeline.
 seconds by dragging it on the timeline.
 
 
 |image14|
 |image14|
@@ -114,9 +124,11 @@ seconds by dragging it on the timeline.
     make them feel alive.
     make them feel alive.
 
 
 Move the time cursor to the end of the animation, at ``1.2`` seconds. Set the Y
 Move the time cursor to the end of the animation, at ``1.2`` seconds. Set the Y
-translation to about ``0.35`` and the X rotation to ``-9`` degrees. Once again,
+position to about ``0.35`` and the X rotation to ``-9`` degrees. Once again,
 create a key for both properties.
 create a key for both properties.
 
 
+.. image:: img/09.adding_animations/animation_final_keyframes.webp
+
 You can preview the result by clicking the play button or pressing :kbd:`Shift + D`.
 You can preview the result by clicking the play button or pressing :kbd:`Shift + D`.
 Click the stop button or press :kbd:`S` to stop playback.
 Click the stop button or press :kbd:`S` to stop playback.
 
 
@@ -170,7 +182,7 @@ Your animation should look something like this.
 
 
 If you play the game, the player's creature will now float!
 If you play the game, the player's creature will now float!
 
 
-If the creature is a little too close to the floor, you can move the *Pivot* up
+If the creature is a little too close to the floor, you can move the ``Pivot`` up
 to offset it.
 to offset it.
 
 
 Controlling the animation in code
 Controlling the animation in code
@@ -179,7 +191,7 @@ Controlling the animation in code
 We can use code to control the animation playback based on the player's input.
 We can use code to control the animation playback based on the player's input.
 Let's change the animation speed when the character is moving.
 Let's change the animation speed when the character is moving.
 
 
-Open the *Player*'s script by clicking the script icon next to it.
+Open the ``Player``'s script by clicking the script icon next to it.
 
 
 |image22|
 |image22|
 
 
@@ -216,7 +228,7 @@ vector, add the following code.
 This code makes it so when the player moves, we multiply the playback speed by
 This code makes it so when the player moves, we multiply the playback speed by
 ``4``. When they stop, we reset it to normal.
 ``4``. When they stop, we reset it to normal.
 
 
-We mentioned that the pivot could layer transforms on top of the animation. We
+We mentioned that the ``Pivot`` could layer transforms on top of the animation. We
 can make the character arc when jumping using the following line of code. Add it
 can make the character arc when jumping using the following line of code. Add it
 at the end of ``_physics_process()``.
 at the end of ``_physics_process()``.
 
 
@@ -242,10 +254,10 @@ Animating the mobs
 Here's another nice trick with animations in Godot: as long as you use a similar
 Here's another nice trick with animations in Godot: as long as you use a similar
 node structure, you can copy them to different scenes.
 node structure, you can copy them to different scenes.
 
 
-For example, both the *Mob* and the *Player* scenes have a *Pivot* and a
-*Character* node, so we can reuse animations between them.
+For example, both the ``Mob`` and the ``Player`` scenes have a ``Pivot`` and a
+``Character`` node, so we can reuse animations between them.
 
 
-Open the *Player* scene, select the animation player node and open the "float" animation.
+Open the ``Player.tscn`` scene, select the ``AnimationPlayer`` node and open the "float" animation.
 Next, click on **Animation > Copy**. Then open ``Mob.tscn`` and open its animation
 Next, click on **Animation > Copy**. Then open ``Mob.tscn`` and open its animation
 player. Click **Animation > Paste**. That's it; all monsters will now play the float
 player. Click **Animation > Paste**. That's it; all monsters will now play the float
 animation.
 animation.
@@ -282,71 +294,90 @@ Here's the *Player* script.
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   extends CharacterBody3D
-
-   # Emitted when the player was hit by a mob.
-   signal hit
-
-   # How fast the player moves in meters per second.
-   @export var speed = 14
-   # The downward acceleration when in the air, in meters per second per second.
-   @export var fall_acceleration = 75
-   # Vertical impulse applied to the character upon jumping in meters per second.
-   @export var jump_impulse = 20
-   # Vertical impulse applied to the character upon bouncing over a mob in meters per second.
-   @export var bounce_impulse = 16
-
-   var velocity = Vector3.ZERO
-
-
-   func _physics_process(delta):
-       var direction = Vector3.ZERO
-
-       if Input.is_action_pressed("move_right"):
-           direction.x += 1
-       if Input.is_action_pressed("move_left"):
-           direction.x -= 1
-       if Input.is_action_pressed("move_back"):
-           direction.z += 1
-       if Input.is_action_pressed("move_forward"):
-           direction.z -= 1
-
-       if direction != Vector3.ZERO:
-           direction = direction.normalized()
-           $Pivot.look_at(translation + direction, Vector3.UP)
-           $AnimationPlayer.playback_speed = 4
-       else:
-           $AnimationPlayer.playback_speed = 1
-
-       velocity.x = direction.x * speed
-       velocity.z = direction.z * speed
-
-       # Jumping
-       if is_on_floor() and Input.is_action_just_pressed("jump"):
-           velocity.y += jump_impulse
-
-       velocity.y -= fall_acceleration * delta
-       velocity = move_and_slide(velocity, Vector3.UP)
-
-       for index in range(get_slide_count()):
-           var collision = get_slide_collision(index)
-           if collision.collider.is_in_group("mob"):
-               var mob = collision.collider
-               if Vector3.UP.dot(collision.normal) > 0.1:
-                   mob.squash()
-                   velocity.y = bounce_impulse
-
-       $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
-
-
-   func die():
-       emit_signal("hit")
-       queue_free()
-
-
-   func _on_MobDetector_body_entered(_body):
-       die()
-
+    extends CharacterBody3D
+
+    signal hit
+
+    # How fast the player moves in meters per second
+    @export var speed = 14
+    # The downward acceleration while in the air, in meters per second squared.
+    @export var fall_acceleration = 75
+    @export var jump_impulse = 20
+    # Vertical impulse applied to the character upon bouncing over a mob
+    # in meters per second.
+    @export var bounce_impulse = 16
+
+    var target_velocity = Vector3.ZERO
+
+
+    func _physics_process(delta):
+        # We create a local variable to store the input direction
+        var direction = Vector3.ZERO
+
+        # We check for each move input and update the direction accordingly
+        if Input.is_action_pressed("move_right"):
+            direction.x = direction.x + 1
+        if Input.is_action_pressed("move_left"):
+            direction.x = direction.x - 1
+        if Input.is_action_pressed("move_back"):
+            # Notice how we are working with the vector's x and z axes.
+            # In 3D, the XZ plane is the ground plane.
+            direction.z = direction.z + 1
+        if Input.is_action_pressed("move_forward"):
+            direction.z = direction.z - 1
+
+        # Prevent diagonal movement being very fast
+        if direction != Vector3.ZERO:
+            direction = direction.normalized()
+            $Pivot.look_at(position + direction,Vector3.UP)
+            $AnimationPlayer.playback_speed = 4
+        else:
+            $AnimationPlayer.playback_speed = 1
+
+        # Ground Velocity
+        target_velocity.x = direction.x * speed
+        target_velocity.z = direction.z * speed
+
+        # Vertical Velocity
+        if not is_on_floor(): # If in the air, fall towards the floor
+            target_velocity.y = target_velocity.y - (fall_acceleration * delta)
+
+        # Jumping.
+        if is_on_floor() and Input.is_action_just_pressed("jump"):
+            target_velocity.y = jump_impulse
+
+        # Iterate through all collisions that occurred this frame
+        # in C this would be for(int i = 0; i < collisions.Count; i++)
+        for index in range(get_slide_collision_count()):
+            # We get one of the collisions with the player
+            var collision = get_slide_collision(index)
+
+            # If the collision is with ground
+            if (collision.get_collider() == null):
+                continue
+
+            # If the collider is with a mob
+            if collision.get_collider().is_in_group("mob"):
+                var mob = collision.get_collider()
+                # we check that we are hitting it from above.
+                if Vector3.UP.dot(collision.get_normal()) > 0.1:
+                    # If so, we squash it and bounce.
+                    mob.squash()
+                    target_velocity.y = bounce_impulse
+
+        # Moving the Character
+        velocity = target_velocity
+        move_and_slide()
+
+        $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
+
+    # And this function at the bottom.
+    func die():
+        emit_signal("hit")
+        queue_free()
+
+    func _on_mob_detector_body_entered(body):
+        die()
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Player : CharacterBody3D
     public class Player : CharacterBody3D
@@ -449,42 +480,44 @@ And the *Mob*'s script.
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
-   extends CharacterBody3D
-
-   # Emitted when the player jumped on the mob.
-   signal squashed
-
-   # Minimum speed of the mob in meters per second.
-   @export var min_speed = 10
-   # Maximum speed of the mob in meters per second.
-   @export var max_speed = 18
-
-   var velocity = Vector3.ZERO
-
+    extends CharacterBody3D
 
 
-   func _physics_process(_delta):
-       move_and_slide(velocity)
+    # Minimum speed of the mob in meters per second.
+    @export var min_speed = 10
+    # Maximum speed of the mob in meters per second.
+    @export var max_speed = 18
 
 
+    # Emitted when the player jumped on the mob
+    signal squashed
 
 
-   func initialize(start_position, player_position):
-       look_at_from_position(start_position, player_position, Vector3.UP)
-       rotate_y(rand_range(-PI / 4, PI / 4))
-
-       var random_speed = rand_range(min_speed, max_speed)
-       velocity = Vector3.FORWARD * random_speed
-       velocity = velocity.rotated(Vector3.UP, rotation.y)
-
-       $AnimationPlayer.playback_speed = random_speed / min_speed
+    func _physics_process(_delta):
+        move_and_slide()
 
 
+    # This function will be called from the Main scene.
+    func initialize(start_position, player_position):
+        # We position the mob by placing it at start_position
+        # and rotate it towards player_position, so it looks at the player.
+        look_at_from_position(start_position, player_position, Vector3.UP)
+        # In this rotation^, the mob will move directly towards the player
+        # so we rotate it randomly within range of -90 and +90 degrees.
+        rotate_y(randf_range(-PI / 4, PI / 4))
 
 
-   func squash():
-       emit_signal("squashed")
-       queue_free()
+        # We calculate a random speed (integer)
+        var random_speed = randi_range(min_speed, max_speed)
+        # We calculate a forward velocity that represents the speed.
+        velocity = Vector3.FORWARD * random_speed
+        # We then rotate the velocity vector based on the mob's Y rotation
+        # in order to move in the direction the mob is looking.
+        velocity = velocity.rotated(Vector3.UP, rotation.y)
 
 
+        $AnimationPlayer.playback_speed = random_speed / min_speed
 
 
-   func _on_VisibilityNotifier_screen_exited():
-       queue_free()
+    func _on_visible_on_screen_notifier_3d_screen_exited():
+        queue_free()
 
 
+    func squash():
+        emit_signal("squashed")
+        queue_free() # Destroy this node
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Mob : CharacterBody3D
     public class Mob : CharacterBody3D
@@ -533,21 +566,21 @@ And the *Mob*'s script.
 
 
 .. |image0| image:: img/squash-the-creeps-final.gif
 .. |image0| image:: img/squash-the-creeps-final.gif
 .. |image1| image:: img/09.adding_animations/01.animation_player_dock.png
 .. |image1| image:: img/09.adding_animations/01.animation_player_dock.png
-.. |image2| image:: img/09.adding_animations/02.new_animation.png
+.. |image2| image:: img/09.adding_animations/02.new_animation.webp
 .. |image3| image:: img/09.adding_animations/03.float_name.png
 .. |image3| image:: img/09.adding_animations/03.float_name.png
 .. |image4| image:: img/09.adding_animations/03.timeline.png
 .. |image4| image:: img/09.adding_animations/03.timeline.png
 .. |image5| image:: img/09.adding_animations/04.autoplay_and_loop.png
 .. |image5| image:: img/09.adding_animations/04.autoplay_and_loop.png
 .. |image6| image:: img/09.adding_animations/05.pin_icon.png
 .. |image6| image:: img/09.adding_animations/05.pin_icon.png
-.. |image7| image:: img/09.adding_animations/06.animation_duration.png
-.. |image8| image:: img/09.adding_animations/07.editable_timeline.png
-.. |image9| image:: img/09.adding_animations/08.zoom_slider.png
-.. |image10| image:: img/09.adding_animations/09.creating_first_keyframe.png
-.. |image11| image:: img/09.adding_animations/10.initial_keys.png
-.. |image12| image:: img/09.adding_animations/11.moving_keys.png
-.. |image13| image:: img/09.adding_animations/12.second_keys_values.png
-.. |image14| image:: img/09.adding_animations/13.second_keys.png
+.. |image7| image:: img/09.adding_animations/06.animation_duration.webp
+.. |image8| image:: img/09.adding_animations/07.editable_timeline.webp
+.. |image9| image:: img/09.adding_animations/08.zoom_slider.webp
+.. |image10| image:: img/09.adding_animations/09.creating_first_keyframe.webp
+.. |image11| image:: img/09.adding_animations/10.initial_keys.webp
+.. |image12| image:: img/09.adding_animations/11.moving_keys.webp
+.. |image13| image:: img/09.adding_animations/12.second_keys_values.webp
+.. |image14| image:: img/09.adding_animations/13.second_keys.webp
 .. |image15| image:: img/09.adding_animations/14.play_button.png
 .. |image15| image:: img/09.adding_animations/14.play_button.png
-.. |image16| image:: img/09.adding_animations/15.box_select.png
+.. |image16| image:: img/09.adding_animations/15.box_select.webp
 .. |image17| image:: img/09.adding_animations/16.easing_property.png
 .. |image17| image:: img/09.adding_animations/16.easing_property.png
 .. |image18| image:: img/09.adding_animations/17.ease_out.png
 .. |image18| image:: img/09.adding_animations/17.ease_out.png
 .. |image19| image:: img/09.adding_animations/18.ease_out_second_rotation_key.png
 .. |image19| image:: img/09.adding_animations/18.ease_out_second_rotation_key.png

BIN
getting_started/first_3d_game/img/01.game_setup/08.create_box_shape.png


BIN
getting_started/first_3d_game/img/01.game_setup/08.create_box_shape3D.jpg


BIN
getting_started/first_3d_game/img/01.game_setup/09.box_extents.png


BIN
getting_started/first_3d_game/img/01.game_setup/09.box_extents.webp


BIN
getting_started/first_3d_game/img/01.game_setup/10.mesh_instance.png


BIN
getting_started/first_3d_game/img/01.game_setup/10.mesh_instance3d.png


BIN
getting_started/first_3d_game/img/01.game_setup/11.box_mesh.webp


BIN
getting_started/first_3d_game/img/01.game_setup/11.cube_mesh.png


BIN
getting_started/first_3d_game/img/01.game_setup/16.turn_on_shadows.png


BIN
getting_started/first_3d_game/img/01.game_setup/16.turn_on_shadows.webp


BIN
getting_started/first_3d_game/img/01.game_setup/17.project_with_light.png


BIN
getting_started/first_3d_game/img/01.game_setup/17.project_with_light.webp


BIN
getting_started/first_3d_game/img/01.game_setup/adding_static_body3D.webp


BIN
getting_started/first_3d_game/img/01.game_setup/create_directional_light3d.webp


BIN
getting_started/first_3d_game/img/01.game_setup/ground_down1meter.webp


BIN
getting_started/first_3d_game/img/02.player_input/02.instantiating_the_model.png


BIN
getting_started/first_3d_game/img/02.player_input/02.instantiating_the_model.webp


BIN
getting_started/first_3d_game/img/02.player_input/06.toggling_visibility.png


BIN
getting_started/first_3d_game/img/02.player_input/06.toggling_visibility.webp


BIN
getting_started/first_3d_game/img/02.player_input/08.create_key_action.png


BIN
getting_started/first_3d_game/img/02.player_input/12.move_inputs_mapped.webp


BIN
getting_started/first_3d_game/img/02.player_input/13.joy_button_option.png


BIN
getting_started/first_3d_game/img/02.player_input/13.joy_button_option.webp


BIN
getting_started/first_3d_game/img/02.player_input/14.jump_input_action.webp


BIN
getting_started/first_3d_game/img/02.player_input/add_capsuleshape3d.webp


BIN
getting_started/first_3d_game/img/02.player_input/add_character_body3D.webp


BIN
getting_started/first_3d_game/img/02.player_input/adding_node3D.webp


BIN
getting_started/first_3d_game/img/02.player_input/joystick_axis_input.webp


BIN
getting_started/first_3d_game/img/02.player_input/left_inputmap.webp


BIN
getting_started/first_3d_game/img/02.player_input/left_joystick_select.webp


BIN
getting_started/first_3d_game/img/03.player_movement_code/01.attach_script_to_player.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/01.attach_script_to_player.webp


BIN
getting_started/first_3d_game/img/03.player_movement_code/12.viewport_change.webp


BIN
getting_started/first_3d_game/img/03.player_movement_code/13.camera3d_values.webp


BIN
getting_started/first_3d_game/img/04.mob_scene/04.create_box_shape.png


BIN
getting_started/first_3d_game/img/04.mob_scene/10.node_dock.png


BIN
getting_started/first_3d_game/img/04.mob_scene/10.node_dock.webp


BIN
getting_started/first_3d_game/img/04.mob_scene/11.connect_signal.png


BIN
getting_started/first_3d_game/img/04.mob_scene/11.connect_signal.webp


BIN
getting_started/first_3d_game/img/04.mob_scene/drag_drop_mob.webp


BIN
getting_started/first_3d_game/img/05.spawning_mobs/03.window_settings.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/03.window_settings.webp


BIN
getting_started/first_3d_game/img/05.spawning_mobs/14.multi_material_selection.webp


BIN
getting_started/first_3d_game/img/05.spawning_mobs/14.spatial_material.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/24.connect_timer_to_main.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/24.connect_timer_to_main.webp


BIN
getting_started/first_3d_game/img/05.spawning_mobs/albedo_section.webp


BIN
getting_started/first_3d_game/img/05.spawning_mobs/standard_material.webp


BIN
getting_started/first_3d_game/img/06.jump_and_squash/03.physics_layers.png


BIN
getting_started/first_3d_game/img/06.jump_and_squash/03.physics_layers.webp


BIN
getting_started/first_3d_game/img/06.jump_and_squash/04.default_physics_properties.png


BIN
getting_started/first_3d_game/img/06.jump_and_squash/04.default_physics_properties.webp


BIN
getting_started/first_3d_game/img/06.jump_and_squash/05.toggle_layer_and_mask.png


BIN
getting_started/first_3d_game/img/06.jump_and_squash/05.toggle_layer_and_mask.webp


BIN
getting_started/first_3d_game/img/06.jump_and_squash/07.player_physics_mask.webp


BIN
getting_started/first_3d_game/img/06.jump_and_squash/08.mob_physics_mask.png


BIN
getting_started/first_3d_game/img/06.jump_and_squash/08.mob_physics_mask.webp


BIN
getting_started/first_3d_game/img/07.killing_player/04.mob_detector_properties.png


BIN
getting_started/first_3d_game/img/07.killing_player/04.mob_detector_properties.webp


BIN
getting_started/first_3d_game/img/08.score_and_replay/02.score_custom_color.png


BIN
getting_started/first_3d_game/img/08.score_and_replay/02.score_custom_color.webp


BIN
getting_started/first_3d_game/img/08.score_and_replay/05.dynamic_font.png


BIN
getting_started/first_3d_game/img/08.score_and_replay/05.dynamic_font.webp


BIN
getting_started/first_3d_game/img/08.score_and_replay/06.font_data.png


BIN
getting_started/first_3d_game/img/08.score_and_replay/06.font_data.webp


BIN
getting_started/first_3d_game/img/08.score_and_replay/07.font_size.png


BIN
getting_started/first_3d_game/img/08.score_and_replay/07.font_size.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/02.new_animation.png


BIN
getting_started/first_3d_game/img/09.adding_animations/02.new_animation.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/06.animation_duration.png


BIN
getting_started/first_3d_game/img/09.adding_animations/06.animation_duration.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/07.editable_timeline.png


BIN
getting_started/first_3d_game/img/09.adding_animations/07.editable_timeline.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/08.zoom_slider.png


BIN
getting_started/first_3d_game/img/09.adding_animations/08.zoom_slider.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/09.creating_first_keyframe.png


BIN
getting_started/first_3d_game/img/09.adding_animations/09.creating_first_keyframe.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/10.initial_keys.png


BIN
getting_started/first_3d_game/img/09.adding_animations/10.initial_keys.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/11.moving_keys.png


BIN
getting_started/first_3d_game/img/09.adding_animations/11.moving_keys.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/12.second_keys_values.png


BIN
getting_started/first_3d_game/img/09.adding_animations/12.second_keys_values.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/13.second_keys.png


BIN
getting_started/first_3d_game/img/09.adding_animations/13.second_keys.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/15.box_select.png


BIN
getting_started/first_3d_game/img/09.adding_animations/15.box_select.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/animation_final_keyframes.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/curves.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/second_keys_both.webp


BIN
getting_started/first_3d_game/img/09.adding_animations/timeline_05_click.webp