05.spawning_mobs.rst 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. .. _doc_first_3d_game_spawning_monsters:
  2. Spawning monsters
  3. =================
  4. In this part, we're going to spawn monsters along a path randomly. By the end,
  5. you will have monsters roaming the game board.
  6. |image0|
  7. Double-click on ``Main.tscn`` in the *FileSystem* dock to open the *Main* scene.
  8. Before drawing the path, we're going to change the game resolution. Our game has
  9. a default window size of ``1024x600``. We're going to set it to ``720x540``, a
  10. nice little box.
  11. Go to *Project -> Project Settings*.
  12. |image1|
  13. In the left menu, navigate down to *Display -> Window*. On the right, set the
  14. *Width* to ``720`` and the *Height* to ``540``.
  15. |image2|
  16. Creating the spawn path
  17. -----------------------
  18. Like you did in the 2D game tutorial, you're going to design a path and use a
  19. *PathFollow* node to sample random locations on it.
  20. In 3D though, it's a bit more complicated to draw the path. We want it to be
  21. around the game view so monsters appear right outside the screen. But if we draw
  22. a path, we won't see it from the camera preview.
  23. To find the view's limits, we can use some placeholder meshes. Your viewport
  24. should still be split into two parts, with the camera preview at the bottom. If
  25. that isn't the case, press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS) to split the view into two.
  26. Select the *Camera* node and click the *Preview* checkbox in the bottom
  27. viewport.
  28. |image3|
  29. Adding placeholder cylinders
  30. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  31. Let's add the placeholder meshes. Add a new *Spatial* node as a child of the
  32. *Main* node and name it *Cylinders*. We'll use it to group the cylinders. As a
  33. child of it, add a *MeshInstance* node.
  34. |image4|
  35. In the *Inspector*, assign a *CylinderMesh* to the *Mesh* property.
  36. |image5|
  37. Set the top viewport to the top orthogonal view using the menu in the viewport's
  38. top-left corner. Alternatively, you can press the keypad's 7 key.
  39. |image6|
  40. The grid is a bit distracting for me. You can toggle it by going to the *View*
  41. menu in the toolbar and clicking *View Grid*.
  42. |image7|
  43. You now want to move the cylinder along the ground plane, looking at the camera
  44. preview in the bottom viewport. I recommend using grid snap to do so. You can
  45. toggle it by clicking the magnet icon in the toolbar or pressing Y.
  46. |image8|
  47. Place the cylinder so it's right outside the camera's view in the top-left
  48. corner.
  49. |image9|
  50. We're going to create copies of the mesh and place them around the game area.
  51. Press :kbd:`Ctrl + D` (:kbd:`Cmd + D` on macOS) to duplicate the node. You can also right-click
  52. the node in the *Scene* dock and select *Duplicate*. Move the copy down along
  53. the blue Z axis until it's right outside the camera's preview.
  54. Select both cylinders by pressing the :kbd:`Shift` key and clicking on the unselected
  55. one and duplicate them.
  56. |image10|
  57. Move them to the right by dragging the red X axis.
  58. |image11|
  59. They're a bit hard to see in white, aren't they? Let's make them stand out by
  60. giving them a new material.
  61. In 3D, materials define a surface's visual properties like its color, how it
  62. reflects light, and more. We can use them to change the color of a mesh.
  63. We can update all four cylinders at once. Select all the mesh instances in the
  64. *Scene* dock. To do so, you can click on the first one and Shift click on the
  65. last one.
  66. |image12|
  67. In the *Inspector*, expand the *Material* section and assign a *SpatialMaterial*
  68. to slot *0*.
  69. |image13|
  70. Click the sphere icon to open the material resource. You get a preview of the
  71. material and a long list of sections filled with properties. You can use these
  72. to create all sorts of surfaces, from metal to rock or water.
  73. Expand the *Albedo* section and set the color to something that contrasts with
  74. the background, like a bright orange.
  75. |image14|
  76. We can now use the cylinders as guides. Fold them in the *Scene* dock by
  77. clicking the grey arrow next to them. Moving forward, you can also toggle their
  78. visibility by clicking the eye icon next to *Cylinders*.
  79. |image15|
  80. Add a *Path* node as a child of *Main*. In the toolbar, four icons appear. Click
  81. the *Add Point* tool, the icon with the green "+" sign.
  82. |image16|
  83. .. note:: You can hover any icon to see a tooltip describing the tool.
  84. Click in the center of each cylinder to create a point. Then, click the *Close
  85. Curve* icon in the toolbar to close the path. If any point is a bit off, you can
  86. click and drag on it to reposition it.
  87. |image17|
  88. Your path should look like this.
  89. |image18|
  90. To sample random positions on it, we need a *PathFollow* node. Add a
  91. *PathFollow* as a child of the *Path*. Rename the two nodes to *SpawnPath* and
  92. *SpawnLocation*, respectively. It's more descriptive of what we'll use them for.
  93. |image19|
  94. With that, we're ready to code the spawn mechanism.
  95. Spawning monsters randomly
  96. --------------------------
  97. Right-click on the *Main* node and attach a new script to it.
  98. We first export a variable to the *Inspector* so that we can assign ``Mob.tscn``
  99. or any other monster to it.
  100. Then, as we're going to spawn the monsters procedurally, we want to randomize
  101. numbers every time we play the game. If we don't do that, the monsters will
  102. always spawn following the same sequence.
  103. .. tabs::
  104. .. code-tab:: gdscript GDScript
  105. extends Node
  106. export (PackedScene) var mob_scene
  107. func _ready():
  108. randomize()
  109. .. code-tab:: csharp
  110. public class Main : Node
  111. {
  112. // Don't forget to rebuild the project so the editor knows about the new export variable.
  113. #pragma warning disable 649
  114. // We assign this in the editor, so we don't need the warning about not being assigned.
  115. [Export]
  116. public PackedScene MobScene;
  117. #pragma warning restore 649
  118. public override void _Ready()
  119. {
  120. GD.Randomize();
  121. }
  122. }
  123. We want to spawn mobs at regular time intervals. To do this, we need to go back
  124. to the scene and add a timer. Before that, though, we need to assign the
  125. ``Mob.tscn`` file to the ``mob_scene`` property.
  126. Head back to the 3D screen and select the *Main* node. Drag ``Mob.tscn`` from
  127. the *FileSystem* dock to the *Mob Scene* slot in the *Inspector*.
  128. |image20|
  129. Add a new *Timer* node as a child of *Main*. Name it *MobTimer*.
  130. |image21|
  131. In the *Inspector*, set its *Wait Time* to ``0.5`` seconds and turn on
  132. *Autostart* so it automatically starts when we run the game.
  133. |image22|
  134. Timers emit a ``timeout`` signal every time they reach the end of their *Wait
  135. Time*. By default, they restart automatically, emitting the signal in a cycle.
  136. We can connect to this signal from the *Main* node to spawn monsters every
  137. ``0.5`` seconds.
  138. With the *MobTimer* still selected, head to the *Node* dock on the right and
  139. double-click the ``timeout`` signal.
  140. |image23|
  141. Connect it to the *Main* node.
  142. |image24|
  143. This will take you back to the script, with a new empty
  144. ``_on_MobTimer_timeout()`` function.
  145. Let's code the mob spawning logic. We're going to:
  146. 1. Instantiate the mob scene.
  147. 2. Sample a random position on the spawn path.
  148. 3. Get the player's position.
  149. 4. Call the mob's ``initialize()`` method, passing it the random position and
  150. the player's position.
  151. 5. Add the mob as a child of the *Main* node.
  152. .. tabs::
  153. .. code-tab:: gdscript GDScript
  154. func _on_MobTimer_timeout():
  155. # Create a new instance of the Mob scene.
  156. var mob = mob_scene.instance()
  157. # Choose a random location on the SpawnPath.
  158. # We store the reference to the SpawnLocation node.
  159. var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
  160. # And give it a random offset.
  161. mob_spawn_location.unit_offset = randf()
  162. var player_position = $Player.transform.origin
  163. mob.initialize(mob_spawn_location.translation, player_position)
  164. add_child(mob)
  165. .. code-tab:: csharp
  166. // We also specified this function name in PascalCase in the editor's connection window
  167. public void OnMobTimerTimeout()
  168. {
  169. // Create a new instance of the Mob scene.
  170. Mob mob = (Mob)MobScene.Instance();
  171. // Choose a random location on the SpawnPath.
  172. // We store the reference to the SpawnLocation node.
  173. var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
  174. // And give it a random offset.
  175. mobSpawnLocation.UnitOffset = GD.Randf();
  176. Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
  177. mob.Initialize(mobSpawnLocation.Translation, playerPosition);
  178. AddChild(mob);
  179. }
  180. Above, ``randf()`` produces a random value between ``0`` and ``1``, which is
  181. what the *PathFollow* node's ``unit_offset`` expects.
  182. Here is the complete ``Main.gd`` script so far, for reference.
  183. .. tabs::
  184. .. code-tab:: gdscript GDScript
  185. extends Node
  186. export (PackedScene) var mob_scene
  187. func _ready():
  188. randomize()
  189. func _on_MobTimer_timeout():
  190. var mob = mob_scene.instance()
  191. var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
  192. mob_spawn_location.unit_offset = randf()
  193. var player_position = $Player.transform.origin
  194. mob.initialize(mob_spawn_location.translation, player_position)
  195. add_child(mob)
  196. .. code-tab:: csharp
  197. public class Main : Node
  198. {
  199. #pragma warning disable 649
  200. [Export]
  201. public PackedScene MobScene;
  202. #pragma warning restore 649
  203. public override void _Ready()
  204. {
  205. GD.Randomize();
  206. }
  207. public void OnMobTimerTimeout()
  208. {
  209. Mob mob = (Mob)MobScene.Instance();
  210. var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
  211. mobSpawnLocation.UnitOffset = GD.Randf();
  212. Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
  213. mob.Initialize(mobSpawnLocation.Translation, playerPosition);
  214. AddChild(mob);
  215. }
  216. }
  217. You can test the scene by pressing :kbd:`F6`. You should see the monsters spawn and
  218. move in a straight line.
  219. |image25|
  220. For now, they bump and slide against one another when their paths cross. We'll
  221. address this in the next part.
  222. .. |image0| image:: img/05.spawning_mobs/01.monsters_path_preview.png
  223. .. |image1| image:: img/05.spawning_mobs/02.project_settings.png
  224. .. |image2| image:: img/05.spawning_mobs/03.window_settings.png
  225. .. |image3| image:: img/05.spawning_mobs/04.camera_preview.png
  226. .. |image4| image:: img/05.spawning_mobs/05.cylinders_node.png
  227. .. |image5| image:: img/05.spawning_mobs/06.cylinder_mesh.png
  228. .. |image6| image:: img/05.spawning_mobs/07.top_view.png
  229. .. |image7| image:: img/05.spawning_mobs/08.toggle_view_grid.png
  230. .. |image8| image:: img/05.spawning_mobs/09.toggle_grid_snap.png
  231. .. |image9| image:: img/05.spawning_mobs/10.place_first_cylinder.png
  232. .. |image10| image:: img/05.spawning_mobs/11.both_cylinders_selected.png
  233. .. |image11| image:: img/05.spawning_mobs/12.four_cylinders.png
  234. .. |image12| image:: img/05.spawning_mobs/13.selecting_all_cylinders.png
  235. .. |image13| image:: img/05.spawning_mobs/14.spatial_material.png
  236. .. |image14| image:: img/05.spawning_mobs/15.bright-cylinders.png
  237. .. |image15| image:: img/05.spawning_mobs/16.cylinders_fold.png
  238. .. |image16| image:: img/05.spawning_mobs/17.points_options.png
  239. .. |image17| image:: img/05.spawning_mobs/18.close_path.png
  240. .. |image18| image:: img/05.spawning_mobs/19.path_result.png
  241. .. |image19| image:: img/05.spawning_mobs/20.spawn_nodes.png
  242. .. |image20| image:: img/05.spawning_mobs/20.mob_scene_property.png
  243. .. |image21| image:: img/05.spawning_mobs/21.mob_timer.png
  244. .. |image22| image:: img/05.spawning_mobs/22.mob_timer_properties.png
  245. .. |image23| image:: img/05.spawning_mobs/23.timeout_signal.png
  246. .. |image24| image:: img/05.spawning_mobs/24.connect_timer_to_main.png
  247. .. |image25| image:: img/05.spawning_mobs/25.spawn_result.png