your_first_game.rst 37 KB

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