diff --git a/files/dodge_assets.zip b/files/dodge_assets.zip new file mode 100644 index 000000000..1dddc4ef8 Binary files /dev/null and b/files/dodge_assets.zip differ diff --git a/img/add_node.png b/img/add_node.png new file mode 100644 index 000000000..93bffb841 Binary files /dev/null and b/img/add_node.png differ diff --git a/img/add_script_button.png b/img/add_script_button.png new file mode 100644 index 000000000..2a5f7e2d6 Binary files /dev/null and b/img/add_script_button.png differ diff --git a/img/attach_node_window.png b/img/attach_node_window.png new file mode 100644 index 000000000..19f4927bd Binary files /dev/null and b/img/attach_node_window.png differ diff --git a/img/custom_font1.png b/img/custom_font1.png new file mode 100644 index 000000000..1489b26b2 Binary files /dev/null and b/img/custom_font1.png differ diff --git a/img/custom_font2.png b/img/custom_font2.png new file mode 100644 index 000000000..f87cb4ae2 Binary files /dev/null and b/img/custom_font2.png differ diff --git a/img/dodge_preview.gif b/img/dodge_preview.gif new file mode 100644 index 000000000..4d3fed7ee Binary files /dev/null and b/img/dodge_preview.gif differ diff --git a/img/draw_path2d.png b/img/draw_path2d.png new file mode 100644 index 000000000..1d8533d31 Binary files /dev/null and b/img/draw_path2d.png differ diff --git a/img/filesystem_dock.png b/img/filesystem_dock.png new file mode 100644 index 000000000..828b02fb4 Binary files /dev/null and b/img/filesystem_dock.png differ diff --git a/img/instance_scene.png b/img/instance_scene.png new file mode 100644 index 000000000..f324a8ace Binary files /dev/null and b/img/instance_scene.png differ diff --git a/img/load_mob_scene.png b/img/load_mob_scene.png new file mode 100644 index 000000000..e831aa676 Binary files /dev/null and b/img/load_mob_scene.png differ diff --git a/img/mob_animations.gif b/img/mob_animations.gif new file mode 100644 index 000000000..8774a9528 Binary files /dev/null and b/img/mob_animations.gif differ diff --git a/img/mob_test.gif b/img/mob_test.gif new file mode 100644 index 000000000..91d0647b8 Binary files /dev/null and b/img/mob_test.gif differ diff --git a/img/particle_trail_settings.png b/img/particle_trail_settings.png new file mode 100644 index 000000000..9ccad2534 Binary files /dev/null and b/img/particle_trail_settings.png differ diff --git a/img/particle_trail_settings2.png b/img/particle_trail_settings2.png new file mode 100644 index 000000000..cfd971336 Binary files /dev/null and b/img/particle_trail_settings2.png differ diff --git a/img/path2d_buttons.png b/img/path2d_buttons.png new file mode 100644 index 000000000..56ad5c5a5 Binary files /dev/null and b/img/path2d_buttons.png differ diff --git a/img/player_coll_shape.png b/img/player_coll_shape.png new file mode 100644 index 000000000..21a420823 Binary files /dev/null and b/img/player_coll_shape.png differ diff --git a/img/player_scene_nodes.png b/img/player_scene_nodes.png new file mode 100644 index 000000000..420ea6332 Binary files /dev/null and b/img/player_scene_nodes.png differ diff --git a/img/player_signals.png b/img/player_signals.png new file mode 100644 index 000000000..fc0daaef8 Binary files /dev/null and b/img/player_signals.png differ diff --git a/img/set_collision_mask.png b/img/set_collision_mask.png new file mode 100644 index 000000000..ba49c8a01 Binary files /dev/null and b/img/set_collision_mask.png differ diff --git a/img/spriteframes_panel.png b/img/spriteframes_panel.png new file mode 100644 index 000000000..5737ed6a6 Binary files /dev/null and b/img/spriteframes_panel.png differ diff --git a/img/spriteframes_panel2.png b/img/spriteframes_panel2.png new file mode 100644 index 000000000..55b552111 Binary files /dev/null and b/img/spriteframes_panel2.png differ diff --git a/img/ui_anchor.png b/img/ui_anchor.png new file mode 100644 index 000000000..d16e015e6 Binary files /dev/null and b/img/ui_anchor.png differ diff --git a/learning/step_by_step/index.rst b/learning/step_by_step/index.rst index a9bb13a53..8ab24051e 100644 --- a/learning/step_by_step/index.rst +++ b/learning/step_by_step/index.rst @@ -10,7 +10,7 @@ Step by step instancing_continued scripting scripting_continued - simple_2d_game + your_first_game gui_tutorial splash_screen animations diff --git a/learning/step_by_step/your_first_game.rst b/learning/step_by_step/your_first_game.rst new file mode 100644 index 000000000..b5fbf4f94 --- /dev/null +++ b/learning/step_by_step/your_first_game.rst @@ -0,0 +1,764 @@ +.. _doc_your_first_game: + +Your First Game +=============== + +Overview +-------- + +This tutorial will guide you through making your first Godot Engine +project. You will learn how the Godot Engine editor works, how to structure +a project, and how to build a 2D game. + +.. note:: This project is an introduction to the Godot Engine. It + assumes that you have some programming experience already. If + you're new to programming entirely, you should start here: + :ref:`doc_scripting`. + +The game is called *"Dodge the Creeps"*. Your character must move and +avoid the enemies for as long as possible. Here is a preview of the +final result: + +.. image:: /img/dodge_preview.gif + +**Why 2D?** + 3D games are much more complex than 2D ones. You should stick to 2D + until you have a good understanding of the game development process. + +Project Setup +------------- + +Launch Godot and create a new project. Then, download +:download:`dodge_assets.zip ` - the images and sounds you'll be +using to make the game. Unzip these files in your new project folder. + +.. note:: For this tutorial, we will assume you are already familiar with the + Godot Engine editor. If you haven't read :ref:`doc_scenes_and_nodes`, do so now. + +This game will use "portrait" mode, so we need to adjust the size of the +game window. Click on Project -> Project Settings -> Display -> Window and +set ``Width`` to ``480`` and ``Height`` to ``720``. + +Organizing the Project +~~~~~~~~~~~~~~~~~~~~~~ + +In this project, we will make 3 independent scenes: ``Player``, +``Mob``, and ``HUD``, which we will combine into the game's ``Main`` +scene. In a larger project, it might be useful to make folders to hold +the various scenes and their scripts, but for this relatively small +game, you can save your scenes and scripts in the root folder, which is +referred to as ``res://``. You can see your project folders in the Filesystem +Dock in the upper left corner: + +.. image:: /img/filesystem_dock.gif + +Player Scene +------------ + +The first scene we make defines the "Player" object. One of the benefits +of creating a separate Player scene is that we can test it separately, even +before we've created the other parts of the game. + +Node Structure +~~~~~~~~~~~~~~ + +To begin, click the "Add/Create a New Node" button and add an :ref:`Area2D ` +node to the scene. + +.. image:: /img/add_node.png + +With ``Area2D`` we can detect other objects that overlap or run into the player. +Change its name to ``Player``. This is the scene's "root" or top-level node. +We can add additional nodes to the player to add functionality. + +Save the scene (click Scene -> Save, or press ``Meta-s``). + +.. note:: In this project, we will be following the Godot Engine naming + conventions. Classes (Nodes) use ``CapWords``, variables and + functions use ``snake_case``, and constants use ``ALL_CAPS``. + +Sprite Animation +~~~~~~~~~~~~~~~~ + +Click on the ``Player`` node and add an :ref:`AnimatedSprite ` node as a +child. The ``AnimatedSprite`` will handle the appearance and animations +for our player. Notice that there is a warning symbol next to the node. +An ``AnimatedSprite`` requires a :ref:`SpriteFrames ` resource, which is a +list of the animation(s) it can display. To create one, find the +``Frames`` property in the Inspector and click "" -> +"New SpriteFrames". Next, in the same location, click +```` to open the "SpriteFrames" panel: + +.. image:: /img/spriteframes_panel.png + + +On the left is a list of animations. Click the "default" one and rename +it to "right". Then click the "Add" button to create a second animation +named "up". Drag the two images for each animation into "Animation +Frames" side of the panel: + +.. image:: /img/spriteframes_panel2.png + + +Finally, add a :ref:`CollisionShape2D ` as a child +of the ``Player``. This will determine the player's "hitbox", or the +bounds of its collision area. For this character, a ``CapsuleShape2D`` +gives the best fit, so next to "Shape" in the Inspector, click +""" -> "New CapsuleShape2D". Resize the shape to cover the sprite: + +.. image:: /img/player_coll_shape.png + +.. warning:: Remember not to scale the shape's outline! Only use the + size handles (red) to adjust the shape! + +When you're finished, your ``Player`` scene should look like this: + +.. image:: /img/player_scene_nodes.png + +Moving the Player +~~~~~~~~~~~~~~~~~ + +Now we need to add some functionality that we can't get from a built-in +node, so we'll add a script. Click the ``Player`` node and click the +"Add Script" button: + +.. image:: /img/add_script_button.png + +In the script settings window, you can leave the default settings, just +click "Create": + +.. image:: /img/attach_node_window.png + +.. note:: If this is your first time encountering GDScript please read + :ref:`doc_scripting` first. + +Start by declaring the member variables this object will need: + +:: + + extends Area2D + + var SPEED = 400 # how fast the player will move (pixels/sec) + var velocity = Vector2() # the player's movement vector + var screensize # size of the game window + +The ``_ready()`` function is called when a node enters the scene tree, so +that's a good time to find the size of the game window: + +:: + + func _ready(): + screensize = get_viewport_rect().size + +Now we can use the ``_process()`` function to define what the player +will do every frame: + +- check for input +- move in the given direction +- play the appropriate animation. + +First, we need to check the inputs - is the player pressing a key? For +this game, we have 4 direction inputs to check. Input actions are defined +in the Project Settings under "Input Map". You can define custom events and +assign different keys, mouse events, or other inputs to them. For this demo, +we will use the default events that are assigned to the arrow keys on the +keyboard. + +You can detect whether a key is pressed using +``Input.is_action_pressed()``, which returns ``true`` if it is pressed +or ``false`` if it isn't. + +:: + + func _process(delta): + velocity = Vector2() + if Input.is_action_pressed("ui_right"): + velocity.x += 1 + if Input.is_action_pressed("ui_left"): + velocity.x -= 1 + if Input.is_action_pressed("ui_down"): + velocity.y += 1 + if Input.is_action_pressed("ui_up"): + velocity.y -= 1 + if velocity.length() > 0: + velocity = velocity.normalized() * SPEED + $AnimatedSprite.play() + else: + $AnimatedSprite.stop() + +We check each input and add/subtract from the ``velocity`` to obtain a +total direction. For example, if you hold ``right`` and ``down`` at +the same time, the resulting ``velocity`` vector will be ``(1, 1)``. In +this case, since we're adding a horizontal and a vertical movement, the +player would move *faster* than if it just moved horizontally. + +We can prevent that if we *normalize* the velocity, which means we set +its *length* to ``1``, and multiply by the desired speed. This means no +more fast diagonal movement. + +We also check whether the player is moving so we can start or stop the +AnimatedSprite animation. + +Now that we have a movement direction, we can update the player's position +and use ``clamp()`` to prevent it from leaving the screen: + +:: + + position += velocity * delta + position.x = clamp(position.x, 0, screensize.x) + position.y = clamp(position.y, 0, screensize.y) + + +.. tip:: *Clamping* a value means restricting it to a given minimum/maximum range. + +Click "Play the Edited Scene. (F6)" and confirm you can move the player +around the screen in all directions. + +Choosing Animations +~~~~~~~~~~~~~~~~~~~ + +Now that the player can move, we need to change which animation the +AnimatedSprite is playing based on direction. We have a "right" +animation, which should be flipped horizontally (using the ``flip_h`` +property) for left movement, and an "up" animation, which should be +flipped vertically (``flip_v``) for downward movement. + +:: + + if velocity.x != 0: + $AnimatedSprite.animation = "right" + $AnimatedSprite.flip_v = false + $AnimatedSprite.flip_h = velocity.x < 0 + elif velocity.y != 0: + $AnimatedSprite.animation = "up" + $AnimatedSprite.flip_v = velocity.y > 0 + +Play the scene again and check that the animations are correct in each +of the directions. + +Preparing for Collisions +~~~~~~~~~~~~~~~~~~~~~~~~ + +We want the player to detect when it is hit by an enemy, but we haven't +made any enemies yet! That's OK because we're going to use Godot's +*signal* functionality to make it work. + +Add the following at the top of the script (after ``extends Area2d``): + +:: + + signal hit + +This defines a custom signal called "hit" that we will have our player +emit (send out) when it collides with an enemy. We will use the Area2D to +detect the collision. Select the ``Player`` node and click the "Node" tab +next to the Inspector to see the list of signals the player can emit: + +.. image:: /img/player_signals.png + +Notice our custom "hit" signal is there as well! Since our enemies are +going to be ``RigidBody2D`` nodes, we want the +``body_entered( Object body )`` signal - that will be emitted when a +body contacts the player. Click "Connect.." and then "Connect" again on +the "Connecting Signal" window - we don't need to change any of those +settings. Godot will automatically create a function called +``_on_Player_body_entered`` in your player's script. + +.. tip:: When connecting a signal, instead of having Godot create a + function for you, you can also give the name of an existing + function that you want to link the signal to. + +Add this code to the function: + +:: + + func _on_Player_body_entered( area ): + hide() + emit_signal("hit") + monitoring = false + +.. warning:: Disabling the ``monitoring`` property of an ``Area2D`` means + it won't detect collisions. By turning it off, we make + sure we don't trigger the ``hit`` signal more than once. However, + changing the property in the midst of an ``area_entered`` signal + will result in an error, because the engine hasn't finished + processing the current frame yet. + +Instead, you can *defer* the change, which will tell the game engine to +wait until it's safe to set monitoring to ``false``. Change the line to +this: + +:: + + call_deferred("set_monitoring", false) + +The last piece for our player is to add a function we can call to reset +the player when starting a new game. + +:: + + func start(pos): + position = pos + show() + monitoring = true + +Enemy Scene +----------- + +Now it's time to make the enemies our player will have to dodge. Their +behavior will not be very complex: mobs will spawn randomly at the edges +of the screen and move in a straight line (in a random direction), then +despawn when they go offscreen. + +We will build this into a ``Mob`` scene, which we can then *instance* to +create any number of independent mobs in the game. + +Node Setup +~~~~~~~~~~ + +The Mob scene will use the following nodes: + +- :ref:`RigidBody2D ` (named ``Mob``) + + - :ref:`AnimatedSprite ` + - :ref:`CollisionShape2D ` + - :ref:`VisibilityNotifier2D ` (named ``Visibility``) + +In the :ref:`RigidBody2D ` properties, set ``Gravity Scale`` to ``0`` (so +that the mob will not fall downward). In addition, under +``PhysicsBody2D``, click the ``Mask`` property and uncheck the first +box. This will ensure that the mobs do not collide with each other. + +.. image:: /img/set_collision_mask.png + +Set up the :ref:`AnimatedSprite ` like you did for the player. This time, we +have 3 animations: "fly", "swim", and "walk". Don't forget to adjust the +"Speed (FPS)" setting as shown below. We'll select one of these randomly +so that the mobs will have some variety. + +.. image:: /img/mob_animations.gif + +As in the ``Player`` scene, add a ``CapsuleShape2D`` for the +collision and then save the scene. + +Enemy Script +~~~~~~~~~~~~ + +Add a script to the ``Enemy`` and add the following member variables: + +:: + + extends RigidBody2D + + var MIN_SPEED = 150 # minimum speed range + var MAX_SPEED = 250 # maximum speed range + var mob_types = ["walk", "swim", "fly"] + +We'll pick a random value between ``MIN_SPEED`` and ``MAX_SPEED`` for +how fast each mob will move (it would be boring if they were all moving +at the same speed). We also have an array containing the names of the three +animations, which we'll use to select a random one. + +Now let's look at the rest of the script. In ``_ready()`` we choose a +random one of the three animation types: + +:: + + func _ready(): + $AnimatedSprite.animation = mob_types[randi() % mob_types.size()] + +.. note:: You must use ``randomize()`` if you want + your sequence of "random" numbers to be different every time you run + the scene. We're going to use ``randomize()`` in our ``Main`` scene, + so we won't need it here. ``randi() % n`` is the standard way to get + a random integer between ``0`` and ``n-1``. + +The last piece is to make the mobs delete themselves when they leave the +screen. Connect the ``screen_exited()`` signal of the ``Visibility`` +node and add this code: + +:: + + func _on_Visible_screen_exited(): + queue_free() + +That completes the `Mob` scene. + +Main Scene +---------- + +Now it's time to bring it all together. Create a new scene and add a +:ref:`Node ` named ``Main``. Click the "Instance" button and select your +saved ``Player.tscn``. + +.. image:: /img/instance_scene.png + +.. note:: See :ref:`doc_instancing` to learn more about instancing. + +Now add the following nodes as children of ``Main``, and name them as +shown (values are in seconds): + +- :ref:`Timer ` (named ``MobTimer``) - to control how often mobs spawn +- :ref:`Timer ` (named ``ScoreTimer``) - to increment the score every second +- :ref:`Timer ` (named ``StartTimer``) - to give a delay before starting +- :ref:`Position2D ` (named ``StartPosition``) - to indicate the player's start position + +Set the ``Wait Time`` property of each of the ``Timer`` nodes as +follows: + +- ``MobTimer``: ``0.5`` +- ``ScoreTimer``: ``1`` +- ``StartTimer``: ``2`` + +In addition, set the ``One Shot`` property of ``StartTimer`` to "On" and +set ``Position`` of the ``StartPos`` node to ``(240, 450)``. Now add a +script to ``Main``. + +Spawning Mobs +~~~~~~~~~~~~~ + +The Main node will be spawning new mobs, and we want them to appear at a +random location on the edge of the screen. Add a :ref:`Path2D ` named +``MobPath`` as a child of ``Main``. When you select the ``Path2D`` node +you will see some new buttons appear at the top of the editor: + +.. image:: /img/path2d_buttons.png + +Select the middle one ("Add Point") and draw the path by clicking to add +the points shown. + +.. image:: /img/draw_path2d.png + +.. important:: Draw the path in *clockwise* order, or your mobs will spawn + pointing *outwards* instead of *inwards*! + +Now that the path is defined, add a :ref:`PathFollow2D ` +node as a child of ``MobPath`` and name it ``MobSpawnLocation``. This node will +automatically rotate and follow the path you've drawn, so we can use it +to select a random position and direction along the path. + +Main Script +~~~~~~~~~~~ + +Add a script to ``Main``. At the top of the script we use +``export (PackedScene)`` to allow us to choose the Mob scene we want to +instance. + +:: + + extends Node + + export (PackedScene) var Mob + var score + + func _ready(): + randomize() + +Using ``export`` lets you set the value of a variable in the Inspector +like so: + +.. image:: /img/load_mob_scene.png + +Click on """ and choose "Load", then select ``Mob.tscn``. + +Next, click on the Player and connect the ``hit`` signal to the +``game_over`` function, which will handle what needs to happen when a +game ends. We will also have a ``new_game`` function to set everything +up for a new game: + +:: + + func new_game(): + score = 0 + $Player.start($StartPos.position) + $StartTimer.start() + + func game_over(): + $ScoreTimer.stop() + $MobTimer.stop() + +Now connect the ``timeout()`` signal of each of the Timer nodes. +``StartTimer`` will start the other two timers. ``ScoreTimer`` will +increment the score by 1. + +:: + + func _on_StartTimer_timeout(): + $MobTimer.start() + $ScoreTimer.start() + + func _on_ScoreTimer_timeout(): + score += 1 + +In ``_on_MobTimer_timeout()`` we will create a mob instance, pick a +random starting location along the ``Path2D``, and set the mob in +motion. The ``PathFollow2D`` node will automatically rotate as it +follows the path, so we will use that to select the mob's direction as +well as its position. + +Note that a new instance must be added to the scene using +``add_child()``. + +:: + + func _on_MobTimer_timeout(): + # choose a random location on the Path2D + $"MobPath/MobSpawnLocation".set_offset(randi()) + # create a Mob instance and add it to the scene + var mob = Mob.instance() + add_child(mob) + # choose a direction and position + var direction = $"MobPath/MobSpawnLocation".rotation + mob.position = $"MobPath/MobSpawnLocation".position + # add some randomness to the direction + direction += rand_range(-PI/4, PI/4) + # textures are oriented pointing up, so add 90 degrees + mob.rotation = direction + PI/2 + # choose the velocity + mob.set_linear_velocity(Vector2(rand_range(mob.MIN_SPEED, mob.MAX_SPEED), 0).rotated(direction)) + +.. important:: In functions requiring angles, GDScript uses *radians*, + not degrees. If you're more comfortable working with + degrees, you'll need to use the ``deg2rad()`` and + ``rad2deg()`` functions to convert between the two measures. + +HUD +--- + +The final piece our game needs is a UI: an interface to display things +like score, a "game over" message, and a restart button. Create a new +scene, and add a ``CanvasLayer`` node named ``HUD`` ("HUD" stands for +"heads-up display", meaning an informational display that appears as an +overlay, on top of the game view). + +The HUD displays the following information: + +- Score, changed by ``ScoreTimer`` +- A message, such as "Game Over" or "Get Ready!" +- A "Start" button to begin the game + +Create the following children of the ``HUD`` node: + +- :ref:`Label ` (named ``ScoreLabel``) +- :ref:`Label ` (named ``MessageLabel``) +- :ref:`Button ` (named ``StartButton``) +- :ref:`Timer ` (named ``MessageTimer``) + +.. note:: **Anchors and Margins** ``Control`` nodes have a position and size, + but they also have anchors and margins. Anchors define the + origin, or the reference point for the edges of the node. Margins + update automatically when you move or resize a control node. They + represent the distance from the control node's edges to its anchor. + See :ref:`doc_gui_tutorial` for more details. + +Arrange the nodes as shown below. Click the "Anchor" button to +set a Control node's anchor: + +.. image:: /img/ui_anchor.png + +You can drag the nodes to place them manually, or for more precise +placement, use the following settings: + +ScoreLabel +~~~~~~~~~~ + +- ``Anchor``: "Center Top" +- ``Margin``: + + - Left: ``240`` + - Top: ``0`` + - Right: ``-240`` + - Bottom: ``100`` + +- Text: ``0`` + +MessageLabel +~~~~~~~~~~~~ + +- ``Anchor``: "Center" +- ``Margin``: + + - Left: ``240`` + - Top: ``260`` + - Right: ``-240`` + - Bottom: ``-60`` + +- Text: ``Dodge the Creeps!`` + +StartButton +~~~~~~~~~~~ + +- ``Anchor``: "Center" +- ``Margin``: + + - Left: ``60`` + - Top: ``-70`` + - Right: ``-60`` + - Bottom: ``-150`` + +- Text: ``Start`` + +The default font for ``Control`` nodes is very small and doesn't scale +well. There is a font file included in the game assets called +"Xolonium-Regular.ttf". To use this font, do the following for each of +the three ``Control`` nodes: + +1. Under "Custom Fonts", choose "New DynamicFont" + +.. image:: /img/custom_font1.png + +2. Click on the "DynamicFont" you just added, and under "Font Data", + choose "Load" and select the "Xolonium-Regular.ttf" file. You must + also set the font's ``Size``. A setting of ``64`` works well. + +.. image:: /img/custom_font2.png + +Now add this script to the ``HUD``: + +:: + + extends CanvasLayer + + signal start_game + +The ``start_game`` signal tells the ``Main`` node that the button +has been pressed. + +:: + + func show_message(text): + $MessageLabel.text = text + $MessageLabel.show() + $MessageTimer.start() + +This function is called when we want to display a message +temporarily, such as "Get Ready". On the ``MessageTimer``, set the +``Wait Time`` to ``2`` and check ``One Shot``. + +:: + + func show_game_over(): + show_message("Game Over") + yield($MessageTimer, "timeout") + $StartButton.show() + $MessageLabel.text = "Dodge the\nCreeps!" + $MessageLabel.show() + +This function is called when the player loses. It will show "Game +Over" for 2 seconds, and then return to the game title and show the +"Start" button. + +:: + + func update_score(score): + $ScoreLabel.text = str(score) + +This function is called in ``Main`` whenever the score changes. + +Connect the ``timout()`` signal of ``MessageTimer`` and the +``pressed()`` signal of ``StartButton``. + +:: + + func _on_StartButton_pressed(): + $StartButton.hide() + emit_signal("start_game") + + func _on_MessageTimer_timeout(): + $MessageLabel.hide() + +Connecting HUD to Main +~~~~~~~~~~~~~~~~~~~~~~ + +Now we need to connect the ``HUD`` functionality to our ``Main`` script. +This requires a few additions to the ``Main`` scene: + +In the Node tab, connect the HUD's ``start_game`` signal to the +``new_game()`` function. + +In ``new_game()``, update the score display and show the "Get Ready" +message: + +:: + + $HUD.update_score(score) + $HUD.show_message("Get Ready") + +In ``game_over()`` we need to call the corresponding ``HUD`` function: + +:: + + $HUD.show_game_over() + +Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in +sync with the changing score: + +:: + + $HUD.update_score(score) + +Finishing Up +------------ + +We've now completed all the functionality for our game. Below are some +remaining steps to add a bit more "juice" and improve the game +experience. Feel free to expand the gameplay with your own ideas. + +Background +~~~~~~~~~~ + +The default gray background is not very appealing, so let's change its +color. One way to do this is to use a :ref:`ColorRect ` node. Make it the +first node under ``Main`` so that it will be drawn behind the other +nodes. ``ColorRect`` only has one property: ``Color``. Choose a color +you like and drag the size of the ``ColorRect`` so that it covers the +screen. + +You can also add a background image, if you have one, by using a +``Sprite`` node. + +Sound Effects +~~~~~~~~~~~~~ + +Sound and music can be the single most effective way to add appeal to +the game experience. In your game assets folder, you have two sound +files: "House In a Forest Loop.ogg", for background music, and +"gameover.wav" for when the player loses. + +Add two :ref:`AudioStreamPlayer ` nodes as children of ``Main``. Name one of +them ``Music`` and the other ``DeathSound``. On each one, click on the +``Stream`` property, select "Load" and choose the corresponding audio +file. + +To play the music, add ``$Music.play()`` in the ``new_game()`` function +and ``$Music.stop()`` in the ``game_over()`` function. + +Finally, add ``$DeathSound.play()`` in the ``game_over()`` function as +well. + +Particles +~~~~~~~~~ + +For one last bit of visual appeal, let's add a trail effect to the +player's movement. Choose your ``Player`` scene and add a +:ref:`Particles2D ` node named ``Trail``. + +There are a very large number of properties to choose from when +configuring particles. Feel free to experiment and create different +effects. For the effect in the example, use the following settings: + +.. image:: /img/particle_trail_settings.png + +You also need to create a ``Material`` by clicking on ```` and +then "New ParticlesMaterial". The settings for that are below: + +.. image:: /img/particle_trail_settings2.png + +.. seealso:: See :ref:`Particles2D ` for more details on using + particle effects. + +Project Files +------------- + +You can find a completed version of this project here: +https://github.com/kidscancode/Godot3_dodge/releases