Compare commits

...

4 Commits

7 changed files with 138 additions and 77 deletions

View File

@ -3,12 +3,10 @@
#include <godot_cpp/classes/packed_scene.hpp> #include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/scene_state.hpp> #include <godot_cpp/classes/scene_state.hpp>
#include "utils/godot_macros.h" #include "utils/godot_macros.h"
#include "game_state.hpp"
namespace utils { namespace utils {
void GameMode::_bind_methods() { void GameMode::_bind_methods() {
#define CLASSNAME GameMode #define CLASSNAME GameMode
GDPROPERTY_HINTED(game_state, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "GameState");
GDPROPERTY_HINTED(player_scene, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); GDPROPERTY_HINTED(player_scene, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
} }
@ -22,16 +20,4 @@ void GameMode::set_player_scene(gd::Ref<gd::PackedScene> scene) {
gd::Ref<gd::PackedScene> GameMode::get_player_scene() const { gd::Ref<gd::PackedScene> GameMode::get_player_scene() const {
return this->player_scene; return this->player_scene;
} }
void GameMode::set_game_state(gd::Ref<GameState> state) {
if(state.is_null() || !state.is_valid()) {
this->game_state.unref();
return;
}
this->game_state = state;
}
gd::Ref<GameState> GameMode::get_game_state() {
return this->game_state;
}
} }

View File

@ -3,25 +3,26 @@
#include <godot_cpp/classes/packed_scene.hpp> #include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/resource.hpp> #include <godot_cpp/classes/resource.hpp>
#include "game_state.hpp"
namespace gd = godot; namespace gd = godot;
namespace utils { namespace utils {
/*! Stores session-relevant data.
*
* Contains any data that is only needed for the duration of the current session/match. Use GameState instead if you want data to be saved between sessions.
* Will be destroyed when a level is loaded that does not match the same game mode class.
*/
class GameMode : public gd::Resource { class GameMode : public gd::Resource {
GDCLASS(GameMode, gd::Resource); GDCLASS(GameMode, gd::Resource);
static void _bind_methods(); static void _bind_methods();
public: public:
virtual void _begin(); virtual void _begin(); //!< Called when the match begins.
virtual void _end(); virtual void _end(); //!< Called when the match is ending.
void set_player_scene(gd::Ref<gd::PackedScene> scene); void set_player_scene(gd::Ref<gd::PackedScene> scene);
gd::Ref<gd::PackedScene> get_player_scene() const; gd::Ref<gd::PackedScene> get_player_scene() const;
void set_game_state(gd::Ref<GameState> state);
gd::Ref<GameState> get_game_state();
private: private:
gd::Ref<gd::PackedScene> player_scene{}; gd::Ref<gd::PackedScene> player_scene{}; //!< The scene to instantiate when spawning a player.
gd::Ref<GameState> game_state{};
}; };
} }

View File

@ -6,6 +6,7 @@
#include "player_input.hpp" #include "player_input.hpp"
#include "player.hpp" #include "player.hpp"
#include "spawn_point.hpp" #include "spawn_point.hpp"
#include "utils/game_state.hpp"
#include <cstdint> #include <cstdint>
#include <godot_cpp/classes/global_constants.hpp> #include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/classes/input.hpp> #include <godot_cpp/classes/input.hpp>
@ -21,6 +22,7 @@ void GameRoot3D::_bind_methods() {
#define CLASSNAME GameRoot3D #define CLASSNAME GameRoot3D
GDFUNCTION(reset_game_mode); GDFUNCTION(reset_game_mode);
GDPROPERTY_HINTED(first_boot_level, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); GDPROPERTY_HINTED(first_boot_level, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
GDPROPERTY_HINTED(game_state_prototype, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "GameState");
GDSIGNAL("player_connected", gd::PropertyInfo(gd::Variant::OBJECT, "player_input", gd::PROPERTY_HINT_NODE_TYPE, "PlayerInput")); GDSIGNAL("player_connected", gd::PropertyInfo(gd::Variant::OBJECT, "player_input", gd::PROPERTY_HINT_NODE_TYPE, "PlayerInput"));
GDSIGNAL("player_disconnected", gd::PropertyInfo(gd::Variant::OBJECT, "player_input", gd::PROPERTY_HINT_NODE_TYPE, "PlayerInput")); GDSIGNAL("player_disconnected", gd::PropertyInfo(gd::Variant::OBJECT, "player_input", gd::PROPERTY_HINT_NODE_TYPE, "PlayerInput"));
GDSIGNAL("player_spawned", gd::PropertyInfo(gd::Variant::OBJECT, "player_info", gd::PROPERTY_HINT_NODE_TYPE, "Node")); GDSIGNAL("player_spawned", gd::PropertyInfo(gd::Variant::OBJECT, "player_info", gd::PROPERTY_HINT_NODE_TYPE, "Node"));
@ -182,8 +184,6 @@ void GameRoot3D::set_game_mode(gd::Ref<GameMode> prototype) {
} }
// shallow clone the game mode prototype .. // shallow clone the game mode prototype ..
this->game_mode = prototype->duplicate(false); this->game_mode = prototype->duplicate(false);
// .. except for the game state, which should be cloned as well
this->game_mode->set_game_state(prototype->get_game_state()->duplicate(false));
this->game_mode->_begin(); this->game_mode->_begin();
if(this->game_mode->get_player_scene().is_valid()) { if(this->game_mode->get_player_scene().is_valid()) {
uint32_t new_player_id = this->find_empty_player_slot(); uint32_t new_player_id = this->find_empty_player_slot();
@ -196,12 +196,28 @@ void GameRoot3D::set_game_mode(gd::Ref<GameMode> prototype) {
} }
} }
gd::Ref<GameMode> GameRoot3D::get_game_mode() const { GameMode *GameRoot3D::get_game_mode() const {
return this->game_mode; return this->game_mode.ptr();
} }
gd::Ref<GameState> GameRoot3D::get_game_state() const { GameState *GameRoot3D::get_game_state() const {
return this->game_mode->get_game_state(); return this->game_state.ptr();
}
gd::HashMap<gd::StringName, Level3D *> &GameRoot3D::get_levels() {
return this->levels;
}
IPlayer *GameRoot3D::get_player(uint32_t id) {
return this->players[id].second;
}
gd::Vector<IPlayer*> GameRoot3D::get_players() {
gd::Vector<IPlayer*> players{};
for(gd::KeyValue<uint32_t, gd::Pair<PlayerInput*, IPlayer*>> pair : this->players) {
players.push_back(pair.value.second);
}
return players;
} }
void GameRoot3D::set_first_boot_level(gd::Ref<gd::PackedScene> level) { void GameRoot3D::set_first_boot_level(gd::Ref<gd::PackedScene> level) {
@ -222,20 +238,12 @@ gd::Ref<gd::PackedScene> GameRoot3D::get_first_boot_level() const {
return this->first_boot_level; return this->first_boot_level;
} }
gd::HashMap<gd::StringName, Level3D *> &GameRoot3D::get_levels() { void GameRoot3D::set_game_state_prototype(gd::Ref<GameState> game_state) {
return this->levels; this->game_state_prototype = game_state;
} }
IPlayer *GameRoot3D::get_player(uint32_t id) { gd::Ref<GameState> GameRoot3D::get_game_state_prototype() const {
return this->players[id].second; return this->game_state_prototype;
}
gd::Vector<IPlayer*> GameRoot3D::get_players() {
gd::Vector<IPlayer*> players{};
for(gd::KeyValue<uint32_t, gd::Pair<PlayerInput*, IPlayer*>> pair : this->players) {
players.push_back(pair.value.second);
}
return players;
} }
void GameRoot3D::grab_singleton() { void GameRoot3D::grab_singleton() {

View File

@ -2,6 +2,7 @@
#define GAME_ROOT_HPP #define GAME_ROOT_HPP
#include "game_mode.hpp" #include "game_mode.hpp"
#include "game_state.hpp"
#include "level.hpp" #include "level.hpp"
#include <godot_cpp/classes/node.hpp> #include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/packed_scene.hpp> #include <godot_cpp/classes/packed_scene.hpp>
@ -19,82 +20,129 @@ class PlayerInput;
class IPlayer; class IPlayer;
class SpawnPoint3D; class SpawnPoint3D;
/*! The root of a game.
*
* A game root node that manages levels and input devices.
* Can be loaded at any point in a game's life, but suggested is setting this as the root of the boot scene.
*/
class GameRoot3D : public gd::Node { class GameRoot3D : public gd::Node {
GDCLASS(GameRoot3D, gd::Node); GDCLASS(GameRoot3D, gd::Node);
static void _bind_methods(); static void _bind_methods();
public: public:
// get the current active singleton instance of GameRoot //! get the current active singleton instance of GameRoot
static GameRoot3D *get_singleton(); static GameRoot3D *get_singleton();
// returns true if there is currently a singleton active for GameRoot //! returns true if there is currently a singleton active for GameRoot
static bool has_singleton(); static bool has_singleton();
virtual void _enter_tree() override; virtual void _enter_tree() override;
virtual void _ready() override; virtual void _ready() override;
virtual void _exit_tree() override; virtual void _exit_tree() override;
/*! Instantiate a new PlayerInput.
*
* Does not automatically spawn a new player, but does notify game mode.
*/
void player_input_connected(); void player_input_connected();
// force-disconnect a player /*! Force-disconnect a player
// calls queue_free on the IPlayer instance *
* Calls queue_free on the IPlayer instance
*/
void remove_player(uint32_t player_id); void remove_player(uint32_t player_id);
// calls remove_player for every used player input slot // calls remove_player for every used player input slot
void remove_all_players(); void remove_all_players();
// initialize and register a player instance /*! Initialize and register a player instance.
// the player will be added to the tree and AFTER setup_player_input will be called *
// this way the player can initialize before setting up input * The player will be added to the tree and AFTER setup_player_input will be called.
* This way the player can initialize before setting up input
*/
bool initialize_player(IPlayer *player, uint32_t id); bool initialize_player(IPlayer *player, uint32_t id);
// shorthand for set_game_mode(Ref<GameMode>()) /*! Un-set game mode.
// unsets the gamemode * Shorthand for set_game_mode(Ref<GameMode>())
*/
void reset_game_mode(); void reset_game_mode();
// shorthand for load_level(level, Transform3D()) //! shorthand for load_level(level, Transform3D())
Level3D *load_level(gd::Ref<gd::PackedScene> level); Level3D *load_level(gd::Ref<gd::PackedScene> level);
// load a level, only works if 'level' is a valid scene where the root Node can cast to 'Level3D' /*! Load a level, only works if 'level' is a valid scene where the root Node can cast to 'Level3D'.
// sets the level's root node's global transform *
* \param at Sets the root node's global transform.
*/
Level3D *load_level_at(gd::Ref<gd::PackedScene> level, gd::Transform3D at); Level3D *load_level_at(gd::Ref<gd::PackedScene> level, gd::Transform3D at);
//! Unload all currently loaded levels.
void unload_all_levels(); void unload_all_levels();
/*! Replace all currently loaded levels with a new level.
*
* Shorthand for
* ```
* unload_all_levels();
* load_level(level);
* ```
*/
void replace_levels(gd::Ref<gd::PackedScene> level); void replace_levels(gd::Ref<gd::PackedScene> level);
//! Register a spawnpoint for use when spawning players
// register a spawnpoint for use when spawning players
void register_spawn_point(SpawnPoint3D *spawn_point); void register_spawn_point(SpawnPoint3D *spawn_point);
// remove a spawnpoint so it can't be used to spawn players // remove a spawnpoint so it can't be used to spawn players
void unregister_spawn_point(SpawnPoint3D *spawn_point); void unregister_spawn_point(SpawnPoint3D *spawn_point);
void place_player_at_spawnpoint(IPlayer *player); void place_player_at_spawnpoint(IPlayer *player);
void player_despawned(uint32_t id); void player_despawned(uint32_t id);
// ----- getter / setters ----- /*! Override the current gamemode.
// override the current gamemode *
// force-respawns all players * Replaces game mode requires destroying and respawning all players
*/
void set_game_mode(gd::Ref<GameMode> prototype); void set_game_mode(gd::Ref<GameMode> prototype);
gd::Ref<GameMode> get_game_mode() const; //! get the current active game mode.
gd::Ref<GameState> get_game_state() const; GameMode *get_game_mode() const;
//! Get the current active game state.
GameState *get_game_state() const;
/*! Returns all currently active levels.
*
* Levels are identified by their packed scene path.
*/
gd::HashMap<gd::StringName, Level3D *> &get_levels();
//! Get the player instance associated with id.
IPlayer *get_player(uint32_t id);
//! Get all players in a list.
gd::Vector<IPlayer*> get_players();
void set_first_boot_level(gd::Ref<gd::PackedScene> level); void set_first_boot_level(gd::Ref<gd::PackedScene> level);
gd::Ref<gd::PackedScene> get_first_boot_level() const; gd::Ref<gd::PackedScene> get_first_boot_level() const;
gd::HashMap<gd::StringName, Level3D *> &get_levels(); void set_game_state_prototype(gd::Ref<GameState> game_state);
IPlayer *get_player(uint32_t id); gd::Ref<GameState> get_game_state_prototype() const;
gd::Vector<IPlayer*> get_players();
protected: protected:
// attempt to make 'this' the current singleton instance //! Attempt to make 'this' the current singleton instance.
void grab_singleton(); void grab_singleton();
// attempt to stop being the active singleton instance /*! Attempt to stop being the active singleton instance.
// only works if the current singleton is 'this' *
* Only works if the current singleton is 'this'.
*/
void release_singleton(); void release_singleton();
//! Find a Player Input device not yet associated with a player.
uint32_t find_empty_player_slot() const; uint32_t find_empty_player_slot() const;
//! Spawn a player to be associated with id.
IPlayer *spawn_player(uint32_t id); IPlayer *spawn_player(uint32_t id);
//! Callback for a level exiting the tree.
void level_unloaded(gd::StringName scene_path); void level_unloaded(gd::StringName scene_path);
//! Check if a scene is a valid level.
static bool is_valid_level(gd::Ref<gd::PackedScene> &level); static bool is_valid_level(gd::Ref<gd::PackedScene> &level);
protected: private:
static GameRoot3D *singleton_instance; static GameRoot3D *singleton_instance;
uint32_t next_player_id{1}; // 0 is the "invalid" player id uint32_t next_player_id{1}; //!< Next available player ID. Default is 1 because 0 is the "invalid" player id.
gd::HashMap<uint32_t, gd::Pair<PlayerInput*, IPlayer*>> players{}; gd::HashMap<uint32_t, gd::Pair<PlayerInput*, IPlayer*>> players{}; //!< all players by id by input device.
gd::Ref<GameMode> game_mode{};
private:
gd::RandomNumberGenerator rng{};
gd::HashMap<gd::StringName, Level3D*> levels{};
gd::Vector<SpawnPoint3D*> spawn_points{};
gd::Ref<gd::PackedScene> first_boot_level{}; gd::RandomNumberGenerator rng{}; //!< Global random number generator.
gd::HashMap<gd::StringName, Level3D*> levels{}; //!< all currently active levels identified by their resource paths.
gd::Vector<SpawnPoint3D*> spawn_points{}; //!< all currently available spawn points.
gd::Ref<GameMode> game_mode{}; //!< current active gamemode.
/*! Active game state.
*
* Will be assigned loaded save data, or game_state_prototype if no save data is found.
*/
gd::Ref<GameState> game_state{};
gd::Ref<gd::PackedScene> first_boot_level{}; //!< The level to boot into on startup.
gd::Ref<GameState> game_state_prototype{}; //!< The default game state data used for game_state if no save data is available.
}; };
} }

View File

@ -7,6 +7,10 @@
namespace gd = godot; namespace gd = godot;
namespace utils { namespace utils {
/*! 3D level root to be used with GameRoot3D.
*
* The configured game mode will become the active GameMode in GameRoot3D if one does not exist yet.
*/
class Level3D : public gd::Node3D { class Level3D : public gd::Node3D {
GDCLASS(Level3D, gd::Node3D); GDCLASS(Level3D, gd::Node3D);
static void _bind_methods(); static void _bind_methods();
@ -14,7 +18,7 @@ public:
void set_game_mode_prototype(gd::Ref<GameMode> prototype); void set_game_mode_prototype(gd::Ref<GameMode> prototype);
gd::Ref<GameMode> get_game_mode_prototype() const; gd::Ref<GameMode> get_game_mode_prototype() const;
private: private:
gd::Ref<GameMode> game_mode_prototype{}; gd::Ref<GameMode> game_mode_prototype{}; //!< The starting state of the game mode to instantiate if this is the "leading" level.
}; };
} }

View File

@ -5,21 +5,31 @@
#include <optional> #include <optional>
#include <godot_cpp/variant/transform3d.hpp> #include <godot_cpp/variant/transform3d.hpp>
namespace godot { class Node; }
namespace gd = godot; namespace gd = godot;
namespace godot { class Node; }
namespace utils { namespace utils {
class PlayerInput; class PlayerInput;
/*! Interface required for player nodes.
*
* Use multiple inheritance and implement IPlayer to make a regular node usable as a player with GameRoot3D.
*/
class IPlayer { class IPlayer {
friend class GameRoot3D; friend class GameRoot3D;
public: public:
/*! Called by GameRoot3D when this player is instantiated or assigned a new PlayerInput.
*
* Use PlayerInput::listen_to to register input callbacks. There's no need to keep the input pointer around. As the instance is managed by the GameRoot3D.
*/
virtual void setup_player_input(PlayerInput *input) = 0; virtual void setup_player_input(PlayerInput *input) = 0;
//! Convert IPlayer instance to node.
virtual gd::Node *to_node() = 0; virtual gd::Node *to_node() = 0;
//! Spawn the player at a given transform, usually the global transform of a SpawnPoint3D.
virtual void spawn_at_position(gd::Transform3D const &at) = 0; virtual void spawn_at_position(gd::Transform3D const &at) = 0;
uint32_t get_player_id(); uint32_t get_player_id(); //!< Returns the player id assigned to this instance.
private: private:
std::optional<uint32_t> player_id{std::nullopt}; std::optional<uint32_t> player_id{std::nullopt};

View File

@ -6,6 +6,10 @@
namespace gd = godot; namespace gd = godot;
namespace utils { namespace utils {
/*! A location in the game world that the player can spawn at.
*
* Registers and de-registers itself with the GameRoot3D to enable/disable this spawnpoint.
*/
class SpawnPoint3D : public gd::Node3D { class SpawnPoint3D : public gd::Node3D {
GDCLASS(SpawnPoint3D, gd::Node3D); GDCLASS(SpawnPoint3D, gd::Node3D);
static void _bind_methods(); static void _bind_methods();