your_first_game.rst 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  1. .. _doc_your_first_game:
  2. Your first game
  3. ===============
  4. Overview
  5. --------
  6. This tutorial will guide you through making your first Godot
  7. project. You will learn how the Godot editor works, how to structure
  8. a project, and how to build a 2D game.
  9. .. note:: This project is an introduction to the Godot engine. It
  10. assumes that you have some programming experience already. If
  11. you're new to programming entirely, you should start here:
  12. :ref:`doc_scripting`.
  13. The game is called "Dodge the Creeps!". Your character must move and
  14. avoid the enemies for as long as possible. Here is a preview of the
  15. final result:
  16. .. image:: img/dodge_preview.gif
  17. **Why 2D?** 3D games are much more complex than 2D ones. You should stick to 2D
  18. until you have a good understanding of the game development process.
  19. Project setup
  20. -------------
  21. Launch Godot and create a new project. Then, download
  22. :download:`dodge_assets.zip <files/dodge_assets.zip>` - the images and sounds you'll be
  23. using to make the game. Unzip these files to your project folder.
  24. .. note:: For this tutorial, we will assume you are familiar with the
  25. editor. If you haven't read :ref:`doc_scenes_and_nodes`, do so now
  26. for an explanation of setting up a project and using the editor.
  27. This game will use portrait mode, so we need to adjust the size of the
  28. game window. Click on Project -> Project Settings -> Display -> Window and
  29. set "Width" to ``480`` and "Height" to ``720``.
  30. Organizing the project
  31. ~~~~~~~~~~~~~~~~~~~~~~
  32. In this project, we will make 3 independent scenes: ``Player``,
  33. ``Mob``, and ``HUD``, which we will combine into the game's ``Main``
  34. scene. In a larger project, it might be useful to make folders to hold
  35. the various scenes and their scripts, but for this relatively small
  36. game, you can save your scenes and scripts in the project's root folder,
  37. referred to as ``res://``. You can see your project folders in the FileSystem
  38. Dock in the lower left corner:
  39. .. image:: img/filesystem_dock.png
  40. Player scene
  41. ------------
  42. The first scene we will make defines the ``Player`` object. One of the benefits
  43. of creating a separate Player scene is that we can test it separately, even
  44. before we've created other parts of the game.
  45. Node structure
  46. ~~~~~~~~~~~~~~
  47. To begin, click the "Add/Create a New Node" button and add an :ref:`Area2D <class_Area2D>`
  48. node to the scene.
  49. .. image:: img/add_node.png
  50. With ``Area2D`` we can detect objects that overlap or run into the player.
  51. Change its name to ``Player`` by clicking on the node's name.
  52. This is the scene's root node. We can add additional nodes to the player to add
  53. functionality.
  54. Before we add any children to the ``Player`` node, we want to make sure we don't
  55. accidentally move or resize them by clicking on them. Select the node and
  56. click the icon to the right of the lock; its tooltip says "Makes sure the object's children
  57. are not selectable."
  58. .. image:: img/lock_children.png
  59. Save the scene. Click Scene -> Save, or press ``Ctrl+S`` on Windows/Linux or ``Command+S`` on Mac.
  60. .. note:: For this project, we will be following the Godot naming conventions.
  61. - **GDScript**: Classes (nodes) use PascalCase, variables and
  62. functions use snake_case, and constants use ALL_CAPS (See
  63. :ref:`doc_gdscript_styleguide`).
  64. - **C#**: Classes, export variables and methods use PascalCase,
  65. private fields use _camelCase, local variables and parameters use
  66. camelCase (See :ref:`doc_c_sharp_styleguide`). Be careful to type
  67. the method names precisely when connecting signals.
  68. Sprite animation
  69. ~~~~~~~~~~~~~~~~
  70. Click on the ``Player`` node and add an :ref:`AnimatedSprite <class_AnimatedSprite>` node as a
  71. child. The ``AnimatedSprite`` will handle the appearance and animations
  72. for our player. Notice that there is a warning symbol next to the node.
  73. An ``AnimatedSprite`` requires a :ref:`SpriteFrames <class_SpriteFrames>` resource, which is a
  74. list of the animations it can display. To create one, find the
  75. ``Frames`` property in the Inspector and click "[empty]" ->
  76. "New SpriteFrames". This should automatically open the SpriteFrames panel.
  77. .. image:: img/spriteframes_panel.png
  78. On the left is a list of animations. Click the "default" one and rename
  79. it to "right". Then click the "Add" button to create a second animation
  80. named "up". Drag the two images for each animation, named ``playerGrey_up[1/2]`` and ``playerGrey_walk[1/2]``,
  81. into the "Animation Frames" side of the panel:
  82. .. image:: img/spriteframes_panel2.png
  83. The player images are a bit too large for the game window, so we need to
  84. scale them down. Click on the ``AnimatedSprite`` node and set the ``Scale``
  85. property to ``(0.5, 0.5)``. You can find it in the Inspector under the
  86. ``Node2D`` heading.
  87. .. image:: img/player_scale.png
  88. Finally, add a :ref:`CollisionShape2D <class_CollisionShape2D>` as a child
  89. of ``Player``. This will determine the player's "hitbox", or the
  90. bounds of its collision area. For this character, a ``CapsuleShape2D``
  91. node gives the best fit, so next to "Shape" in the Inspector, click
  92. "[empty]"" -> "New CapsuleShape2D". Using the two size handles, resize the
  93. shape to cover the sprite:
  94. .. image:: img/player_coll_shape.png
  95. When you're finished, your ``Player`` scene should look like this:
  96. .. image:: img/player_scene_nodes.png
  97. Moving the player
  98. ~~~~~~~~~~~~~~~~~
  99. Now we need to add some functionality that we can't get from a built-in
  100. node, so we'll add a script. Click the ``Player`` node and click the
  101. "Add Script" button:
  102. .. image:: img/add_script_button.png
  103. In the script settings window, you can leave the default settings alone. Just
  104. click "Create":
  105. .. note:: If you're creating a C# script or other languages, select the
  106. language from the `language` drop down menu before hitting create.
  107. .. image:: img/attach_node_window.png
  108. .. note:: If this is your first time encountering GDScript, please read
  109. :ref:`doc_scripting` before continuing.
  110. Start by declaring the member variables this object will need:
  111. .. tabs::
  112. .. code-tab:: gdscript GDScript
  113. extends Area2D
  114. export var speed = 400 # How fast the player will move (pixels/sec).
  115. var screen_size # Size of the game window.
  116. .. code-tab:: csharp
  117. public class Player : Area2D
  118. {
  119. [Export]
  120. public int Speed = 400; // How fast the player will move (pixels/sec).
  121. private Vector2 _screenSize; // Size of the game window.
  122. }
  123. Using the ``export`` keyword on the first variable ``speed`` allows us to
  124. set its value in the Inspector. This can be handy for values that you
  125. want to be able to adjust just like a node's built-in properties. Click on
  126. the ``Player`` node and you'll see the property now appears in the "Script
  127. Variables" section of the Inspector. Remember, if you change the value here, it
  128. will override the value written in the script.
  129. .. warning:: If you're using C#, you need to (re)build the project assemblies
  130. whenever you want to see new export variables or signals. This
  131. build can be manually triggered by clicking the word "Mono" at the
  132. bottom of the editor window to reveal the Mono Panel, then
  133. clicking the "Build Project" button.
  134. .. image:: img/export_variable.png
  135. The ``_ready()`` function is called when a node enters the scene tree,
  136. which is a good time to find the size of the game window:
  137. .. tabs::
  138. .. code-tab:: gdscript GDScript
  139. func _ready():
  140. screen_size = get_viewport_rect().size
  141. .. code-tab:: csharp
  142. public override void _Ready()
  143. {
  144. _screenSize = GetViewport().GetSize();
  145. }
  146. Now we can use the ``_process()`` function to define what the player will do.
  147. ``_process()`` is called every frame, so we'll use it to update
  148. elements of our game, which we expect will change often. For the player, we
  149. need to do the following:
  150. - Check for input.
  151. - Move in the given direction.
  152. - Play the appropriate animation.
  153. First, we need to check for input - is the player pressing a key? For
  154. this game, we have 4 direction inputs to check. Input actions are defined
  155. in the Project Settings under "Input Map". Here, you can define custom events and
  156. assign different keys, mouse events, or other inputs to them. For this demo,
  157. we will use the default events that are assigned to the arrow keys on the
  158. keyboard.
  159. You can detect whether a key is pressed using
  160. ``Input.is_action_pressed()``, which returns ``true`` if it is pressed
  161. or ``false`` if it isn't.
  162. .. tabs::
  163. .. code-tab:: gdscript GDScript
  164. func _process(delta):
  165. var velocity = Vector2() # The player's movement vector.
  166. if Input.is_action_pressed("ui_right"):
  167. velocity.x += 1
  168. if Input.is_action_pressed("ui_left"):
  169. velocity.x -= 1
  170. if Input.is_action_pressed("ui_down"):
  171. velocity.y += 1
  172. if Input.is_action_pressed("ui_up"):
  173. velocity.y -= 1
  174. if velocity.length() > 0:
  175. velocity = velocity.normalized() * speed
  176. $AnimatedSprite.play()
  177. else:
  178. $AnimatedSprite.stop()
  179. .. code-tab:: csharp
  180. public override void _Process(float delta)
  181. {
  182. var velocity = new Vector2(); // The player's movement vector.
  183. if (Input.IsActionPressed("ui_right"))
  184. {
  185. velocity.x += 1;
  186. }
  187. if (Input.IsActionPressed("ui_left"))
  188. {
  189. velocity.x -= 1;
  190. }
  191. if (Input.IsActionPressed("ui_down"))
  192. {
  193. velocity.y += 1;
  194. }
  195. if (Input.IsActionPressed("ui_up"))
  196. {
  197. velocity.y -= 1;
  198. }
  199. var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
  200. if (velocity.Length() > 0)
  201. {
  202. velocity = velocity.Normalized() * Speed;
  203. animatedSprite.Play();
  204. }
  205. else
  206. {
  207. animatedSprite.Stop();
  208. }
  209. }
  210. We start by setting the ``velocity`` to ``(0, 0)`` - by default the player
  211. should not be moving. Then we check each input and add/subtract from the
  212. ``velocity`` to obtain a total direction. For example, if you hold ``right``
  213. and ``down`` at the same time, the resulting ``velocity`` vector will be
  214. ``(1, 1)``. In this case, since we're adding a horizontal and a vertical
  215. movement, the player would move *faster* than if it just moved horizontally.
  216. We can prevent that if we *normalize* the velocity, which means we set
  217. its *length* to ``1``, and multiply by the desired speed. This means no
  218. more fast diagonal movement.
  219. .. tip:: If you've never used vector math before, or need a refresher,
  220. you can see an explanation of vector usage in Godot at :ref:`doc_vector_math`.
  221. It's good to know but won't be necessary for the rest of this tutorial.
  222. We also check whether the player is moving so we can start or stop the
  223. AnimatedSprite animation.
  224. .. tip:: In GDScript, ``$`` returns the node at the relative path from the current node, or returns ``null`` if the node is not found.
  225. Since AnimatedSprite is a child of the current node, we can use ``$AnimatedSprite``.
  226. ``$`` is shorthand for ``get_node()``.
  227. So in the code above, ``$AnimatedSprite.play()`` is the same as ``get_node("AnimatedSprite").play()``.
  228. Now that we have a movement direction, we can update the player's position
  229. and use ``clamp()`` to prevent it from leaving the screen by adding the following
  230. to the bottom of the ``_process`` function:
  231. .. tabs::
  232. .. code-tab:: gdscript GDScript
  233. position += velocity * delta
  234. position.x = clamp(position.x, 0, screen_size.x)
  235. position.y = clamp(position.y, 0, screen_size.y)
  236. .. code-tab:: csharp
  237. Position += velocity * delta;
  238. Position = new Vector2(
  239. x: Mathf.Clamp(Position.x, 0, _screenSize.x),
  240. y: Mathf.Clamp(Position.y, 0, _screenSize.y)
  241. );
  242. .. tip:: *Clamping* a value means restricting it to a given range.
  243. Click "Play Scene" (``F6``) and confirm you can move the player
  244. around the screen in all directions. The console output that opens upon playing
  245. the scene can be closed by clicking ``Output`` (which should be highlighted in
  246. blue) in the lower left of the Bottom Panel.
  247. .. warning:: If you get an error in the "Debugger" panel that refers to a "null instance",
  248. this likely means you spelled the node name wrong. Node names are case-sensitive
  249. and ``$NodeName`` or ``get_node("NodeName")`` must match the name you see in the scene tree.
  250. Choosing animations
  251. ~~~~~~~~~~~~~~~~~~~
  252. Now that the player can move, we need to change which animation the
  253. AnimatedSprite is playing based on direction. We have a "right"
  254. animation, which should be flipped horizontally using the ``flip_h``
  255. property for left movement, and an "up" animation, which should be
  256. flipped vertically with ``flip_v`` for downward movement.
  257. Let's place this code at the end of our ``_process()`` function:
  258. .. tabs::
  259. .. code-tab:: gdscript GDScript
  260. if velocity.x != 0:
  261. $AnimatedSprite.animation = "right"
  262. $AnimatedSprite.flip_v = false
  263. # See the note below about boolean assignment
  264. $AnimatedSprite.flip_h = velocity.x < 0
  265. elif velocity.y != 0:
  266. $AnimatedSprite.animation = "up"
  267. $AnimatedSprite.flip_v = velocity.y > 0
  268. .. code-tab:: csharp
  269. if (velocity.x != 0)
  270. {
  271. animatedSprite.Animation = "right";
  272. // See the note below about boolean assignment
  273. animatedSprite.FlipH = velocity.x < 0;
  274. animatedSprite.FlipV = false;
  275. }
  276. else if(velocity.y != 0)
  277. {
  278. animatedSprite.Animation = "up";
  279. animatedSprite.FlipV = velocity.y > 0;
  280. }
  281. .. Note:: The boolean assignments in the code above are a common shorthand
  282. for programmers. Consider this code versus the shortened
  283. boolean assignment above:
  284. .. tabs::
  285. .. code-tab :: gdscript GDScript
  286. if velocity.x < 0:
  287. $AnimatedSprite.flip_h = true
  288. else:
  289. $AnimatedSprite.flip_h = false
  290. .. code-tab:: csharp
  291. if velocity.x < 0:
  292. animatedSprite.FlipH = true
  293. else:
  294. animatedSprite.FlipH = false
  295. Play the scene again and check that the animations are correct in each
  296. of the directions. When you're sure the movement is working correctly,
  297. add this line to ``_ready()``, so the player will be hidden when the game
  298. starts:
  299. .. tabs::
  300. .. code-tab:: gdscript GDScript
  301. hide()
  302. .. code-tab:: csharp
  303. Hide();
  304. Preparing for collisions
  305. ~~~~~~~~~~~~~~~~~~~~~~~~
  306. We want ``Player`` to detect when it's hit by an enemy, but we haven't
  307. made any enemies yet! That's OK, because we're going to use Godot's
  308. *signal* functionality to make it work.
  309. Add the following at the top of the script, after ``extends Area2d``:
  310. .. tabs::
  311. .. code-tab:: gdscript GDScript
  312. signal hit
  313. .. code-tab:: csharp
  314. // Don't forget to rebuild the project so the editor knows about the new signal.
  315. [Signal]
  316. public delegate void Hit();
  317. This defines a custom signal called "hit" that we will have our player
  318. emit (send out) when it collides with an enemy. We will use ``Area2D`` to
  319. detect the collision. Select the ``Player`` node and click the "Node" tab
  320. next to the Inspector tab to see the list of signals the player can emit:
  321. .. image:: img/player_signals.png
  322. Notice our custom "hit" signal is there as well! Since our enemies are
  323. going to be ``RigidBody2D`` nodes, we want the
  324. ``body_entered( Object body )`` signal; this will be emitted when a
  325. body contacts the player. Click "Connect.." and then "Connect" again on
  326. the "Connecting Signal" window. We don't need to change any of these
  327. settings - Godot will automatically create a function in your player's script.
  328. This function will be called whenever the signal is emitted - it *handles* the
  329. signal.
  330. .. tip:: When connecting a signal, instead of having Godot create a
  331. function for you, you can also give the name of an existing
  332. function that you want to link the signal to.
  333. Add this code to the function:
  334. .. tabs::
  335. .. code-tab:: gdscript GDScript
  336. func _on_Player_body_entered(body):
  337. hide() # Player disappears after being hit.
  338. emit_signal("hit")
  339. $CollisionShape2D.set_deferred("disabled", true)
  340. .. code-tab:: csharp
  341. public void OnPlayerBodyEntered(PhysicsBody2D body)
  342. {
  343. Hide(); // Player disappears after being hit.
  344. EmitSignal("Hit");
  345. GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
  346. }
  347. Each time an enemy hits the player, the signal is going to be emitted. We need
  348. to disable the player's collision so that we don't trigger the ``hit`` signal
  349. more than once.
  350. .. Note:: Disabling the area's collision shape can cause an error if it happens
  351. in the middle of the engine's collision processing. Using ``set_deferred()``
  352. allows us to have Godot wait to disable the shape until it's safe to
  353. do so.
  354. The last piece for our player is to add a function we can call to reset
  355. the player when starting a new game.
  356. .. tabs::
  357. .. code-tab:: gdscript GDScript
  358. func start(pos):
  359. position = pos
  360. show()
  361. $CollisionShape2D.disabled = false
  362. .. code-tab:: csharp
  363. public void Start(Vector2 pos)
  364. {
  365. Position = pos;
  366. Show();
  367. GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
  368. }
  369. Enemy scene
  370. -----------
  371. Now it's time to make the enemies our player will have to dodge. Their
  372. behavior will not be very complex: mobs will spawn randomly at the edges
  373. of the screen and move in a random direction in a straight line, then
  374. despawn when they go offscreen.
  375. We will build this into a ``Mob`` scene, which we can then *instance* to
  376. create any number of independent mobs in the game.
  377. Node setup
  378. ~~~~~~~~~~
  379. Click Scene -> New Scene and we'll create the Mob.
  380. The Mob scene will use the following nodes:
  381. - :ref:`RigidBody2D <class_RigidBody2D>` (named ``Mob``)
  382. - :ref:`AnimatedSprite <class_AnimatedSprite>`
  383. - :ref:`CollisionShape2D <class_CollisionShape2D>`
  384. - :ref:`VisibilityNotifier2D <class_VisibilityNotifier2D>` (named ``Visibility``)
  385. Don't forget to set the children so they can't be selected, like you did with the
  386. Player scene.
  387. In the :ref:`RigidBody2D <class_RigidBody2D>` properties, set ``Gravity Scale`` to ``0``, so
  388. the mob will not fall downward. In addition, under the
  389. ``PhysicsBody2D`` section, click the ``Mask`` property and
  390. uncheck the first box. This will ensure the mobs do not collide with each other.
  391. .. image:: img/set_collision_mask.png
  392. Set up the :ref:`AnimatedSprite <class_AnimatedSprite>` like you did for the player.
  393. This time, we have 3 animations: ``fly``, ``swim``, and ``walk``. Set the ``Playing``
  394. property in the Inspector to "On" and adjust the "Speed (FPS)" setting as shown below.
  395. We'll select one of these animations randomly so that the mobs will have some variety.
  396. .. image:: img/mob_animations.gif
  397. ``fly`` should be set to 3 FPS, with ``swim`` and ``walk`` set to 4 FPS.
  398. Like the player images, these mob images need to be scaled down. Set the
  399. ``AnimatedSprite``'s ``Scale`` property to ``(0.75, 0.75)``.
  400. As in the ``Player`` scene, add a ``CapsuleShape2D`` for the
  401. collision. To align the shape with the image, you'll need to set the
  402. ``Rotation Degrees`` property to ``90`` under ``Node2D``.
  403. Enemy script
  404. ~~~~~~~~~~~~
  405. Add a script to the ``Mob`` and add the following member variables:
  406. .. tabs::
  407. .. code-tab:: gdscript GDScript
  408. extends RigidBody2D
  409. export var min_speed = 150 # Minimum speed range.
  410. export var max_speed = 250 # Maximum speed range.
  411. var mob_types = ["walk", "swim", "fly"]
  412. .. code-tab:: csharp
  413. public class Mob : RigidBody2D
  414. {
  415. // Don't forget to rebuild the project so the editor knows about the new export variables.
  416. [Export]
  417. public int MinSpeed = 150; // Minimum speed range.
  418. [Export]
  419. public int MaxSpeed = 250; // Maximum speed range.
  420. private String[] _mobTypes = {"walk", "swim", "fly"};
  421. }
  422. When we spawn a mob, we'll pick a random value between ``min_speed`` and
  423. ``max_speed`` for how fast each mob will move (it would be boring if they
  424. were all moving at the same speed). We also have an array containing the names
  425. of the three animations, which we'll use to select a random one. Make sure
  426. you've spelled these the same in the script and in the SpriteFrames resource.
  427. Now let's look at the rest of the script. In ``_ready()`` we randomly
  428. choose one of the three animation types:
  429. .. tabs::
  430. .. code-tab:: gdscript GDScript
  431. func _ready():
  432. $AnimatedSprite.animation = mob_types[randi() % mob_types.size()]
  433. .. code-tab:: csharp
  434. // C# doesn't implement GDScript's random methods, so we use 'System.Random' as an alternative.
  435. static private Random _random = new Random();
  436. public override void _Ready()
  437. {
  438. GetNode<AnimatedSprite>("AnimatedSprite").Animation = _mobTypes[_random.Next(0, _mobTypes.Length)];
  439. }
  440. .. note:: You must use ``randomize()`` if you want
  441. your sequence of "random" numbers to be different every time you run
  442. the scene. We're going to use ``randomize()`` in our ``Main`` scene,
  443. so we won't need it here. ``randi() % n`` is the standard way to get
  444. a random integer between ``0`` and ``n-1``.
  445. The last piece is to make the mobs delete themselves when they leave the
  446. screen. Connect the ``screen_exited()`` signal of the ``Visibility``
  447. node and add this code:
  448. .. tabs::
  449. .. code-tab:: gdscript GDScript
  450. func _on_Visibility_screen_exited():
  451. queue_free()
  452. .. code-tab:: csharp
  453. public void OnVisibilityScreenExited()
  454. {
  455. QueueFree();
  456. }
  457. This completes the `Mob` scene.
  458. Main scene
  459. ----------
  460. Now it's time to bring it all together. Create a new scene and add a
  461. :ref:`Node <class_Node>` named ``Main``. Click the "Instance" button and select your
  462. saved ``Player.tscn``.
  463. .. image:: img/instance_scene.png
  464. .. note:: See :ref:`doc_instancing` to learn more about instancing.
  465. Now, add the following nodes as children of ``Main``, and name them as
  466. shown (values are in seconds):
  467. - :ref:`Timer <class_Timer>` (named ``MobTimer``) - to control how often mobs spawn
  468. - :ref:`Timer <class_Timer>` (named ``ScoreTimer``) - to increment the score every second
  469. - :ref:`Timer <class_Timer>` (named ``StartTimer``) - to give a delay before starting
  470. - :ref:`Position2D <class_Position2D>` (named ``StartPosition``) - to indicate the player's start position
  471. Set the ``Wait Time`` property of each of the ``Timer`` nodes as
  472. follows:
  473. - ``MobTimer``: ``0.5``
  474. - ``ScoreTimer``: ``1``
  475. - ``StartTimer``: ``2``
  476. In addition, set the ``One Shot`` property of ``StartTimer`` to "On" and
  477. set ``Position`` of the ``StartPosition`` node to ``(240, 450)``.
  478. Spawning mobs
  479. ~~~~~~~~~~~~~
  480. The Main node will be spawning new mobs, and we want them to appear at a
  481. random location on the edge of the screen. Add a :ref:`Path2D <class_Path2D>` node named
  482. ``MobPath`` as a child of ``Main``. When you select ``Path2D``,
  483. you will see some new buttons at the top of the editor:
  484. .. image:: img/path2d_buttons.png
  485. Select the middle one ("Add Point") and draw the path by clicking to add
  486. the points at the corners shown. To have the points snap to the grid, make sure "Snap to
  487. Grid" is checked. This option can be found under the "Snapping options"
  488. button to the left of the "Lock" button, appearing as a series of three
  489. vertical dots.
  490. .. image:: img/draw_path2d.gif
  491. .. important:: Draw the path in *clockwise* order, or your mobs will spawn
  492. pointing *outwards* instead of *inwards*!
  493. After placing point ``4`` in the image, click the "Close Curve" button and
  494. your curve will be complete.
  495. Now that the path is defined, add a :ref:`PathFollow2D <class_PathFollow2D>`
  496. node as a child of ``MobPath`` and name it ``MobSpawnLocation``. This node will
  497. automatically rotate and follow the path as it moves, so we can use it
  498. to select a random position and direction along the path.
  499. Main script
  500. ~~~~~~~~~~~
  501. Add a script to ``Main``. At the top of the script, we use
  502. ``export (PackedScene)`` to allow us to choose the Mob scene we want to
  503. instance.
  504. .. tabs::
  505. .. code-tab:: gdscript GDScript
  506. extends Node
  507. export (PackedScene) var Mob
  508. var score
  509. func _ready():
  510. randomize()
  511. .. code-tab:: csharp
  512. public class Main : Node
  513. {
  514. // Don't forget to rebuild the project so the editor knows about the new export variable.
  515. [Export]
  516. public PackedScene Mob;
  517. private int _score;
  518. // We use 'System.Random' as an alternative to GDScript's random methods.
  519. private Random _random = new Random();
  520. public override void _Ready()
  521. {
  522. }
  523. // We'll use this later because C# doesn't support GDScript's randi().
  524. private float RandRange(float min, float max)
  525. {
  526. return (float)_random.NextDouble() * (max - min) + min;
  527. }
  528. }
  529. Drag ``Mob.tscn`` from the "FileSystem" panel and drop it in the
  530. ``Mob`` property under the Script Variables of the ``Main`` node.
  531. Next, click on the Player and connect the ``hit`` signal. We want to make a
  532. new function named ``game_over``, which will handle what needs to happen when a
  533. game ends. Type "game_over" in the "Method In Node" box at the bottom of the
  534. "Connecting Signal" window. Add the following code, as well as a ``new_game``
  535. function to set everything up for a new game:
  536. .. tabs::
  537. .. code-tab:: gdscript GDScript
  538. func game_over():
  539. $ScoreTimer.stop()
  540. $MobTimer.stop()
  541. func new_game():
  542. score = 0
  543. $Player.start($StartPosition.position)
  544. $StartTimer.start()
  545. .. code-tab:: csharp
  546. public void GameOver()
  547. {
  548. GetNode<Timer>("MobTimer").Stop();
  549. GetNode<Timer>("ScoreTimer").Stop();
  550. }
  551. public void NewGame()
  552. {
  553. _score = 0;
  554. var player = GetNode<Player>("Player");
  555. var startPosition = GetNode<Position2D>("StartPosition");
  556. player.Start(startPosition.Position);
  557. GetNode<Timer>("StartTimer").Start();
  558. }
  559. Now connect the ``timeout()`` signal of each of the Timer nodes (``StartTimer``,
  560. ``ScoreTimer``, and ``MobTimer``) to the main script. ``StartTimer`` will start
  561. the other two timers. ``ScoreTimer`` will increment the score by 1.
  562. .. tabs::
  563. .. code-tab:: gdscript GDScript
  564. func _on_StartTimer_timeout():
  565. $MobTimer.start()
  566. $ScoreTimer.start()
  567. func _on_ScoreTimer_timeout():
  568. score += 1
  569. .. code-tab:: csharp
  570. public void OnStartTimerTimeout()
  571. {
  572. GetNode<Timer>("MobTimer").Start();
  573. GetNode<Timer>("ScoreTimer").Start();
  574. }
  575. public void OnScoreTimerTimeout()
  576. {
  577. _score++;
  578. }
  579. In ``_on_MobTimer_timeout()``, we will create a mob instance, pick a
  580. random starting location along the ``Path2D``, and set the mob in
  581. motion. The ``PathFollow2D`` node will automatically rotate as it
  582. follows the path, so we will use that to select the mob's direction as
  583. well as its position.
  584. Note that a new instance must be added to the scene using
  585. ``add_child()``.
  586. Now click on ``MobTimer`` in the scene window then head to inspector window,
  587. switch to node view then click on ``timeout()`` and connect the signal.
  588. Add the following code:
  589. .. tabs::
  590. .. code-tab:: gdscript GDScript
  591. func _on_MobTimer_timeout():
  592. # Choose a random location on Path2D.
  593. $MobPath/MobSpawnLocation.set_offset(randi())
  594. # Create a Mob instance and add it to the scene.
  595. var mob = Mob.instance()
  596. add_child(mob)
  597. # Set the mob's direction perpendicular to the path direction.
  598. var direction = $MobPath/MobSpawnLocation.rotation + PI / 2
  599. # Set the mob's position to a random location.
  600. mob.position = $MobPath/MobSpawnLocation.position
  601. # Add some randomness to the direction.
  602. direction += rand_range(-PI / 4, PI / 4)
  603. mob.rotation = direction
  604. # Set the velocity (speed & direction).
  605. mob.linear_velocity = Vector2(rand_range(mob.min_speed, mob.max_speed), 0)
  606. mob.linear_velocity = mob.linear_velocity.rotated(direction)
  607. .. code-tab:: csharp
  608. public void OnMobTimerTimeout()
  609. {
  610. // Choose a random location on Path2D.
  611. var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
  612. mobSpawnLocation.SetOffset(_random.Next());
  613. // Create a Mob instance and add it to the scene.
  614. var mobInstance = (RigidBody2D)Mob.Instance();
  615. AddChild(mobInstance);
  616. // Set the mob's direction perpendicular to the path direction.
  617. float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;
  618. // Set the mob's position to a random location.
  619. mobInstance.Position = mobSpawnLocation.Position;
  620. // Add some randomness to the direction.
  621. direction += RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
  622. mobInstance.Rotation = direction;
  623. // Choose the velocity.
  624. mobInstance.SetLinearVelocity(new Vector2(RandRange(150f, 250f), 0).Rotated(direction));
  625. }
  626. .. important:: In functions requiring angles, GDScript uses *radians*,
  627. not degrees. If you're more comfortable working with
  628. degrees, you'll need to use the ``deg2rad()`` and
  629. ``rad2deg()`` functions to convert between the two.
  630. HUD
  631. ---
  632. The final piece our game needs is a UI: an interface to display things
  633. like score, a "game over" message, and a restart button. Create a new
  634. scene, and add a :ref:`CanvasLayer <class_CanvasLayer>` node named ``HUD``. "HUD" stands for
  635. "heads-up display", an informational display that appears as an
  636. overlay on top of the game view.
  637. The :ref:`CanvasLayer <class_CanvasLayer>` node lets us draw our UI elements on
  638. a layer above the rest of the game, so that the information it displays isn't
  639. covered up by any game elements like the player or mobs.
  640. The HUD displays the following information:
  641. - Score, changed by ``ScoreTimer``.
  642. - A message, such as "Game Over" or "Get Ready!"
  643. - A "Start" button to begin the game.
  644. The basic node for UI elements is :ref:`Control <class_Control>`. To create our UI,
  645. we'll use two types of :ref:`Control <class_Control>` nodes: :ref:`Label <class_Label>`
  646. and :ref:`Button <class_Button>`.
  647. Create the following as children of the ``HUD`` node:
  648. - :ref:`Label <class_Label>` named ``ScoreLabel``.
  649. - :ref:`Label <class_Label>` named ``MessageLabel``.
  650. - :ref:`Button <class_Button>` named ``StartButton``.
  651. - :ref:`Timer <class_Timer>` named ``MessageTimer``.
  652. Click on the ``ScoreLabel`` and type a number into the *Text* field in the
  653. Inspector. The default font for ``Control`` nodes is small and doesn't scale
  654. well. There is a font file included in the game assets called
  655. "Xolonium-Regular.ttf". To use this font, do the following for each of
  656. the three ``Control`` nodes:
  657. 1. Under "Custom Fonts", choose "New DynamicFont"
  658. .. image:: img/custom_font1.png
  659. 2. Click on the "DynamicFont" you added, and under "Font/Font Data",
  660. choose "Load" and select the "Xolonium-Regular.ttf" file. You must
  661. also set the font's ``Size``. A setting of ``64`` works well.
  662. .. image:: img/custom_font2.png
  663. .. note:: **Anchors and Margins:** ``Control`` nodes have a position and size,
  664. but they also have anchors and margins. Anchors define the
  665. origin - the reference point for the edges of the node. Margins
  666. update automatically when you move or resize a control node. They
  667. represent the distance from the control node's edges to its anchor.
  668. See :ref:`doc_design_interfaces_with_the_control_nodes` for more details.
  669. Arrange the nodes as shown below. Click the "Anchor" button to
  670. set a Control node's anchor:
  671. .. image:: img/ui_anchor.png
  672. You can drag the nodes to place them manually, or for more precise
  673. placement, use the following settings:
  674. ScoreLabel
  675. ~~~~~~~~~~
  676. - *Text* : ``0``
  677. - *Layout* : "Top Wide"
  678. - *Align* : "Center"
  679. MessageLabel
  680. ~~~~~~~~~~~~
  681. - *Text* : ``Dodge the Creeps!``
  682. - *Layout* : "HCenter Wide"
  683. - *Align* : "Center"
  684. StartButton
  685. ~~~~~~~~~~~
  686. - *Text* : ``Start``
  687. - *Layout* : "Center Bottom"
  688. - *Margin* :
  689. - Top: ``-200``
  690. - Bottom: ``-100``
  691. Now add this script to ``HUD``:
  692. .. tabs::
  693. .. code-tab:: gdscript GDScript
  694. extends CanvasLayer
  695. signal start_game
  696. .. code-tab:: csharp
  697. public class HUD : CanvasLayer
  698. {
  699. // Don't forget to rebuild the project so the editor knows about the new signal.
  700. [Signal]
  701. public delegate void StartGame();
  702. }
  703. The ``start_game`` signal tells the ``Main`` node that the button
  704. has been pressed.
  705. .. tabs::
  706. .. code-tab:: gdscript GDScript
  707. func show_message(text):
  708. $MessageLabel.text = text
  709. $MessageLabel.show()
  710. $MessageTimer.start()
  711. .. code-tab:: csharp
  712. public void ShowMessage(string text)
  713. {
  714. var messageLabel = GetNode<Label>("MessageLabel");
  715. messageLabel.Text = text;
  716. messageLabel.Show();
  717. GetNode<Timer>("MessageTimer").Start();
  718. }
  719. This function is called when we want to display a message
  720. temporarily, such as "Get Ready". On the ``MessageTimer``, set the
  721. ``Wait Time`` to ``2`` and set the ``One Shot`` property to "On".
  722. .. tabs::
  723. .. code-tab:: gdscript GDScript
  724. func show_game_over():
  725. show_message("Game Over")
  726. yield($MessageTimer, "timeout")
  727. $MessageLabel.text = "Dodge the\nCreeps!"
  728. $MessageLabel.show()
  729. yield(get_tree().create_timer(1), 'timeout')
  730. $StartButton.show()
  731. .. code-tab:: csharp
  732. async public void ShowGameOver()
  733. {
  734. ShowMessage("Game Over");
  735. var messageTimer = GetNode<Timer>("MessageTimer");
  736. await ToSignal(messageTimer, "timeout");
  737. var messageLabel = GetNode<Label>("MessageLabel");
  738. messageLabel.Text = "Dodge the\nCreeps!";
  739. messageLabel.Show();
  740. GetNode<Button>("StartButton").Show();
  741. }
  742. This function is called when the player loses. It will show "Game
  743. Over" for 2 seconds, then return to the title screen and, after a brief pause,
  744. show the "Start" button.
  745. .. note:: When you need to pause for a brief time, an alternative to using a
  746. Timer node is to use the SceneTree's ``create_timer()`` function. This
  747. can be very useful to delay, such as in the above code, where we want
  748. to wait a little bit of time before showing the "Start" button.
  749. .. tabs::
  750. .. code-tab:: gdscript GDScript
  751. func update_score(score):
  752. $ScoreLabel.text = str(score)
  753. .. code-tab:: csharp
  754. public void UpdateScore(int score)
  755. {
  756. GetNode<Label>("ScoreLabel").Text = score.ToString();
  757. }
  758. This function is called by ``Main`` whenever the score changes.
  759. Connect the ``timeout()`` signal of ``MessageTimer`` and the
  760. ``pressed()`` signal of ``StartButton``.
  761. .. tabs::
  762. .. code-tab:: gdscript GDScript
  763. func _on_StartButton_pressed():
  764. $StartButton.hide()
  765. emit_signal("start_game")
  766. func _on_MessageTimer_timeout():
  767. $MessageLabel.hide()
  768. .. code-tab:: csharp
  769. public void OnStartButtonPressed()
  770. {
  771. GetNode<Button>("StartButton").Hide();
  772. EmitSignal("StartGame");
  773. }
  774. public void OnMessageTimerTimeout()
  775. {
  776. GetNode<Label>("MessageLabel").Hide();
  777. }
  778. Connecting HUD to Main
  779. ~~~~~~~~~~~~~~~~~~~~~~
  780. Now that we're done creating the ``HUD`` scene, save it and go back to ``Main``.
  781. Instance the ``HUD`` scene in ``Main`` like you did the ``Player`` scene, and
  782. place it at the bottom of the tree. The full tree should look like this,
  783. so make sure you didn't miss anything:
  784. .. image:: img/completed_main_scene.png
  785. Now we need to connect the ``HUD`` functionality to our ``Main`` script.
  786. This requires a few additions to the ``Main`` scene:
  787. In the Node tab, connect the HUD's ``start_game`` signal to the
  788. ``new_game()`` function.
  789. In ``new_game()``, update the score display and show the "Get Ready"
  790. message:
  791. .. tabs::
  792. .. code-tab:: gdscript GDScript
  793. $HUD.update_score(score)
  794. $HUD.show_message("Get Ready")
  795. .. code-tab:: csharp
  796. var hud = GetNode<HUD>("HUD");
  797. hud.UpdateScore(_score);
  798. hud.ShowMessage("Get Ready!");
  799. In ``game_over()`` we need to call the corresponding ``HUD`` function:
  800. .. tabs::
  801. .. code-tab:: gdscript GDScript
  802. $HUD.show_game_over()
  803. .. code-tab:: csharp
  804. GetNode<HUD>("HUD").ShowGameOver();
  805. Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in
  806. sync with the changing score:
  807. .. tabs::
  808. .. code-tab:: gdscript GDScript
  809. $HUD.update_score(score)
  810. .. code-tab:: csharp
  811. GetNode<HUD>("HUD").UpdateScore(_score);
  812. Now you're ready to play! Click the "Play the Project" button. You will
  813. be asked to select a main scene, so choose ``Main.tscn``.
  814. Removing old creeps
  815. ~~~~~~~~~~~~~~~~~~~
  816. If you play until "Game Over" and then start a new game the creeps from the
  817. previous game are still on screen. It would be better if they all disappeared
  818. at the start of a new game.
  819. We'll use the ``start_game`` signal that's already being emitted by the ``HUD``
  820. node to remove the remaining creeps. We can't use the editor to connect the
  821. signal to the mobs in the way we need because there are no ``Mob`` nodes in the
  822. ``Main`` scene tree until we run the game. Instead we'll use code.
  823. Start by adding a new function to ``Mob.gd``. ``queue_free()`` will delete the
  824. current node at the end of the current frame.
  825. .. tabs::
  826. .. code-tab:: gdscript GDScript
  827. func _on_start_game():
  828. queue_free()
  829. .. code-tab:: csharp
  830. public void OnStartGame()
  831. {
  832. QueueFree();
  833. }
  834. Then in ``Main.gd`` add a new line inside the ``_on_MobTimer_timeout()`` function,
  835. at the end.
  836. .. tabs::
  837. .. code-tab:: gdscript GDScript
  838. $HUD.connect("start_game", mob, "_on_start_game")
  839. .. code-tab:: csharp
  840. GetNode("HUD").Connect("StartGame", mobInstance, "OnStartGame");
  841. This line tells the new Mob node (referenced by the ``mob`` variable) to respond
  842. to any ``start_game`` signal emitted by the ``HUD`` node by running its
  843. ``_on_start_game()`` function.
  844. Finishing up
  845. ------------
  846. We have now completed all the functionality for our game. Below are some
  847. remaining steps to add a bit more "juice" to improve the game
  848. experience. Feel free to expand the gameplay with your own ideas.
  849. Background
  850. ~~~~~~~~~~
  851. The default gray background is not very appealing, so let's change its
  852. color. One way to do this is to use a :ref:`ColorRect <class_ColorRect>` node.
  853. Make it the first node under ``Main`` so that it will be drawn behind the other
  854. nodes. ``ColorRect`` only has one property: ``Color``. Choose a color
  855. you like and drag the size of the ``ColorRect`` so that it covers the
  856. screen.
  857. You could also add a background image, if you have one, by using a
  858. ``Sprite`` node.
  859. Sound effects
  860. ~~~~~~~~~~~~~
  861. Sound and music can be the single most effective way to add appeal to
  862. the game experience. In your game assets folder, you have two sound
  863. files: "House In a Forest Loop.ogg" for background music, and
  864. "gameover.wav" for when the player loses.
  865. Add two :ref:`AudioStreamPlayer <class_AudioStreamPlayer>` nodes as children of ``Main``. Name one of
  866. them ``Music`` and the other ``DeathSound``. On each one, click on the
  867. ``Stream`` property, select "Load", and choose the corresponding audio
  868. file.
  869. To play the music, add ``$Music.play()`` in the ``new_game()`` function
  870. and ``$Music.stop()`` in the ``game_over()`` function.
  871. Finally, add ``$DeathSound.play()`` in the ``game_over()`` function.
  872. Keyboard Shortcut
  873. ~~~~~~~~~~~~~~~~~
  874. Since the game is played with keyboard controls, it would be convenient if we
  875. could also start the game by pressing a key on the keyboard. One way to do this
  876. is using the "Shortcut" property of the ``Button`` node.
  877. In the ``HUD`` scene, select the ``StartButton`` and find its *Shortcut* property
  878. in the Inspector. Select "New Shortcut" and click on the "Shortcut" item. A
  879. second *Shortcut* property will appear. Select "New InputEventAction" and click
  880. the new "InputEvent". Finally, in the *Action* property, type the name ``ui_select``.
  881. This is the default input event associated with the spacebar.
  882. .. image:: img/start_button_shortcut.png
  883. Now when the start button appears, you can either click it or press the spacebar
  884. to start the game.
  885. Project files
  886. -------------
  887. You can find a completed version of this project at these locations:
  888. - https://github.com/kidscancode/Godot3_dodge/releases
  889. - https://github.com/godotengine/godot-demo-projects