Browse Source

Add GDNative C++ code examples to DTC aka Your first 2D game

Aaron Franke 4 years ago
parent
commit
e3f55967fa

+ 34 - 4
getting_started/first_2d_game/01.project_setup.rst

@@ -9,10 +9,40 @@ Launch Godot and create a new project.
 
 .. image:: img/new-project-button.png
 
-If you haven't already, download :download:`dodge_assets.zip
-<files/dodge_assets.zip>`. The archive contains the images and sounds you'll be
-using to make the game. Extract the archive and move the ``art/`` and ``fonts/``
-directories to your project's directory.
+.. tabs::
+ .. tab:: GDScript
+
+    Download :download:`dodge_assets.zip <files/dodge_assets.zip>`.
+    The archive contains the images and sounds you'll be using
+    to make the game. Extract the archive and move the ``art/``
+    and ``fonts/`` directories to your project's directory.
+
+ .. tab:: C#
+
+    Download :download:`dodge_assets.zip <files/dodge_assets.zip>`.
+    The archive contains the images and sounds you'll be using
+    to make the game. Extract the archive and move the ``art/``
+    and ``fonts/`` directories to your project's directory.
+
+    Ensure that you have the required dependencies to use C# in Godot.
+    You need the .NET Core 3.1 SDK, and an editor such as VS Code.
+    See https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/c_sharp_basics.html#setting-up-c-for-godot
+
+ .. tab:: GDNative C++
+
+    Download :download:`dodge_assets_with_gdnative.zip
+    <files/dodge_assets_with_gdnative.zip>`.
+    The archive contains the images and sounds you'll be using
+    to make the game. It also contains a starter GDNative project
+    including a ``SConstruct`` file, a ``dodge_the_creeps.gdnlib``
+    file, a ``player.gdns`` file, and an ``entry.cpp`` file.
+
+    Ensure that you have the required dependencies to use GDNative C++.
+    You need a C++ compiler such as GCC or Clang or MSVC that supports C++14.
+    On Windows you can download Visual Studio 2019 and select the C++ workload.
+    You also need SCons to use the build system (the SConstruct file).
+    Then you need to download the Godot C++ bindings from
+    https://github.com/godotengine/godot-cpp and place them in your project.
 
 Your project folder should look like this.
 

+ 122 - 0
getting_started/first_2d_game/03.coding_the_player.rst

@@ -46,6 +46,42 @@ Start by declaring the member variables this object will need:
         public Vector2 ScreenSize; // Size of the game window.
     }
 
+ .. code-tab:: cpp
+
+    // A `player.gdns` file has already been created for you. Attach it to the Player node.
+
+    // Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
+    // This code goes in `player.hpp`. We also define the methods we'll be using here.
+    #ifndef PLAYER_H
+    #define PLAYER_H
+
+    #include <AnimatedSprite.hpp>
+    #include <Area2D.hpp>
+    #include <CollisionShape2D.hpp>
+    #include <Godot.hpp>
+    #include <Input.hpp>
+
+    class Player : public godot::Area2D {
+        GODOT_CLASS(Player, godot::Area2D)
+
+        godot::AnimatedSprite *_animated_sprite;
+        godot::CollisionShape2D *_collision_shape;
+        godot::Input *_input;
+        godot::Vector2 _screen_size; // Size of the game window.
+
+    public:
+        real_t speed = 400; // How fast the player will move (pixels/sec).
+
+        void _init() {}
+        void _ready();
+        void _process(const double p_delta);
+        void start(const godot::Vector2 p_position);
+        void _on_Player_body_entered(godot::Node2D *_body);
+
+        static void _register_methods();
+    };
+
+    #endif // PLAYER_H
 
 Using the ``export`` keyword on the first variable ``speed`` allows us to set
 its value in the Inspector. This can be handy for values that you want to be
@@ -78,6 +114,18 @@ a good time to find the size of the game window:
         ScreenSize = GetViewportRect().Size;
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `player.cpp`.
+    #include "player.hpp"
+
+    void Player::_ready() {
+        _animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
+        _collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
+        _input = godot::Input::get_singleton();
+        _screen_size = get_viewport_rect().size;
+    }
+
 Now we can use the ``_process()`` function to define what the player will do.
 ``_process()`` is called every frame, so we'll use it to update elements of our
 game, which we expect will change often. For the player, we need to do the
@@ -156,6 +204,23 @@ which returns ``true`` if it's pressed or ``false`` if it isn't.
         }
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `player.cpp`.
+    void Player::_process(const double p_delta) {
+        godot::Vector2 velocity(0, 0);
+
+        velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
+        velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
+
+        if (velocity.length() > 0) {
+            velocity = velocity.normalized() * speed;
+            _animated_sprite->play();
+        } else {
+            _animated_sprite->stop();
+        }
+    }
+
 We start by setting the ``velocity`` to ``(0, 0)`` - by default, the player
 should not be moving. Then we check each input and add/subtract from the
 ``velocity`` to obtain a total direction. For example, if you hold ``right`` and
@@ -203,6 +268,13 @@ the ``_process`` function (make sure it's not indented under the `else`):
             y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
         );
 
+ .. code-tab:: cpp
+
+        godot::Vector2 position = get_position();
+        position += velocity * (real_t)p_delta;
+        position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
+        position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
+        set_position(position);
 
 .. tip:: The `delta` parameter in the `_process()` function refers to the *frame
         length* - the amount of time that the previous frame took to complete.
@@ -258,6 +330,18 @@ movement. Let's place this code at the end of the ``_process()`` function:
             animatedSprite.FlipV = velocity.y > 0;
         }
 
+ .. code-tab:: cpp
+
+        if (velocity.x != 0) {
+            _animated_sprite->set_animation("right");
+            _animated_sprite->set_flip_v(false);
+            // See the note below about boolean assignment.
+            _animated_sprite->set_flip_h(velocity.x < 0);
+        } else if (velocity.y != 0) {
+            _animated_sprite->set_animation("up");
+            _animated_sprite->set_flip_v(velocity.y > 0);
+        }
+
 .. Note:: The boolean assignments in the code above are a common shorthand for
           programmers. Since we're doing a comparison test (boolean) and also
           *assigning* a boolean value, we can do both at the same time. Consider
@@ -302,6 +386,10 @@ When you're sure the movement is working correctly, add this line to
 
     Hide();
 
+ .. code-tab:: cpp
+
+    hide();
+
 Preparing for collisions
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -323,6 +411,21 @@ Add the following at the top of the script, after ``extends Area2D``:
     [Signal]
     public delegate void Hit();
 
+ .. code-tab:: cpp
+
+    // This code goes in `player.cpp`.
+    // We need to register the signal here, and while we're here, we can also
+    // register the other methods and register the speed property.
+    void Player::_register_methods() {
+        godot::register_method("_ready", &Player::_ready);
+        godot::register_method("_process", &Player::_process);
+        godot::register_method("start", &Player::start);
+        godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
+        godot::register_property("speed", &Player::speed, (real_t)400.0);
+        // This below line is the signal.
+        godot::register_signal<Player>("hit", godot::Dictionary());
+    }
+
 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 ``Area2D`` to detect the
 collision. Select the ``Player`` node and click the "Node" tab next to the
@@ -361,6 +464,16 @@ this code to the function:
         GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `player.cpp`.
+    void Player::_on_Player_body_entered(godot::Node2D *_body) {
+        hide(); // Player disappears after being hit.
+        emit_signal("hit");
+        // Must be deferred as we can't change physics properties on a physics callback.
+        _collision_shape->set_deferred("disabled", true);
+    }
+
 Each time an enemy hits the player, the signal is going to be emitted. We need
 to disable the player's collision so that we don't trigger the ``hit`` signal
 more than once.
@@ -390,4 +503,13 @@ starting a new game.
         GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `player.cpp`.
+    void Player::start(const godot::Vector2 p_position) {
+        set_position(p_position);
+        show();
+        _collision_shape->set_disabled(false);
+    }
+
 With the player working, we'll work on the enemy in the next lesson.

+ 53 - 13
getting_started/first_2d_game/04.creating_the_enemy.rst

@@ -56,32 +56,48 @@ Save the scene.
 Enemy script
 ~~~~~~~~~~~~
 
-Add a script to the ``Mob`` and add the following member variables:
+Add a script to the ``Mob`` like this:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
     extends RigidBody2D
 
-    export var min_speed = 150  # Minimum speed range.
-    export var max_speed = 250  # Maximum speed range.
-
  .. code-tab:: csharp
 
     public class Mob : RigidBody2D
     {
-        // Don't forget to rebuild the project so the editor knows about the new export variables.
+        // Don't forget to rebuild the project.
+    }
 
-        [Export]
-        public int MinSpeed = 150; // Minimum speed range.
+ .. code-tab:: cpp
 
-        [Export]
-        public int MaxSpeed = 250; // Maximum speed range.
-    }
+    // Copy `player.gdns` to `mob.gdns` and replace `Player` with `Mob`.
+    // Attach the `mob.gdns` file to the Mob node.
+
+    // Create two files `mob.cpp` and `mob.hpp` next to `entry.cpp` in `src`.
+    // This code goes in `mob.hpp`. We also define the methods we'll be using here.
+    #ifndef MOB_H
+    #define MOB_H
+
+    #include <AnimatedSprite.hpp>
+    #include <Godot.hpp>
+    #include <RigidBody2D.hpp>
+
+    class Mob : public godot::RigidBody2D {
+        GODOT_CLASS(Mob, godot::RigidBody2D)
+
+        godot::AnimatedSprite *_animated_sprite;
+
+    public:
+        void _init() {}
+        void _ready();
+        void _on_VisibilityNotifier2D_screen_exited();
+
+        static void _register_methods();
+    };
 
-When we spawn a mob, 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).
+    #endif // MOB_H
 
 Now let's look at the rest of the script. In ``_ready()`` we play the animation
 and randomly choose one of the three animation types:
@@ -104,6 +120,23 @@ and randomly choose one of the three animation types:
         animSprite.Animation = mobTypes[GD.Randi() % mobTypes.Length];
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `mob.cpp`.
+    #include "mob.hpp"
+
+    #include <RandomNumberGenerator.hpp>
+    #include <SpriteFrames.hpp>
+
+    void Mob::_ready() {
+        godot::Ref<godot::RandomNumberGenerator> random = godot::RandomNumberGenerator::_new();
+        random->randomize();
+        _animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
+        _animated_sprite->_set_playing(true);
+        godot::PoolStringArray mob_types = _animated_sprite->get_sprite_frames()->get_animation_names();
+        _animated_sprite->set_animation(mob_types[random->randi() % mob_types.size()]);
+    }
+
 First, we get the list of animation names from the AnimatedSprite's ``frames``
 property. This returns an Array containing all three animation names: ``["walk",
 "swim", "fly"]``.
@@ -133,6 +166,13 @@ add this code:
         QueueFree();
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `mob.cpp`.
+    void Mob::_on_VisibilityNotifier2D_screen_exited() {
+        queue_free();
+    }
+
 This completes the `Mob` scene.
 
 With the player and enemies ready, in the next part, we'll bring them together

+ 175 - 17
getting_started/first_2d_game/05.the_main_game_scene.rst

@@ -81,9 +81,6 @@ Add a script to ``Main``. At the top of the script, we use ``export
     export(PackedScene) var mob_scene
     var score
 
-    func _ready():
-        randomize()
-
  .. code-tab:: csharp
 
     public class Main : Node
@@ -97,11 +94,99 @@ Add a script to ``Main``. At the top of the script, we use ``export
     #pragma warning restore 649
 
         public int Score;
+    }
+
+ .. code-tab:: cpp
+
+    // Copy `player.gdns` to `main.gdns` and replace `Player` with `Main`.
+    // Attach the `main.gdns` file to the Main node.
+
+    // Create two files `main.cpp` and `main.hpp` next to `entry.cpp` in `src`.
+    // This code goes in `main.hpp`. We also define the methods we'll be using here.
+    #ifndef MAIN_H
+    #define MAIN_H
+
+    #include <AudioStreamPlayer.hpp>
+    #include <CanvasLayer.hpp>
+    #include <Godot.hpp>
+    #include <Node.hpp>
+    #include <PackedScene.hpp>
+    #include <PathFollow2D.hpp>
+    #include <RandomNumberGenerator.hpp>
+    #include <Timer.hpp>
+
+    #include "hud.hpp"
+    #include "player.hpp"
+
+    class Main : public godot::Node {
+        GODOT_CLASS(Main, godot::Node)
+
+        int score;
+        HUD *_hud;
+        Player *_player;
+        godot::Node2D *_start_position;
+        godot::PathFollow2D *_mob_spawn_location;
+        godot::Timer *_mob_timer;
+        godot::Timer *_score_timer;
+        godot::Timer *_start_timer;
+        godot::AudioStreamPlayer *_music;
+        godot::AudioStreamPlayer *_death_sound;
+        godot::Ref<godot::RandomNumberGenerator> _random;
+
+    public:
+        godot::Ref<godot::PackedScene> mob_scene;
+
+        void _init() {}
+        void _ready();
+        void game_over();
+        void new_game();
+        void _on_MobTimer_timeout();
+        void _on_ScoreTimer_timeout();
+        void _on_StartTimer_timeout();
+
+        static void _register_methods();
+    };
+
+    #endif // MAIN_H
+
+We also add a call to ``randomize()`` here so that the random number
+generator generates different random numbers each time the game is run:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
-        public override void _Ready()
-        {
-            GD.Randomize();
-        }
+    func _ready():
+        randomize()
+
+ .. code-tab:: csharp
+
+    public override void _Ready()
+    {
+        GD.Randomize();
+    }
+
+ .. code-tab:: cpp
+
+    // This code goes in `main.cpp`.
+    #include "main.hpp"
+
+    #include <SceneTree.hpp>
+
+    #include "mob.hpp"
+
+    void Main::_ready() {
+        _hud = get_node<HUD>("HUD");
+        _player = get_node<Player>("Player");
+        _start_position = get_node<godot::Node2D>("StartPosition");
+        _mob_spawn_location = get_node<godot::PathFollow2D>("MobPath/MobSpawnLocation");
+        _mob_timer = get_node<godot::Timer>("MobTimer");
+        _score_timer = get_node<godot::Timer>("ScoreTimer");
+        _start_timer = get_node<godot::Timer>("StartTimer");
+        // Uncomment these after adding the nodes in the "Sound effects" section of "Finishing up".
+        //_music = get_node<godot::AudioStreamPlayer>("Music");
+        //_death_sound = get_node<godot::AudioStreamPlayer>("DeathSound");
+        _random = (godot::Ref<godot::RandomNumberGenerator>)godot::RandomNumberGenerator::_new();
+        _random->randomize();
     }
 
 Click the ``Main`` node and you will see the ``Mob Scene`` property in the Inspector
@@ -156,6 +241,20 @@ new game:
         GetNode<Timer>("StartTimer").Start();
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `main.cpp`.
+    void Main::game_over() {
+        _score_timer->stop();
+        _mob_timer->stop();
+    }
+
+    void Main::new_game() {
+        score = 0;
+        _player->start(_start_position->get_position());
+        _start_timer->start();
+    }
+
 Now connect the ``timeout()`` signal of each of the Timer nodes (``StartTimer``,
 ``ScoreTimer`` , and ``MobTimer``) to the main script. ``StartTimer`` will start
 the other two timers. ``ScoreTimer`` will increment the score by 1.
@@ -163,30 +262,56 @@ the other two timers. ``ScoreTimer`` will increment the score by 1.
 .. tabs::
  .. code-tab:: gdscript GDScript
 
+    func _on_ScoreTimer_timeout():
+        score += 1
+
     func _on_StartTimer_timeout():
         $MobTimer.start()
         $ScoreTimer.start()
 
-    func _on_ScoreTimer_timeout():
-        score += 1
-
  .. code-tab:: csharp
 
+    public void OnScoreTimerTimeout()
+    {
+        Score++;
+    }
+
     public void OnStartTimerTimeout()
     {
         GetNode<Timer>("MobTimer").Start();
         GetNode<Timer>("ScoreTimer").Start();
     }
 
-    public void OnScoreTimerTimeout()
-    {
-        Score++;
+ .. code-tab:: cpp
+
+    // This code goes in `main.cpp`.
+    void Main::_on_ScoreTimer_timeout() {
+        score += 1;
+    }
+
+    void Main::_on_StartTimer_timeout() {
+        _mob_timer->start();
+        _score_timer->start();
+    }
+
+    // Also add this to register all methods and the mob scene property.
+    void Main::_register_methods() {
+        godot::register_method("_ready", &Main::_ready);
+        godot::register_method("game_over", &Main::game_over);
+        godot::register_method("new_game", &Main::new_game);
+        godot::register_method("_on_MobTimer_timeout", &Main::_on_MobTimer_timeout);
+        godot::register_method("_on_ScoreTimer_timeout", &Main::_on_ScoreTimer_timeout);
+        godot::register_method("_on_StartTimer_timeout", &Main::_on_StartTimer_timeout);
+        godot::register_property("mob_scene", &Main::mob_scene, (godot::Ref<godot::PackedScene>)nullptr);
     }
 
 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.
+When we spawn a mob, we'll pick a random value between ``150.0`` and
+``250.0`` for how fast each mob will move (it would be boring if they were
+all moving at the same speed).
 
 Note that a new instance must be added to the scene using ``add_child()``.
 
@@ -213,7 +338,7 @@ Note that a new instance must be added to the scene using ``add_child()``.
         mob.rotation = direction
 
         # Choose the velocity.
-        var velocity = Vector2(rand_range(mob.min_speed, mob.max_speed), 0)
+        var velocity = Vector2(rand_range(150.0, 250.0), 0.0)
         mob.linear_velocity = velocity.rotated(direction)
 
  .. code-tab:: csharp
@@ -243,10 +368,36 @@ Note that a new instance must be added to the scene using ``add_child()``.
         mob.Rotation = direction;
 
         // Choose the velocity.
-        var velocity = new Vector2((float)GD.RandRange(mob.MinSpeed, mob.MaxSpeed), 0);
+        var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0);
         mob.LinearVelocity = velocity.Rotated(direction);
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `main.cpp`.
+    void Main::_on_MobTimer_timeout() {
+        // Choose a random location on Path2D.
+        _mob_spawn_location->set_offset((real_t)_random->randi());
+
+        // Create a Mob instance and add it to the scene.
+        godot::Node *mob = mob_scene->instance();
+        add_child(mob);
+
+        // Set the mob's direction perpendicular to the path direction.
+        real_t direction = _mob_spawn_location->get_rotation() + (real_t)Math_PI / 2;
+
+        // Set the mob's position to a random location.
+        mob->set("position", _mob_spawn_location->get_position());
+
+        // Add some randomness to the direction.
+        direction += _random->randf_range((real_t)-Math_PI / 4, (real_t)Math_PI / 4);
+        mob->set("rotation", direction);
+
+        // Choose the velocity for the mob.
+        godot::Vector2 velocity = godot::Vector2(_random->randf_range(150.0, 250.0), 0.0);
+        mob->set("linear_velocity", velocity.rotated(direction));
+    }
+
 .. important:: Why ``PI``? In functions requiring angles, Godot uses *radians*,
                not degrees. Pi represents a half turn in radians, about
                ``3.1415`` (there is also ``TAU`` which is equal to ``2 * PI``).
@@ -257,8 +408,8 @@ Note that a new instance must be added to the scene using ``add_child()``.
 Testing the scene
 ~~~~~~~~~~~~~~~~~
 
-Let's test the scene to make sure everything is working. Add this to
-``_ready()``:
+Let's test the scene to make sure everything is working. Add this ``new_game``
+call to ``_ready()``:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
@@ -274,6 +425,13 @@ Let's test the scene to make sure everything is working. Add this to
         NewGame();
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `main.cpp`.
+    void Main::_ready() {
+        new_game();
+    }
+
 Let's also assign ``Main`` as our "Main Scene" - the one that runs automatically
 when the game launches. Press the "Play" button and select ``Main.tscn`` when
 prompted.

+ 126 - 0
getting_started/first_2d_game/06.heads_up_display.rst

@@ -113,6 +113,47 @@ Now add this script to ``HUD``:
         public delegate void StartGame();
     }
 
+ .. code-tab:: cpp
+
+    // Copy `player.gdns` to `hud.gdns` and replace `Player` with `HUD`.
+    // Attach the `hud.gdns` file to the HUD node.
+
+    // Create two files `hud.cpp` and `hud.hpp` next to `entry.cpp` in `src`.
+    // This code goes in `hud.hpp`. We also define the methods we'll be using here.
+    #ifndef HUD_H
+    #define HUD_H
+
+    #include <Button.hpp>
+    #include <CanvasLayer.hpp>
+    #include <Godot.hpp>
+    #include <Label.hpp>
+    #include <Timer.hpp>
+
+    class HUD : public godot::CanvasLayer {
+        GODOT_CLASS(HUD, godot::CanvasLayer)
+
+        godot::Label *_score_label;
+        godot::Label *_message_label;
+        godot::Timer *_start_message_timer;
+        godot::Timer *_get_ready_message_timer;
+        godot::Button *_start_button;
+        godot::Timer *_start_button_timer;
+
+    public:
+        void _init() {}
+        void _ready();
+        void show_get_ready();
+        void show_game_over();
+        void update_score(const int score);
+        void _on_StartButton_pressed();
+        void _on_StartMessageTimer_timeout();
+        void _on_GetReadyMessageTimer_timeout();
+
+        static void _register_methods();
+    };
+
+    #endif // HUD_H
+
 The ``start_game`` signal tells the ``Main`` node that the button
 has been pressed.
 
@@ -135,6 +176,31 @@ has been pressed.
         GetNode<Timer>("MessageTimer").Start();
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `hud.cpp`.
+    #include "hud.hpp"
+
+    void HUD::_ready() {
+        _score_label = get_node<godot::Label>("ScoreLabel");
+        _message_label = get_node<godot::Label>("MessageLabel");
+        _start_message_timer = get_node<godot::Timer>("StartMessageTimer");
+        _get_ready_message_timer = get_node<godot::Timer>("GetReadyMessageTimer");
+        _start_button = get_node<godot::Button>("StartButton");
+        _start_button_timer = get_node<godot::Timer>("StartButtonTimer");
+    }
+
+    void HUD::_register_methods() {
+        godot::register_method("_ready", &HUD::_ready);
+        godot::register_method("show_get_ready", &HUD::show_get_ready);
+        godot::register_method("show_game_over", &HUD::show_game_over);
+        godot::register_method("update_score", &HUD::update_score);
+        godot::register_method("_on_StartButton_pressed", &HUD::_on_StartButton_pressed);
+        godot::register_method("_on_StartMessageTimer_timeout", &HUD::_on_StartMessageTimer_timeout);
+        godot::register_method("_on_GetReadyMessageTimer_timeout", &HUD::_on_GetReadyMessageTimer_timeout);
+        godot::register_signal<HUD>("start_game", godot::Dictionary());
+    }
+
 This function is called when we want to display a message
 temporarily, such as "Get Ready".
 
@@ -169,6 +235,23 @@ temporarily, such as "Get Ready".
         GetNode<Button>("StartButton").Show();
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `hud.cpp`.
+    // There is no `yield` in GDNative, so we need to have every
+    // step be its own method that is called on timer timeout.
+    void HUD::show_get_ready() {
+        _message_label->set_text("Get Ready");
+        _message_label->show();
+        _get_ready_message_timer->start();
+    }
+
+    void HUD::show_game_over() {
+        _message_label->set_text("Game Over");
+        _message_label->show();
+        _start_message_timer->start();
+    }
+
 This function is called when the player loses. It will show "Game Over" for 2
 seconds, then return to the title screen and, after a brief pause, show the
 "Start" button.
@@ -191,6 +274,13 @@ seconds, then return to the title screen and, after a brief pause, show the
         GetNode<Label>("ScoreLabel").Text = score.ToString();
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `hud.cpp`.
+    void HUD::update_score(const int p_score) {
+        _score_label->set_text(godot::Variant(p_score));
+    }
+
 This function is called by ``Main`` whenever the score changes.
 
 Connect the ``timeout()`` signal of ``MessageTimer`` and the ``pressed()``
@@ -219,6 +309,25 @@ signal of ``StartButton`` and add the following code to the new functions:
         GetNode<Label>("Message").Hide();
     }
 
+ .. code-tab:: cpp
+
+    // This code goes in `hud.cpp`.
+    void HUD::_on_StartButton_pressed() {
+        _start_button_timer->stop();
+        _start_button->hide();
+        emit_signal("start_game");
+    }
+
+    void HUD::_on_StartMessageTimer_timeout() {
+        _message_label->set_text("Dodge the\nCreeps");
+        _message_label->show();
+        _start_button_timer->start();
+    }
+
+    void HUD::_on_GetReadyMessageTimer_timeout() {
+        _message_label->hide();
+    }
+
 Connecting HUD to Main
 ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -250,6 +359,11 @@ In ``new_game()``, update the score display and show the "Get Ready" message:
         hud.UpdateScore(Score);
         hud.ShowMessage("Get Ready!");
 
+ .. code-tab:: cpp
+
+        _hud->update_score(score);
+        _hud->show_get_ready();
+
 In ``game_over()`` we need to call the corresponding ``HUD`` function:
 
 .. tabs::
@@ -261,6 +375,10 @@ In ``game_over()`` we need to call the corresponding ``HUD`` function:
 
         GetNode<HUD>("HUD").ShowGameOver();
 
+ .. code-tab:: cpp
+
+        _hud->show_game_over();
+
 Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in sync
 with the changing score:
 
@@ -273,6 +391,10 @@ with the changing score:
 
         GetNode<HUD>("HUD").UpdateScore(Score);
 
+ .. code-tab:: cpp
+
+        _hud->update_score(score);
+
 Now you're ready to play! Click the "Play the Project" button. You will be asked
 to select a main scene, so choose ``Main.tscn``.
 
@@ -304,6 +426,10 @@ the ``new_game()`` function in ``Main``:
         // we have to use the original Godot snake_case name.
         GetTree().CallGroup("mobs", "queue_free");
 
+ .. code-tab:: cpp
+
+        get_tree()->call_group("mobs", "queue_free");
+
 The ``call_group()`` function calls the named function on every node in a
 group - in this case we are telling every mob to delete itself.
 

BIN
getting_started/first_2d_game/files/dodge_assets_with_gdnative.zip