Browse Source

Merge pull request #5078 from aaronfranke/gdnative-dtc

Add GDNative C++ code examples to Dodge the Creeps aka Your first 2D game
Max Hilbrunner 4 years ago
parent
commit
680b7e97de

+ 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
 .. 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.
 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.
         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
 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
 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;
         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.
 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
 ``_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
 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
 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
 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
 ``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)
             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
 .. tip:: The `delta` parameter in the `_process()` function refers to the *frame
         length* - the amount of time that the previous frame took to complete.
         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;
             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
 .. Note:: The boolean assignments in the code above are a common shorthand for
           programmers. Since we're doing a comparison test (boolean) and also
           programmers. Since we're doing a comparison test (boolean) and also
           *assigning* a boolean value, we can do both at the same time. Consider
           *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();
     Hide();
 
 
+ .. code-tab:: cpp
+
+    hide();
+
 Preparing for collisions
 Preparing for collisions
 ~~~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
 
@@ -323,6 +411,21 @@ Add the following at the top of the script, after ``extends Area2D``:
     [Signal]
     [Signal]
     public delegate void Hit();
     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
 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
 (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
 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);
         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
 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
 to disable the player's collision so that we don't trigger the ``hit`` signal
 more than once.
 more than once.
@@ -390,4 +503,13 @@ starting a new game.
         GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
         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.
 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
 Enemy script
 ~~~~~~~~~~~~
 ~~~~~~~~~~~~
 
 
-Add a script to the ``Mob`` and add the following member variables:
+Add a script to the ``Mob`` like this:
 
 
 .. tabs::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
     extends RigidBody2D
     extends RigidBody2D
 
 
-    export var min_speed = 150  # Minimum speed range.
-    export var max_speed = 250  # Maximum speed range.
-
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Mob : RigidBody2D
     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
 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:
 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];
         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``
 First, we get the list of animation names from the AnimatedSprite's ``frames``
 property. This returns an Array containing all three animation names: ``["walk",
 property. This returns an Array containing all three animation names: ``["walk",
 "swim", "fly"]``.
 "swim", "fly"]``.
@@ -133,6 +166,13 @@ add this code:
         QueueFree();
         QueueFree();
     }
     }
 
 
+ .. code-tab:: cpp
+
+    // This code goes in `mob.cpp`.
+    void Mob::_on_VisibilityNotifier2D_screen_exited() {
+        queue_free();
+    }
+
 This completes the `Mob` scene.
 This completes the `Mob` scene.
 
 
 With the player and enemies ready, in the next part, we'll bring them together
 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
     export(PackedScene) var mob_scene
     var score
     var score
 
 
-    func _ready():
-        randomize()
-
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     public class Main : Node
     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
     #pragma warning restore 649
 
 
         public int Score;
         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
 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();
         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``,
 Now connect the ``timeout()`` signal of each of the Timer nodes (``StartTimer``,
 ``ScoreTimer`` , and ``MobTimer``) to the main script. ``StartTimer`` will start
 ``ScoreTimer`` , and ``MobTimer``) to the main script. ``StartTimer`` will start
 the other two timers. ``ScoreTimer`` will increment the score by 1.
 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
+    func _on_ScoreTimer_timeout():
+        score += 1
+
     func _on_StartTimer_timeout():
     func _on_StartTimer_timeout():
         $MobTimer.start()
         $MobTimer.start()
         $ScoreTimer.start()
         $ScoreTimer.start()
 
 
-    func _on_ScoreTimer_timeout():
-        score += 1
-
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
+    public void OnScoreTimerTimeout()
+    {
+        Score++;
+    }
+
     public void OnStartTimerTimeout()
     public void OnStartTimerTimeout()
     {
     {
         GetNode<Timer>("MobTimer").Start();
         GetNode<Timer>("MobTimer").Start();
         GetNode<Timer>("ScoreTimer").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
 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
 starting location along the ``Path2D``, and set the mob in motion. The
 ``PathFollow2D`` node will automatically rotate as it follows the path, so we
 ``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.
 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()``.
 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
         mob.rotation = direction
 
 
         # Choose the velocity.
         # 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)
         mob.linear_velocity = velocity.rotated(direction)
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
@@ -243,10 +368,36 @@ Note that a new instance must be added to the scene using ``add_child()``.
         mob.Rotation = direction;
         mob.Rotation = direction;
 
 
         // Choose the velocity.
         // 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);
         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*,
 .. important:: Why ``PI``? In functions requiring angles, Godot uses *radians*,
                not degrees. Pi represents a half turn in radians, about
                not degrees. Pi represents a half turn in radians, about
                ``3.1415`` (there is also ``TAU`` which is equal to ``2 * PI``).
                ``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
 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -274,6 +425,13 @@ Let's test the scene to make sure everything is working. Add this to
         NewGame();
         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
 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
 when the game launches. Press the "Play" button and select ``Main.tscn`` when
 prompted.
 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();
         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
 The ``start_game`` signal tells the ``Main`` node that the button
 has been pressed.
 has been pressed.
 
 
@@ -135,6 +176,31 @@ has been pressed.
         GetNode<Timer>("MessageTimer").Start();
         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
 This function is called when we want to display a message
 temporarily, such as "Get Ready".
 temporarily, such as "Get Ready".
 
 
@@ -169,6 +235,23 @@ temporarily, such as "Get Ready".
         GetNode<Button>("StartButton").Show();
         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
 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
 seconds, then return to the title screen and, after a brief pause, show the
 "Start" button.
 "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();
         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.
 This function is called by ``Main`` whenever the score changes.
 
 
 Connect the ``timeout()`` signal of ``MessageTimer`` and the ``pressed()``
 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();
         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
 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.UpdateScore(Score);
         hud.ShowMessage("Get Ready!");
         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:
 In ``game_over()`` we need to call the corresponding ``HUD`` function:
 
 
 .. tabs::
 .. tabs::
@@ -261,6 +375,10 @@ In ``game_over()`` we need to call the corresponding ``HUD`` function:
 
 
         GetNode<HUD>("HUD").ShowGameOver();
         GetNode<HUD>("HUD").ShowGameOver();
 
 
+ .. code-tab:: cpp
+
+        _hud->show_game_over();
+
 Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in sync
 Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in sync
 with the changing score:
 with the changing score:
 
 
@@ -273,6 +391,10 @@ with the changing score:
 
 
         GetNode<HUD>("HUD").UpdateScore(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
 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``.
 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.
         // we have to use the original Godot snake_case name.
         GetTree().CallGroup("mobs", "queue_free");
         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
 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.
 group - in this case we are telling every mob to delete itself.
 
 

BIN
getting_started/first_2d_game/files/dodge_assets_with_gdnative.zip