feat: updated player and added player input
parent
666d417264
commit
2ff77e8f2a
1702
godot/player.tscn
1702
godot/player.tscn
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,16 @@
|
||||||
#include "game_mode.hpp"
|
#include "game_mode.hpp"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include "godot_cpp/variant/utility_functions.hpp"
|
#include "godot_cpp/variant/utility_functions.hpp"
|
||||||
|
#include "godot_cpp/classes/viewport.hpp"
|
||||||
#include "godot_macros.h"
|
#include "godot_macros.h"
|
||||||
#include "level.hpp"
|
#include "level.hpp"
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
|
||||||
namespace godot {
|
namespace godot {
|
||||||
GameMode* GameMode::static_instance{nullptr};
|
GameMode *GameMode::static_instance{nullptr};
|
||||||
|
|
||||||
|
GameMode *GameMode::get_singleton() { return GameMode::static_instance; }
|
||||||
|
bool GameMode::has_singleton() { return GameMode::static_instance != nullptr; }
|
||||||
|
|
||||||
void GameMode::_bind_methods() {
|
void GameMode::_bind_methods() {
|
||||||
#define CLASSNAME GameMode
|
#define CLASSNAME GameMode
|
||||||
|
|
|
@ -31,6 +31,9 @@ public:
|
||||||
void load_level(Ref<PackedScene>& levelScene,
|
void load_level(Ref<PackedScene>& levelScene,
|
||||||
std::optional<String> entrance);
|
std::optional<String> entrance);
|
||||||
|
|
||||||
|
static GameMode *get_singleton();
|
||||||
|
static bool has_singleton();
|
||||||
|
|
||||||
Player* get_player_instance() const;
|
Player* get_player_instance() const;
|
||||||
|
|
||||||
void set_first_level(Ref<PackedScene> level);
|
void set_first_level(Ref<PackedScene> level);
|
||||||
|
|
116
src/player.cpp
116
src/player.cpp
|
@ -1,8 +1,10 @@
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include "game_mode.hpp"
|
||||||
#include "godot_cpp/classes/animation_tree.hpp"
|
#include "godot_cpp/classes/animation_tree.hpp"
|
||||||
#include "godot_cpp/classes/node3d.hpp"
|
#include "godot_cpp/classes/node3d.hpp"
|
||||||
#include "godot_cpp/classes/skeleton3d.hpp"
|
#include "godot_cpp/classes/skeleton3d.hpp"
|
||||||
|
#include "godot_cpp/variant/utility_functions.hpp"
|
||||||
#include "godot_macros.h"
|
#include "godot_macros.h"
|
||||||
|
|
||||||
namespace godot {
|
namespace godot {
|
||||||
|
@ -11,109 +13,131 @@ void Player::_bind_methods() {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
GDPROPERTY(active_customization, Variant::DICTIONARY);
|
GDPROPERTY(active_customization, Variant::DICTIONARY);
|
||||||
#endif
|
#endif
|
||||||
|
GDFUNCTION_ARGS(on_horizontal, "event", "value");
|
||||||
|
GDFUNCTION_ARGS(on_vertical, "event", "value");
|
||||||
}
|
}
|
||||||
void Player::_enter_tree() {
|
void Player::_enter_tree() {
|
||||||
this->model = this->get_node<Node3D>("Model");
|
this->model = this->get_node<Node3D>("Model");
|
||||||
this->animTree = this->get_node<AnimationTree>("AnimationTree");
|
this->animTree = this->get_node<AnimationTree>("AnimationTree");
|
||||||
this->customization_init();
|
GDGAMEONLY();
|
||||||
|
this->input = this->get_node<PlayerInput>("PlayerInput");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::_exit_tree() {
|
void Player::_ready() {
|
||||||
// free the customization options that were removed from the tree
|
this->customization_init();
|
||||||
for(std::pair<const String, CustomizationState>& pair: this->customization) {
|
GDGAMEONLY();
|
||||||
for(Node3D *option: pair.second.options) {
|
this->input->listen_to(PlayerInput::Listener("move_left", "move_right", this, "on_horizontal"));
|
||||||
if(!option->is_inside_tree()) {
|
this->input->listen_to(PlayerInput::Listener("move_forward", "move_back", this, "on_vertical"));
|
||||||
option->queue_free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::_process(double deltaTime) {}
|
void Player::_process(double deltaTime) {}
|
||||||
void Player::_physics_process(double deltaTime) {}
|
void Player::_physics_process(double deltaTime) {}
|
||||||
|
|
||||||
void Player::select_customization(String category, std::optional<size_t> index) {
|
void Player::set_customization_active(Node3D *node, bool active) {
|
||||||
if(this->customization.find(category) == this->customization.end())
|
node->set_visible(active);
|
||||||
|
node->set_process(active);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::on_horizontal(Ref<InputEvent> event, float value) {
|
||||||
|
this->moveInput.x = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::on_vertical(Ref<InputEvent> event, float value) {
|
||||||
|
this->moveInput.y = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::select_customization(String category,
|
||||||
|
std::optional<size_t> index) {
|
||||||
|
if (this->customization.find(category) == this->customization.end())
|
||||||
return;
|
return;
|
||||||
CustomizationState &pair = this->customization.at(category);
|
CustomizationState& pair = this->customization.at(category);
|
||||||
// invalid keys are stored as the size of the array
|
// invalid keys are stored as the size of the array
|
||||||
if(index.has_value() && index.value() > pair.options.size())
|
if (index.has_value() && index.value() > pair.options.size())
|
||||||
index = std::nullopt;
|
index = std::nullopt;
|
||||||
// nothing changes
|
// nothing changes
|
||||||
if(index == pair.currentSelected)
|
if (index == pair.currentSelected)
|
||||||
return;
|
return;
|
||||||
// disable previous chosen option, if any
|
// disable previous chosen option, if any
|
||||||
if(pair.currentSelected.has_value())
|
if (pair.currentSelected.has_value())
|
||||||
customizationParent->remove_child(pair.options.at(pair.currentSelected.value()));
|
Player::set_customization_active(pair.options.at(pair.currentSelected.value()), false);
|
||||||
// enable chosen option, if any
|
// enable chosen option, if any
|
||||||
if(index.has_value())
|
if (index.has_value())
|
||||||
customizationParent->add_child(pair.options.at(index.value()));
|
Player::set_customization_active(pair.options.at(index.value()), false);
|
||||||
pair.currentSelected = index;
|
pair.currentSelected = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary Player::get_active_customization() const {
|
Dictionary Player::get_active_customization() const {
|
||||||
Dictionary result{};
|
Dictionary result{};
|
||||||
// translate the map to a dictionary of keys and indexes
|
// translate the map to a dictionary of keys and indexes
|
||||||
for(std::pair<String, CustomizationState> const &pair: this->customization)
|
for (std::pair<String, CustomizationState> const& pair :
|
||||||
|
this->customization)
|
||||||
result[pair.first] = pair.second.currentSelected.has_value()
|
result[pair.first] = pair.second.currentSelected.has_value()
|
||||||
? pair.second.currentSelected.value()
|
? pair.second.currentSelected.value()
|
||||||
: -1; // an invalid key will always be -1
|
: -1; // an invalid key will always be -1
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::set_active_customization(Dictionary value) {
|
void Player::set_active_customization(Dictionary value) {
|
||||||
for(size_t i = 0; i < value.size(); ++i) {
|
for (size_t i = 0; i < value.size(); ++i) {
|
||||||
String key = value.keys()[i];
|
String key = value.keys()[i];
|
||||||
size_t selection = value[key];
|
size_t selection = value[key];
|
||||||
bool exists = this->customization.find(key) != this->customization.end(); // wether or not the a given key exists in the customization map
|
// wether or not the a given key exists in the customization map
|
||||||
// if not, it can be added, as long as the customization map has not yet been initialized by _enter_tree
|
bool exists = this->customization.find(key) != this->customization.end();
|
||||||
if(!exists && !this->is_inside_tree())
|
// if not, it can be added, as long as the customization map has not yet
|
||||||
|
// been initialized by _ready
|
||||||
|
if (!exists && !this->is_inside_tree())
|
||||||
this->customization.insert({key, {std::optional{selection}, {}}});
|
this->customization.insert({key, {std::optional{selection}, {}}});
|
||||||
// if the key does not exist in customization and _enter_tree is already called, the key is not valid and discarted
|
// if the key does not exist in customization and _ready is already
|
||||||
else if(!exists)
|
// called, the key is not valid and discarted
|
||||||
|
else if (!exists)
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
this->select_customization(key, selection >= 0 ? std::optional{selection} : std::nullopt);
|
this->select_customization(
|
||||||
|
key, selection >= 0 ? std::optional{selection} : std::nullopt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::customization_init() {
|
void Player::customization_init() {
|
||||||
this->customizationParent = this->get_node<Skeleton3D>("Model/RootNode/Skeleton3D");
|
this->customizationParent =
|
||||||
if(!this->customizationParent)
|
this->get_node<Skeleton3D>("Model/RootNode/GeneralSkeleton");
|
||||||
|
if (this->customizationParent == nullptr)
|
||||||
return;
|
return;
|
||||||
for(int i = 0; i < customizationParent->get_child_count(); ++i) {
|
for (int i = 0; i < customizationParent->get_child_count(); ++i) {
|
||||||
// get the next valid child
|
// get the next valid child
|
||||||
Node3D *child = Object::cast_to<Node3D>(this->customizationParent->get_child(i));
|
Node3D* child = Object::cast_to<Node3D>(this->customizationParent->get_child(i));
|
||||||
if(!child) continue;
|
if (child == nullptr)
|
||||||
|
continue;
|
||||||
// split it's name into parts based on _
|
// split it's name into parts based on _
|
||||||
// the format for the names will be Chr_<CATEGORY NAME>_<ADDITIONAL IDENTIFIERS>_<NUMBER>
|
// the format for the names will be Chr_<CATEGORY NAME>_<ADDITIONAL IDENTIFIERS>_<NUMBER> numbering IS NOT guaranteed to start at 0 OR 1
|
||||||
// numbering IS NOT guaranteed to start at 0 OR 1
|
|
||||||
// the category name will serve as the key to the customization map
|
// the category name will serve as the key to the customization map
|
||||||
// additional identifiers are ignored
|
// additional identifiers are ignored
|
||||||
PackedStringArray slices = child->get_name().split("_", false);
|
PackedStringArray slices = child->get_name().split("_", false);
|
||||||
// create a new customization state if one does not exist for this category
|
// create a new customization state if one does not exist for this category
|
||||||
if(this->customization.find(slices[1]) == this->customization.end())
|
if (this->customization.find(slices[1]) == this->customization.end())
|
||||||
this->customization.insert({slices[1], {std::optional{0}, {}}});
|
this->customization.insert({slices[1], {std::optional{0}, {}}});
|
||||||
// add the new child to the customization options for this category
|
// add the new child to the customization options for this category
|
||||||
CustomizationState &state = this->customization.at(slices[1]);
|
CustomizationState& state = this->customization.at(slices[1]);
|
||||||
state.options.push_back(child);
|
state.options.push_back(child);
|
||||||
// only allow this child to exist in the scene tree if it was saved as the current selection
|
// set invisible if this child is not the current selection
|
||||||
if(state.currentSelected != state.options.size()-1) {
|
if (state.currentSelected != state.options.size() - 1) {
|
||||||
this->customizationParent->remove_child(child);
|
Player::set_customization_active(child, false);
|
||||||
--i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// collect customization categories that do not contain any options
|
// collect customization categories that do not contain any options
|
||||||
std::vector<String> empty{};
|
std::vector<String> empty{};
|
||||||
for(std::pair<const String, CustomizationState>& pair: this->customization) {
|
for (std::pair<const String, CustomizationState>& pair :
|
||||||
if(pair.second.options.size() == 0)
|
this->customization) {
|
||||||
|
if (pair.second.options.size() == 0)
|
||||||
empty.push_back(pair.first);
|
empty.push_back(pair.first);
|
||||||
if(pair.second.currentSelected.has_value() && pair.second.currentSelected.value() > pair.second.options.size())
|
if (pair.second.currentSelected.has_value() &&
|
||||||
|
pair.second.currentSelected.value() > pair.second.options.size())
|
||||||
pair.second.currentSelected = std::nullopt;
|
pair.second.currentSelected = std::nullopt;
|
||||||
|
UtilityFunctions::print("- ", pair.first);
|
||||||
}
|
}
|
||||||
// remove collected empty categories
|
// remove collected empty categories
|
||||||
for(String const& key: empty) {
|
for (String const& key : empty) {
|
||||||
this->customization.erase(key);
|
this->customization.erase(key);
|
||||||
|
UtilityFunctions::print("removing ", key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace godot
|
} // namespace godot
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "godot_cpp/classes/animation_tree.hpp"
|
#include "godot_cpp/classes/animation_tree.hpp"
|
||||||
#include "godot_cpp/classes/character_body3d.hpp"
|
#include "godot_cpp/classes/character_body3d.hpp"
|
||||||
|
#include "godot_cpp/classes/input_event.hpp"
|
||||||
#include "godot_cpp/classes/skeleton3d.hpp"
|
#include "godot_cpp/classes/skeleton3d.hpp"
|
||||||
|
#include "player_input.hpp"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace godot {
|
namespace godot {
|
||||||
|
@ -26,13 +28,23 @@ protected:
|
||||||
AnimationTree* animTree{nullptr};
|
AnimationTree* animTree{nullptr};
|
||||||
// the skeleton of the customizable character model
|
// the skeleton of the customizable character model
|
||||||
Skeleton3D *customizationParent{nullptr};
|
Skeleton3D *customizationParent{nullptr};
|
||||||
|
PlayerInput *input{nullptr};
|
||||||
|
|
||||||
|
float walkSpeed{10.f};
|
||||||
|
|
||||||
|
Vector2 moveInput{0.f, 0.f};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void _enter_tree() override;
|
virtual void _enter_tree() override;
|
||||||
virtual void _exit_tree() override;
|
virtual void _ready() override;
|
||||||
virtual void _process(double deltaTime) override;
|
virtual void _process(double deltaTime) override;
|
||||||
virtual void _physics_process(double deltaTime) override;
|
virtual void _physics_process(double deltaTime) override;
|
||||||
|
|
||||||
|
static void set_customization_active(Node3D *node, bool active);
|
||||||
|
|
||||||
|
void on_horizontal(Ref<InputEvent> event, float value);
|
||||||
|
void on_vertical(Ref<InputEvent> event, float value);
|
||||||
|
|
||||||
// Swap out the currently active customization for this category
|
// Swap out the currently active customization for this category
|
||||||
void select_customization(String category, std::optional<size_t> value);
|
void select_customization(String category, std::optional<size_t> value);
|
||||||
// Construct a dictionary of [String, int] where the key is a category
|
// Construct a dictionary of [String, int] where the key is a category
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "player_input.hpp"
|
||||||
|
#include "godot_macros.h"
|
||||||
|
#include "godot_cpp/classes/input.hpp"
|
||||||
|
#include "godot_cpp/classes/input_event.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace godot {
|
||||||
|
void PlayerInput::_bind_methods() {}
|
||||||
|
|
||||||
|
PlayerInput::Listener::Listener(String positive, String negative, Node *object, String method)
|
||||||
|
: actionNegative{negative}
|
||||||
|
, actionPositive{positive}
|
||||||
|
, methodName{method}
|
||||||
|
, object{object} {}
|
||||||
|
|
||||||
|
bool PlayerInput::Listener::has_changed(Ref<InputEvent> const &event) {
|
||||||
|
return event->is_action(this->actionNegative) || event->is_action(this->actionPositive);
|
||||||
|
}
|
||||||
|
float PlayerInput::Listener::evaluate(Ref<InputEvent> const &event) {
|
||||||
|
float newest = static_cast<float>(event->is_action_pressed(this->actionPositive))
|
||||||
|
- static_cast<float>(event->is_action_pressed(this->actionNegative));
|
||||||
|
if(lastCached != newest)
|
||||||
|
this->object->call(this->methodName, event, newest);
|
||||||
|
return (lastCached = newest);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerInput::Listener::operator==(godot::PlayerInput::Listener const& b) {
|
||||||
|
return this->methodName == b.methodName
|
||||||
|
&& this->object == b.object
|
||||||
|
&& this->actionNegative == b.actionNegative
|
||||||
|
&& this->actionPositive == b.actionPositive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerInput::_unhandled_input(Ref<InputEvent> const &event) {
|
||||||
|
GDGAMEONLY();
|
||||||
|
for(Listener& listener: this->listeners) {
|
||||||
|
if(listener.has_changed(event)) {
|
||||||
|
listener.evaluate(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerInput::listen_to(Listener const& listener) {
|
||||||
|
this->listeners.push_back(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerInput::stop_listening(Node *node) {
|
||||||
|
for(size_t i = 0; i < this->listeners.size(); ++i) {
|
||||||
|
Listener& l = this->listeners.at(i);
|
||||||
|
if(l.object == node) {
|
||||||
|
this->listeners.erase(this->listeners.begin() + i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerInput::stop_listening(Listener const& listener) {
|
||||||
|
std::vector<Listener>::iterator itr = std::find(this->listeners.begin(), this->listeners.end(), listener);
|
||||||
|
if(itr != this->listeners.end())
|
||||||
|
this->listeners.erase(itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef PLAYER_INPUT_HPP
|
||||||
|
#define PLAYER_INPUT_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "godot_cpp/classes/input_event.hpp"
|
||||||
|
#include "godot_cpp/classes/node.hpp"
|
||||||
|
|
||||||
|
namespace godot {
|
||||||
|
class PlayerInput : public Node {
|
||||||
|
GDCLASS(PlayerInput, Node)
|
||||||
|
static void _bind_methods();
|
||||||
|
public:
|
||||||
|
struct Listener {
|
||||||
|
String actionNegative{""};
|
||||||
|
String actionPositive{""};
|
||||||
|
float lastCached{0.f};
|
||||||
|
String methodName{""};
|
||||||
|
Node *object;
|
||||||
|
|
||||||
|
Listener(String positive, String negative, Node *object, String method);
|
||||||
|
bool has_changed(Ref<InputEvent> const &event);
|
||||||
|
float evaluate(Ref<InputEvent> const &event);
|
||||||
|
bool operator==(godot::PlayerInput::Listener const& b);
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
std::vector<Listener> listeners;
|
||||||
|
public:
|
||||||
|
virtual void _unhandled_input(Ref<InputEvent> const &event) override;
|
||||||
|
|
||||||
|
void listen_to(Listener const& listener);
|
||||||
|
void stop_listening(Node *node);
|
||||||
|
void stop_listening(Listener const& listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // !PLAYER_INPUT_HPP
|
|
@ -8,6 +8,7 @@
|
||||||
#include "game_mode.hpp"
|
#include "game_mode.hpp"
|
||||||
#include "level.hpp"
|
#include "level.hpp"
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
#include "player_input.hpp"
|
||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) {
|
||||||
ClassDB::register_class<Level>();
|
ClassDB::register_class<Level>();
|
||||||
ClassDB::register_class<Entrance>();
|
ClassDB::register_class<Entrance>();
|
||||||
ClassDB::register_class<GameMode>();
|
ClassDB::register_class<GameMode>();
|
||||||
|
ClassDB::register_class<PlayerInput>();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
Loading…
Reference in New Issue