Переглянути джерело

Merge pull request #4403 from NathanLovato/content/first_3d_game_tutorial

Getting started: your first 3D game tutorial
Nathan Lovato 4 роки тому
батько
коміт
9f3f93912f
100 змінених файлів з 2462 додано та 0 видалено
  1. 164 0
      getting_started/first_3d_game/01.game_setup.rst
  2. 177 0
      getting_started/first_3d_game/02.player_input.rst
  3. 281 0
      getting_started/first_3d_game/03.player_movement_code.rst
  4. 231 0
      getting_started/first_3d_game/04.mob_scene.rst
  5. 291 0
      getting_started/first_3d_game/05.spawning_mobs.rst
  6. 276 0
      getting_started/first_3d_game/06.jump_and_squash.rst
  7. 253 0
      getting_started/first_3d_game/07.killing_player.rst
  8. 372 0
      getting_started/first_3d_game/08.score_and_replay.rst
  9. 375 0
      getting_started/first_3d_game/09.adding_animations.rst
  10. 42 0
      getting_started/first_3d_game/going_further.rst
  11. BIN
      getting_started/first_3d_game/img/01.game_setup/01.import_button.png
  12. BIN
      getting_started/first_3d_game/img/01.game_setup/02.browse_to_project_folder.png
  13. BIN
      getting_started/first_3d_game/img/01.game_setup/03.import_and_edit.png
  14. BIN
      getting_started/first_3d_game/img/01.game_setup/04.start_assets.png
  15. BIN
      getting_started/first_3d_game/img/01.game_setup/05.main_node.png
  16. BIN
      getting_started/first_3d_game/img/01.game_setup/06.staticbody_node.png
  17. BIN
      getting_started/first_3d_game/img/01.game_setup/07.collision_shape_warning.png
  18. BIN
      getting_started/first_3d_game/img/01.game_setup/08.create_box_shape.png
  19. BIN
      getting_started/first_3d_game/img/01.game_setup/09.box_extents.png
  20. BIN
      getting_started/first_3d_game/img/01.game_setup/10.mesh_instance.png
  21. BIN
      getting_started/first_3d_game/img/01.game_setup/11.cube_mesh.png
  22. BIN
      getting_started/first_3d_game/img/01.game_setup/12.cube_resized.png
  23. BIN
      getting_started/first_3d_game/img/01.game_setup/13.move_gizmo_y_axis.png
  24. BIN
      getting_started/first_3d_game/img/01.game_setup/14.select_mode_icon.png
  25. BIN
      getting_started/first_3d_game/img/01.game_setup/15.translation_amount.png
  26. BIN
      getting_started/first_3d_game/img/01.game_setup/16.turn_on_shadows.png
  27. BIN
      getting_started/first_3d_game/img/01.game_setup/17.project_with_light.png
  28. BIN
      getting_started/first_3d_game/img/02.player_input/01.new_scene.png
  29. BIN
      getting_started/first_3d_game/img/02.player_input/02.instantiating_the_model.png
  30. BIN
      getting_started/first_3d_game/img/02.player_input/03.scene_structure.png
  31. BIN
      getting_started/first_3d_game/img/02.player_input/04.sphere_shape.png
  32. BIN
      getting_started/first_3d_game/img/02.player_input/05.moving_the_sphere_up.png
  33. BIN
      getting_started/first_3d_game/img/02.player_input/06.toggling_visibility.png
  34. BIN
      getting_started/first_3d_game/img/02.player_input/07.adding_action.png
  35. BIN
      getting_started/first_3d_game/img/02.player_input/07.input_map_tab.png
  36. BIN
      getting_started/first_3d_game/img/02.player_input/07.project_settings.png
  37. BIN
      getting_started/first_3d_game/img/02.player_input/08.actions_list_empty.png
  38. BIN
      getting_started/first_3d_game/img/02.player_input/08.create_key_action.png
  39. BIN
      getting_started/first_3d_game/img/02.player_input/09.keyboard_key_popup.png
  40. BIN
      getting_started/first_3d_game/img/02.player_input/09.keyboard_keys.png
  41. BIN
      getting_started/first_3d_game/img/02.player_input/10.joy_axis_option.png
  42. BIN
      getting_started/first_3d_game/img/02.player_input/11.joy_axis_popup.png
  43. BIN
      getting_started/first_3d_game/img/02.player_input/12.move_inputs_mapped.png
  44. BIN
      getting_started/first_3d_game/img/02.player_input/13.joy_button_option.png
  45. BIN
      getting_started/first_3d_game/img/02.player_input/14.add_jump_button.png
  46. BIN
      getting_started/first_3d_game/img/02.player_input/14.jump_input_action.png
  47. BIN
      getting_started/first_3d_game/img/03.player_movement_code/01.attach_script_to_player.png
  48. BIN
      getting_started/first_3d_game/img/03.player_movement_code/02.clicking_main_tab.png
  49. BIN
      getting_started/first_3d_game/img/03.player_movement_code/03.instance_child_scene.png
  50. BIN
      getting_started/first_3d_game/img/03.player_movement_code/04.scene_tree_with_camera.png
  51. BIN
      getting_started/first_3d_game/img/03.player_movement_code/05.camera_preview_checkbox.png
  52. BIN
      getting_started/first_3d_game/img/03.player_movement_code/06.two_viewports.png
  53. BIN
      getting_started/first_3d_game/img/03.player_movement_code/07.camera_preview_checkbox.png
  54. BIN
      getting_started/first_3d_game/img/03.player_movement_code/08.camera_moved.png
  55. BIN
      getting_started/first_3d_game/img/03.player_movement_code/09.camera_rotated.png
  56. BIN
      getting_started/first_3d_game/img/03.player_movement_code/10.camera_perspective.png
  57. BIN
      getting_started/first_3d_game/img/03.player_movement_code/11.camera_orthographic.png
  58. BIN
      getting_started/first_3d_game/img/04.mob_scene/01.initial_three_nodes.png
  59. BIN
      getting_started/first_3d_game/img/04.mob_scene/02.add_child_node.png
  60. BIN
      getting_started/first_3d_game/img/04.mob_scene/03.scene_with_collision_shape.png
  61. BIN
      getting_started/first_3d_game/img/04.mob_scene/04.create_box_shape.png
  62. BIN
      getting_started/first_3d_game/img/04.mob_scene/05.box_final_size.png
  63. BIN
      getting_started/first_3d_game/img/04.mob_scene/06.visibility_notifier.png
  64. BIN
      getting_started/first_3d_game/img/04.mob_scene/07.visibility_notifier_bbox_resized.png
  65. BIN
      getting_started/first_3d_game/img/04.mob_scene/08.mob_attach_script.png
  66. BIN
      getting_started/first_3d_game/img/04.mob_scene/09.switch_to_3d_workspace.png
  67. BIN
      getting_started/first_3d_game/img/04.mob_scene/10.node_dock.png
  68. BIN
      getting_started/first_3d_game/img/04.mob_scene/11.connect_signal.png
  69. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/01.monsters_path_preview.png
  70. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/02.project_settings.png
  71. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/03.window_settings.png
  72. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/04.camera_preview.png
  73. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/05.cylinders_node.png
  74. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/06.cylinder_mesh.png
  75. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/07.top_view.png
  76. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/08.toggle_view_grid.png
  77. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/09.toggle_grid_snap.png
  78. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/10.place_first_cylinder.png
  79. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/11.both_cylinders_selected.png
  80. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/12.four_cylinders.png
  81. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/13.selecting_all_cylinders.png
  82. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/14.spatial_material.png
  83. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/15.bright-cylinders.png
  84. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/16.cylinders_fold.png
  85. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/17.points_options.png
  86. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/18.close_path.png
  87. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/19.path_result.png
  88. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/20.mob_scene_property.png
  89. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/20.spawn_nodes.png
  90. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/21.mob_timer.png
  91. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/22.mob_timer_properties.png
  92. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/23.timeout_signal.png
  93. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/24.connect_timer_to_main.png
  94. BIN
      getting_started/first_3d_game/img/05.spawning_mobs/25.spawn_result.png
  95. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/02.project_settings.png
  96. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/03.physics_layers.png
  97. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/04.default_physics_properties.png
  98. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/05.toggle_layer_and_mask.png
  99. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/06.named_checkboxes.png
  100. BIN
      getting_started/first_3d_game/img/06.jump_and_squash/07.player_physics_mask.png

+ 164 - 0
getting_started/first_3d_game/01.game_setup.rst

@@ -0,0 +1,164 @@
+.. _doc_first_3d_game_game_area:
+
+Setting up the game area
+========================
+
+In this first part, we're going to set up the game area. Let's get started by
+importing the start assets and setting up the game scene.
+
+We've prepared a Godot project with the 3D models and sounds we'll use for this
+tutorial, linked in the index page. If you haven't done so yet, you can download
+the archive here: `Squash the Creeps assets
+<https://github.com/GDQuest/godot-3d-dodge-the-creeps/releases/tag/1.0.0>`__.
+
+Once you downloaded it, extract the .zip archive on your computer. Open the
+Godot project manager and click the *Import* button.
+
+|image1|
+
+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
+open a file browser and navigate to the ``project.godot`` file the folder
+contains.
+
+|image2|
+
+Click *Import & Edit* to open the project in the editor.
+
+|image3|
+
+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.
+
+|image4|
+
+There are two 3D models, ``player.glb`` and ``mob.glb``, some materials that
+belong to these models, and a music track.
+
+Setting up the playable area
+----------------------------
+
+We're going to create our main scene with a plain *Node* as its root. 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
+a node to the scene, you can press :kbd:`Ctrl + a` (or :kbd:`Cmd + a` on MacOS).
+
+|image5|
+
+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
+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*.
+
+|image6|
+
+A warning sign next to the *CollisionShape* appears because we haven't defined
+its shape. If you click the icon, a popup appears to give you more information.
+
+|image7|
+
+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*.
+
+|image8|
+
+The box shape is perfect for flat ground and walls. Its thickness makes it
+reliable to block even fast-moving objects.
+
+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
+set the size in the inspector. Click on the *BoxShape* to expand the resource.
+Set its *Extents* to ``30`` on the X axis, ``1`` for the Y axis, and ``30`` for
+the Z axis.
+
+|image9|
+
+.. note::
+
+    In 3D, translation and size units are in meters. The box's total size is
+    twice its extents: ``60`` by ``60`` meters on the ground plane and ``2``
+    units tall. The ground plane is defined by the X and Z axes, while the Y
+    axis represents the height.
+
+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.
+
+|image10|
+
+In the *Inspector*, click on the field next to *Mesh* and create a *CubeMesh*
+resource to create a visible cube.
+
+|image11|
+
+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 works with a size rather than extents, we need to use these values so
+it matches our collision shape.
+
+|image12|
+
+You should see a wide grey slab that covers the grid and blue and red axes in
+the viewport.
+
+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),
+and click and drag down on the Y axis. It's the green arrow in the move gizmo.
+
+|image13|
+
+.. note::
+
+    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.
+
+|image14|
+
+Move the ground down ``1`` meter. A label in the bottom-left corner of the
+viewport tells you how much you're translating the node.
+
+|image15|
+
+.. note::
+
+    Moving the *Ground* node down moves both children along with it.
+    Ensure you move the *Ground* node, **not** the *MeshInstance* or the
+    *CollisionShape*.
+
+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
+and click and drag on the red arc to rotate it around the X axis, until the
+ground is lit.
+
+In the *Inspector*, turn on *Shadow -> Enabled* by clicking the checkbox.
+
+|image16|
+
+At this point, your project should look like this.
+
+|image17|
+
+That's our starting point. In the next part, we will work on the player scene
+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

+ 177 - 0
getting_started/first_3d_game/02.player_input.rst

@@ -0,0 +1,177 @@
+.. _doc_first_3d_game_player_scene_and_input:
+
+Player scene and input actions
+==============================
+
+In the next two lessons, we will design the player scene, register custom input
+actions, and code player movement. By the end, you'll have a playable character
+that moves in eight directions.
+
+.. TODO: add player animated gif?
+.. player_movement.gif
+
+Create a new scene by going to the Scene menu in the top-left and clicking *New
+Scene*. Create a *KinematicBody* node as the root and name it *Player*.
+
+|image0|
+
+Kinematic 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
+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
+the jump and squash mechanics.
+
+.. seealso::
+
+    To learn more about the different physics node types, see the
+    :ref:`doc_physics_introduction`.
+
+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.
+
+Add a *Spatial* 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.
+
+|image1|
+
+This should instantiate the model as a child of *Pivot*. You can rename it to
+*Character*.
+
+|image2|
+
+.. note::
+
+    The ``.glb`` files contain 3D scene data based on the open-source GLTF 2.0
+    specification. They're a modern and powerful alternative to a proprietary format
+    like FBX, which Godot also supports. To produce these files, we designed the
+    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
+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.
+
+|image3|
+
+It will be the shape the physics engine uses to collide with the environment, so
+we want it to better fit the 3D model. Shrink it a bit by dragging the orange
+dot in the viewport. My sphere has a radius of about ``0.8`` meters.
+
+Then, move the shape up so its bottom roughly aligns with the grid's plane.
+
+|image4|
+
+You can toggle the model's visibility by clicking the eye icon next to the
+*Character* or the *Pivot* nodes.
+
+|image5|
+
+Save the scene as ``Player.tscn``.
+
+With the nodes ready, we can almost get coding. But first, we need to define
+some input actions.
+
+Creating input actions
+----------------------
+
+To move the character, we will listen to the player's input, like pressing the
+arrow keys. In Godot, while we could write all the key bindings in code, there's
+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.
+
+This system is the Input Map. To access its editor, head to the *Project* menu
+and select *Project Settings…*.
+
+|image6|
+
+At the top, there are multiple tabs. Click on *Input Map*. This window allows
+you to add new actions at the top; they are your labels. In the bottom part, you
+can bind keys to these actions.
+
+|image7|
+
+Godot projects come with some predefined actions designed for user interface
+design, which we could use here. But we're defining our own to support gamepads.
+
+We're going to name our actions ``move_left``, ``move_right``, ``move_forward``,
+``move_back``, and ``jump``.
+
+To add an action, write its name in the bar at the top and press Enter.
+
+|image8|
+
+Create the five actions. Your window should have them all listed at the bottom.
+
+|image9|
+
+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|
+
+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*.
+
+|image11|
+
+Do the same for the A key.
+
+|image12|
+
+Let's now add support for a gamepad's left joystick. Click the "+" button again
+but this time, select *Joy Axis*.
+
+|image13|
+
+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.
+
+|image14|
+
+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,
+your interface should look like this.
+
+|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.
+
+|image16|
+
+Leave the default values and click the *Add* button.
+
+|image17|
+
+Your jump input action should look like this.
+
+|image18|
+
+That's all the actions we need for this game. You can use this menu to label any
+groups of keys and buttons in your projects.
+
+In the next part, we'll code and test the player's movement.
+
+.. |image0| image:: img/02.player_input/01.new_scene.png
+.. |image1| image:: img/02.player_input/02.instantiating_the_model.png
+.. |image2| image:: img/02.player_input/03.scene_structure.png
+.. |image3| image:: img/02.player_input/04.sphere_shape.png
+.. |image4| image:: img/02.player_input/05.moving_the_sphere_up.png
+.. |image5| image:: img/02.player_input/06.toggling_visibility.png
+.. |image6| image:: img/02.player_input/07.project_settings.png
+.. |image7| image:: img/02.player_input/07.input_map_tab.png
+.. |image8| image:: img/02.player_input/07.adding_action.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
+.. |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
+.. |image17| image:: img/02.player_input/14.add_jump_button.png
+.. |image18| image:: img/02.player_input/14.jump_input_action.png

+ 281 - 0
getting_started/first_3d_game/03.player_movement_code.rst

@@ -0,0 +1,281 @@
+.. _doc_first_3d_game_player_movement:
+
+Moving the player with code
+===========================
+
+It's time to code! We're going to use the input actions we created in the last
+part to move the character.
+
+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*
+button.
+
+|image0|
+
+Let's start with the class's properties. We're going to define a movement speed,
+a fall acceleration representing gravity, and a velocity we'll use to move the
+character.
+
+::
+
+   extends KinematicBody
+
+   # 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
+
+   var velocity = Vector3.ZERO
+
+These are common properties for a moving body. The ``velocity`` is a 3D vector
+combining a speed with a direction. Here, we define it as a property because
+we want to update and reuse its value across frames.
+
+.. note::
+
+    The values are quite different from 2D code because distances are in meters.
+    While in 2D, a thousand units (pixels) may only correspond to half of your
+    screen's width, in 3D, it's a kilometer.
+
+Let's code the movement now. We start by calculating the input direction vector
+using the global ``Input`` object, in ``_physics_process()``.
+
+::
+
+   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 += 1
+       if Input.is_action_pressed("move_left"):
+           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 += 1
+       if Input.is_action_pressed("move_forward"):
+           direction.z -= 1
+
+Here, we're going to make all calculations using the ``_physics_process()``
+virtual function. Like ``_process()``, it allows you to update the node every
+frame, but it's designed specifically for physics-related code like moving a
+kinematic or rigid body.
+
+.. seealso::
+
+    To learn more about the difference between ``_process()`` and
+    ``_physics_process()``, see :ref:`doc_idle_and_physics_processing`.
+
+We start by initializing a ``direction`` variable to ``Vector3.ZERO``. Then, we
+check if the player is pressing one or more of the ``move_*`` inputs and update
+the vector's ``x`` and ``z`` components accordingly. These correspond to the
+ground plane's axes.
+
+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
+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
+call its ``normalize()`` method.
+
+::
+
+   #func _physics_process(delta):
+       #...
+
+       if direction != Vector3.ZERO:
+           direction = direction.normalized()
+           $Pivot.look_at(translation + direction, Vector3.UP)
+
+Here, we only normalize the vector if the direction has a length greater than
+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.
+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.
+
+.. 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
+    in the viewport instead.
+
+In 3D, the property that contains a node's position is ``translation``. By
+adding the ``direction`` to it, we get a position to look at that's one meter
+away from the *Player*.
+
+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
+``_physics_process()`` function but outside the condition we just wrote.
+
+::
+
+    func _physics_process(delta):_
+        #...
+        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)
+
+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 - ...``.
+
+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.
+
+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
+property later to code the jump.
+
+On the last line, we call ``KinematicBody.move_and_slide()``. It's a powerful
+method of the ``KinematicBody`` class that allows you to move a character
+smoothly. If it hits a wall midway through a motion, the engine will try to
+smooth it out for you.
+
+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.
+
+Here is the complete ``Player.gd`` code for reference.
+
+::
+
+   extends KinematicBody
+
+   # 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
+
+   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
+       velocity.y -= fall_acceleration * delta
+       velocity = move_and_slide(velocity, Vector3.UP)
+
+Testing our player's movement
+-----------------------------
+
+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
+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*
+tab at the top of the editor to do so.
+
+|image1|
+
+If you closed the scene before, head to the *FileSystem* dock and double-click
+``Main.tscn`` to re-open it.
+
+To instantiate the *Player*, right-click on the *Main* node and select *Instance
+Child Scene*.
+
+|image2|
+
+In the popup, double-click *Player.tscn*. The character should appear in the
+center of the viewport.
+
+Adding a camera
+~~~~~~~~~~~~~~~
+
+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 *Position3D*, name it *CameraPivot*,
+and add a *Camera* node as a child of it. Your scene tree should look like this.
+
+|image3|
+
+Notice the *Preview* checkbox that appears in the top-left when you have the
+*Camera* selected. You can click it to preview the in-game camera projection.
+
+|image4|
+
+We're going to use the *Pivot* to rotate the camera as if it was on a crane.
+Let's first split the 3D view to be able to freely navigate the scene and see
+what the camera sees.
+
+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).
+
+|image5|
+
+On the bottom view, select the *Camera* and turn on camera preview by clicking
+the checkbox.
+
+|image6|
+
+In the top view, move the camera about ``19`` units on the Z axis (the blue
+one).
+
+|image7|
+
+Here's where the magic happens. Select the *CameraPivot* and rotate it ``45``
+degrees around the X axis (using the red circle). You'll see the camera move as
+if it was attached to a crane.
+
+|image8|
+
+You can run the scene by pressing :kbd:`F6` and press the arrow keys to move the
+character.
+
+|image9|
+
+We can see some empty space around the character due to the perspective
+projection. In this game, we're going to use an orthographic projection instead
+to better frame the gameplay area and make it easier for the player to read
+distances.
+
+Select the *Camera* again and in the *Inspector*, set the *Projection* to
+*Orthogonal* and the *Size* to ``19``. The character should now look flatter and
+the ground should fill the background.
+
+|image10|
+
+With that, we have both player movement and the view in place. Next, we will
+work on the monsters.
+
+.. |image0| image:: img/03.player_movement_code/01.attach_script_to_player.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
+.. |image3| image:: img/03.player_movement_code/04.scene_tree_with_camera.png
+.. |image4| image:: img/03.player_movement_code/05.camera_preview_checkbox.png
+.. |image5| image:: img/03.player_movement_code/06.two_viewports.png
+.. |image6| image:: img/03.player_movement_code/07.camera_preview_checkbox.png
+.. |image7| image:: img/03.player_movement_code/08.camera_moved.png
+.. |image8| image:: img/03.player_movement_code/09.camera_rotated.png
+.. |image9| image:: img/03.player_movement_code/10.camera_perspective.png
+.. |image10| image:: img/03.player_movement_code/11.camera_orthographic.png

+ 231 - 0
getting_started/first_3d_game/04.mob_scene.rst

@@ -0,0 +1,231 @@
+.. _doc_first_3d_game_designing_the_mob_scene:
+
+Designing the mob scene
+=======================
+
+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.
+
+Let's design the monsters themselves in a new scene. The node structure is going
+to be similar to the *Player* scene.
+
+Create a scene with, once again, a *KinematicBody* node as its root. Name it
+*Mob*. Add a *Spatial* 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*.
+
+|image0|
+
+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*.
+
+|image1|
+
+Add a *CollisionShape*.
+
+|image2|
+
+In the *Inspector*, assign a *BoxShape* to the *Shape* property.
+
+|image3|
+
+We should change its size to fit the 3D model better. You can do so
+interactively by clicking and dragging on the orange dots.
+
+The box should touch the floor and be a little thinner than the model. Physics
+engines work in such a way that if the player's sphere touches even the box's
+corner, a collision will occur. If the box is a little too big compared to the
+3D model, you may die at a distance from the monster, and the game will feel
+unfair to the players.
+
+|image4|
+
+Notice that my box is taller than the monster. It is okay in this game because
+we're looking at the scene from above and using a fixed perspective. Collision
+shapes don't have to match the model exactly. It's the way the game feels when
+you test it that should dictate their form and size.
+
+Removing monsters off-screen
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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
+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.
+
+Once a monster leaves the screen, we don't need it anymore, so we can delete it.
+Godot has a node that detects when objects leave the screen,
+*VisibilityNotifier*, and we're going to use it to destroy our mobs.
+
+.. note::
+
+    When you keep instancing an object in games, there's a technique you can
+    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
+    them over and over.
+
+    When working with GDScript, you don't need to worry about this. The main
+    reason to use pools is to avoid freezes with garbage-collected languages
+    like C# or Lua. GDScript uses a different technique to manage memory,
+    reference counting, which doesn't have that caveat. You can learn more
+    about that here :ref:`doc_gdscript_basics_memory_management`.
+
+Select the *Mob* node and add a *VisibilityNotifier* as a child of it. Another
+box, pink this time, appears. When this box completely leaves the screen, the
+node will emit a signal.
+
+|image5|
+
+Resize it using the orange dots until it covers the entire 3D model.
+
+|image6|
+
+Coding the mob's movement
+-------------------------
+
+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
+and call the function from there.
+
+Attach a script to the *Mob*.
+
+|image7|
+
+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``.
+
+::
+
+   extends KinematicBody
+
+   # 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)
+
+Similarly to the player, we move the mob every frame by calling
+``KinematicBody``\ '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
+and leave the screen, even if it were to hit an obstacle.
+
+We need to define another function to calculate the start velocity. This
+function will turn the monster towards the player and randomize both its angle
+of motion and its velocity.
+
+The function will take a ``start_position``, the mob's spawn position, and the
+``player_position`` as its arguments.
+
+We first position the mob at ``start_position``. Then, we turn it towards the
+player using the ``look_at()`` method and randomize the angle by rotating a
+random amount around the Y axis. Below, ``rand_range()`` outputs a random value
+between ``-PI / 4`` radians and ``PI / 4`` radians.
+
+::
+
+   # We will call this function from the Main scene.
+   func initialize(start_position, player_position):
+       translation = start_position
+       # We turn the mob so it looks at the player.
+       look_at(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))
+
+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 calculate a random speed.
+       var random_speed = rand_range(min_speed, max_speed)
+       # We calculate a forward velocity that represents the 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.
+       velocity = velocity.rotated(Vector3.UP, rotation.y)
+
+Leaving the screen
+------------------
+
+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*.
+
+Head back to the 3D viewport by clicking on the *3D* label at the top of the
+editor. You can also press :kbd:`F2`.
+
+|image8|
+
+Select the *VisibilityNotifier* node and on the right side of the interface,
+navigate to the *Node* dock. Double-click the *screen_exited()* signal.
+
+|image9|
+
+Connect the signal to the *Mob*.
+
+|image10|
+
+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.
+
+::
+
+   func _on_VisibilityNotifier_screen_exited():
+       queue_free()
+
+Our monster is ready to enter the game! In the next part, you will spawn
+monsters in the game level.
+
+Here is the complete ``Mob.gd`` script for reference.
+
+::
+
+   extends KinematicBody
+
+   # 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):
+       translation = start_position
+       look_at(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()
+
+.. |image0| image:: img/04.mob_scene/01.initial_three_nodes.png
+.. |image1| image:: img/04.mob_scene/02.add_child_node.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
+.. |image5| image:: img/04.mob_scene/06.visibility_notifier.png
+.. |image6| image:: img/04.mob_scene/07.visibility_notifier_bbox_resized.png
+.. |image7| image:: img/04.mob_scene/08.mob_attach_script.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

+ 291 - 0
getting_started/first_3d_game/05.spawning_mobs.rst

@@ -0,0 +1,291 @@
+.. _doc_first_3d_game_spawning_monsters:
+
+Spawning monsters
+=================
+
+In this part, we're going to spawn monsters along a path randomly. By the end,
+you will have monsters roaming the game board.
+
+|image0|
+
+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
+a default window size of ``1024x600``. We're going to set it to ``720x540``, a
+nice little box.
+
+Go to *Project -> Project Settings*.
+
+|image1|
+
+In the left menu, navigate down to *Display -> Window*. On the right, set the
+*Width* to ``720`` and the *Height* to ``540``.
+
+|image2|
+
+Creating the spawn path
+-----------------------
+
+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.
+
+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
+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
+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.
+Select the *Camera* node and click the *Preview* checkbox in the bottom
+viewport.
+
+|image3|
+
+Adding placeholder cylinders
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's add the placeholder meshes. Add a new *Spatial* 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.
+
+|image4|
+
+In the *Inspector*, assign a *CylinderMesh* to the *Mesh* property.
+
+|image5|
+
+Set the top viewport to the top orthogonal view using the menu in the viewport's
+top-left corner. Alternatively, you can press the keypad's 7 key.
+
+|image6|
+
+The grid is a bit distracting for me. You can toggle it by going to the *View*
+menu in the toolbar and clicking *View Grid*.
+
+|image7|
+
+You now want to move the cylinder along the ground plane, looking at the camera
+preview in the bottom viewport. I recommend using grid snap to do so. You can
+toggle it by clicking the magnet icon in the toolbar or pressing Y.
+
+|image8|
+
+Place the cylinder so it's right outside the camera's view in the top-left
+corner.
+
+|image9|
+
+We're going to create copies of the mesh and place them around the game area.
+Press :kbd:`Ctrl + D` (:kbd:`Cmd + D` on MacOS) to duplicate the node. You can also right-click
+the node in the *Scene* dock and select *Duplicate*. Move the copy down along
+the blue Z axis until it's right outside the camera's preview.
+
+Select both cylinders by pressing the :kbd:`Shift` key and clicking on the unselected
+one and duplicate them.
+
+|image10|
+
+Move them to the right by dragging the red X axis.
+
+|image11|
+
+They're a bit hard to see in white, aren't they? Let's make them stand out by
+giving them a new material.
+
+In 3D, materials define a surface's visual properties like its color, how it
+reflects light, and more. We can use them to change the color of a mesh.
+
+We can update all four cylinders at once. Select all the mesh instances in the
+*Scene* dock. To do so, you can click on the first one and Shift click on the
+last one.
+
+|image12|
+
+In the *Inspector*, expand the *Material* section and assign a *SpatialMaterial*
+to slot *0*.
+
+|image13|
+
+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
+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
+the background, like a bright orange.
+
+|image14|
+
+We can now use the cylinders as guides. Fold them in the *Scene* dock by
+clicking the grey arrow next to them. Moving forward, you can also toggle their
+visibility by clicking the eye icon next to *Cylinders*.
+
+|image15|
+
+Add a *Path* node as a child of *Main*. In the toolbar, four icons appear. Click
+the *Add Point* tool, the icon with the green "+" sign.
+
+|image16|
+
+.. note:: You can hover any icon to see a tooltip describing the tool.
+
+Click in the center of each cylinder to create a point. Then, click the *Close
+Curve* icon in the toolbar to close the path. If any point is a bit off, you can
+click and drag on it to reposition it.
+
+|image17|
+
+Your path should look like this.
+
+|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.
+
+|image19|
+
+With that, we're ready to code the spawn mechanism.
+
+Spawning monsters randomly
+--------------------------
+
+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``
+or any other monster to it.
+
+Then, as we're going to spawn the monsters procedurally, we want to randomize
+numbers every time we play the game. If we don't do that, the monsters will
+always spawn following the same sequence.
+
+::
+
+   extends Node
+
+   export (PackedScene) var mob_scene
+
+
+   func _ready():
+       randomize()
+
+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
+``Mob.tscn`` file to the ``mob_scene`` property.
+
+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*.
+
+|image20|
+
+Add a new *Timer* node as a child of *Main*. Name it *MobTimer*.
+
+|image21|
+
+In the *Inspector*, set its *Wait Time* to ``0.5`` seconds and turn on
+*Autostart* so it automatically starts when we run the game.
+
+|image22|
+
+Timers emit a ``timeout`` signal every time they reach the end of their *Wait
+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
+``0.5`` seconds.
+
+With the *MobTimer* still selected, head to the *Node* dock on the right and
+double-click the ``timeout`` signal.
+
+|image23|
+
+Connect it to the *Main* node.
+
+|image24|
+
+This will take you back to the script, with a new empty
+``_on_MobTimer_timeout()`` function.
+
+Let's code the mob spawning logic. We're going to:
+
+1. Instantiate the mob scene.
+2. Sample a random position on the spawn path.
+3. Get the player's position.
+4. Add the mob as a child of the *Main* node.
+5. Call the mob's ``initialize()`` method, passing it the random position and
+   the player's position.
+
+::
+
+   func _on_MobTimer_timeout():
+       # Create a Mob instance and add it to the scene.
+       var mob = mob_scene.instance()
+
+       # Choose a random location on Path2D.
+       # 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()
+
+       var player_position = $Player.transform.origin
+
+       add_child(mob)
+       mob.initialize(mob_spawn_location.translation, player_position)
+
+Above, ``randf()`` produces a random value between ``0`` and ``1``, which is
+what the *PathFollow* node's ``unit_offset`` expects.
+
+Here is the complete ``Main.gd`` script so far, for reference.
+
+::
+
+   extends Node
+
+   export (PackedScene) var mob_scene
+
+
+   func _ready():
+       randomize()
+
+
+   func _on_MobTimer_timeout():
+       var mob = mob_scene.instance()
+
+       var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
+       mob_spawn_location.unit_offset = randf()
+       var player_position = $Player.transform.origin
+
+       add_child(mob)
+       mob.initialize(mob_spawn_location.translation, player_position)
+
+You can test the scene by pressing :kbd:`F6`. You should see the monsters spawn and
+move in a straight line.
+
+|image25|
+
+For now, they bump and slide against one another when their paths cross. We'll
+address this in the next part.
+
+.. |image0| image:: img/05.spawning_mobs/01.monsters_path_preview.png
+.. |image1| image:: img/05.spawning_mobs/02.project_settings.png
+.. |image2| image:: img/05.spawning_mobs/03.window_settings.png
+.. |image3| image:: img/05.spawning_mobs/04.camera_preview.png
+.. |image4| image:: img/05.spawning_mobs/05.cylinders_node.png
+.. |image5| image:: img/05.spawning_mobs/06.cylinder_mesh.png
+.. |image6| image:: img/05.spawning_mobs/07.top_view.png
+.. |image7| image:: img/05.spawning_mobs/08.toggle_view_grid.png
+.. |image8| image:: img/05.spawning_mobs/09.toggle_grid_snap.png
+.. |image9| image:: img/05.spawning_mobs/10.place_first_cylinder.png
+.. |image10| image:: img/05.spawning_mobs/11.both_cylinders_selected.png
+.. |image11| image:: img/05.spawning_mobs/12.four_cylinders.png
+.. |image12| image:: img/05.spawning_mobs/13.selecting_all_cylinders.png
+.. |image13| image:: img/05.spawning_mobs/14.spatial_material.png
+.. |image14| image:: img/05.spawning_mobs/15.bright-cylinders.png
+.. |image15| image:: img/05.spawning_mobs/16.cylinders_fold.png
+.. |image16| image:: img/05.spawning_mobs/17.points_options.png
+.. |image17| image:: img/05.spawning_mobs/18.close_path.png
+.. |image18| image:: img/05.spawning_mobs/19.path_result.png
+.. |image19| image:: img/05.spawning_mobs/20.spawn_nodes.png
+.. |image20| image:: img/05.spawning_mobs/20.mob_scene_property.png
+.. |image21| image:: img/05.spawning_mobs/21.mob_timer.png
+.. |image22| image:: img/05.spawning_mobs/22.mob_timer_properties.png
+.. |image23| image:: img/05.spawning_mobs/23.timeout_signal.png
+.. |image24| image:: img/05.spawning_mobs/24.connect_timer_to_main.png
+.. |image25| image:: img/05.spawning_mobs/25.spawn_result.png

+ 276 - 0
getting_started/first_3d_game/06.jump_and_squash.rst

@@ -0,0 +1,276 @@
+.. _doc_first_3d_game_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
+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
+the world of :ref:`physics layers
+<doc_physics_introduction_collision_layers_and_masks>`.
+
+Controlling physics interactions
+--------------------------------
+
+Physics bodies have access to two complementary properties: layers and masks.
+Layers define on which physics layer(s) an object is.
+
+Masks control the layers that a body will listen to and detect. This affects
+collision detection. When you want two bodies to interact, you need at least one
+to have a mask corresponding to the other.
+
+If that's confusing, don't worry, we'll see three examples in a second.
+
+The important point is that you can use layers and masks to filter physics
+interactions, control performance, and remove the need for extra conditions in
+your code.
+
+By default, all physics bodies and areas are set to both layer and mask ``0``.
+This means they all collide with each other.
+
+Physics layers are represented by numbers, but we can give them names to keep
+track of what's what.
+
+Setting layer names
+~~~~~~~~~~~~~~~~~~~
+
+Let's give our physics layers a name. Go to *Project -> Project Settings*.
+
+|image0|
+
+In the left menu, navigate down to *Layer Names -> 3D Physics*. You can see a
+list of layers with a field next to each of them on the right. You can set their
+names there. Name the first three layers *player*, *enemies*, and *world*,
+respectively.
+
+|image1|
+
+Now, we can assign them to our physics nodes.
+
+Assigning layers and masks
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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
+buttons.
+
+|image2|
+
+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.
+
+|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
+creatures from falling.
+
+Note that you can click the "..." button on the right side of the properties to
+see a list of named checkboxes.
+
+|image4|
+
+Next up are the *Player* and the *Mob*. Open ``Player.tscn`` by double-clicking
+the file in the *FileSystem* dock.
+
+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.
+
+|image5|
+
+Then, open the *Mob* scene by double-clicking on ``Mob.tscn`` and select the
+*Mob* node.
+
+Set its *Collision -> Layer* to "enemies" and its *Collision -> Mask* to both
+"player".
+
+|image6|
+
+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"
+mask.
+
+.. note::
+
+    The mobs don't need to mask the "world" layer because they only move
+    on the XZ plane. We don't apply any gravity to them by design.
+
+Jumping
+-------
+
+The jumping mechanic itself requires only two lines of code. Open the *Player*
+script. We need a value to control the jump's strength and update
+``_physics_process()`` to code the jump.
+
+After the line that defines ``fall_acceleration``, at the top of the script, add
+the ``jump_impulse``.
+
+::
+
+   #...
+   # Vertical impulse applied to the character upon jumping in meters per second.
+   export var jump_impulse = 20
+
+Inside ``_physics_process()``, add the following code before the line where we
+called ``move_and_slide()``.
+
+::
+
+   func _physics_process(delta):
+       #...
+
+       # Jumping.
+       if is_on_floor() and Input.is_action_just_pressed("jump"):
+           velocity.y += jump_impulse
+
+       #...
+
+That's all you need to jump!
+
+The ``is_on_floor()`` method is a tool from the ``KinematicBody`` class. It
+returns ``true`` if the body collided with the floor in this frame. That's why
+we apply gravity to the *Player*: so we collide with the floor instead of
+floating over it like the monsters.
+
+If the character is on the floor and the player presses "jump", we instantly
+give them a lot of vertical speed. In games, you really want controls to be
+responsive and giving instant speed boosts like these, while unrealistic, feel
+great.
+
+Notice that the Y axis is positive upwards. That's unlike 2D, where the Y axis
+is positive downward.
+
+Squashing monsters
+------------------
+
+Let's add the squash mechanic next. We're going to make the character bounce
+over monsters and kill them at the same time.
+
+We need to detect collisions with a monster and to differentiate them from
+collisions with the floor. To do so, we can use Godot's :ref:`group
+<doc_groups>` tagging feature.
+
+Open the scene ``Mob.tscn`` again and select the *Mob* node. Go to the *Node*
+dock on the right to see a list of signals. The *Node* dock has two tabs:
+*Signals*, which you've already used, and *Groups*, which allows you to assign
+tags to nodes.
+
+Click on it to reveal a field where you can write a tag name. Enter "mob" in the
+field and click the *Add* button.
+
+|image7|
+
+An icon appears in the *Scene* dock to indicate the node is part of at least one
+group.
+
+|image8|
+
+We can now use the group from the code to distinguish collisions with monsters
+from collisions with the floor.
+
+Coding the squash mechanic
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Head back to the *Player* script to code the squash and bounce.
+
+At the top of the script, we need another property, ``bounce_impulse``. When
+squashing an enemy, we don't necessarily want the character to go as high up as
+when jumping.
+
+::
+
+   # Vertical impulse applied to the character upon bouncing over a mob in
+   # meters per second.
+   export var bounce_impulse = 16
+
+Then, at the bottom of ``_physics_process()``, add the following loop. With
+``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
+that may have happened.
+
+In every iteration of the loop, we check if we landed on a mob. If so, we kill
+it and bounce.
+
+With this code, if no collisions occurred on a given frame, the loop won't run.
+
+::
+
+   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
+
+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:`KinematicBody<class_KinematicBody>` class and are related to
+``move_and_slide()``.
+
+``get_slide_collision()`` returns a
+:ref:`KinematicCollision<class_KinematicCollision>` object that holds
+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")``.
+
+.. note::
+
+    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:
+``Vector3.UP.dot(collision.normal) > 0.1``. The collision normal is a 3D vector
+that is perpendicular to the plane where the collision occurred. The dot product
+allows us to compare it to the up direction.
+
+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
+are roughly above the monster.
+
+We are calling one undefined function, ``mob.squash()``. We have to add it to
+the Mob class.
+
+Open the script ``Mob.gd`` by double-clicking on it in the *FileSystem* dock. At
+the top of the script, we want to define a new signal named ``squashed``. And at
+the bottom, you can add the squash function, where we emit the signal and
+destroy the mob.
+
+::
+
+   # Emitted when the player jumped on the mob.
+   signal squashed
+
+   # ...
+
+
+   func squash():
+       emit_signal("squashed")
+       queue_free()
+
+We will use the signal to add points to the score in the next lesson.
+
+With that, you should be able to kill monsters by jumping on them. You can press
+:kbd:`F5` to try the game and set ``Main.tscn`` as your project's main scene.
+
+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
+.. |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
+.. |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
+.. |image7| image:: img/06.jump_and_squash/09.groups_tab.png
+.. |image8| image:: img/06.jump_and_squash/10.group_scene_icon.png

+ 253 - 0
getting_started/first_3d_game/07.killing_player.rst

@@ -0,0 +1,253 @@
+.. _doc_first_3d_game_killing_the_player:
+
+Killing the player
+==================
+
+We can kill enemies by jumping on them, but the player still can't die.
+Let's fix this.
+
+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
+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
+works well for hitboxes.
+
+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.
+
+|image0|
+
+In the *Inspector*, assign a cylinder shape to it.
+
+|image1|
+
+Here is a trick you can use to make the collisions only happen when the
+player is on the ground or close to it. You can reduce the cylinder's
+height and move it up to the top of the character. This way, when the
+player jumps, the shape will be too high up for the enemies to collide
+with it.
+
+|image2|
+
+You also want the cylinder to be wider than the sphere. This way, the
+player gets hit before colliding and being pushed on top of the
+monster's collision box.
+
+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
+cannot detect the area. The complementary *Monitoring* property allows
+it to detect collisions. Then, remove the *Collision -> Layer* and sets
+the mask to the "enemies" layer.
+
+|image3|
+
+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*.
+
+|image4|
+
+The *MobDetector* will emit ``body_entered`` when a *KinematicBody* or a
+*RigidBody* 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
+to end the game and destroy the player. We can wrap these operations in
+a ``die()`` function that helps us put a descriptive label on the code.
+
+::
+
+   # Emitted when the player was hit by a mob.
+   # Put this at the top of the script.
+   signal hit
+
+
+   # And this function at the bottom.
+   func die():
+       emit_signal("hit")
+       queue_free()
+
+
+   func _on_MobDetector_body_entered(_body):
+       die()
+
+Try the game again by pressing :kbd:`F5`. If everything is set up correctly,
+the character should die when an enemy runs into it.
+
+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
+and resize them to achieve a tight game feel.
+
+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
+reaction.
+
+Open ``Main.tscn``, select the *Player* node, and in the *Node* dock,
+connect its ``hit`` signal to the *Main* node.
+
+|image5|
+
+Get and stop the timer in the ``_on_Player_hit()`` function.
+
+::
+
+   func _on_Player_hit():
+       $MobTimer.stop()
+
+If you try the game now, the monsters will stop spawning when you die,
+and the remaining ones will leave the screen.
+
+You can pat yourself in the back: you prototyped a complete 3D game,
+even if it's still a bit rough.
+
+From there, we'll add a score, the option to retry the game, and you'll
+see how you can make the game feel much more alive with minimalistic
+animations.
+
+Code checkpoint
+---------------
+
+Here are the complete scripts for the *Main*, *Mob*, and *Player* nodes,
+for reference. You can use them to compare and check your code.
+
+Starting with ``Main.gd``.
+
+::
+
+   extends Node
+
+   export(PackedScene) var mob_scene
+
+
+   func _ready():
+       randomize()
+
+
+   func _on_MobTimer_timeout():
+       # Create a Mob instance and add it to the scene.
+       var mob = mob_scene.instance()
+
+       # Choose a random location on Path2D.
+       var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
+       mob_spawn_location.unit_offset = randf()
+
+       var player_position = $Player.transform.origin
+
+       add_child(mob)
+       mob.initialize(mob_spawn_location.translation, player_position)
+
+
+   func _on_Player_hit():
+       $MobTimer.stop()
+
+Next is ``Mob.gd``.
+
+::
+
+   extends KinematicBody
+
+   # 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):
+       translation = start_position
+       look_at(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()
+
+Finally, the longest script, ``Player.gd``.
+
+::
+
+   extends KinematicBody
+
+   # 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()
+
+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
+.. |image1| image:: img/07.killing_player/02.cylinder_shape.png
+.. |image2| image:: img/07.killing_player/03.cylinder_in_editor.png
+.. |image3| image:: img/07.killing_player/04.mob_detector_properties.png
+.. |image4| image:: img/07.killing_player/05.body_entered_signal.png
+.. |image5| image:: img/07.killing_player/06.player_hit_signal.png

+ 372 - 0
getting_started/first_3d_game/08.score_and_replay.rst

@@ -0,0 +1,372 @@
+.. _doc_first_3d_game_score_and_replay:
+
+Score and replay
+================
+
+In this part, we'll add the score, music playback, and the ability to restart
+the game.
+
+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.
+
+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
+edit your User Interface (UI).
+
+Add a *Label* node and rename it to *ScoreLabel*.
+
+|image0|
+
+In the *Inspector*, set the *Label*'s *Text* to a placeholder like "Score: 0".
+
+|image1|
+
+Also, the text is white by default, like our game's background. We need to
+change its color to see it at runtime.
+
+Scroll down to *Custom Colors* and click the black box next to *Font Color* to
+tint the text.
+
+|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
+top-left corner.
+
+|image4|
+
+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
+to set our game's font.
+
+Creating a UI theme
+-------------------
+
+Once again, select the *UserInterface* node. In the *Inspector*, create a new
+theme resource in *Theme -> Theme*.
+
+|image5|
+
+Click on it to open the theme editor In the bottom panel. It gives you a preview
+of how all the built-in UI widgets will look with your theme resource.
+
+|image6|
+
+By default, a theme only has one property, the *Default Font*.
+
+.. seealso::
+
+    You can add more properties to the theme resource to design complex user
+    interfaces, but that is beyond the scope of this series. To learn more about
+    creating and editing themes, see :ref:`doc_gui_skinning`.
+
+Click the *Default Font* property and create a new *DynamicFont*.
+
+|image7|
+
+Expand the *DynamicFont* by clicking on it and expand its *Font* section. There,
+you will see an empty *Font Data* field.
+
+|image8|
+
+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).
+
+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*.
+The text will reappear in the theme preview.
+
+The text is a bit small. Set the *Settings -> Size* to ``22`` pixels to increase
+the text's size.
+
+|image9|
+
+Keeping track of the score
+--------------------------
+
+Let's work on the score next. Attach a new script to the *ScoreLabel* and define
+the ``score`` variable.
+
+::
+
+   extends Label
+
+   var score = 0
+
+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.
+
+Instead, we have to make the connection from the code every time we spawn a
+monster.
+
+Open the script ``Main.gd``. If it's still open, you can click on its name in
+the script editor's left column.
+
+|image10|
+
+Alternatively, you can double-click the ``Main.gd`` file in the *FileSystem*
+dock.
+
+At the bottom of the ``_on_MobTimer_timeout()`` function, add the following
+line.
+
+::
+
+   func _on_MobTimer_timeout():
+       #...
+       # We connect the mob to the score label to update the score upon squashing one.
+       mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
+
+This line means that when the mob emits the ``squashed`` signal, the
+*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()``
+callback function.
+
+There, we increment the score and update the displayed text.
+
+::
+
+   func _on_Mob_squashed():
+       score += 1
+       text = "Score: %s" % score
+
+The second line uses the value of the ``score`` variable to replace the
+placeholder ``%s``. When using this feature, Godot automatically converts values
+to text, which is convenient to output text in labels or using the ``print()``
+function.
+
+.. seealso::
+
+    You can learn more about string formatting here: :ref:`doc_gdscript_printf`.
+
+You can now play the game and squash a few enemies to see the score
+increase.
+
+|image11|
+
+.. note::
+
+    In a complex game, you may want to completely separate your user interface
+    from the game world. In that case, you would not keep track of the score on
+    the label. Instead, you may want to store it in a separate, dedicated
+    object. But when prototyping or when your project is simple, it is fine to
+    keep your code simple. Programming is always a balancing act.
+
+Retrying the game
+-----------------
+
+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.
+
+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
+rectangle with a uniform color and will serve as an overlay to darken the
+screen.
+
+To make it span over the whole viewport, you can use the *Layout* menu in the
+toolbar.
+
+|image12|
+
+Open it and apply the *Full Rect* command.
+
+|image13|
+
+Nothing happens. Well, almost nothing: only the four green pins move to the
+corners of the selection box.
+
+|image14|
+
+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.
+
+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
+*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
+alpha channel, that is to say, its opacity.
+
+|image15|
+
+Next, add a *Label* as a child of *Retry* and give it the *Text* "Press Enter to
+retry."
+
+|image16|
+
+To move it and anchor it in the center of the screen, apply *Layout -> Center*
+to it.
+
+|image17|
+
+Coding the retry option
+~~~~~~~~~~~~~~~~~~~~~~~
+
+We can now head to the code to show and hide the *Retry* node when the player
+dies and plays again.
+
+Open the script ``Main.gd``. First, we want to hide the overlay at the start of
+the game. Add this line to the ``_ready()`` function.
+
+::
+
+   func _ready():
+       #...
+       $UserInterface/Retry.hide()
+
+Then, when the player gets hit, we show the overlay.
+
+::
+
+   func _on_Player_hit():
+       #...
+       $UserInterface/Retry.show()
+
+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
+``_unhandled_input()`` callback.
+
+If the player pressed the predefined ``ui_accept`` input action and *Retry* is
+visible, we reload the current scene.
+
+::
+
+   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()
+
+The function ``get_tree()`` gives us access to the global :ref:`SceneTree
+<class_SceneTree>` object, which allows us to reload and restart the current
+scene.
+
+Adding music
+------------
+
+To add music that plays continuously in the background, we're going to use
+another feature in Godot: :ref:`autoloads <doc_singletons_autoload>`.
+
+To play audio, all you need to do is add an *AudioStreamPlayer* node to your
+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
+audio nodes are also reset, and the music starts back from the beginning.
+
+You can use the autoload feature to have Godot load a node or a scene
+automatically at the start of the game, outside the current scene. You can also
+use it to create globally accessible objects.
+
+Create a new scene by going to the *Scene* menu and clicking *New Scene*.
+
+|image18|
+
+Click the *Other Node* button to create an *AudioStreamPlayer* and rename it to
+*MusicPlayer*.
+
+|image19|
+
+We included a music soundtrack in the ``art/`` directory, ``House In a Forest
+Loop.ogg``. Click and drag it onto the *Stream* property in the *Inspector*.
+Also, turn on *Autoplay* so the music plays automatically at the start of the
+game.
+
+|image20|
+
+Save the scene as ``MusicPlayer.tscn``.
+
+We have to register it as an autoload. Head to the *Project -> Project
+Settings…* menu and click on the *Autoload* tab.
+
+In the *Path* field, you want to enter the path to your scene. Click the folder
+icon to open the file browser and double-click on ``MusicPlayer.tscn``. Then,
+click the *Add* button on the right to register the node.
+
+|image21|
+
+If you run the game now, the music will play automatically. And even when you
+lose and retry, it keeps going.
+
+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:
+*Remote* and *Local*.
+
+|image22|
+
+The *Remote* tab allows you to visualize the node tree of your running game.
+There, you will see the *Main* node and everything the scene contains and the
+instantiated mobs at the bottom.
+
+|image23|
+
+At the top are the autoloaded *MusicPlayer* and a *root* node, which is your
+game's viewport.
+
+And that does it for this lesson. In the next part, we'll add an animation to
+make the game both look and feel much nicer.
+
+Here is the complete ``Main.gd`` script for reference.
+
+::
+
+   extends Node
+
+   export (PackedScene) var mob_scene
+
+
+   func _ready():
+       randomize()
+       $UserInterface/Retry.hide()
+
+
+   func _unhandled_input(event):
+       if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
+           get_tree().reload_current_scene()
+
+
+   func _on_MobTimer_timeout():
+       var mob = mob_scene.instance()
+
+       var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
+       mob_spawn_location.unit_offset = randf()
+
+       var player_position = $Player.transform.origin
+
+       add_child(mob)
+       mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
+       mob.initialize(mob_spawn_location.translation, player_position)
+
+
+   func _on_Player_hit():
+       $MobTimer.stop()
+       $UserInterface/Retry.show()
+
+.. |image0| image:: img/08.score_and_replay/01.label_node.png
+.. |image1| image:: img/08.score_and_replay/02.score_placeholder.png
+.. |image2| image:: img/08.score_and_replay/02.score_custom_color.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
+.. |image5| image:: img/08.score_and_replay/03.creating_theme.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
+.. |image10| image:: img/08.score_and_replay/08.open_main_script.png
+.. |image11| image:: img/08.score_and_replay/09.score_in_game.png
+.. |image12| image:: img/08.score_and_replay/10.layout_icon.png
+.. |image13| image:: img/08.score_and_replay/11.full_rect_option.png
+.. |image14| image:: img/08.score_and_replay/12.anchors_updated.png
+.. |image15| image:: img/08.score_and_replay/13.retry_color_picker.png
+.. |image16| image:: img/08.score_and_replay/14.retry_node.png
+.. |image17| image:: img/08.score_and_replay/15.layout_center.png
+.. |image18| image:: img/08.score_and_replay/16.new_scene.png
+.. |image19| image:: img/08.score_and_replay/17.music_player_node.png
+.. |image20| image:: img/08.score_and_replay/18.music_node_properties.png
+.. |image21| image:: img/08.score_and_replay/19.register_autoload.png
+.. |image22| image:: img/08.score_and_replay/20.scene_dock_tabs.png
+.. |image23| image:: img/08.score_and_replay/21.remote_scene_tree.png

+ 375 - 0
getting_started/first_3d_game/09.adding_animations.rst

@@ -0,0 +1,375 @@
+.. _doc_first_3d_game_character_animation:
+
+Character animation
+===================
+
+In this final lesson, we'll use Godot's built-in animation tools to make our
+characters float and flap. You'll learn to design animations in the editor and
+use code to make your game feel alive.
+
+|image0|
+
+We'll start with an introduction to using the animation editor.
+
+Using the animation editor
+--------------------------
+
+The engine comes with tools to author animations in the editor. You can then use
+the code to play and control them at runtime.
+
+Open the player scene, select the player node, and add an animation player node.
+
+The *Animation* dock appears in the bottom panel.
+
+|image1|
+
+It features a toolbar and the animation drop-down menu at the top, a track
+editor in the middle that's currently empty, and filter, snap, and zoom options
+at the bottom.
+
+Let's create an animation. Click on *Animation -> New*.
+
+|image2|
+
+Name the animation "float".
+
+|image3|
+
+Once you created the animation, the timeline appears with numbers representing
+time in seconds.
+
+|image4|
+
+We want the animation to start playback automatically at the start of the game.
+Also, it should loop.
+
+To do so, you can click the button with an "A+" icon in the animation toolbar
+and the looping arrows, respectively.
+
+|image5|
+
+You can also pin the animation editor by clicking the pin icon in the top-right.
+This prevents it from folding when you click on the viewport and deselect the
+nodes.
+
+|image6|
+
+Set the animation duration to ``1.2`` seconds in the top-right of the dock.
+
+|image7|
+
+You should see the gray ribbon widen a bit. It shows you the start and end of
+your animation and the vertical blue line is your time cursor.
+
+|image8|
+
+You can click and drag the slider in the bottom-right to zoom in and out of the
+timeline.
+
+|image9|
+
+The float animation
+-------------------
+
+With the animation player node, you can animate most properties on as many nodes
+as you need. Notice the key icon next to properties in the *Inspector*. You can
+click any of them to create a keyframe, a time and value pair for the
+corresponding property. The keyframe gets inserted where your time cursor is in
+the timeline.
+
+Let's insert our first keys. Here, we will animate both the translation and the
+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*.
+
+|image10|
+
+Two tracks appear in the editor with a diamond icon representing each keyframe.
+
+|image11|
+
+You can click and drag on the diamonds to move them in time. Move the
+translation key to ``0.1`` seconds and the rotation key to ``0.2`` seconds.
+
+|image12|
+
+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``.
+
+|image13|
+
+Create a keyframe for both properties and shift the translation key to ``0.7``
+seconds by dragging it on the timeline.
+
+|image14|
+
+.. note::
+
+    A lecture on the principles of animation is beyond the scope of this
+    tutorial. Just note that you don't want to time and space everything evenly.
+    Instead, animators play with timing and spacing, two core animation
+    principles. You want to offset and contrast in your character's motion to
+    make them feel alive.
+
+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,
+create a key for both properties.
+
+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.
+
+|image15|
+
+You can see that the engine interpolates between your keyframes to produce a
+continuous animation. At the moment, though, the motion feels very robotic. This
+is because the default interpolation is linear, causing constant transitions,
+unlike how living things move in the real world.
+
+We can control the transition between keyframes using easing curves.
+
+Click and drag around the first two keys in the timeline to box select them.
+
+|image16|
+
+You can edit the properties of both keys simultaneously in the *Inspector*,
+where you can see an *Easing* property.
+
+|image17|
+
+Click and drag on the curve, pulling it towards the left. This will make it
+ease-out, that is to say, transition fast initially and slow down as the time
+cursor reaches the next keyframe.
+
+|image18|
+
+Play the animation again to see the difference. The first half should already
+feel a bit bouncier.
+
+Apply an ease-out to the second keyframe in the rotation track.
+
+|image19|
+
+Do the opposite for the second translation keyframe, dragging it to the right.
+
+|image20|
+
+Your animation should look something like this.
+
+|image21|
+
+.. note::
+
+    Animations update the properties of the animated nodes every frame,
+    overriding initial values. If we directly animated the *Player* node, it
+    would prevent us from moving it in code. This is where the *Pivot* node
+    comes in handy: even though we animated the *Character*, we can still move
+    and rotate the *Pivot* and layer changes on top of the animation in a
+    script.
+
+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
+to offset it.
+
+Controlling the animation in code
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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.
+
+Open the *Player*'s script by clicking the script icon next to it.
+
+|image22|
+
+In ``_physics_process()``, after the line where we check the ``direction``
+vector, add the following code.
+
+::
+
+   func _physics_process(delta):
+       #...
+       #if direction != Vector3.ZERO:
+           #...
+           $AnimationPlayer.playback_speed = 4
+       else:
+           $AnimationPlayer.playback_speed = 1
+
+This code makes it so when the player moves, we multiply the playback speed by
+``4``. When they stop, we reset it to normal.
+
+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
+at the end of ``_physics_process()``.
+
+::
+
+   func _physics_process(delta):
+       #...
+       $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
+
+Animating the mobs
+------------------
+
+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.
+
+For example, both the *Mob* and the *Player* scenes have a *Pivot* and a
+*Character* node, so we can reuse animations between them.
+
+We're going to duplicate the animation using a feature called "merge from
+scene".
+
+Open the *Mob* scene, right-click on the *Mob* node and select *Merge From
+Scene*.
+
+|image23|
+
+Double-click ``Player.tscn`` to open it and import the *AnimationPlayer*. That's
+it; all monsters will now play the float animation.
+
+We can change the playback speed based on the creature's ``random_speed``. Open
+the *Mob*'s script and at the end of the ``initialize()`` function, add the
+following line.
+
+::
+
+   func initialize(start_position, player_position):
+       #...
+       $AnimationPlayer.playback_speed = random_speed / min_speed
+
+And with that, you finished coding your first complete 3D game.
+
+**Congratulations**!
+
+In the next part, we'll quickly recap what you learned and give you some links
+to keep learning more. But for now, here are the complete ``Player.gd`` and
+``Mob.gd`` so you can check your code against them.
+
+Here's the *Player* script.
+
+::
+
+   extends KinematicBody
+
+   # 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()
+
+And the *Mob*'s script.
+
+::
+
+   extends KinematicBody
+
+   # 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):
+       translation = start_position
+       look_at(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 _on_VisibilityNotifier_screen_exited():
+       queue_free()
+
+.. |image0| image:: img/squash-the-creeps-final.gif
+.. |image1| image:: img/09.adding_animations/01.animation_player_dock.png
+.. |image2| image:: img/09.adding_animations/02.new_animation.png
+.. |image3| image:: img/09.adding_animations/03.float_name.png
+.. |image4| image:: img/09.adding_animations/03.timeline.png
+.. |image5| image:: img/09.adding_animations/04.autoplay_and_loop.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
+.. |image15| image:: img/09.adding_animations/14.play_button.png
+.. |image16| image:: img/09.adding_animations/15.box_select.png
+.. |image17| image:: img/09.adding_animations/16.easing_property.png
+.. |image18| image:: img/09.adding_animations/17.ease_out.png
+.. |image19| image:: img/09.adding_animations/18.ease_out_second_rotation_key.png
+.. |image20| image:: img/09.adding_animations/19.ease_in_second_translation_key.png
+.. |image21| image:: img/09.adding_animations/20.float_animation.gif
+.. |image22| image:: img/09.adding_animations/21.script_icon.png
+.. |image23| image:: img/09.adding_animations/22.merge_from_scene.png

+ 42 - 0
getting_started/first_3d_game/going_further.rst

@@ -0,0 +1,42 @@
+.. _doc_first_3d_game_going_further:
+
+Going further
+=============
+
+You can pat yourself on the back for having completed your first 3D game with
+Godot.
+
+In this series, we went over a wide range of techniques and editor features.
+Hopefully, you’ve witnessed how intuitive Godot’s scene system can be and
+learned a few tricks you can apply in your projects.
+
+But we just scratched the surface: Godot has a lot more in store for you to save
+time creating games. And you can learn all that by browsing the documentation.
+
+Where should you begin? Below, you’ll find a few pages to start exploring and
+build upon what you’ve learned so far.
+
+But before that, here’s a link to download a completed version of the project:
+`<https://github.com/GDQuest/godot-3d-dodge-the-creeps>`_.
+
+Exploring the manual
+--------------------
+
+The manual is your ally whenever you have a doubt or you’re curious about a
+feature. It does not contain tutorials about specific game genres or mechanics.
+Instead, it explains how Godot works in general. In it, you will find
+information about 2D, 3D, physics, rendering and performance, and much more.
+
+Here are the sections we recommend you to explore next:
+
+1. Read the :ref:`Scripting section <toc-scripting-core-features>` to learn essential programming features you’ll use
+   in every project.
+2. The :ref:`3D <toc-learn-features-3d>` and :ref:`Physics <toc-learn-features-physics>` sections will teach you more about 3D game creation in the
+   engine.
+3. :ref:`Inputs <toc-learn-features-inputs>` is another important one for any game project.
+
+You can start with these or, if you prefer, look at the sidebar menu on the left
+and pick your options.
+
+We hope you enjoyed this tutorial series, and we’re looking forward to seeing
+what you achieve using Godot.

BIN
getting_started/first_3d_game/img/01.game_setup/01.import_button.png


BIN
getting_started/first_3d_game/img/01.game_setup/02.browse_to_project_folder.png


BIN
getting_started/first_3d_game/img/01.game_setup/03.import_and_edit.png


BIN
getting_started/first_3d_game/img/01.game_setup/04.start_assets.png


BIN
getting_started/first_3d_game/img/01.game_setup/05.main_node.png


BIN
getting_started/first_3d_game/img/01.game_setup/06.staticbody_node.png


BIN
getting_started/first_3d_game/img/01.game_setup/07.collision_shape_warning.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/09.box_extents.png


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


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


BIN
getting_started/first_3d_game/img/01.game_setup/12.cube_resized.png


BIN
getting_started/first_3d_game/img/01.game_setup/13.move_gizmo_y_axis.png


BIN
getting_started/first_3d_game/img/01.game_setup/14.select_mode_icon.png


BIN
getting_started/first_3d_game/img/01.game_setup/15.translation_amount.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/17.project_with_light.png


BIN
getting_started/first_3d_game/img/02.player_input/01.new_scene.png


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/03.scene_structure.png


BIN
getting_started/first_3d_game/img/02.player_input/04.sphere_shape.png


BIN
getting_started/first_3d_game/img/02.player_input/05.moving_the_sphere_up.png


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


BIN
getting_started/first_3d_game/img/02.player_input/07.adding_action.png


BIN
getting_started/first_3d_game/img/02.player_input/07.input_map_tab.png


BIN
getting_started/first_3d_game/img/02.player_input/07.project_settings.png


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


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/09.keyboard_key_popup.png


BIN
getting_started/first_3d_game/img/02.player_input/09.keyboard_keys.png


BIN
getting_started/first_3d_game/img/02.player_input/10.joy_axis_option.png


BIN
getting_started/first_3d_game/img/02.player_input/11.joy_axis_popup.png


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


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/14.add_jump_button.png


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


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/02.clicking_main_tab.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/03.instance_child_scene.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/04.scene_tree_with_camera.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/05.camera_preview_checkbox.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/06.two_viewports.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/07.camera_preview_checkbox.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/08.camera_moved.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/09.camera_rotated.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/10.camera_perspective.png


BIN
getting_started/first_3d_game/img/03.player_movement_code/11.camera_orthographic.png


BIN
getting_started/first_3d_game/img/04.mob_scene/01.initial_three_nodes.png


BIN
getting_started/first_3d_game/img/04.mob_scene/02.add_child_node.png


BIN
getting_started/first_3d_game/img/04.mob_scene/03.scene_with_collision_shape.png


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/05.box_final_size.png


BIN
getting_started/first_3d_game/img/04.mob_scene/06.visibility_notifier.png


BIN
getting_started/first_3d_game/img/04.mob_scene/07.visibility_notifier_bbox_resized.png


BIN
getting_started/first_3d_game/img/04.mob_scene/08.mob_attach_script.png


BIN
getting_started/first_3d_game/img/04.mob_scene/09.switch_to_3d_workspace.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/11.connect_signal.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/01.monsters_path_preview.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/02.project_settings.png


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


BIN
getting_started/first_3d_game/img/05.spawning_mobs/04.camera_preview.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/05.cylinders_node.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/06.cylinder_mesh.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/07.top_view.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/08.toggle_view_grid.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/09.toggle_grid_snap.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/10.place_first_cylinder.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/11.both_cylinders_selected.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/12.four_cylinders.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/13.selecting_all_cylinders.png


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


BIN
getting_started/first_3d_game/img/05.spawning_mobs/15.bright-cylinders.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/16.cylinders_fold.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/17.points_options.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/18.close_path.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/19.path_result.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/20.mob_scene_property.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/20.spawn_nodes.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/21.mob_timer.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/22.mob_timer_properties.png


BIN
getting_started/first_3d_game/img/05.spawning_mobs/23.timeout_signal.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/25.spawn_result.png


BIN
getting_started/first_3d_game/img/06.jump_and_squash/02.project_settings.png


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/04.default_physics_properties.png


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/06.named_checkboxes.png


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


Деякі файли не було показано, через те що забагато файлів було змінено