Compare commits
10 Commits
Author | SHA1 | Date |
---|---|---|
Sara | c3f104b630 | |
Sara | 885d843cda | |
Sara | b07f0e299e | |
Sara | c568673f18 | |
Sara | cb6fd87e72 | |
Sara | e2d56ed6d4 | |
Sara | 528db9415c | |
Sara | b1d5e5d263 | |
Sara | 76fa8d3e3f | |
Sara | 8dc8144915 |
|
@ -0,0 +1,20 @@
|
|||
#include "game_mode.hpp"
|
||||
#include <godot_cpp/classes/global_constants.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
#include <godot_cpp/classes/scene_state.hpp>
|
||||
#include "godot_macros.hpp"
|
||||
|
||||
namespace utils {
|
||||
void GameMode::_bind_methods() {
|
||||
#define CLASSNAME GameMode
|
||||
GDPROPERTY_HINTED(player_scene, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
||||
}
|
||||
|
||||
void GameMode::set_player_scene(gd::Ref<gd::PackedScene> scene) {
|
||||
this->player_scene = scene;
|
||||
}
|
||||
|
||||
gd::Ref<gd::PackedScene> GameMode::get_player_scene() const {
|
||||
return this->player_scene;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef UTILS_GAME_MODE_HPP
|
||||
#define UTILS_GAME_MODE_HPP
|
||||
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace utils {
|
||||
/*! Stores session-relevant data.
|
||||
*
|
||||
* Inheriting classes are intended to keep only data that is relevant 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.
|
||||
* The current active game mode can be gotten from the GameRoot3D singleton instance.
|
||||
*/
|
||||
class GameMode : public gd::Node {
|
||||
GDCLASS(GameMode, gd::Node);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
void set_player_scene(gd::Ref<gd::PackedScene> scene);
|
||||
gd::Ref<gd::PackedScene> get_player_scene() const;
|
||||
private:
|
||||
gd::Ref<gd::PackedScene> player_scene{}; //!< The scene to instantiate when spawning a player.
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !UTILS_GAME_MODE_HPP
|
|
@ -0,0 +1,328 @@
|
|||
#include "game_root.hpp"
|
||||
#include "game_mode.hpp"
|
||||
#include "godot_macros.hpp"
|
||||
#include "level.hpp"
|
||||
#include "player.hpp"
|
||||
#include "player_input.hpp"
|
||||
#include "player.hpp"
|
||||
#include "spawn_point.hpp"
|
||||
#include "game_state.hpp"
|
||||
#include <cstdint>
|
||||
#include <godot_cpp/classes/global_constants.hpp>
|
||||
#include <godot_cpp/classes/input.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
#include <godot_cpp/classes/scene_state.hpp>
|
||||
#include <godot_cpp/classes/resource_loader.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/templates/pair.hpp>
|
||||
#include <godot_cpp/variant/string_name.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
namespace utils {
|
||||
void GameRoot3D::_bind_methods() {
|
||||
#define CLASSNAME GameRoot3D
|
||||
GDFUNCTION(reset_game_mode);
|
||||
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_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"));
|
||||
}
|
||||
|
||||
GameRoot3D *GameRoot3D::get_singleton() {
|
||||
return GameRoot3D::singleton_instance;
|
||||
}
|
||||
|
||||
bool GameRoot3D::has_singleton() {
|
||||
return GameRoot3D::singleton_instance != nullptr;
|
||||
}
|
||||
|
||||
void GameRoot3D::_enter_tree() {
|
||||
// TODO: Replace this with detecting input devices
|
||||
this->player_input_connected();
|
||||
this->grab_singleton();
|
||||
this->rng = godot::Ref<godot::RandomNumberGenerator>(memnew(godot::RandomNumberGenerator));
|
||||
}
|
||||
|
||||
void GameRoot3D::_ready() {
|
||||
this->game_state = gd::ResourceLoader::get_singleton()->load("user://savegame.res");
|
||||
if(!this->game_state.is_valid())
|
||||
this->game_state = this->game_state_prototype->duplicate(true);
|
||||
this->load_level(this->first_boot_level);
|
||||
}
|
||||
|
||||
void GameRoot3D::_exit_tree() {
|
||||
this->release_singleton();
|
||||
}
|
||||
|
||||
void GameRoot3D::player_input_connected() {
|
||||
PlayerInput *input = memnew(PlayerInput);
|
||||
this->add_child(input);
|
||||
this->players.insert(this->next_player_id++, {input, nullptr});
|
||||
this->emit_signal(gd::StringName("player_connected"), input);
|
||||
}
|
||||
|
||||
void GameRoot3D::remove_player(uint32_t player_id) {
|
||||
if(!this->players.has(player_id))
|
||||
return;
|
||||
// convert player object to node
|
||||
gd::Node *node = this->players.get(player_id).second->to_node();
|
||||
if(node == nullptr) {
|
||||
gd::UtilityFunctions::push_error("IPlayer::to_node failed for player with id '", player_id, "'");
|
||||
return;
|
||||
}
|
||||
node->queue_free();
|
||||
this->players.get(player_id).second = nullptr;
|
||||
}
|
||||
|
||||
void GameRoot3D::remove_all_players() {
|
||||
// free all player instances in use
|
||||
for(gd::KeyValue<uint32_t, gd::Pair<PlayerInput*, IPlayer*>> &pair : this->players) {
|
||||
// skip unused player slots
|
||||
if(pair.value.second == nullptr)
|
||||
continue;
|
||||
else
|
||||
this->remove_player(pair.key);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameRoot3D::initialize_player(IPlayer *player, uint32_t id) {
|
||||
if(!this->players.has(id))
|
||||
return false;
|
||||
// register the player
|
||||
gd::Pair<PlayerInput*, IPlayer*> &found{this->players.get(id)};
|
||||
found.second = player;
|
||||
// set player id
|
||||
player->player_id = id;
|
||||
this->emit_signal("player_spawned", player->to_node());
|
||||
this->add_child(player->to_node());
|
||||
player->setup_player_input(found.first);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameRoot3D::reset_game_mode() {
|
||||
this->set_game_mode(nullptr);
|
||||
}
|
||||
|
||||
Level3D *GameRoot3D::load_level(gd::Ref<gd::PackedScene> level) {
|
||||
return this->load_level_at(level, gd::Transform3D());
|
||||
}
|
||||
|
||||
Level3D *GameRoot3D::load_level_at(gd::Ref<gd::PackedScene> level, gd::Transform3D at) {
|
||||
if(!GameRoot3D::is_valid_level(level)) {
|
||||
return nullptr;
|
||||
}
|
||||
Level3D *instance = Object::cast_to<Level3D>(level->instantiate());
|
||||
if(instance == nullptr) {
|
||||
gd::UtilityFunctions::push_error("Unexpected failure to instantiate level scene '", level->get_path(), "'.");
|
||||
return nullptr;
|
||||
}
|
||||
this->levels.insert(level->get_path(), instance);
|
||||
instance->connect("tree_exited", callable_mp(this, &GameRoot3D::level_unloaded).bind(level->get_path()));
|
||||
// store and add to tree at desired transform
|
||||
// if this is the first level containing a game mode currently active use it's gamemode as a prototype
|
||||
gd::Ref<gd::PackedScene> game_mode_prototype{instance->get_game_mode_prototype()};
|
||||
bool const switch_game_mode{!this->game_mode || !game_mode_prototype.is_valid() || this->game_mode->get_scene_file_path() != game_mode_prototype->get_path()};
|
||||
if(switch_game_mode) {
|
||||
this->set_game_mode(instance->get_game_mode_prototype());
|
||||
}
|
||||
this->add_child(instance);
|
||||
instance->set_global_transform(at);
|
||||
// set initial player positions if new player were spawned due to game mode switch
|
||||
if(switch_game_mode && this->game_mode != nullptr) {
|
||||
for(gd::KeyValue<uint32_t, gd::Pair<PlayerInput *, IPlayer *>> const &kvp : this->players) {
|
||||
this->place_player_at_spawnpoint(kvp.value.second);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
void GameRoot3D::unload_all_levels() {
|
||||
gd::HashMap<gd::StringName, Level3D*> levels = this->get_levels();
|
||||
for(gd::KeyValue<gd::StringName, Level3D*> &kvp : levels)
|
||||
kvp.value->call_deferred("queue_free");
|
||||
this->levels.clear();
|
||||
this->reset_game_mode();
|
||||
}
|
||||
|
||||
void GameRoot3D::replace_levels(gd::Ref<gd::PackedScene> scene) {
|
||||
this->unload_all_levels();
|
||||
this->load_level(scene);
|
||||
}
|
||||
|
||||
void GameRoot3D::register_spawn_point(SpawnPoint3D *spawn_point) {
|
||||
if(this->spawn_points.has(spawn_point)) {
|
||||
gd::UtilityFunctions::push_error("Duplicate attempt to register spawnpoint '", spawn_point->get_path(), "'");
|
||||
return;
|
||||
}
|
||||
if(!this->spawn_points.has(spawn_point))
|
||||
this->spawn_points.push_back(spawn_point);
|
||||
}
|
||||
|
||||
void GameRoot3D::unregister_spawn_point(SpawnPoint3D *spawn_point) {
|
||||
if(!this->spawn_points.has(spawn_point)) {
|
||||
gd::UtilityFunctions::push_error("Attempt to unregister spawnpoint '", spawn_point->get_path(), "', which is not registered.");
|
||||
return;
|
||||
}
|
||||
this->spawn_points.erase(spawn_point);
|
||||
}
|
||||
|
||||
void GameRoot3D::place_player_at_spawnpoint(IPlayer *player) {
|
||||
if(this->spawn_points.is_empty()) return;
|
||||
SpawnPoint3D *spawn_point = this->spawn_points[rng->randi() % this->spawn_points.size()];
|
||||
player->spawn_at_position(spawn_point->get_global_transform());
|
||||
}
|
||||
|
||||
void GameRoot3D::player_despawned(uint32_t id) {
|
||||
gd::Pair<PlayerInput*, IPlayer*> &pair = this->players.get(id);
|
||||
pair.second = nullptr;
|
||||
pair.first->clear_listeners();
|
||||
}
|
||||
|
||||
void GameRoot3D::set_game_mode(gd::Ref<gd::PackedScene> prototype) {
|
||||
this->remove_all_players();
|
||||
if(this->game_mode != nullptr)
|
||||
this->game_mode->queue_free();
|
||||
if(prototype.is_null() || !prototype.is_valid()) {
|
||||
this->game_mode = nullptr;
|
||||
return; // allow "unsetting" the gamemode by passing an invalid gamemode
|
||||
}
|
||||
// Detect passing of valid scene that is an invalid game mode
|
||||
if(!gd::ClassDB::is_parent_class(prototype->get_state()->get_node_type(0), "GameMode")) {
|
||||
gd::UtilityFunctions::push_error("Attempted to load non-gamemode scene as gamemode");
|
||||
return;
|
||||
}
|
||||
// instantiate the game mode as a child
|
||||
this->game_mode = Object::cast_to<GameMode>(prototype->instantiate());
|
||||
this->add_child(game_mode);
|
||||
// instantiate players
|
||||
if(this->game_mode->get_player_scene().is_valid()) {
|
||||
uint32_t new_player_id = this->find_empty_player_slot();
|
||||
do {
|
||||
IPlayer *player = this->spawn_player(new_player_id);
|
||||
if(player != nullptr)
|
||||
this->initialize_player(player, new_player_id);
|
||||
new_player_id = this->find_empty_player_slot();
|
||||
} while(new_player_id != 0);
|
||||
}
|
||||
}
|
||||
|
||||
GameMode *GameRoot3D::get_game_mode() const {
|
||||
return this->game_mode;
|
||||
}
|
||||
|
||||
GameState *GameRoot3D::get_game_state() const {
|
||||
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) {
|
||||
if(level.is_null() || !level.is_valid()) {
|
||||
this->first_boot_level.unref();
|
||||
return;
|
||||
}
|
||||
gd::StringName const root_type = level->get_state()->get_node_type(0);
|
||||
if(!gd::ClassDB::is_parent_class(root_type, "Level3D")) {
|
||||
gd::UtilityFunctions::push_error("First boot level cannot be of type '", root_type, "'. First boot level has to inherit from Level3D");
|
||||
this->first_boot_level.unref();
|
||||
return;
|
||||
}
|
||||
this->first_boot_level = level;
|
||||
}
|
||||
|
||||
gd::Ref<gd::PackedScene> GameRoot3D::get_first_boot_level() const {
|
||||
return this->first_boot_level;
|
||||
}
|
||||
|
||||
void GameRoot3D::set_game_state_prototype(gd::Ref<GameState> game_state) {
|
||||
this->game_state_prototype = game_state;
|
||||
}
|
||||
|
||||
gd::Ref<GameState> GameRoot3D::get_game_state_prototype() const {
|
||||
return this->game_state_prototype;
|
||||
}
|
||||
|
||||
gd::RandomNumberGenerator &GameRoot3D::get_rng() {
|
||||
return *this->rng.ptr();
|
||||
}
|
||||
|
||||
void GameRoot3D::grab_singleton() {
|
||||
if(GameRoot3D::has_singleton()) {
|
||||
this->set_process_mode(PROCESS_MODE_DISABLED);
|
||||
gd::UtilityFunctions::push_error("More than one GameRoot instance active");
|
||||
} else {
|
||||
GameRoot3D::singleton_instance = this;
|
||||
}
|
||||
}
|
||||
|
||||
void GameRoot3D::release_singleton() {
|
||||
if(GameRoot3D::singleton_instance == this) {
|
||||
GameRoot3D::singleton_instance = nullptr;
|
||||
} else {
|
||||
gd::UtilityFunctions::push_error("GameRoot instance attempted to release singleton while it is not the singleton instance");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GameRoot3D::find_empty_player_slot() const {
|
||||
for(gd::KeyValue<uint32_t, gd::Pair<PlayerInput*, IPlayer*>> const &kvp : this->players) {
|
||||
if(kvp.value.second == nullptr) {
|
||||
return kvp.key;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
IPlayer *GameRoot3D::spawn_player(uint32_t id) {
|
||||
if(id == 0) {
|
||||
gd::UtilityFunctions::push_error("Failed to find any valid player slot when spawning player");
|
||||
return nullptr;
|
||||
}
|
||||
Node *player_node = this->game_mode->get_player_scene()->instantiate();
|
||||
if(player_node == nullptr) {
|
||||
gd::UtilityFunctions::push_error("Failed to instantiate player scene '", this->game_mode->get_player_scene()->get_path(), "'");
|
||||
return nullptr;
|
||||
}
|
||||
IPlayer *player = dynamic_cast<IPlayer*>(player_node);
|
||||
if(player == nullptr) {
|
||||
gd::UtilityFunctions::push_error("Player scene does not implement required IPlayer interface");
|
||||
player_node->queue_free();
|
||||
return nullptr;
|
||||
}
|
||||
player_node->connect("tree_exited", callable_mp(this, &GameRoot3D::player_despawned).bind(id));
|
||||
return player;
|
||||
}
|
||||
|
||||
void GameRoot3D::level_unloaded(gd::StringName scene_path) {
|
||||
this->levels.erase(scene_path);
|
||||
}
|
||||
|
||||
bool GameRoot3D::is_valid_level(gd::Ref<gd::PackedScene> &level) {
|
||||
if(level.is_null() || !level.is_valid() || !level->can_instantiate()) {
|
||||
gd::UtilityFunctions::push_error("Can't load level from invalid packed scene");
|
||||
return false;
|
||||
}
|
||||
gd::StringName const root_type = level->get_state()->get_node_type(0);
|
||||
if(!gd::ClassDB::is_parent_class(root_type, "Level3D")) {
|
||||
gd::UtilityFunctions::push_error("Can't load level with root type '", root_type, "'. Root node has to be of type Level3D");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
GameRoot3D *GameRoot3D::singleton_instance{nullptr};
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
#ifndef UTILS_GAME_ROOT_HPP
|
||||
#define UTILS_GAME_ROOT_HPP
|
||||
|
||||
#include "game_mode.hpp"
|
||||
#include "game_state.hpp"
|
||||
#include "level.hpp"
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
#include <godot_cpp/classes/random_number_generator.hpp>
|
||||
#include <godot_cpp/templates/hash_map.hpp>
|
||||
#include <godot_cpp/templates/hash_set.hpp>
|
||||
#include <godot_cpp/templates/pair.hpp>
|
||||
#include <godot_cpp/templates/vector.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace utils {
|
||||
class PlayerInput;
|
||||
class IPlayer;
|
||||
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 {
|
||||
GDCLASS(GameRoot3D, gd::Node);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
//! get the current active singleton instance of GameRoot
|
||||
static GameRoot3D *get_singleton();
|
||||
//! returns true if there is currently a singleton active for GameRoot
|
||||
static bool has_singleton();
|
||||
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _ready() 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();
|
||||
/*! Force-disconnect a player
|
||||
*
|
||||
* Calls queue_free on the IPlayer instance
|
||||
*/
|
||||
void remove_player(uint32_t player_id);
|
||||
// calls remove_player for every used player input slot
|
||||
void remove_all_players();
|
||||
/*! 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
|
||||
*/
|
||||
bool initialize_player(IPlayer *player, uint32_t id);
|
||||
|
||||
/*! Un-set game mode.
|
||||
* Shorthand for `set_game_mode(Ref<PackedScene>())`
|
||||
*/
|
||||
void reset_game_mode();
|
||||
|
||||
//! shorthand for load_level(level, Transform3D())
|
||||
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'.
|
||||
*
|
||||
* \param at Sets the root node's global transform.
|
||||
*/
|
||||
Level3D *load_level_at(gd::Ref<gd::PackedScene> level, gd::Transform3D at);
|
||||
//! Unload all currently loaded 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);
|
||||
//! Register a spawnpoint for use when spawning players
|
||||
void register_spawn_point(SpawnPoint3D *spawn_point);
|
||||
// remove a spawnpoint so it can't be used to spawn players
|
||||
void unregister_spawn_point(SpawnPoint3D *spawn_point);
|
||||
void place_player_at_spawnpoint(IPlayer *player);
|
||||
void player_despawned(uint32_t id);
|
||||
|
||||
/*! Override the current gamemode.
|
||||
*
|
||||
* Replaces game mode requires destroying and respawning all players
|
||||
*/
|
||||
void set_game_mode(gd::Ref<gd::PackedScene> prototype);
|
||||
//! get the current active game mode.
|
||||
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);
|
||||
gd::Ref<gd::PackedScene> get_first_boot_level() const;
|
||||
void set_game_state_prototype(gd::Ref<GameState> game_state);
|
||||
gd::Ref<GameState> get_game_state_prototype() const;
|
||||
gd::RandomNumberGenerator &get_rng();
|
||||
protected:
|
||||
//! Attempt to make 'this' the current singleton instance.
|
||||
void grab_singleton();
|
||||
/*! Attempt to stop being the active singleton instance.
|
||||
*
|
||||
* Only works if the current singleton is 'this'.
|
||||
*/
|
||||
void release_singleton();
|
||||
//! Find a Player Input device not yet associated with a player.
|
||||
uint32_t find_empty_player_slot() const;
|
||||
//! Spawn a player to be associated with id.
|
||||
IPlayer *spawn_player(uint32_t id);
|
||||
//! Callback for a level exiting the tree.
|
||||
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);
|
||||
private:
|
||||
static GameRoot3D *singleton_instance;
|
||||
/*! Next available player ID.
|
||||
*
|
||||
* Default is 1 because 0 is the "invalid" player id.
|
||||
*/
|
||||
uint32_t next_player_id{1};
|
||||
/*! All players by id by input device.
|
||||
*
|
||||
* `get_players()`
|
||||
*/
|
||||
gd::HashMap<uint32_t, gd::Pair<PlayerInput*, IPlayer*>> players{};
|
||||
/*! Global random number generator.
|
||||
*
|
||||
* `&get_rng()`
|
||||
*/
|
||||
gd::Ref<gd::RandomNumberGenerator> rng{};
|
||||
/*! All currently active levels.
|
||||
*
|
||||
* Each identified by their resource paths.
|
||||
*
|
||||
* `&get_levels()`
|
||||
*/
|
||||
gd::HashMap<gd::StringName, Level3D*> levels{};
|
||||
/*! All currently available spawn points.
|
||||
*/
|
||||
gd::Vector<SpawnPoint3D*> spawn_points{};
|
||||
/*! Current active gamemode.
|
||||
*
|
||||
* Replaced when a level is loaded that references a different game mode.
|
||||
*
|
||||
* `*get_game_mode()`
|
||||
*/
|
||||
GameMode *game_mode{};
|
||||
/*! Active game state.
|
||||
*
|
||||
* Will be assigned loaded save data, or game_state_prototype if no save data is found.
|
||||
*
|
||||
* `*get_game_mode()`
|
||||
*/
|
||||
gd::Ref<GameState> game_state{};
|
||||
/*! The level to boot into on startup.
|
||||
*
|
||||
* `get_first_boot_level()` `set_first_boot_level(value)`
|
||||
*/
|
||||
gd::Ref<gd::PackedScene> first_boot_level{};
|
||||
/*! The default game state data.
|
||||
*
|
||||
* Duplicated and assigned to game_state if no save data is available.
|
||||
*
|
||||
* `get_game_state_prototype()` `set_game_state_prototype(value)`
|
||||
*/
|
||||
gd::Ref<GameState> game_state_prototype{};
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !UTILS_GAME_ROOT_HPP
|
|
@ -0,0 +1,9 @@
|
|||
#include "game_state.hpp"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
namespace utils {
|
||||
void GameState::_bind_methods() {
|
||||
#define CLASSNAME GameState
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef UTILS_GAME_STATE_HPP
|
||||
#define UTILS_GAME_STATE_HPP
|
||||
|
||||
#include <godot_cpp/classes/resource.hpp>
|
||||
|
||||
namespace utils {
|
||||
/*! Parent class for saved game state.
|
||||
*
|
||||
* Inherit and add godot properties to save persistently.
|
||||
*/
|
||||
class GameState : public godot::Resource {
|
||||
GDCLASS(GameState, godot::Resource);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !UTILS_GAME_STATE_HPP
|
|
@ -37,28 +37,28 @@
|
|||
|
||||
/*! \def GDFUNCTION(FnName_)
|
||||
* \brief Register a function CLASSNAME::FnName_() to godot.
|
||||
*
|
||||
*
|
||||
* Requires setting CLASSNAME as a #define first.
|
||||
*/
|
||||
#define GDFUNCTION(FnName_) godot::ClassDB::bind_method(godot::D_METHOD(#FnName_), &CLASSNAME::FnName_)
|
||||
|
||||
/*! \def GDFUNCTION_ARGS(FnName_, ...)
|
||||
* \brief Register a function CLASSNAME::FnName_(...) to godot.
|
||||
*
|
||||
*
|
||||
* Requires setting CLASSNAME as a #define first.
|
||||
*/
|
||||
#define GDFUNCTION_ARGS(FnName_, ...) godot::ClassDB::bind_method(godot::D_METHOD(#FnName_, __VA_ARGS__), &CLASSNAME::FnName_)
|
||||
|
||||
/*! \def GDFUNCTION_STATIC(FnName_)
|
||||
* \brief Register a static member function CLASSNAME::FnName_() to godot.
|
||||
*
|
||||
*
|
||||
* Requires setting CLASSNAME as a #define first.
|
||||
*/
|
||||
#define GDFUNCTION_STATIC(FnName_) godot::ClassDB::bind_static_method(MACRO_STRING(CLASSNAME), godot::D_METHOD(#FnName_), &CLASSNAME::_FnName)
|
||||
|
||||
/*! \def GDFUNCTION_STATIC_ARGS(FnName_, ...)
|
||||
* \brief Register a static member function CLASSNAME::FnName_(...) to godot.
|
||||
*
|
||||
*
|
||||
* Requires setting CLASSNAME as a #define first.
|
||||
*/
|
||||
#define GDFUNCTION_STATIC_ARGS(FnName_, ...) godot::ClassDB::bind_static_method(MACRO_STRING(CLASSNAME), godot::D_METHOD(#FnName_, __VA_ARGS__), &CLASSNAME::FnName_)
|
||||
|
@ -77,19 +77,6 @@
|
|||
#define GDNODETYPE(Class_) godot::vformat("%s/%s:%s", godot::Variant::OBJECT, godot::PROPERTY_HINT_NODE_TYPE, Class_)
|
||||
#define GDENUMTYPE(EnumString_) godot::vformat("%s/%s:%s", godot::Variant::INT, godot::PROPERTY_HINT_ENUM, EnumString_)
|
||||
|
||||
/*! \def GDEDITORONLY()
|
||||
* \brief Execute the rest of the function only if currently running as editor.
|
||||
*
|
||||
* Useful for _ready, _enter/_exit, _process, etc. functions.
|
||||
*/
|
||||
#define GDEDITORONLY() if(!godot::Engine::get_singleton()->is_editor_hint()) return;
|
||||
/*! \def GDGAMEONLY()
|
||||
* \brief Execute the rest of the function only if currently running as game.
|
||||
*
|
||||
* Useful for _ready, _enter/_exit, _process, etc. functions.
|
||||
*/
|
||||
#define GDGAMEONLY() if(godot::Engine::get_singleton()->is_editor_hint()) return;
|
||||
|
||||
/*! \def GDENUM(Name_, ...)
|
||||
* \brief Declare a scoped enum struct.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
#include "level.hpp"
|
||||
#include "godot_macros.hpp"
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/classes/scene_state.hpp>
|
||||
|
||||
namespace utils {
|
||||
void Level3D::_bind_methods() {
|
||||
#define CLASSNAME Level3D
|
||||
GDPROPERTY_HINTED(game_mode_prototype, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
||||
}
|
||||
|
||||
void Level3D::set_game_mode_prototype(gd::Ref<gd::PackedScene> prototype) {
|
||||
if(prototype.is_null() || !prototype.is_valid())
|
||||
this->game_mode_prototype = gd::Ref<gd::PackedScene>(nullptr);
|
||||
else if(!gd::ClassDB::is_parent_class(prototype->get_state()->get_node_type(0), "GameMode"))
|
||||
return;
|
||||
else
|
||||
this->game_mode_prototype = prototype;
|
||||
}
|
||||
|
||||
gd::Ref<gd::PackedScene> Level3D::get_game_mode_prototype() const {
|
||||
return this->game_mode_prototype;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef UTILS_LEVEL_HPP
|
||||
#define UTILS_LEVEL_HPP
|
||||
|
||||
#include "game_mode.hpp"
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
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 {
|
||||
GDCLASS(Level3D, gd::Node3D);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
void set_game_mode_prototype(gd::Ref<gd::PackedScene> prototype);
|
||||
gd::Ref<gd::PackedScene> get_game_mode_prototype() const;
|
||||
private:
|
||||
gd::Ref<gd::PackedScene> game_mode_prototype{}; //!< The starting state of the game mode to instantiate if this is the "leading" level.
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !UTILS_LEVEL_HPP
|
|
@ -0,0 +1,8 @@
|
|||
#include "player.hpp"
|
||||
|
||||
namespace utils {
|
||||
uint32_t IPlayer::get_player_id() {
|
||||
return this->player_id.value_or(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef UTILS_PLAYER_HPP
|
||||
#define UTILS_PLAYER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <godot_cpp/variant/transform3d.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace godot { class Node; }
|
||||
|
||||
namespace utils {
|
||||
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 {
|
||||
friend class GameRoot3D;
|
||||
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;
|
||||
//! Convert IPlayer instance to node.
|
||||
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;
|
||||
|
||||
uint32_t get_player_id(); //!< Returns the player id assigned to this instance.
|
||||
|
||||
private:
|
||||
std::optional<uint32_t> player_id{std::nullopt};
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !UTILS_PLAYER_HPP
|
|
@ -74,21 +74,21 @@ gd::Vector2 PlayerInput::get_last_mouse_motion() {
|
|||
return PlayerInput::lastMouseMotion;
|
||||
}
|
||||
|
||||
void PlayerInput::_enter_tree() { GDGAMEONLY();
|
||||
void PlayerInput::_enter_tree() {
|
||||
if(!PlayerInput::primaryExists) {
|
||||
this->isPrimary = true;
|
||||
PlayerInput::primaryExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerInput::_exit_tree() { GDGAMEONLY();
|
||||
void PlayerInput::_exit_tree() {
|
||||
if(this->isPrimary) {
|
||||
this->isPrimary = false;
|
||||
PlayerInput::primaryExists = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerInput::_unhandled_input(gd::Ref<gd::InputEvent> const &event) { GDGAMEONLY();
|
||||
void PlayerInput::_unhandled_input(gd::Ref<gd::InputEvent> const &event) {
|
||||
if(this->isPrimary && event->is_class("InputEventMouseMotion"))
|
||||
PlayerInput::lastMouseMotion = gd::Object::cast_to<gd::InputEventMouseMotion>(*event)->get_relative();
|
||||
for(Listener& listener: this->listeners) {
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
#include "register_types.hpp"
|
||||
#include "game_mode.hpp"
|
||||
#include "game_root.hpp"
|
||||
#include "game_state.hpp"
|
||||
#include "level.hpp"
|
||||
#include "player_input.hpp"
|
||||
#include "spawn_point.hpp"
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
namespace utils {
|
||||
void godot_cpp_utils_register_types() {
|
||||
GDREGISTER_CLASS(utils::PlayerInput);
|
||||
GDREGISTER_RUNTIME_CLASS(utils::GameMode);
|
||||
GDREGISTER_RUNTIME_CLASS(utils::GameRoot3D);
|
||||
GDREGISTER_CLASS(utils::GameState);
|
||||
GDREGISTER_RUNTIME_CLASS(utils::Level3D);
|
||||
GDREGISTER_RUNTIME_CLASS(utils::PlayerInput);
|
||||
GDREGISTER_RUNTIME_CLASS(utils::SpawnPoint3D);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#include "spawn_point.hpp"
|
||||
#include "game_root.hpp"
|
||||
|
||||
namespace utils {
|
||||
void SpawnPoint3D::_bind_methods() {
|
||||
}
|
||||
|
||||
void SpawnPoint3D::_enter_tree() {
|
||||
GameRoot3D *root = gd::Object::cast_to<GameRoot3D>(GameRoot3D::get_singleton());
|
||||
if(root == nullptr) {
|
||||
return;
|
||||
}
|
||||
root->register_spawn_point(this);
|
||||
}
|
||||
|
||||
void SpawnPoint3D::_exit_tree() {
|
||||
GameRoot3D *root = Object::cast_to<GameRoot3D>(GameRoot3D::get_singleton());
|
||||
if(root == nullptr) {
|
||||
return;
|
||||
}
|
||||
root->unregister_spawn_point(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef UTILS_SPAWN_POINT_HPP
|
||||
#define UTILS_SPAWN_POINT_HPP
|
||||
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
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 {
|
||||
GDCLASS(SpawnPoint3D, gd::Node3D);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _exit_tree() override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !UTILS_SPAWN_POINT_HPP
|
Loading…
Reference in New Issue