why doesn't this work :(
Some checks failed
GDExtension CMake / build-linux (push) Has been cancelled

This commit is contained in:
PoliEcho 2025-02-13 15:26:50 +01:00
parent c723c3f55b
commit f9406aa9a9
48 changed files with 43 additions and 2623 deletions

2
extern/godot-cpp vendored

@ -1 +1 @@
Subproject commit 56571dc584ee7d14919996c6d58fb5b35e64af63
Subproject commit f3a1a2fd458dfaf4de08c906f22a2fe9e924b16f

View File

@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dw7fcik6u7kkn"
uid="uid://dmcjlguuq8xqt"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false

9
project/main.tscn Normal file
View File

@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://beuvt3vhjibjt"]
[ext_resource type="Texture2D" uid="uid://dmcjlguuq8xqt" path="res://icon.svg" id="1_lodlx"]
[node name="Main" type="Node"]
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_lodlx")
centered = false

View File

@ -11,5 +11,6 @@ config_version=5
[application]
config/name="Naval Swarm"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"

View File

@ -1,89 +0,0 @@
#include <type_traits>
#include <gdextension_interface.h>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/variant/string_name.hpp>
#include "api/extension_interface.hpp"
#include "entity/camera.hpp"
#include "entity/character/character.hpp"
#include "entity/character/enemy.hpp"
#include "entity/character/player.hpp"
#include "entity/controller/character_controller.hpp"
#include "entity/controller/enemy_controller.hpp"
#include "entity/controller/player_controller.hpp"
#include "entity/level.hpp"
#include "entity/projectile/projectile_spawner.hpp"
#include "main.hpp"
#include "singletons/console.hpp"
#include "ui/main_dialog.hpp"
#include "util/engine.hpp"
namespace rl
{
static inline console* console_singleton{ nullptr };
void initialize_static_objects()
{
console_singleton = memnew(console);
rl::engine::get()->register_singleton("Console", console::get());
}
void teardown_static_objects()
{
rl::engine::get()->unregister_singleton("Console");
memdelete(console_singleton);
}
void initialize_extension_module(godot::ModuleInitializationLevel init_level)
{
if (init_level != godot::MODULE_INITIALIZATION_LEVEL_SCENE)
return;
godot::ClassDB::register_class<rl::Projectile>();
godot::ClassDB::register_class<rl::ProjectileSpawner>();
godot::ClassDB::register_abstract_class<rl::CharacterController>();
godot::ClassDB::register_class<rl::PlayerController>(true);
godot::ClassDB::register_class<rl::EnemyController>();
godot::ClassDB::register_class<rl::Camera>();
godot::ClassDB::register_class<rl::Character>();
godot::ClassDB::register_class<rl::Enemy>();
godot::ClassDB::register_class<rl::Player>();
godot::ClassDB::register_class<rl::Level>();
godot::ClassDB::register_class<rl::MainDialog>();
godot::ClassDB::register_class<rl::Main>();
godot::ClassDB::register_class<console>();
initialize_static_objects();
}
void uninitialize_extension_module(godot::ModuleInitializationLevel init_level)
{
if (init_level != godot::MODULE_INITIALIZATION_LEVEL_SCENE)
return;
teardown_static_objects();
}
extern "C"
{
GDExtensionBool GDE_EXPORT extension_library_init(GDExtensionInterfaceGetProcAddress addr,
GDExtensionClassLibraryPtr lib,
GDExtensionInitialization* init)
{
const auto init_level = godot::MODULE_INITIALIZATION_LEVEL_SCENE;
godot::GDExtensionBinding::InitObject init_obj(addr, lib, init);
init_obj.register_initializer(initialize_extension_module);
init_obj.register_terminator(uninitialize_extension_module);
init_obj.set_minimum_library_initialization_level(init_level);
return init_obj.init();
}
}
}

View File

@ -1,18 +0,0 @@
#pragma once
#include <godot_cpp/core/class_db.hpp>
namespace godot
{
void initialize_static_objects();
void teardown_static_objects();
void initialize_extension_module(ModuleInitializationLevel init_level);
void uninitialize_extension_module(ModuleInitializationLevel init_level);
extern "C"
{
GDExtensionBool GDE_EXPORT extension_library_init(GDExtensionInterfaceGetProcAddress addr,
GDExtensionClassLibraryPtr lib,
GDExtensionInitialization* init);
}
}

View File

@ -1,47 +0,0 @@
#pragma once
#include <cstdio>
#include <iostream>
#include <godot_cpp/core/error_macros.hpp>
#ifndef NDEBUG
//
// In debug mode, checks the passed in condition and outputs
// detailed information to stederr, including a custom error
// message when the condition evaluates to false.
//
#define assertion(condition, message) \
do \
{ \
if (!(condition)) [[unlikely]] \
{ \
::godot::_err_print_error(__FUNCTION__, __FILE__, __LINE__, \
message " => condition: (" #condition ")"); \
::godot::_err_flush_stdout(); \
GENERATE_TRAP(); \
} \
} \
while (false)
#define error_msg(message) \
do \
{ \
::godot::_err_print_error(__FUNCTION__, __FILE__, __LINE__, message); \
::godot::_err_flush_stdout(); \
GENERATE_TRAP(); \
} \
while (false)
#define runtime_assert(condition) assertion(condition, "validation check failed")
#else
//
// In release mode the macro does nothing ((void)0), including
// the execution of the condition so don't define the expression
// as anything that would be considered program logis.
//
#define assertion(condition, message) static_cast<void>(0)
#define error_msg(message) static_cast<void>(0)
#define runtime_assert(condition) static_cast<void>(0)
#endif

View File

@ -1,19 +0,0 @@
#pragma once
namespace rl::inline utils
{
#if defined(_MSC_VER)
// MSVC doesn't have a clean way to ignore custom
// attributes within a namespace like clang or gcc
#define property
#define signal_slot
#elif defined(__GNUG__) || defined(__clang__)
// these macros are used to define custom attributes to label godot signal callbacks
// and node properties. they don't do anything other than making it easier to spot
// these functions when reading the code or searching for them in an IDE.
#define property __attribute__((rl("property")))
#define signal_slot __attribute__((rl("signal_slot")))
#endif
}

View File

@ -1,40 +0,0 @@
#pragma once
#include <concepts>
#include <string>
#include <string_view>
#include <type_traits>
namespace godot
{
template <typename T, typename V>
struct GetTypeInfo;
class Variant;
class Object;
}
namespace rl::inline utils
{
template <auto S>
concept CompileTimeStr = std::same_as<decltype(S), std::string_view> ||
std::same_as<decltype(S), std::string> ||
std::same_as<decltype(S), const char*>;
template <typename T>
concept GDObjectDerived =
std::derived_from<std::remove_cvref_t<std::remove_pointer_t<T>>, godot::Object> ||
std::same_as<std::remove_cvref_t<std::remove_pointer_t<T>>, godot::Object>;
template <typename T>
concept VariantConstructable = requires(const T& t) { static_cast<godot::Variant>(t); };
template <typename T>
concept VariantConvertable = requires(T t) { static_cast<T>(t); };
template <typename T>
concept VariantCompatible =
VariantConstructable<std::remove_cvref_t<T>> &&
VariantConvertable<std::remove_cvref_t<T>> && requires {
godot::GetTypeInfo<std::type_identity_t<std::remove_cvref_t<T>>, void>::VARIANT_TYPE;
};
}

View File

@ -1,80 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
namespace rl::inline constants
{
namespace name
{
namespace level
{
constexpr inline auto level1{ "Level1" };
constexpr inline auto physics_box{ "PhysicsBox" };
}
namespace dialog
{
constexpr inline auto console{ "ConsolePanel" };
constexpr inline auto canvas_layer{ "MainCanvasLayer" };
}
namespace character
{
constexpr inline auto player{ "Player" };
constexpr inline auto enemy{ "Enemy" };
constexpr inline auto firing_pt{ "FiringPoint" };
}
}
namespace event
{
constexpr inline auto position_changed{ "position_changed" };
constexpr inline auto entered_area{ "entered_area" };
constexpr inline auto exited_area{ "exited_area" };
constexpr inline auto spawn_projectile{ "spawn_projectile" };
constexpr inline auto character_move{ "character_move" };
constexpr inline auto character_rotate{ "character_rotate" };
constexpr inline auto character_shoot{ "character_shoot" };
constexpr inline auto body_entered{ "body_entered" };
constexpr inline auto body_exited{ "body_exited" };
constexpr inline auto signal_example{ "custom_signal_example" };
}
enum class LayerID : uint32_t {
Player = 0x00000001,
NPCs = 0x00000002,
Projectiles = 0x00000004,
Walls = 0x00000008,
DamageZones = 0x00000010,
DeathZones = 0x00000020,
PhysicsObjects = 0x00000040,
Layer08 = 0x00000080,
Layer09 = 0x00000100,
Layer10 = 0x00000200,
Layer11 = 0x00000400,
Layer12 = 0x00000800,
Layer13 = 0x00001000,
Layer14 = 0x00002000,
Layer15 = 0x00004000,
Layer16 = 0x00008000,
};
namespace path
{
namespace scene
{
constexpr inline auto Level1{ "res://scenes/levels/level1.tscn" };
constexpr inline auto Player{ "res://scenes/characters/player.tscn" };
constexpr inline auto Bullet{ "res://scenes/projectiles/bullet.tscn" };
}
namespace ui
{
constexpr inline auto MainDialog{ "res://scenes/ui/main_dialog.tscn" };
}
}
}

View File

@ -1,121 +0,0 @@
#pragma once
#include <concepts>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>
#include <godot_cpp/variant/string.hpp>
#if defined(__GNUG__)
#define stdcall __attribute__((stdcall))
#elif defined(__clang__)
#define stdcall __stdcall
#elif defined(_MSC_VER)
#define stdcall __cdecl
#endif
namespace rl::inline utils
{
namespace detail
{
template <typename H>
void to_arg_vec_impl(std::vector<godot::String>& s, H&& h)
{
s.push_back(typeid(decltype(h)).name());
}
template <typename H, typename... T>
void to_arg_vec_impl(std::vector<godot::String>& s, H&& h, T&&... t)
{
s.push_back(typeid(decltype(h)).name());
to_arg_vec_impl(s, std::forward<T>(t)...);
}
template <typename... TupleTypes, std::size_t... Idx>
std::vector<godot::String> to_arg_vec(const std::tuple<TupleTypes...>& tup,
std::integer_sequence<std::size_t, Idx...>)
{
std::vector<godot::String> result = {};
to_arg_vec_impl(result, std::get<Idx>(tup)...);
return result;
}
template <typename... TupleTypes>
std::vector<godot::String> to_arg_vec(const std::tuple<TupleTypes...>& tup)
{
static constexpr size_t arg_count = sizeof...(TupleTypes);
return to_arg_vec(tup, std::make_index_sequence<arg_count>());
}
template <std::size_t... Indexes>
auto arg_vec_to_tuple(const std::vector<godot::String>& v, std::index_sequence<Indexes...>)
{
return std::make_tuple(v[Indexes]...);
}
template <std::size_t N>
auto arg_vec_to_tuple(const std::vector<godot::String>& v)
{
return arg_vec_to_tuple(v, std::make_index_sequence<N>());
}
}
template <typename>
struct function_traits;
// non-const member function
template <typename TRet, typename TClass, typename... TArgs>
struct function_traits<TRet (TClass::*)(TArgs...)>
{
using return_type = TRet;
using class_type = TClass;
using arg_types = std::tuple<TArgs...>;
using arg_types_nocvref = std::tuple<std::remove_cvref_t<TArgs>...>;
inline static const std::string_view class_name{ typeid(class_type).name() };
static constexpr size_t arg_count = sizeof...(TArgs);
};
// const member function
template <typename TRet, typename TClass, typename... TArgs>
struct function_traits<TRet (TClass::*)(TArgs...) const>
: public function_traits<TRet (TClass::*)(TArgs...)>
{
using class_type = const TClass;
};
// functor / lambda
template <typename TCallable>
struct function_traits
: public function_traits<decltype(&std::remove_reference_t<TCallable>::operator())>
{
inline static const std::string_view class_name{};
};
// leaves function signature untouched if
// __stdcall or __cdecl isn't specified
template <typename TFunction>
struct remove_callingconv
{
using type = TFunction;
};
// strips __stdcall or __cdecl out of
// lambdas function/lambda type signatures
template <typename TRet, typename... TArgs>
struct remove_callingconv<TRet(stdcall*)(TArgs...)>
{
using type = TRet(TArgs...);
};
// strips __stdcall or __cdecl out of
// noexcept function/lambda type signatures
template <typename TRet, typename... TArgs>
struct remove_callingconv<TRet stdcall(TArgs...) noexcept>
{
using type = TRet(TArgs...) noexcept;
};
}

View File

@ -1,14 +0,0 @@
#include <godot_cpp/classes/camera2d.hpp>
#include "entity/camera.hpp"
namespace rl
{
Camera::Camera()
{
this->set_name("PlayerCamera");
this->set_margin_drawing_enabled(true);
auto anchor{ AnchorMode::ANCHOR_MODE_DRAG_CENTER };
this->set_anchor_mode(anchor);
}
}

View File

@ -1,20 +0,0 @@
#pragma once
#include <godot_cpp/classes/camera2d.hpp>
namespace rl
{
class Camera : public godot::Camera2D
{
GDCLASS(Camera, godot::Camera2D);
public:
Camera();
~Camera() = default;
protected:
static void _bind_methods()
{
}
};
}

View File

@ -1,134 +0,0 @@
#include <array>
#include <tuple>
#include <type_traits>
#include <vector>
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/marker2d.hpp>
#include <godot_cpp/classes/rectangle_shape2d.hpp>
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/math.hpp>
#include <godot_cpp/variant/callable.hpp>
#include <godot_cpp/variant/color.hpp>
#include <godot_cpp/variant/typed_array.hpp>
#include <godot_cpp/variant/vector2.hpp>
#include "core/assert.hpp"
#include "core/concepts.hpp"
#include "core/constants.hpp"
#include "entity/camera.hpp"
#include "entity/character/character.hpp"
#include "entity/controller/character_controller.hpp"
#include "util/bind.hpp"
#include "util/engine.hpp"
#include "util/input.hpp"
#include "util/io.hpp"
#include "util/scene.hpp"
namespace rl
{
Character::Character()
{
this->set_motion_mode(MotionMode::MOTION_MODE_FLOATING);
}
void Character::_ready()
{
this->add_child(m_camera);
this->add_child(m_character_controller);
m_firing_point = gdcast<godot::Marker2D>(
this->find_child(name::character::firing_pt, true, false));
runtime_assert(m_firing_point != nullptr);
if (!engine::editor_active())
runtime_assert(m_character_controller != nullptr);
if (m_character_controller != nullptr)
{
signal<event::character_move>::connect<CharacterController>(m_character_controller) <=>
signal_callback(this, on_character_movement);
signal<event::character_rotate>::connect<CharacterController>(m_character_controller) <=>
signal_callback(this, on_character_rotate);
signal<event::character_shoot>::connect<CharacterController>(m_character_controller) <=>
signal_callback(this, on_character_shoot);
}
}
void Character::set_controller(CharacterController* controller)
{
m_character_controller = controller;
runtime_assert(m_character_controller != nullptr);
}
CharacterController* Character::get_controller() const
{
return m_character_controller;
}
[[signal_slot]]
void Character::on_character_movement(godot::Vector2 movement_velocity, double delta_time)
{
double increment = m_movement_friction * delta_time;
godot::Vector2 velocity{ this->get_velocity().lerp(movement_velocity, increment) };
velocity = velocity.clamp({ -1.0, -1.0 }, { 1.0, 1.0 });
this->translate(velocity * this->get_movement_speed() * delta_time);
this->set_velocity(velocity);
this->move_and_slide();
}
[[signal_slot]]
void Character::on_character_rotate(double rotation_angle, double delta_time)
{
const double smoothed_angle = godot::Math::lerp_angle(
static_cast<double>(this->get_rotation()), rotation_angle, m_rotation_speed * delta_time);
this->set_rotation(smoothed_angle);
}
[[signal_slot]]
void Character::on_character_shoot()
{
// TODO: fix this
this->emit_signal(event::spawn_projectile, m_firing_point);
}
[[property]]
double Character::get_movement_speed() const
{
return m_movement_speed;
}
[[property]]
void Character::set_movement_speed(const double move_speed)
{
m_movement_speed = move_speed;
}
[[property]]
double Character::get_movement_friction() const
{
return m_movement_friction;
}
[[property]]
void Character::set_movement_friction(const double move_friction)
{
m_movement_friction = move_friction;
}
[[property]]
double Character::get_rotation_speed() const
{
return m_rotation_speed;
}
[[property]]
void Character::set_rotation_speed(const double rotation_speed)
{
m_rotation_speed = rotation_speed;
}
}

View File

@ -1,87 +0,0 @@
#pragma once
#include <concepts>
#include <godot_cpp/classes/character_body2d.hpp>
#include "core/attributes.hpp"
#include "core/constants.hpp"
#include "entity/camera.hpp"
#include "singletons/console.hpp"
#include "util/bind.hpp"
namespace godot
{
class Marker2D;
class Object;
struct Vector2;
}
namespace rl
{
class CharacterController;
}
namespace rl
{
class Character : public godot::CharacterBody2D
{
GDCLASS(Character, godot::CharacterBody2D);
public:
Character();
virtual ~Character() = default;
virtual void _ready() override;
public:
CharacterController* get_controller() const;
void set_controller(CharacterController* controller);
protected:
[[property]] double get_movement_speed() const;
[[property]] double get_movement_friction() const;
[[property]] double get_rotation_speed() const;
[[property]] void set_movement_speed(const double move_speed);
[[property]] void set_movement_friction(const double move_friction);
[[property]] void set_rotation_speed(const double rotation_speed);
[[signal_slot]] void on_character_shoot();
[[signal_slot]] void on_character_rotate(double rotation_angle, double delta_time);
[[signal_slot]] void on_character_movement(godot::Vector2 movement_velocity,
double delta_time);
protected:
static void _bind_methods()
{
bind_member_function(Character, on_character_movement);
bind_member_function(Character, on_character_rotate);
bind_member_function(Character, on_character_shoot);
bind_property(Character, movement_speed, double);
bind_property(Character, movement_friction, double);
bind_property(Character, rotation_speed, double);
signal_binding<Character, event::position_changed>::add<godot::Object*, godot::Vector2>();
signal_binding<Character, event::spawn_projectile>::add<godot::Object*, godot::Vector2>();
}
protected:
// Rate of acceleration/deceleration (unit/s/s)
double m_movement_friction{ 5.0 };
// Rate of rotational acceleration/deceleration (unit/s/s)
double m_rotation_speed{ 10.0 };
// target movement speed (units/s)
double m_movement_speed{ 500.0 };
// target facing angle (radians)
double m_rotation_angle{ 0.0 };
// the player character camera
Camera* m_camera{ memnew(Camera) };
// handles all input related player controls
CharacterController* m_character_controller{ nullptr };
// marker identifying location where to spwwn projectiles
godot::Marker2D* m_firing_point{ nullptr };
};
}

View File

@ -1,17 +0,0 @@
#include "core/constants.hpp"
#include "entity/character/character.hpp"
#include "entity/character/enemy.hpp"
#include "util/scene.hpp"
namespace rl
{
Enemy::Enemy()
: Character()
{
scene::node::set_unique_name(this, name::character::enemy);
}
void Enemy::_bind_methods()
{
}
}

View File

@ -1,21 +0,0 @@
#pragma once
#include "core/constants.hpp"
#include "entity/character/character.hpp"
#include "util/bind.hpp"
namespace rl
{
class Enemy : public Character
{
GDCLASS(Enemy, Character);
public:
Enemy();
using Character::_ready;
protected:
static void _bind_methods();
};
}

View File

@ -1,22 +0,0 @@
#include "core/constants.hpp"
#include "entity/character/character.hpp"
#include "entity/character/player.hpp"
#include "util/scene.hpp"
namespace rl
{
Player::Player()
: Character()
{
scene::node::set_unique_name(this, name::character::player);
}
void Player::_ready()
{
Character::_ready();
}
void Player::_bind_methods()
{
}
}

View File

@ -1,22 +0,0 @@
#pragma once
#include "core/constants.hpp"
#include "entity/character/character.hpp"
#include "util/bind.hpp"
namespace rl
{
class Player : public Character
{
GDCLASS(Player, Character);
public:
Player();
~Player() = default;
void _ready() override;
protected:
static void _bind_methods();
};
}

View File

@ -1,61 +0,0 @@
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/node2d.hpp>
#include <godot_cpp/variant/variant.hpp>
#include "core/assert.hpp"
#include "core/constants.hpp"
#include "entity/controller/character_controller.hpp"
#include "singletons/console.hpp"
#include "util/bind.hpp"
#include "util/engine.hpp"
#include "util/input.hpp"
#include "util/io.hpp"
namespace rl
{
void CharacterController::_process(double delta_time)
{
if (engine::editor_active())
return;
godot::Input* input_handler{ input::get() };
if (input_handler != nullptr)
{
this->process_movement_input(input_handler, delta_time);
this->process_rotation_input(input_handler, delta_time);
this->process_action_input(input_handler, delta_time);
m_elapsed_time += delta_time;
if (m_elapsed_time > 1.0)
{
m_elapsed_time = 0.0;
this->emit_signal(event::position_changed, this->get_parent(),
this->get_global_position());
}
}
}
void CharacterController::process_action_input(godot::Input* const input, double delta_time)
{
error_msg("process_action_input() not implemented in derived class");
}
void CharacterController::process_movement_input(godot::Input* const input, double delta_time)
{
error_msg("process_movement_input() not implemented in derived class");
}
void CharacterController::process_rotation_input(godot::Input* const input, double delta_time)
{
error_msg("process_rotation_input() not implemented in derived class");
}
void CharacterController::_bind_methods()
{
signal_binding<CharacterController, event::character_move>::add<godot::Vector2, double>();
signal_binding<CharacterController, event::character_rotate>::add<double, double>();
signal_binding<CharacterController, event::character_shoot>::add<godot::Object*>();
signal_binding<CharacterController, event::position_changed>::add<godot::Object*,
godot::Vector2>();
}
}

View File

@ -1,48 +0,0 @@
#pragma once
#include <type_traits>
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/node2d.hpp>
#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/variant/vector2.hpp>
#include "core/concepts.hpp"
#include "core/constants.hpp"
#include "util/bind.hpp"
namespace rl
{
class CharacterController : public godot::Node2D
{
GDCLASS(CharacterController, godot::Node2D);
public:
enum class InputMode {
MouseAndKeyboard,
Controller,
AI
};
public:
CharacterController() = default;
virtual ~CharacterController() = default;
void _process(double delta_time) override;
virtual void process_action_input(godot::Input* const input, double delta_time);
virtual void process_movement_input(godot::Input* const input, double delta_time);
virtual void process_rotation_input(godot::Input* const input, double delta_time);
protected:
static void _bind_methods();
protected:
// the active input mode for character controls
InputMode m_input_mode{ InputMode::MouseAndKeyboard };
// target rotation
double m_rotation_angle{ 0.0 };
// elapsed runtime (seconds)
double m_elapsed_time{ 0.0 };
};
}

View File

@ -1,16 +0,0 @@
#include "entity/controller/enemy_controller.hpp"
namespace rl
{
void EnemyController::process_action_input(godot::Input* const input, double delta_time)
{
}
void EnemyController::process_movement_input(godot::Input* const input, double delta_time)
{
}
void EnemyController::process_rotation_input(godot::Input* const input, double delta_time)
{
}
}

View File

@ -1,29 +0,0 @@
#pragma once
#include "entity/controller/character_controller.hpp"
namespace godot
{
class Input;
}
namespace rl
{
class EnemyController : public CharacterController
{
GDCLASS(EnemyController, CharacterController);
public:
EnemyController() = default;
~EnemyController() = default;
void process_action_input(godot::Input* const input, double delta_time) override;
void process_movement_input(godot::Input* const input, double delta_time) override;
void process_rotation_input(godot::Input* const input, double delta_time) override;
protected:
static void _bind_methods()
{
}
};
}

View File

@ -1,106 +0,0 @@
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/node2d.hpp>
#include <godot_cpp/variant/variant.hpp>
#include "core/constants.hpp"
#include "entity/controller/player_controller.hpp"
#include "singletons/console.hpp"
#include "util/engine.hpp"
#include "util/input.hpp"
#include "util/io.hpp"
namespace rl
{
void PlayerController::process_action_input(godot::Input* const input, double delta_time)
{
if (input->is_action_pressed("shoot"))
this->emit_signal(event::character_shoot);
}
void PlayerController::process_movement_input(godot::Input* const input, double delta_time)
{
auto velocity{ input->get_vector(input::action::move_left, input::action::move_right,
input::action::move_up, input::action::move_down) };
this->emit_signal(event::character_move, velocity, delta_time);
}
// void PlayerController::_notification(int notification)
// {
// switch (notification)
// {
// case NOTIFICATION_PREDELETE:
// [[fallthrough]];
// case NOTIFICATION_UNPARENTED:
// {
// console::get()->clear_context();
// console::get()->stop_logging();
// break;
// }
// default:
// break;
// }
// auto console{ console::get() };
// console->print("PlayerController: {}", notification);
// CharacterController::_notification(notification);
// }
PlayerController::InputMode PlayerController::get_input_mode(godot::Input* const input)
{
switch (m_input_mode)
{
default:
[[fallthrough]];
case InputMode::MouseAndKeyboard:
{
bool controller_input_detected{ input->is_action_pressed("controller_any") };
if (controller_input_detected)
m_input_mode = InputMode::Controller;
break;
}
case InputMode::Controller:
{
godot::Vector2 mouse_velocity{ input->get_last_mouse_velocity() };
if (!mouse_velocity.is_zero_approx())
m_input_mode = InputMode::MouseAndKeyboard;
break;
}
}
return m_input_mode;
}
void PlayerController::process_rotation_input(godot::Input* const input, double delta_time)
{
switch (this->get_input_mode(input))
{
default:
[[fallthrough]];
case InputMode::MouseAndKeyboard:
{
godot::Vector2 rotation_dir{ this->get_global_mouse_position() -
this->get_global_position() };
m_rotation_angle = rotation_dir.angle() + godot::Math::deg_to_rad(90.0);
break;
}
case InputMode::Controller:
{
godot::TypedArray<int32_t> controllers{ input->get_connected_joypads() };
if (controllers.is_empty())
log::warning("InputMode = Controller, but no controllers detected");
else
{
godot::Vector2 target_rotation{ input->get_vector("rotate_left", "rotate_right",
"rotate_up", "rotate_down") };
if (!target_rotation.is_zero_approx())
m_rotation_angle = godot::Vector2(0, 0).angle_to_point(target_rotation) +
godot::Math::deg_to_rad(90.0);
}
break;
}
}
this->emit_signal(event::character_rotate, m_rotation_angle, delta_time);
}
}

View File

@ -1,25 +0,0 @@
#pragma once
#include "entity/controller/character_controller.hpp"
namespace rl
{
class PlayerController : public CharacterController
{
GDCLASS(PlayerController, CharacterController);
public:
PlayerController() = default;
~PlayerController() = default;
void process_action_input(godot::Input* const input, double delta_time) override;
void process_movement_input(godot::Input* const input, double delta_time) override;
void process_rotation_input(godot::Input* const input, double delta_time) override;
InputMode get_input_mode(godot::Input* const input);
protected:
static void _bind_methods()
{
}
};
}

View File

@ -1,133 +0,0 @@
#include <godot_cpp/classes/collision_polygon2d.hpp>
#include <godot_cpp/classes/marker2d.hpp>
#include <godot_cpp/classes/rigid_body2d.hpp>
#include <godot_cpp/variant/callable.hpp>
#include <godot_cpp/variant/vector2.hpp>
#include "entity/character/character.hpp"
#include "entity/controller/player_controller.hpp"
#include "entity/level.hpp"
#include "singletons/console.hpp"
#include "util/bind.hpp"
#include "util/conversions.hpp"
#include "util/debug.hpp"
#include "util/engine.hpp"
#include "util/input.hpp"
#include "util/io.hpp"
namespace rl
{
Level::Level()
{
scene::node::set_unique_name(this, name::level::level1);
this->activate(true);
}
void Level::_ready()
{
godot::Node* box{ this->find_child(name::level::physics_box) };
m_physics_box = gdcast<godot::RigidBody2D>(box);
resource::preload::packed_scene<Player> player_scene{ path::scene::Player };
m_player = player_scene.instantiate();
m_player->set_controller(memnew(PlayerController));
this->add_child(m_player);
this->add_child(m_projectile_spawner);
PlayerController* controller{ gdcast<PlayerController>(m_player->get_controller()) };
if (controller != nullptr)
{
signal<event::position_changed>::connect<CharacterController>(controller) <=>
signal_callback(this, on_character_position_changed);
signal<event::spawn_projectile>::connect<Player>(m_player) <=>
signal_callback(this, on_player_spawn_projectile);
}
}
void Level::_process(double delta_time)
{
if (engine::editor_active())
return;
if (this->active() && input::cursor_visible()) [[unlikely]]
input::hide_cursor();
else if (!this->active() && !input::cursor_visible()) [[unlikely]]
input::show_cursor();
this->queue_redraw();
}
void Level::_draw()
{
if (this->active()) [[likely]]
{
godot::Point2 mouse_pos{ this->get_global_mouse_position() };
this->draw_circle(mouse_pos, 5, { "DARK_CYAN" });
}
}
void Level::activate(bool active)
{
m_active = active;
}
bool Level::active() const
{
return m_active;
}
[[signal_slot]]
void Level::on_physics_box_entered(godot::Node* node) const
{
console::get()->print("{} > {}", io::yellow("projectile"), to<std::string>(node->get_name()));
}
[[signal_slot]]
void Level::on_physics_box_exited(godot::Node* node) const
{
console::get()->print("{} < {}", io::red("projectile"), to<std::string>(node->get_name()));
}
[[signal_slot]]
void Level::on_player_spawn_projectile(godot::Node* obj)
{
Projectile* projectile{ m_projectile_spawner->spawn_projectile() };
if (projectile != nullptr)
{
godot::Marker2D* firing_pt{ gdcast<godot::Marker2D>(obj) };
if (firing_pt != nullptr)
{
projectile->set_position(firing_pt->get_global_position());
projectile->set_rotation(firing_pt->get_global_rotation());
signal<event::body_entered>::connect<Projectile>(projectile) <=>
signal_callback(this, on_physics_box_entered);
signal<event::body_exited>::connect<Projectile>(projectile) <=>
signal_callback(this, on_physics_box_exited);
}
this->add_child(projectile);
}
}
[[signal_slot]]
void Level::on_character_position_changed(const godot::Object* const node,
godot::Vector2 location) const
{
runtime_assert(node != nullptr);
auto console{ console::get() };
console->print("{} ({},{})", io::green(to<std::string>(node->get_class()) + " location: "),
io::orange(location.x), io::orange(location.y));
}
void Level::_bind_methods()
{
bind_member_function(Level, on_character_position_changed);
bind_member_function(Level, on_player_spawn_projectile);
bind_member_function(Level, on_physics_box_entered);
bind_member_function(Level, on_physics_box_exited);
}
}

View File

@ -1,56 +0,0 @@
#pragma once
#include <atomic>
#include <vector>
#include <godot_cpp/classes/node2d.hpp>
#include <godot_cpp/classes/sprite2d.hpp>
#include "core/constants.hpp"
#include "entity/character/player.hpp"
#include "entity/controller/player_controller.hpp"
#include "entity/projectile/projectile_spawner.hpp"
#include "util/bind.hpp"
#include "util/scene.hpp"
namespace godot
{
class RigidBody2D;
}
namespace rl
{
class Player;
class Level : public godot::Node2D
{
GDCLASS(Level, godot::Node2D);
public:
Level();
~Level() = default;
virtual void _ready() override;
void _draw() override;
void _process(double delta_time) override;
void activate(bool active = true);
bool active() const;
protected:
static void _bind_methods();
[[signal_slot]] void on_physics_box_entered(godot::Node* node) const;
[[signal_slot]] void on_physics_box_exited(godot::Node* node) const;
[[signal_slot]] void on_player_spawn_projectile(godot::Node* obj);
[[signal_slot]] void on_character_position_changed(const godot::Object* const obj,
godot::Vector2 location) const;
private:
std::atomic<bool> m_active{ false };
godot::Node* m_background{ nullptr };
ProjectileSpawner* m_projectile_spawner{ memnew(rl::ProjectileSpawner) };
Player* m_player{ nullptr };
godot::RigidBody2D* m_physics_box{ nullptr };
};
}

View File

@ -1,95 +0,0 @@
#include <godot_cpp/variant/vector2.hpp>
#include "entity/projectile/projectile.hpp"
#include "util/engine.hpp"
namespace rl
{
void Projectile::_ready()
{
m_start_pos = this->get_global_position();
auto forward{ this->get_transform()[0].normalized() };
this->apply_impulse(forward * m_velocity);
}
void Projectile::_process(double delta_time)
{
if (engine::editor_active())
return;
m_time_to_live -= delta_time;
if (m_time_to_live <= 0)
{
this->queue_free();
return;
}
godot::Vector2 curr_pos{ this->get_global_position() };
double dist_traveled{ m_start_pos.distance_squared_to(curr_pos) };
if (dist_traveled >= m_max_travel_dist)
{
this->queue_free();
return;
}
}
[[property]]
double Projectile::get_movement_speed() const
{
return m_movement_speed;
}
[[property]]
double Projectile::get_time_to_live() const
{
return m_time_to_live;
}
[[property]]
double Projectile::get_acceleration() const
{
return m_acceleration;
}
[[property]]
double Projectile::get_max_travel_dist() const
{
return godot::Math::sqrt(m_max_travel_dist);
}
[[property]]
double Projectile::get_velocity() const
{
return m_velocity;
}
[[property]]
void Projectile::set_movement_speed(double speed)
{
m_movement_speed = speed;
}
[[property]]
void Projectile::set_time_to_live(double ttl)
{
m_time_to_live = ttl;
}
[[property]]
void Projectile::set_acceleration(double acceleration)
{
m_acceleration = acceleration;
}
[[property]]
void Projectile::set_max_travel_dist(double dist)
{
m_max_travel_dist = dist * dist;
}
[[property]]
void Projectile::set_velocity(double velocity)
{
m_velocity = velocity;
}
}

View File

@ -1,65 +0,0 @@
#pragma once
#include <godot_cpp/classes/rigid_body2d.hpp>
#include <godot_cpp/variant/vector2.hpp>
#include "core/attributes.hpp"
#include "singletons/console.hpp"
#include "util/bind.hpp"
#include "util/conversions.hpp"
#include "util/io.hpp"
namespace rl
{
class Projectile : public godot::RigidBody2D
{
GDCLASS(Projectile, godot::RigidBody2D);
public:
Projectile() = default;
virtual ~Projectile() = default;
void _ready() override;
void _process(double delta_time) override;
[[property]] double get_movement_speed() const;
[[property]] double get_time_to_live() const;
[[property]] double get_max_travel_dist() const;
[[property]] double get_acceleration() const;
[[property]] double get_velocity() const;
[[property]] void set_movement_speed(double speed);
[[property]] void set_time_to_live(double ttl);
[[property]] void set_max_travel_dist(double dist);
[[property]] void set_acceleration(double acceleration);
[[property]] void set_velocity(double velocity);
protected:
static void _bind_methods()
{
bind_member_function(Projectile, get_movement_speed);
bind_member_function(Projectile, get_time_to_live);
bind_member_function(Projectile, get_max_travel_dist);
bind_member_function(Projectile, get_acceleration);
bind_member_function(Projectile, get_velocity);
bind_member_function(Projectile, set_movement_speed);
bind_member_function(Projectile, set_time_to_live);
bind_member_function(Projectile, set_max_travel_dist);
bind_member_function(Projectile, set_acceleration);
bind_member_function(Projectile, set_velocity);
}
protected:
godot::Vector2 m_start_pos{ 0.0, 0.0 };
// projectile movement velocity (pixels)
double m_velocity{ 1500 };
// projectile movement speed (pixels/s)
double m_movement_speed{ 1000.0 };
// projectile acceleration (pixels/s/s)
double m_acceleration{ 100.0 };
// max time duration alive (seconds)
double m_time_to_live{ 2.5 };
// max travel distance (pixels) - uint32_t?
double m_max_travel_dist{ 1000.0 * 1000.0 };
};
}

View File

@ -1,45 +0,0 @@
#include "entity/projectile/projectile_spawner.hpp"
#include "util/bind.hpp"
namespace rl
{
[[nodiscard]]
Projectile* ProjectileSpawner::spawn_projectile()
{
auto elapsed{ clock_t::now() - m_prev_spawn_time };
if (elapsed < m_spawn_delay)
return nullptr;
else
{
Projectile* projectile{ m_scene.instantiate() };
m_prev_spawn_time = clock_t::now();
return projectile;
}
}
[[property]]
double ProjectileSpawner::get_fire_rate() const
{
return m_fire_rate;
}
[[property]]
void ProjectileSpawner::set_fire_rate(double fire_rate)
{
m_fire_rate = fire_rate;
m_spawn_delay = ProjectileSpawner::calculate_spawn_delay(m_fire_rate);
}
ProjectileSpawner::millisec_t ProjectileSpawner::calculate_spawn_delay(double fire_rate)
{
// converts fire rate (shots per second) to the time delay between shots in ms.
// the multiplication by 100 is just to offset the rounding errors by shifting
// the decimal place to the right a few places before dividing.
return (1000ms * 100) / static_cast<uint64_t>(fire_rate * 100);
}
void ProjectileSpawner::_bind_methods()
{
bind_property(ProjectileSpawner, fire_rate, double);
}
}

View File

@ -1,48 +0,0 @@
#pragma once
#include <chrono>
#include <godot_cpp/classes/node2d.hpp>
#include <godot_cpp/variant/typed_array.hpp>
#include "core/constants.hpp"
#include "entity/projectile/projectile.hpp"
#include "util/bind.hpp"
#include "util/scene.hpp"
namespace rl
{
using namespace std::chrono_literals;
class ProjectileSpawner : public godot::Node2D
{
GDCLASS(ProjectileSpawner, godot::Node2D);
public:
ProjectileSpawner() = default;
~ProjectileSpawner() = default;
Projectile* spawn_projectile();
protected:
[[property]] double get_fire_rate() const;
[[property]] void set_fire_rate(double fire_rate);
static void _bind_methods();
private:
using clock_t = std::chrono::high_resolution_clock;
using millisec_t = std::chrono::milliseconds;
static millisec_t calculate_spawn_delay(double fire_rate);
private:
// number of prjectiles per second
double m_fire_rate{ 10.0 };
// time delay between shots (ms). multiplication by 100 is just to offset rounding errors.
millisec_t m_spawn_delay{ ProjectileSpawner::calculate_spawn_delay(m_fire_rate) };
// the time point that keeps track of when the last projectile was spawned.
clock_t::time_point m_prev_spawn_time{ clock_t::now() };
// preloaded packed scene that will be instantiated per spawn
resource::preload::packed_scene<Projectile> m_scene{ path::scene::Bullet };
};
}

View File

@ -1,59 +1,22 @@
#include "core/assert.hpp"
#include "main.hpp"
#include "util/conversions.hpp"
#include "util/engine.hpp"
#include "util/input.hpp"
namespace rl
Main::Main()
{
Main::Main()
{
resource::preload::packed_scene<Level> level{ path::scene::Level1 };
resource::preload::packed_scene<MainDialog> dialog{ path::ui::MainDialog };
m_active_level = level.instantiate();
runtime_assert(m_active_level != nullptr);
m_main_dialog = dialog.instantiate();
runtime_assert(m_main_dialog != nullptr);
if (m_main_dialog != nullptr)
{
m_canvas_layer = gdcast<godot::CanvasLayer>(
m_main_dialog->find_child(name::dialog::canvas_layer, true, false));
runtime_assert(m_canvas_layer != nullptr);
if (m_active_level != nullptr && m_canvas_layer != nullptr)
m_canvas_layer->add_child(m_active_level);
if (m_main_dialog != nullptr)
this->add_child(m_main_dialog);
}
}
void Main::_ready()
{
this->apply_default_settings();
}
void Main::_physics_process(double delta)
{
if (engine::editor_active())
return;
m_signal_timer += delta;
if (m_signal_timer > 1.0)
{
this->emit_signal(event::signal_example, delta);
m_signal_timer -= 1.0;
}
}
void Main::apply_default_settings()
{
engine::set_fps(60);
input::use_accumulated_inputs(false);
if (not engine::editor_active())
engine::root_window()->set_size({ 1920, 1080 });
}
time_passed = 0.0;
}
Main::~Main()
{
// Required destructor implementation
}
void Main::_bind_methods()
{
ClassDB::bind_method(D_METHOD("_process", "delta"), &Main::_process);
}
void Main::_process(double delta)
{
time_passed += delta;
set_rotation(time_passed);
}

View File

@ -1,36 +1,20 @@
#pragma once
#include <godot_cpp/classes/sprite2d.hpp>
#include <godot_cpp/classes/canvas_layer.hpp>
#include <godot_cpp/classes/node.hpp>
using namespace godot;
#include "entity/level.hpp"
#include "ui/main_dialog.hpp"
namespace rl
class Main : public Sprite2D
{
class Main : public godot::Node
{
GDCLASS(Main, godot::Node);
GDCLASS(Main, Sprite2D) // Must be in public section
public:
Main();
~Main() = default;
private:
double time_passed;
void _ready() override;
void _physics_process(double delta) override;
protected:
static void _bind_methods();
protected:
void apply_default_settings();
public:
Main();
virtual ~Main() override; // Required for proper destruction
static void _bind_methods()
{
signal_binding<Main, event::signal_example>::add<double>();
}
private:
double m_signal_timer{ 0.0 };
godot::CanvasLayer* m_canvas_layer{ nullptr };
MainDialog* m_main_dialog{ nullptr };
Level* m_active_level{ nullptr };
};
}
void _process(double delta) override;
};

View File

@ -1,117 +0,0 @@
#pragma once
#include <chrono>
#include <string>
#include <type_traits>
#include <fmt/chrono.h>
#include <fmt/compile.h>
#include <fmt/core.h>
#include <spdlog/sinks/callback_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/classes/rich_text_label.hpp>
namespace rl
{
template <typename TContext>
class Console : public godot::Object
{
GDCLASS(Console, godot::Object);
public:
Console()
{
m_static_inst = this;
init_loggers();
}
~Console()
{
m_static_inst = nullptr;
}
static inline rl::Console<TContext>* get()
{
return m_static_inst;
}
void set_context(TContext* context)
{
m_gui_console = context;
}
void clear_context()
{
m_gui_console = nullptr;
}
void stop_logging()
{
m_logger->flush();
m_stop = true;
}
void init_loggers()
requires std::same_as<TContext, godot::RichTextLabel>
{
auto stdout_sink{ std::make_shared<spdlog::sinks::stdout_color_sink_mt>() };
auto stderr_sink{ std::make_shared<spdlog::sinks::stderr_color_sink_mt>() };
auto callbk_sink{ std::make_shared<spdlog::sinks::callback_sink_mt>(
[this](const spdlog::details::log_msg& msg) {
if (!m_stop.load(std::memory_order_relaxed))
{
if (m_gui_console == nullptr)
return;
using duration_t = std::chrono::duration<double>;
const duration_t elapsed{ clock_t::now() - m_start_time };
m_gui_console->append_text(
fmt::format("[color=gray]{:5} [{:>7.2}] [b]=>[/b] {}[/color]\n",
m_line_num.fetch_add(1, std::memory_order_relaxed), elapsed,
msg.payload)
.c_str());
}
}) };
stderr_sink->set_level(spdlog::level::err);
stdout_sink->set_level(spdlog::level::info);
callbk_sink->set_level(spdlog::level::debug);
m_logger = std::unique_ptr<spdlog::logger>(
new spdlog::logger{ "custom_callback_logger",
{ stdout_sink, stderr_sink, callbk_sink } });
using namespace std::chrono_literals;
spdlog::flush_every(0.25s);
}
template <typename... TArgs>
void print(fmt::format_string<TArgs...> format_str, TArgs&&... args)
{
m_logger->info(format_str, std::forward<TArgs>(args)...);
}
protected:
static void _bind_methods()
{
}
private:
std::unique_ptr<spdlog::logger> m_logger{ nullptr };
std::atomic<bool> m_stop{ false };
std::atomic<uint32_t> m_line_num{ 0 };
TContext* m_gui_console{ nullptr };
using clock_t = std::chrono::high_resolution_clock;
const clock_t::time_point m_start_time{ clock_t::now() };
private:
static inline Console<TContext>* m_static_inst{ nullptr };
};
using console = Console<godot::RichTextLabel>;
}

View File

@ -1,57 +0,0 @@
#include <godot_cpp/classes/canvas_layer.hpp>
#include <godot_cpp/classes/control.hpp>
#include "core/assert.hpp"
#include "core/constants.hpp"
#include "singletons/console.hpp"
#include "ui/main_dialog.hpp"
#include "util/conversions.hpp"
#include "util/engine.hpp"
namespace rl::inline ui
{
void MainDialog::_ready()
{
if (engine::editor_active())
return;
Console<godot::RichTextLabel>* game_console{ console::get() };
godot::Node* root{ scene::tree::root_node(this) };
godot::Node* label{ root->find_child(name::dialog::console, true, false) };
godot::Node* level{ this->find_child(name::level::level1, true, false) };
m_level = gdcast<Level>(level);
m_console_label = gdcast<godot::RichTextLabel>(label);
game_console->set_context(m_console_label);
}
void MainDialog::_notification(int notification)
{
switch (notification)
{
case Object::NOTIFICATION_PREDELETE:
[[fallthrough]];
case Node::NOTIFICATION_UNPARENTED:
{
console::get()->clear_context();
console::get()->stop_logging();
break;
}
case Control::NOTIFICATION_MOUSE_ENTER:
{
m_level->activate(true);
break;
}
case Control::NOTIFICATION_MOUSE_EXIT:
{
m_level->activate(false);
break;
}
}
auto console{ console::get() };
console->print("notification: {}", notification);
}
}

View File

@ -1,29 +0,0 @@
#pragma once
#include <godot_cpp/classes/panel.hpp>
#include <godot_cpp/classes/rich_text_label.hpp>
#include "entity/level.hpp"
namespace rl::inline ui
{
class MainDialog : public godot::Panel
{
GDCLASS(MainDialog, godot::Panel);
public:
MainDialog() = default;
~MainDialog() = default;
void _ready() override;
void _notification(int notification);
static void _bind_methods()
{
}
protected:
Level* m_level{ nullptr };
godot::RichTextLabel* m_console_label{ nullptr };
};
}

View File

@ -1,216 +0,0 @@
#pragma once
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/callable.hpp>
#include <godot_cpp/variant/string.hpp>
#include <godot_cpp/variant/string_name.hpp>
#include <godot_cpp/variant/vector2.hpp>
#include "core/assert.hpp"
#include "core/concepts.hpp"
#include "core/function_traits.hpp"
#include "util/conversions.hpp"
#include "util/variant.hpp"
#define bind_member_function(class_name, func_name) method<&class_name::func_name>::bind(#func_name)
#define signal_callback(slot_owner, slot_callback) \
std::forward_as_tuple(godot::Callable(slot_owner, #slot_callback), slot_owner)
#define bind_property(class_name, prop_name, prop_type) \
node_property<class_name, prop_type, &class_name::get_##prop_name, \
&class_name::set_##prop_name>::add(#prop_name)
namespace rl::inline utils
{
template <auto Method>
requires std::is_member_function_pointer_v<decltype(Method)>
struct method : public function_traits<decltype(Method)>
{
using traits_t = function_traits<decltype(Method)>;
static constexpr void bind(std::string_view&& func_name)
{
constexpr std::size_t tup_size = std::tuple_size_v<typename traits_t::arg_types>;
if constexpr (tup_size == 0)
godot::ClassDB::bind_method(godot::D_METHOD(func_name.data()), Method);
else
{
const typename traits_t::arg_types_nocvref func_args{};
std::apply(
[&](auto&&... args) {
godot::ClassDB::bind_method(godot::D_METHOD(func_name.data()), Method,
args...);
},
func_args);
}
}
};
template <GDObjectDerived TNode, VariantCompatible TProperty, auto GetterMethod, auto SetterMethod>
struct node_property
{
using getter_traits = function_traits<decltype(GetterMethod)>;
using setter_traits = function_traits<decltype(SetterMethod)>;
static constexpr void add(std::string&& prop_name)
{
const std::string getter_name{ "get_" + prop_name };
const std::string setter_name{ "set_" + prop_name };
method<GetterMethod>::bind(getter_name);
method<SetterMethod>::bind(setter_name);
const godot::PropertyInfo property_info(
variant_traits<TProperty>::type_info::get_class_info().type,
godot::String(prop_name.c_str()));
godot::ClassDB::add_property(TNode::get_class_static(), property_info,
godot::String(setter_name.c_str()),
godot::String(getter_name.c_str()));
}
};
template <auto Function>
struct callback_func : public function_traits<decltype(Function)>
{
using traits_t = function_traits<decltype(Function)>;
static void bind(std::string_view&& func_name)
{
auto class_name = godot::StringName("Main");
static constexpr std::size_t tup_size = std::tuple_size_v<typename traits_t::arg_types>;
if constexpr (tup_size == 0)
godot::ClassDB::bind_static_method(class_name, godot::D_METHOD(func_name.data()),
Function);
else
{
const typename traits_t::arg_types_nocvref func_args{};
std::vector<godot::String> vec_strs = rl::detail::to_arg_vec(func_args);
std::tuple arg_types_str{ detail::arg_vec_to_tuple<tup_size>(vec_strs) };
std::apply(
[&](auto&&... args) {
godot::ClassDB::bind_static_method(
class_name, godot::D_METHOD(func_name.data()), Function, args...);
},
func_args);
}
}
};
template <GDObjectDerived TObject, auto& SignalName>
class signal_binding
{
public:
using object_t = std::type_identity_t<TObject>;
using signal_t = std::type_identity_t<signal_binding<object_t, SignalName>>;
static inline constexpr std::string_view signal_name{ SignalName };
static inline std::vector<godot::PropertyInfo> signal_params{};
// even though we know what TObject is here (the class type adding the signal binding)
// we can't call TObject::get_class_static() yet since this struct is instantiated before
// the bindings library initializes the gdextension library, which will just lead to a
// crash. this will just be set with the class name at runtime when signal_binding::add() is
// invoked.
static inline std::string class_name{};
public:
template <typename... TArgs>
struct add
{
using arg_types = std::tuple<std::type_identity_t<TArgs>...>;
static constexpr inline size_t arg_count{ std::tuple_size_v<arg_types> };
add()
{
if (class_name.empty())
class_name = rl::to<std::string>(object_t::get_class_static());
else
{
std::string temp_name = rl::to<std::string>(object_t::get_class_static());
runtime_assert(class_name == temp_name);
}
if constexpr (arg_count == 0)
godot::ClassDB::add_signal(class_name.data(),
godot::MethodInfo(signal_name.data()));
else
{
arg_types signal_args{};
std::apply(
[&](auto&&... arg) {
signal_params = {
variant_traits<decltype(arg)>::type_info::get_class_info()...
};
},
signal_args);
godot::ClassDB::add_signal(
class_name.data(),
godot::MethodInfo(signal_name.data(),
std::forward<decltype(signal_params)>(signal_params)));
}
runtime_assert(signal_params.size() == arg_count);
}
};
};
template <auto&& SignalName>
struct signal
{
public:
static inline constexpr std::string_view signal_name{ SignalName };
public:
template <GDObjectDerived TOwnerObj>
struct connect
{
private:
static inline TOwnerObj* m_signal_owner{ nullptr };
public:
explicit connect(TOwnerObj* signal_owner)
{
static_assert(std::is_same_v<decltype(signal_owner), decltype(m_signal_owner)>);
m_signal_owner = signal_owner;
const std::string class_name = rl::to<std::string>(m_signal_owner->get_class());
runtime_assert(m_signal_owner->has_signal(signal_name.data()));
}
template <typename TNode>
auto operator<=>(std::tuple<godot::Callable&&, TNode&&> callback)
{
auto&& cb{ std::forward<godot::Callable>(std::get<0>(callback)) };
auto&& callback_owner{ std::get<1>(callback) };
if (m_signal_owner == nullptr)
{
error_msg("Attempting to connect a signal to a null object");
return godot::Error::ERR_DOES_NOT_EXIST;
}
if (!callback_owner->has_method(cb.get_method()))
{
error_msg("Signal connection method missing bindings");
return godot::Error::ERR_METHOD_NOT_FOUND;
}
// TODO: compare and validate the arg count, raw types, and variant conversions
// between the matching signal binding record and the callback being connected.
return m_signal_owner->connect(signal_name.data(), cb);
}
};
};
}

View File

@ -1,71 +0,0 @@
#pragma once
#include <concepts>
#include <string>
#include <string_view>
#include <type_traits>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/variant/char_string.hpp>
#include <godot_cpp/variant/string.hpp>
#include <godot_cpp/variant/string_name.hpp>
#include "core/assert.hpp"
namespace rl::inline utils
{
/** Converts std string types to godot::String at compile time */
template <typename TStr>
struct gd_str_conv
{
explicit constexpr gd_str_conv(TStr&& s)
: m_str{ std::forward<TStr>(s) }
{
}
explicit operator godot::String()
requires std::same_as<TStr, std::string>
{
return godot::String(m_str.c_str());
}
explicit operator godot::String()
requires std::same_as<TStr, std::string_view>
{
return godot::String(m_str.data());
}
TStr m_str{};
};
template <typename TOut, typename TIn>
requires std::derived_from<std::remove_cvref_t<TIn>, godot::Object>
constexpr inline TOut* gdcast(TIn* obj)
{
runtime_assert(obj != nullptr);
auto ret{ godot::Object::cast_to<TOut>(obj) };
runtime_assert(ret != nullptr);
return ret;
}
template <typename TOut>
inline auto to(const auto& in) -> TOut
{
return TOut(in);
}
template <>
inline auto to(const godot::String& in) -> std::string
{
static_assert(std::is_same_v<godot::String, std::remove_cvref_t<decltype(in)>>);
return std::string(in.ascii().ptr());
}
template <>
inline auto to(const godot::StringName& in) -> std::string
{
static_assert(std::is_same_v<godot::StringName, std::remove_cvref_t<decltype(in)>>);
godot::String tmp(in);
return std::string(tmp.utf8().ptr());
}
}

View File

@ -1,54 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
#include <tuple>
#include <type_traits>
#include <utility>
#include <godot_cpp/core/error_macros.hpp>
namespace rl::inline utils
{
namespace diag
{
enum Option : uint_fast8_t {
RootProcess,
RootPhysics,
RootViewport,
RootInputs,
LevelProcess,
LevelLoad,
LevelUnload,
MainMenuInit,
All
};
using optval_t = std::pair<Option, bool>;
static constexpr inline auto DebugSettings{
std::to_array<std::type_identity_t<optval_t>>({
{ Option::All, false },
{ Option::RootProcess, true },
{ Option::RootPhysics, true },
{ Option::RootViewport, true },
{ Option::RootInputs, true },
{ Option::LevelProcess, true },
{ Option::LevelLoad, true },
{ Option::LevelUnload, true },
{ Option::MainMenuInit, true },
}),
};
static constexpr inline bool is_enabled(const Option& diag_type)
{
for (auto&& [opt, val] : DebugSettings)
{
if (opt == Option::All && val)
return true;
if (opt == diag_type)
return val;
}
return false;
}
}
}

View File

@ -1,57 +0,0 @@
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/main_loop.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/window.hpp>
#include "util/engine.hpp"
namespace rl::inline utils
{
godot::Engine* engine::get()
{
godot::Engine* engine{ godot::Engine::get_singleton() };
return engine;
}
godot::MainLoop* engine::main_loop()
{
auto engine{ engine::get() };
return engine->get_main_loop();
}
godot::SceneTree* engine::scene_tree()
{
auto loop{ engine::main_loop() };
return ::godot::Object::cast_to<godot::SceneTree>(loop);
}
godot::Window* engine::root_window()
{
auto tree{ engine::scene_tree() };
return tree->get_root();
}
godot::Node* engine::root_node()
{
return godot::Object::cast_to<godot::Node>(engine::root_window());
}
void engine::set_fps(const uint32_t fps)
{
auto engine{ engine::get() };
return engine->set_max_fps(fps);
}
int32_t engine::max_fps()
{
auto engine{ engine::get() };
return engine->get_max_fps();
}
bool engine::editor_active()
{
auto engine{ engine::get() };
return engine->is_editor_hint();
}
}

View File

@ -1,27 +0,0 @@
#pragma once
#include <cstdint>
namespace godot
{
class Engine;
class MainLoop;
class Node;
class SceneTree;
class Window;
}
namespace rl::inline utils
{
struct engine
{
static godot::Engine* get();
static godot::MainLoop* main_loop();
static godot::SceneTree* scene_tree();
static godot::Window* root_window();
static godot::Node* root_node();
static void set_fps(const uint32_t fps);
static int32_t max_fps();
static bool editor_active();
};
}

View File

@ -1,51 +0,0 @@
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/input_map.hpp>
#include <godot_cpp/core/method_ptrcall.hpp>
#include <godot_cpp/core/type_info.hpp>
#include "util/input.hpp"
namespace rl::inline utils
{
godot::InputMap* input::map::get()
{
godot::InputMap* mappings{ godot::InputMap::get_singleton() };
return mappings;
}
godot::Input* input::get()
{
godot::Input* input{ godot::Input::get_singleton() };
return input;
}
void input::hide_cursor()
{
godot::Input* const input{ input::get() };
input->set_mouse_mode(godot::Input::MOUSE_MODE_HIDDEN);
}
void input::show_cursor()
{
godot::Input* const input{ input::get() };
input->set_mouse_mode(godot::Input::MOUSE_MODE_VISIBLE);
}
bool input::cursor_visible()
{
godot::Input* const input{ input::get() };
return input->get_mouse_mode() == godot::Input::MOUSE_MODE_VISIBLE;
}
void input::load_project_inputs()
{
auto input_map{ godot::InputMap::get_singleton() };
return input_map->load_from_project_settings();
}
void input::use_accumulated_inputs(bool enable)
{
auto input{ godot::Input::get_singleton() };
return input->set_use_accumulated_input(enable);
}
}

View File

@ -1,35 +0,0 @@
#pragma once
namespace godot
{
class InputMap;
class Input;
}
namespace rl::inline utils
{
namespace input
{
namespace action
{
constexpr inline auto move_left{ "move_left" };
constexpr inline auto move_right{ "move_right" };
constexpr inline auto move_up{ "move_up" };
constexpr inline auto move_down{ "move_down" };
constexpr inline auto shoot{ "shoot" };
}
struct map
{
static godot::InputMap* get();
};
godot::Input* get();
void hide_cursor();
void show_cursor();
bool cursor_visible();
void load_project_inputs();
void use_accumulated_inputs(bool enable);
};
}

View File

@ -1,134 +0,0 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <fmt/compile.h>
#include <fmt/core.h>
#include <fmt/format-inl.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
#include <godot_cpp/core/error_macros.hpp>
#include "core/concepts.hpp"
namespace rl::inline utils
{
namespace io
{
static inline auto white(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=white]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto gray(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=gray]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto black(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=black]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto red(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=red]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto orange(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=orange]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto yellow(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=yellow]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto green(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=green]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto blue(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=blue]{}[/color]"),
std::forward<decltype(val)>(val));
}
static inline auto purple(auto&& val)
{
return fmt::format(FMT_COMPILE("[color=purple]{}[/color]"),
std::forward<decltype(val)>(val));
}
}
struct log
{
enum DetailLevel : uint_fast8_t {
None,
Error,
Warning,
Info,
Debug,
Trace
};
static constexpr const DetailLevel DETAIL_LEVEL{ log::DetailLevel::Debug };
static constexpr inline bool level_active(const log::DetailLevel level)
{
return level <= log::DETAIL_LEVEL;
}
template <typename TString>
static inline void error(TString msg)
{
if (log::level_active(log::DetailLevel::Error))
ERR_PRINT_ED(std::forward<TString>(msg));
}
template <typename TString>
static inline void warning(TString msg)
{
if (log::level_active(log::DetailLevel::Warning))
WARN_PRINT_ED(std::forward<TString>(msg));
}
template <typename TString>
static inline void info(TString msg)
{
if (log::level_active(log::DetailLevel::Info))
WARN_PRINT_ED(std::forward<TString>(msg));
}
template <typename TString>
static inline void trace(TString msg)
{
if (log::level_active(log::DetailLevel::Trace))
WARN_PRINT_ED(std::forward<TString>(msg));
}
template <typename... Args>
static inline void print(spdlog::format_string_t<Args...> fmt, Args&&... args)
{
std::string msg{ fmt::format(fmt, std::forward<Args>(args)...) };
ERR_PRINT_ED(msg.data());
}
};
}

View File

@ -1,14 +0,0 @@
#pragma once
#include <godot_cpp/classes/os.hpp>
namespace rl::inline utils
{
struct os
{
static inline godot::OS* get()
{
return godot::OS::get_singleton();
}
};
}

View File

@ -1,177 +0,0 @@
#pragma once
#include <concepts>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/resource_saver.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/window.hpp>
#include "core/assert.hpp"
#include "util/conversions.hpp"
namespace rl::inline utils
{
namespace scene
{
namespace node
{
template <typename TNode>
requires std::derived_from<TNode, godot::Node>
static inline void set_unique_name(TNode* node, const char* name)
{
runtime_assert(node != nullptr);
node->set_name(name);
node->set_unique_name_in_owner(true);
}
/** Sets the owner of a node and all it's children. */
template <typename TNodeA, typename TNodeB>
requires std::derived_from<TNodeB, godot::Node> &&
std::derived_from<TNodeA, godot::Node>
static inline void set_owner(TNodeA* node, TNodeB* owner)
{
runtime_assert(node != nullptr && owner != nullptr);
const int node_child_count = node->get_child_count();
for (int i = 0; i < node_child_count; ++i)
{
auto child = node->get_child(i);
child->set_owner(owner);
set_owner(child, owner);
}
}
}
namespace tree
{
template <typename TNode>
requires std::derived_from<TNode, godot::Node>
static inline godot::SceneTree* get(TNode* node)
{
godot::SceneTree* scene_tree{ node->get_tree() };
return scene_tree;
}
template <typename TNode>
requires std::derived_from<TNode, godot::Node>
static inline godot::Node* edited_root(TNode* node)
{
godot::Node* edited_root{ node->get_tree()->get_edited_scene_root() };
return edited_root;
}
template <typename TNode>
requires std::derived_from<TNode, godot::Node>
static inline godot::Node* root_node(TNode* node)
{
godot::SceneTree* scene_tree{ tree::get(node) };
godot::Window* root_window{ scene_tree->get_root() };
godot::Node* root_node{ gdcast<godot::Node>(root_window) };
return root_node;
}
}
namespace packer
{
/**
@return PackedScene from godot::Node parameter.
*/
template <typename TNode>
requires std::derived_from<TNode, godot::Node>
static inline godot::PackedScene* pack(TNode* node)
{
node::set_owner<TNode, TNode>(node, node);
godot::PackedScene* package = memnew(godot::PackedScene);
package->pack(node);
return package;
}
}
}
namespace resource
{
namespace loader
{
static inline godot::ResourceLoader* get()
{
return godot::ResourceLoader::get_singleton();
}
}
namespace saver
{
static inline godot::ResourceSaver* get()
{
return godot::ResourceSaver::get_singleton();
}
}
namespace preload
{
template <typename TObj, typename TScene = godot::PackedScene>
requires std::derived_from<TScene, godot::Resource> &&
std::convertible_to<TObj, godot::Object>
class packed_scene
{
public:
using scene_t = TScene;
using object_t = TObj;
/** Load and pack from path. */
packed_scene(const godot::String& load_resource_path,
const godot::String& load_type_hint = godot::String(),
godot::ResourceLoader::CacheMode load_cache_mode =
godot::ResourceLoader::CacheMode::CACHE_MODE_REUSE)
{
godot::ResourceLoader* resource_loader{ loader::get() };
bool resource_exists{ resource_loader->exists(load_resource_path) };
runtime_assert(resource_exists);
if (resource_exists)
{
m_packed_resource = resource_loader->load(load_resource_path,
load_type_hint, load_cache_mode);
initialized = m_packed_resource.is_valid();
}
}
/* Pack from existing instance. */
packed_scene(godot::Node* node)
{
m_packed_resource = scene::packer::pack(node);
initialized = m_packed_resource.is_valid();
}
[[nodiscard]] auto instantiate() -> object_t*
{
assertion(initialized,
"Resource instantiation invoked from uninitialized scene loader.");
if (!initialized) [[unlikely]]
return nullptr;
object_t* obj{ gdcast<object_t>(m_packed_resource->instantiate()) };
runtime_assert(obj != nullptr);
return obj;
}
/** Save this resource to specified path. */
void save(godot::String& resource_save_path)
{
if (initialized)
{
auto error = saver::get()->save(m_packed_resource, resource_save_path);
assertion(error != godot::Error::OK, "Packed resource save failed.");
}
}
private:
godot::Ref<scene_t> m_packed_resource{};
bool initialized{ false };
};
}
}
}

View File

@ -1,20 +0,0 @@
#pragma once
#include <tuple>
#include <type_traits>
#include <godot_cpp/classes/ref.hpp>
#include "core/concepts.hpp"
namespace rl::inline utils
{
template <VariantCompatible T>
struct variant_traits
{
using raw_type = T;
using type_info = godot::GetTypeInfo<std::remove_cvref_t<T>>;
static constexpr inline godot::Variant::Type variant_type =
static_cast<godot::Variant::Type>(type_info::VARIANT_TYPE);
};
}