# A Basic Networked Godot 3D Game in C++ (GDExtension)

## Pretext

When writing a game in Godot you have a few options in terms of coding languages.  
(Very simplified):  
[You can write in GDScript (First class support)](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html)  
[You can write in C# (Somewhat first class support)](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/index.html)  
[You can write in C++ GDExtension (Essentially a dll you inject into godot to load)](https://docs.godotengine.org/en/stable/tutorials/scripting/cpp/index.html)  
[And you can write in third party languages like Rust in GDExtension (Also a dll I assume)](https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/index.html)  
[You can write in C++ modules (I’m not sure how this works)](https://docs.godotengine.org/en/4.4/contributing/development/core_and_modules/custom_modules_in_cpp.html)

If you are coming from an Unreal Engine background or even lower level you may be used to C++ and want to stick with C++, unfortunately the documentation for ‘getting started’ in C++ Godot is a bit confusing, this guide is just a writeup of what I did to get a basic ‘starter’ template that is essentially the boilerplate for a Roblox like movement 3D game with networking.

## Getting A Development Environment Setup

If you’re expecting to be able to setup Rider (or Visual Studio) easily, I found it quite hard, I am sure you can find someone who has written up a third-party approach to getting it working but officially it is not supported.

If you need more help - [GDExtension C++ example — Godot Engine (4.4) documentation in English](https://docs.godotengine.org/en/4.4/tutorials/scripting/gdextension/gdextension_cpp_example.html) is fairly good for the basics

### [End Goal - Project Structure](https://docs.godotengine.org/en/4.4/tutorials/scripting/gdextension/gdextension_cpp_example.html)

[Project structure wise we are going to](https://docs.godotengine.org/en/4.4/tutorials/scripting/gdextension/gdextension_cpp_example.html) end up looking like this.

```plaintext
-demo => your test godot game to essentially treat as staging grounds
-src => your cpp source code for the extension
-godot => the engine for godot (optional but recommended) => git submodule
-godot-cpp => the official c++ gdextension binding => git submodule
-.vscode => configurations to make vscode build test easier 
```

### Step 1 - Create and clone relevant folders

Before cloning on anything decide on a godot version, i chose 4.5 so replace &lt;desired\_branch\_version&gt; with 4.5

```plaintext
mkdir <godot_project_foldername>
cd <godot_project_foldername>
git init
git submodule add -b <desired_branch_version> https://github.com/godotengine/godot-cpp
cd godot-cpp
git submodule update --init
cd ..
git submodule add -b <desired_branch_version> https://github.com/godotengine/godot
cd godot
git submodule update --init
cd ..
mkdir src
```

Now you are going to have the godot engine source code as a submodule in godot (you will need this for breakpoints but its optional, feel free to use a release from godot but debugging will be painful), you will have the gd extension bindings in godot-cpp and you have a folder called src we are going to dump our awful C++ code in.

### Step 2 - Create some initial files

in `src` we are going to create a few files, you can structure this however you want however i chose to do `src/core/…` `src/resources/…` `src/state_machines/…` and `src/register_types.hpp`/cpp

For now, I would suggest copypasta the below, you can tweak/delete after you get this garbage running.  
**arora\_player.hpp**

```cpp
#pragma once

#include <godot_cpp/classes/character_body3d.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include "resources/arora_player_stats.hpp"
#include "resources/arora_settings.hpp"

namespace godot
{
    class AroraPlayer : public CharacterBody3D
    {
        GDCLASS(AroraPlayer, CharacterBody3D)
    private:
        class AroraPlayerMovementStateMachine* movement_state_machine;
        class CollisionShape3D* collider;
        class Node3D* pivot, *third_person_body, *first_person_body;
        class SpringArm3D* camera_boom;
        class Camera3D* camera;

        Ref<AroraPlayerStats> player_stats;
        Ref<AroraSettings> arora_settings;
        Ref<PackedScene> third_person_mesh;
        Ref<PackedScene> first_person_mesh;

        float boom_rot_x = 0.0f, boom_rot_y = 0.0f;

        class Input* InputSubsystem;
        const class Engine* EngineSubsystem;
        class Node3D* _create_pivot();
        class CollisionShape3D* _create_collider();
        class SpringArm3D* _create_spring_arm();
        class Camera3D* _create_camera(class Node3D* parent);
        class Node3D* _create_third_person_body(class Node3D* parent);
        class Node3D* _create_first_person_body(class Node3D* parent);

        void _swap_bodies_visibility(bool p_is_first_person);
        bool _is_editor() const;
    protected:
        static void _bind_methods();
    public:
        AroraPlayer();
        ~AroraPlayer();
        Ref<AroraPlayerStats> get_player_stats() const;
        void set_player_stats(const Ref<AroraPlayerStats> &p_player_stats);
        Ref<PackedScene> get_third_person_mesh() const;
        void set_third_person_mesh(const Ref<PackedScene>& p_third_person_mesh);
        Ref<PackedScene> get_first_person_mesh() const;
        void set_first_person_mesh(const Ref<PackedScene>& p_first_person_mesh);
        Ref<AroraSettings> get_arora_settings() const;
        void set_arora_settings(const Ref<AroraSettings>& p_arora_settings);
        const float get_length() const;
        void set_length(float p_length);
        void rotate_boom_xy(float p_x, float p_y);
        const Basis get_camera_boom_basis() const;
        void face_pivot_to(const Basis& basis);
        bool is_third_person() const;
        void set_first_person();
        void set_third_person();
        virtual void _physics_process(double p_delta) override;
        virtual void _ready() override;
        virtual void _unhandled_input(const Ref<InputEvent> &p_event) override;
        virtual void _input(const Ref<InputEvent> &p_event) override;
    };
}
```

**arora\_player.cpp**

```cpp
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/collision_shape3d.hpp>
#include <godot_cpp/classes/capsule_shape3d.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/input_event_mouse_motion.hpp>
#include <godot_cpp/classes/input_event_key.hpp>
#include <godot_cpp/classes/camera3d.hpp>
#include <godot_cpp/classes/spring_arm3d.hpp>
#include <godot_cpp/classes/animation_tree.hpp>
#include "arora_player.hpp"
#include "resources/arora_player_stats.hpp"
#include "state_machines/arora_player_movement_statemachine.hpp"

using namespace godot;

static constexpr char *pivot_name = "Pivot";
static constexpr char *collider_name = "Collider";
static constexpr char *camera_name = "Camera";
static constexpr char *spring_arm_name = "CameraBoom";
static constexpr char *third_person_mesh_name = "ThirdPersonBody";
static constexpr char *first_person_mesh_name = "FirstPersonBody";
static constexpr float collider_radius = 2.0f;
static constexpr float collider_height = 8.0f;

Node3D *AroraPlayer::_create_pivot()
{
    if (Node *local_pivot = get_node_or_null(pivot_name))
    {
        print_line("pivot exists, not creating a new one");
        return Object::cast_to<Node3D>(local_pivot);
    }
    print_line("creating a new pivot");
    Node3D *local_pivot = memnew(Node3D);
    local_pivot->set_name(pivot_name);
    add_child(local_pivot);
    local_pivot->set_owner(get_tree()->get_edited_scene_root());
    return local_pivot;
}

CollisionShape3D *AroraPlayer::_create_collider()
{
    if (Node *local_collider = get_node_or_null(collider_name))
    {
        print_line("collider exists, not creating a new one");
        return Object::cast_to<CollisionShape3D>(local_collider);
    }
    print_line("creating a new collider");
    CapsuleShape3D *collider_shape = memnew(CapsuleShape3D);
    collider_shape->set_radius(collider_radius);
    collider_shape->set_height(collider_height);
    CollisionShape3D *local_collider = memnew(CollisionShape3D);
    local_collider->set_shape(collider_shape);
    local_collider->set_name(collider_name);
    add_child(local_collider);
    local_collider->set_owner(get_tree()->get_edited_scene_root());
    return local_collider;
}

SpringArm3D *AroraPlayer::_create_spring_arm()
{
    if (Node *local_spring_arm = get_node_or_null(spring_arm_name))
    {
        print_line("spring_arm exists, not creating a new one");
        return Object::cast_to<SpringArm3D>(local_spring_arm);
    }
    print_line("creating a spring arm");
    SpringArm3D *local_spring_arm = memnew(SpringArm3D);
    local_spring_arm->set_name(spring_arm_name);
    add_child(local_spring_arm);
    local_spring_arm->set_owner(get_tree()->get_edited_scene_root());
    return local_spring_arm;
}

Camera3D *AroraPlayer::_create_camera(Node3D *parent)
{
    if (Node *local_camera = parent->get_node_or_null(camera_name))
    {
        print_line("camera exists, not creating a new one");
        return Object::cast_to<Camera3D>(local_camera);
    }
    print_line("creating a camera");
    Camera3D *local_camera = memnew(Camera3D);
    local_camera->set_name(camera_name);
    parent->add_child(local_camera);
    local_camera->set_owner(parent->get_tree()->get_edited_scene_root());
    return local_camera;
}

Node3D *AroraPlayer::_create_third_person_body(Node3D *parent)
{
    if (!third_person_mesh.is_valid())
    {
        print_line("warning: third_person_mesh not set, this will crash at runtime");
        return nullptr;
    }
    if (Node* local_third_person_body = parent->get_node_or_null(third_person_mesh_name))
    {
        print_line("third_person_mesh exists, not creating a new one");
        return Object::cast_to<Node3D>(local_third_person_body);
    }
    print_line("third_person_mesh doesn't exist, creating a new one");
    Node *instanced_third_person = third_person_mesh->instantiate();
    instanced_third_person->set_name(third_person_mesh_name);
    parent->add_child(instanced_third_person);
    instanced_third_person->set_owner(parent->get_tree()->get_edited_scene_root());
    return Object::cast_to<Node3D>(instanced_third_person);
}

Node3D *godot::AroraPlayer::_create_first_person_body(Node3D *parent)
{
    if (!first_person_mesh.is_valid())
    {
        print_line("warning: first_person_mesh not set, this will crash at runtime");
        return nullptr;
    }
    if (Node* local_first_person_body = parent->get_node_or_null(first_person_mesh_name))
    {
        print_line("first_person_mesh exists, not creating a new one");
        return Object::cast_to<Node3D>(local_first_person_body);
    }
    print_line("first_person_mesh doesn't exist, creating a new one");
    Node *instanced_first_person = first_person_mesh->instantiate();
    instanced_first_person->set_name(first_person_mesh_name);
    parent->add_child(instanced_first_person);
    instanced_first_person->set_owner(parent->get_tree()->get_edited_scene_root());
    return Object::cast_to<Node3D>(instanced_first_person);
}

void AroraPlayer::set_first_person()
{
    _swap_bodies_visibility(true);
    InputSubsystem->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);
}

void AroraPlayer::set_third_person()
{
    _swap_bodies_visibility(false);
    InputSubsystem->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);
}

void AroraPlayer::_swap_bodies_visibility(bool p_is_first_person)
{
    third_person_body->set_visible(!p_is_first_person);
    first_person_body->set_visible(p_is_first_person);
}

// in godot version currently used is_editor_hint causes crash sometimes (likely some weird engine race condition)
// this is a hack to try and avoid that
bool AroraPlayer::_is_editor() const
{
    try
    {
        return !EngineSubsystem || EngineSubsystem->is_editor_hint();
    }
    catch (...)
    {
        // very noisy log...
        // print_error("editor threw error trying to use the hint, going to just return true and continue rather then hard crashing");
    }
    return true;
}

void AroraPlayer::_bind_methods()
{
    ClassDB::bind_method(D_METHOD("get_third_person_mesh"), &AroraPlayer::get_third_person_mesh);
    ClassDB::bind_method(D_METHOD("set_third_person_mesh", "p_third_person_mesh"), &AroraPlayer::set_third_person_mesh);
    ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "third_person_mesh", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), "set_third_person_mesh", "get_third_person_mesh");
    ClassDB::bind_method(D_METHOD("get_player_stats"), &AroraPlayer::get_player_stats);
    ClassDB::bind_method(D_METHOD("set_player_stats", "p_player_stats"), &AroraPlayer::set_player_stats);
    ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "player_stats", PROPERTY_HINT_RESOURCE_TYPE, "AroraPlayerStats"), "set_player_stats", "get_player_stats");
    ClassDB::bind_method(D_METHOD("get_arora_settings"), &AroraPlayer::get_arora_settings);
    ClassDB::bind_method(D_METHOD("set_arora_settings", "p_arora_settings"), &AroraPlayer::set_arora_settings);
    ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "arora_settings", PROPERTY_HINT_RESOURCE_TYPE, "AroraSettings"), "set_arora_settings", "get_arora_settings");
    ClassDB::bind_method(D_METHOD("get_first_person_mesh"), &AroraPlayer::get_first_person_mesh);
    ClassDB::bind_method(D_METHOD("set_first_person_mesh", "p_first_person_mesh"), &AroraPlayer::set_first_person_mesh);
    ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "first_person_mesh", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), "set_first_person_mesh", "get_first_person_mesh");
}

AroraPlayer::AroraPlayer()
{
}

AroraPlayer::~AroraPlayer()
{
}

Ref<AroraPlayerStats> AroraPlayer::get_player_stats() const
{
    return player_stats;
}

void AroraPlayer::set_player_stats(const Ref<AroraPlayerStats>& p_player_stats)
{
    player_stats = p_player_stats;
}

Ref<PackedScene> AroraPlayer::get_third_person_mesh() const
{
    return third_person_mesh;
}

void AroraPlayer::set_third_person_mesh(const Ref<PackedScene> &p_third_person_mesh)
{
    third_person_mesh = p_third_person_mesh;
}

Ref<PackedScene> godot::AroraPlayer::get_first_person_mesh() const
{
    return first_person_mesh;
}

void godot::AroraPlayer::set_first_person_mesh(const Ref<PackedScene> &p_first_person_mesh)
{
    first_person_mesh = p_first_person_mesh;
}

Ref<AroraSettings> AroraPlayer::get_arora_settings() const
{
    return arora_settings;
}

void AroraPlayer::set_arora_settings(const Ref<AroraSettings> &p_arora_settings)
{
    arora_settings = p_arora_settings;
}

const float godot::AroraPlayer::get_length() const
{
    return camera_boom->get_length();
}

static constexpr float THIRD_PERSON_MIN_DISTANCE = 0.05f;
void AroraPlayer::set_length(float p_length)
{
    if (third_person_body)
    {
        if (third_person_body->is_visible() && p_length <= THIRD_PERSON_MIN_DISTANCE)
        {
            set_first_person();
        }
        if (!third_person_body->is_visible() && p_length >= THIRD_PERSON_MIN_DISTANCE)
        {
            set_third_person();
        }
    }
    camera_boom->set_length(p_length);
}

static constexpr float MAX_X_ROTATION_DEGREES = 85.0f;
static float MAX_X_ROTATION_RADIANS = Math::deg_to_rad(MAX_X_ROTATION_DEGREES);
// https://docs.godotengine.org/en/stable/tutorials/3d/using_transforms.html
void AroraPlayer::rotate_boom_xy(float p_x, float p_y)
{
    boom_rot_x = CLAMP(boom_rot_x + p_x, MAX_X_ROTATION_RADIANS * -1, MAX_X_ROTATION_RADIANS);
    boom_rot_y += p_y;
    if (Math::abs(Math::rad_to_deg(boom_rot_x)) >= 360.0f)
    {
        boom_rot_x = 0;
        print_line("reset x rotation to 0");
    }
    if (Math::abs(Math::rad_to_deg(boom_rot_y)) >= 360.0f)
    {
        boom_rot_y = 0;
        print_line("reset y rotation to 0");
    }
    camera_boom->set_basis(Basis());
    camera_boom->rotate_object_local(Vector3(0,1,0), boom_rot_y);
    camera_boom->rotate_object_local(Vector3(1,0,0), boom_rot_x);
}

const Basis AroraPlayer::get_camera_boom_basis() const
{
    return camera_boom->get_basis();
}

void AroraPlayer::face_pivot_to(const Basis& basis)
{
    pivot->set_basis(basis);
}

bool AroraPlayer::is_third_person() const
{
    return camera_boom->get_length() >= THIRD_PERSON_MIN_DISTANCE;
}

void AroraPlayer::_physics_process(double p_delta)
{
    if (!InputSubsystem || !EngineSubsystem)
    {
        return;
    }
    if (!_is_editor())
    {
        movement_state_machine->_physics_process(InputSubsystem, p_delta);
    }
}

void AroraPlayer::_ready()
{
    InputSubsystem = Input::get_singleton();
    EngineSubsystem = Engine::get_singleton();
    pivot = _create_pivot();
    collider = _create_collider();
    camera_boom = _create_spring_arm();
    camera = _create_camera(camera_boom);
    third_person_body = _create_third_person_body(pivot);
    first_person_body = _create_first_person_body(camera);
    // AnimationTree* third_person_state_machine = third_person_body->get_node<AnimationTree>("AnimationTree");
    movement_state_machine = new AroraPlayerMovementStateMachine(this, player_stats, arora_settings);
    if (!_is_editor())
    {
        set_length(0.0f);
    }
}

void AroraPlayer::_unhandled_input(const Ref<InputEvent> &p_event)
{
    if (!_is_editor() && movement_state_machine)
    {
        movement_state_machine->_unhandled_input(p_event);
    }
}

void AroraPlayer::_input(const Ref<InputEvent> &p_event)
{
    if (!p_event.is_valid() || !InputSubsystem)
    {
        return;
    }
    if (movement_state_machine)
    {
        movement_state_machine->_input(InputSubsystem, p_event);
    }
}
```

**arora\_player\_movement\_statemachine.hpp**

```cpp
#pragma once

#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include "core/arora_player.hpp"

namespace godot
{
    class AroraPlayerMovementStateMachine
    {
    private:
        class AroraPlayer* arora_player;
        class Ref<class AroraPlayerStats> arora_player_stats;
        class Ref<class AroraSettings> arora_settings;
        Vector3 target_velocity;
        bool is_rotating_in_third_person;
    public:
        AroraPlayerMovementStateMachine(class AroraPlayer* InAroraPlayer, class Ref<class AroraPlayerStats> AroraPlayerStats, class Ref<class AroraSettings> AroraSettings);
        ~AroraPlayerMovementStateMachine();
        void _unhandled_input(const Ref<InputEvent> &p_event);
        void _physics_process(class Input* InputSubsystem, double p_delta);
        void _input(class Input* InputSubsystem, const Ref<InputEvent> &p_event);
    };
}
```

**arora\_player\_movement\_statemachine.cpp**

```cpp
#include <godot_cpp/classes/input_event_mouse_motion.hpp>
#include <godot_cpp/classes/character_body3d.hpp>
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/spring_arm3d.hpp>
#include "arora_player_movement_statemachine.hpp"
#include "resources/arora_player_stats.hpp"
#include "resources/arora_settings.hpp"

using namespace godot;

AroraPlayerMovementStateMachine::AroraPlayerMovementStateMachine(AroraPlayer* InAroraPlayer, Ref<AroraPlayerStats> AroraPlayerStats, Ref<AroraSettings> AroraSettings)
{
    is_rotating_in_third_person = false;
    arora_player = InAroraPlayer;
    arora_player_stats = AroraPlayerStats;
    arora_settings = AroraSettings;
}

AroraPlayerMovementStateMachine::~AroraPlayerMovementStateMachine()
{
}

void AroraPlayerMovementStateMachine::_unhandled_input(const Ref<InputEvent> &p_event)
{
    if (!p_event.is_valid())
    {
        return;
    }
    Ref<InputEventMouseMotion> input_mouse_motion = p_event;
    if (input_mouse_motion.is_valid() && (is_rotating_in_third_person || !arora_player->is_third_person()))
    {
        arora_player->rotate_boom_xy(
            -input_mouse_motion->get_relative().y * arora_settings->get_mouse_sensitivity(),
            -input_mouse_motion->get_relative().x * arora_settings->get_mouse_sensitivity()
        );
    }
}

void AroraPlayerMovementStateMachine::_physics_process(Input* InputSubsystem, double p_delta)
{
    Vector2 input_direction = InputSubsystem->get_vector("move_left", "move_right", "move_back", "move_forward");
    Basis boom_basis = arora_player->get_camera_boom_basis();
    Vector3 movement_direction = boom_basis.xform(Vector3(input_direction.x, 0.0f, input_direction.y));
    if (!movement_direction.is_zero_approx())
    {
        movement_direction.normalize();
    }
    target_velocity.x = movement_direction.x * -1 * arora_player_stats->get_speed();
    target_velocity.z = movement_direction.z * -1 * arora_player_stats->get_speed();
    if (!arora_player->is_on_floor())
    {
        target_velocity.y = target_velocity.y - (arora_player_stats->get_fall_acceleration() * p_delta);
    }
    else if (InputSubsystem->is_action_pressed("jump"))
    {
        target_velocity.y = arora_player_stats->get_jump_speed();
    }
    // Can now safely modify the vector3 movement_direction to remove y component?
    if (!movement_direction.is_zero_approx())
    {
        movement_direction.y = 0.0f;
        arora_player->face_pivot_to(Basis::looking_at(movement_direction));
    }
    arora_player->set_velocity(target_velocity);
    arora_player->move_and_slide();
}

void AroraPlayerMovementStateMachine::_input(Input *InputSubsystem, const Ref<InputEvent> &p_event)
{
    if (p_event->is_action_pressed("third_person_rotate"))
    {
        InputSubsystem->set_mouse_mode(arora_player->is_third_person() ? Input::MOUSE_MODE_CAPTURED : InputSubsystem->get_mouse_mode());
        is_rotating_in_third_person = true;
    }
    if (p_event->is_action_released("third_person_rotate"))
    {
        InputSubsystem->set_mouse_mode(arora_player->is_third_person() ? Input::MOUSE_MODE_VISIBLE : InputSubsystem->get_mouse_mode());
        is_rotating_in_third_person = false;
    }
    if (p_event->is_action_pressed("open_menu"))
    {
        InputSubsystem->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_CAPTURED ? InputSubsystem->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE) : InputSubsystem->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);
    }
    if (p_event->is_action_released("zoom_out"))
    {
        float newLength = MIN(arora_player->get_length() + arora_settings->get_zoom_speed(), arora_settings->get_max_camera_length());
        arora_player->set_length(newLength);
    }
    if (p_event->is_action_released("zoom_in"))
    {
        float newLength = MAX(arora_player->get_length() - arora_settings->get_zoom_speed(), arora_settings->get_min_camera_length());
        arora_player->set_length(newLength);
    }
}
```

**arora\_player\_stats.hpp**

```cpp
#pragma once

#include <godot_cpp/classes/resource.hpp>
namespace godot
{
    class AroraPlayerStats : public Resource
    {
        GDCLASS(AroraPlayerStats, Resource)
    private:
        float speed, fall_acceleration, jump_speed;
    protected:
        static void _bind_methods();
    public:
        AroraPlayerStats();
        ~AroraPlayerStats();
        
        void set_speed(const float p_speed);
        float get_speed() const;
        void set_jump_speed(const float p_jump_speed);
        float get_jump_speed() const;
        void set_fall_acceleration(const float p_fall_acceleration);
        float get_fall_acceleration() const;
    };
}
```

**arora\_player\_stats.cpp**

```cpp
#include "arora_player_stats.hpp"

using namespace godot;

void AroraPlayerStats::_bind_methods()
{
    ClassDB::bind_method(D_METHOD("get_speed"), &AroraPlayerStats::get_speed);
    ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &AroraPlayerStats::set_speed);
    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed");
    ClassDB::bind_method(D_METHOD("get_jump_speed"), &AroraPlayerStats::get_jump_speed);
    ClassDB::bind_method(D_METHOD("set_jump_speed", "p_jump_speed"), &AroraPlayerStats::set_jump_speed);
    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "jump_speed"), "set_jump_speed", "get_jump_speed");
    ClassDB::bind_method(D_METHOD("get_fall_acceleration"), &AroraPlayerStats::get_fall_acceleration);
    ClassDB::bind_method(D_METHOD("set_fall_acceleration", "p_fall_acceleration"), &AroraPlayerStats::set_fall_acceleration);
    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fall_acceleration"), "set_fall_acceleration", "get_fall_acceleration");
}

AroraPlayerStats::AroraPlayerStats()
{
    jump_speed = 8.0f;
    speed = 14;
    fall_acceleration = 75;
}

AroraPlayerStats::~AroraPlayerStats()
{
}

void AroraPlayerStats::set_speed(const float p_speed)
{
    speed = p_speed;
}

float AroraPlayerStats::get_speed() const
{
    return speed;
}

void AroraPlayerStats::set_jump_speed(const float p_jump_speed)
{
    jump_speed = p_jump_speed;
}

float AroraPlayerStats::get_jump_speed() const
{
    return jump_speed;
}

void AroraPlayerStats::set_fall_acceleration(const float p_fall_acceleration)
{
    fall_acceleration = p_fall_acceleration;
}

float AroraPlayerStats::get_fall_acceleration() const
{
    return fall_acceleration;
}
```

**arora\_settings.hpp**

```cpp
#pragma once

#include <godot_cpp/classes/resource.hpp>
namespace godot
{
    // TODO: swap to configfile? https://docs.godotengine.org/en/stable/classes/class_configfile.html
    class AroraSettings : public Resource
    {
        GDCLASS(AroraSettings, Resource)
    private:
        float mouse_sensitivity, max_camera_length, min_camera_length, zoom_speed;
    protected:
        static void _bind_methods();
    public:
        AroraSettings();
        ~AroraSettings();

        void set_mouse_sensitivity(const float p_mouse_sensitivity);
        float get_mouse_sensitivity() const;
        void set_max_camera_length(const float p_max_camera_length);
        float get_max_camera_length() const;
        void set_min_camera_length(const float p_min_camera_length);
        float get_min_camera_length() const;
        void set_zoom_speed(const float p_zoom_speed);
        float get_zoom_speed() const;
    };
}
```

**arora\_settings.cpp**

```cpp
#include "arora_settings.hpp"

using namespace godot;

void AroraSettings::_bind_methods()
{
    ClassDB::bind_method(D_METHOD("get_mouse_sensitivity"), &AroraSettings::get_mouse_sensitivity);
    ClassDB::bind_method(D_METHOD("set_mouse_sensitivity", "p_mouse_sensitivity"), &AroraSettings::set_mouse_sensitivity);
    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mouse_sensitivity"), "set_mouse_sensitivity", "get_mouse_sensitivity");
    ClassDB::bind_method(D_METHOD("get_max_camera_length"), &AroraSettings::get_max_camera_length);
    ClassDB::bind_method(D_METHOD("set_max_camera_length", "p_max_camera_length"), &AroraSettings::set_max_camera_length);
    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_camera_length"), "set_max_camera_length", "get_max_camera_length");
    ClassDB::bind_method(D_METHOD("get_min_camera_length"), &AroraSettings::get_min_camera_length);
    ClassDB::bind_method(D_METHOD("set_min_camera_length", "p_min_camera_length"), &AroraSettings::set_min_camera_length);
    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_camera_length"), "set_min_camera_length", "get_min_camera_length");
    ClassDB::bind_method(D_METHOD("get_zoom_speed"), &AroraSettings::get_zoom_speed);
    ClassDB::bind_method(D_METHOD("set_zoom_speed", "p_zoom_speed"), &AroraSettings::set_zoom_speed);
    ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_speed"), "set_zoom_speed", "get_zoom_speed");
}

AroraSettings::AroraSettings()
{
}

AroraSettings::~AroraSettings()
{
}

void AroraSettings::set_mouse_sensitivity(const float p_mouse_sensitivity)
{
    mouse_sensitivity = p_mouse_sensitivity;
}

float AroraSettings::get_mouse_sensitivity() const
{
    return mouse_sensitivity;
}

void godot::AroraSettings::set_max_camera_length(const float p_max_camera_length)
{
    max_camera_length = p_max_camera_length;
}

float godot::AroraSettings::get_max_camera_length() const
{
    return max_camera_length;
}

void godot::AroraSettings::set_min_camera_length(const float p_min_camera_length)
{
    min_camera_length = p_min_camera_length;
}

float godot::AroraSettings::get_min_camera_length() const
{
    return min_camera_length;
}

void AroraSettings::set_zoom_speed(const float p_zoom_speed)
{
    zoom_speed = p_zoom_speed;
}

float AroraSettings::get_zoom_speed() const
{
    return zoom_speed;
}
```

### Step 3 - Register Types

In GD Extension we essentially boil down to a register\_types file, this file will handle saying what classes you have created for runtime/internal/etc and just in general what to call as the initializer.  
In my case I have called my library `arora_library` but feel free to just replace with whatever name you want like `mycoollib_library`

**src/register\_types.cpp**

```cpp
#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
#include "register_types.h"
#include "core/arora_player.hpp"
#include "core/arora_multiplayer.hpp"
#include "resources/arora_player_stats.hpp"
#include "resources/arora_settings.hpp"

using namespace godot;

void initialize_arora_module(ModuleInitializationLevel p_level) {
	if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
		return;
	}
	GDREGISTER_CLASS(AroraPlayer);
	GDREGISTER_CLASS(AroraPlayerStats);
	GDREGISTER_CLASS(AroraSettings);
	GDREGISTER_RUNTIME_CLASS(AroraMultiplayer);
}

void uninitialize_arora_module(ModuleInitializationLevel p_level) {
	if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
		return;
	}
}

extern "C" {
// Initialization.
GDExtensionBool GDE_EXPORT arora_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
	godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);

	init_obj.register_initializer(initialize_arora_module);
	init_obj.register_terminator(uninitialize_arora_module);
	init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

	return init_obj.init();
}
}
```

**src/register\_types.h**

```cpp
#ifndef GDEXAMPLE_REGISTER_TYPES_H
#define GDEXAMPLE_REGISTER_TYPES_H

#include <godot_cpp/core/class_db.hpp>

using namespace godot;

void initialize_arora_module(ModuleInitializationLevel p_level);
void uninitialize_arora_module(ModuleInitializationLevel p_level);

#endif // GDEXAMPLE_REGISTER_TYPES_H
```

### Step 4 - Setting up Godot

Next build the godot folder so you have a editor to use:

```bash
cd godot
scons dev_build=true target=editor debug_symbols=yes
```

Let this run for a bit and finish, if you fail because of any reason, you’re likely missing some dependency, [Building from source — Godot Engine (latest) documentation in English](https://docs.godotengine.org/en/latest/engine_details/development/compiling/) has a good explanation of what’s needed.

Now open the built godot `.\godot\bin\`[`godot.windows.editor.dev`](http://godot.windows.editor.dev)`.x86_64.exe` was what i need but for you it might be called something else, create a new godot project in the `demo` folder and save, close godot.

Create a new file in demo/bin called `mylibname.gdextension` in my case it was `libarora.gdextension`

```bash
[configuration]

entry_symbol = "arora_library_init"
compatibility_minimum = "4.1"
reloadable = true

[libraries]

macos.debug = "res://bin/libarora.macos.template_debug.dev.framework"
macos.release = "res://bin/libarora.macos.template_release.framework"
ios.debug = "res://bin/libarora.ios.template_debug.dev.xcframework"
ios.release = "res://bin/libarora.ios.template_release.xcframework"
windows.debug.x86_32 = "res://bin/libarora.windows.template_debug.dev.x86_32.dll"
windows.release.x86_32 = "res://bin/libarora.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "res://bin/libarora.windows.template_debug.dev.x86_64.dll"
windows.release.x86_64 = "res://bin/libarora.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libarora.linux.template_debug.dev.x86_64.so"
linux.release.x86_64 = "res://bin/libarora.linux.template_release.x86_64.so"
linux.debug.arm64 = "res://bin/libarora.linux.template_debug.dev.arm64.so"
linux.release.arm64 = "res://bin/libarora.linux.template_release.arm64.so"
linux.debug.rv64 = "res://bin/libarora.linux.template_debug.dev.rv64.so"
linux.release.rv64 = "res://bin/libarora.linux.template_release.rv64.so"
android.debug.x86_64 = "res://bin/libarora.android.template_debug.dev.x86_64.so"
android.release.x86_64 = "res://bin/libarora.android.template_release.x86_64.so"
android.debug.arm64 = "res://bin/libarora.android.template_debug.dev.arm64.so"
android.release.arm64 = "res://bin/libarora.android.template_release.arm64.so"

[dependencies]
ios.debug = {
    "res://bin/libgodot-cpp.ios.template_debug.dev.xcframework": ""
}
ios.release = {
    "res://bin/libgodot-cpp.ios.template_release.xcframework": ""
}
```

You can modify the values once you see the compiled results (they will be dumped in the bin folder, and you can modify to what you need).

### Step 5 - Setup Compilation

In .vscode create the following files:

**.vscode/launch.json**

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Godot",
            "request": "launch",
            "type": "cppvsdbg",
            "args": [
                "--editor",
                "--path",
                "demo"
            ],
            "cwd": "${workspaceFolder}",
            "console": "internalConsole",
            "program": "${workspaceFolder}/godot/bin/godot.windows.editor.dev.x86_64.exe",
            "visualizerFile": "${workspaceFolder}/godot/platform/windows/godot.natvis"
        }
    ]
}
```

**.vscode/tasks.json**

```json
{
	"version": "2.0.0",
	"tasks": [
		{
			"label": "build",
			"type": "shell",
			"command": "scons",
			"args": [
    			// enable for debugging with breakpoints
    			"dev_build=yes"
  			],
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"problemMatcher": "$msCompile"
		}
	]
}
```

in the root of the project create a file called **SConstruct**  
This is where the lib output for bin folder is defined (replace libarora with what you want it called)

```python
#!/usr/bin/env python
import os
import sys
import warnings

env = SConscript("godot-cpp/SConstruct")
if "dev_build" in ARGUMENTS and ARGUMENTS["dev_build"] == "yes" or "dev_build" in ARGUMENTS and ARGUMENTS["dev_build"] == True or "include_server_content" in ARGUMENTS:
    #warnings.warn("include_server_content is specified, the output will have server code")
    env.Append(CPPDEFINES=["INCLUDE_SERVER_CONTENT"])

# For reference:
# - CCFLAGS are compilation flags shared between C and C++
# - CFLAGS are for C-specific compilation flags
# - CXXFLAGS are for C++-specific compilation flags
# - CPPFLAGS are for pre-processor flags
# - CPPDEFINES are for pre-processor defines
# - LINKFLAGS are for linking flags

# tweak this if you want to use different folders, or more folders, to store your source code in.
env.Append(CPPPATH=["src/"])
sources = Glob("src/**/*.cpp") + Glob("src/*.cpp")

if env["platform"] == "macos":
    library = env.SharedLibrary(
        "demo/bin/libarora.{}.{}.framework/libarora.{}.{}".format(
            env["platform"], env["target"], env["platform"], env["target"]
        ),
        source=sources,
    )
elif env["platform"] == "ios":
    if env["ios_simulator"]:
        library = env.StaticLibrary(
            "demo/bin/libarora.{}.{}.simulator.a".format(env["platform"], env["target"]),
            source=sources,
        )
    else:
        library = env.StaticLibrary(
            "demo/bin/libarora.{}.{}.a".format(env["platform"], env["target"]),
            source=sources,
        )
else:
    library = env.SharedLibrary(
        "demo/bin/libarora{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
        source=sources,
    )

Default(library)
```

Now when you hit `Control Shift B` it should compile and output relevant bin to bin folder  
And hit `Control F5` it will start Godot in your project, if you use just `F5` (the debugger) you can place breakpoints anywhere and it will work engine code or your code, try it out.

## Follow Up

If you got to this point and it builds/runs successfully, you can probably do a final read over [GDExtension C++ example — Godot Engine (4.4) documentation in English](https://docs.godotengine.org/en/4.4/tutorials/scripting/gdextension/gdextension_cpp_example.html) and understand what each file here is doing.  
With this setup you can easily debug and add as you go, goodluck.
