diff --git a/player_input.cpp b/player_input.cpp index db0d0cf..5d1ce0e 100644 --- a/player_input.cpp +++ b/player_input.cpp @@ -4,10 +4,14 @@ #include "godot_cpp/classes/input_event.hpp" #include "godot_cpp/classes/input_event_mouse_motion.hpp" #include +#include namespace godot { void PlayerInput::_bind_methods() {} +Vector2 PlayerInput::lastMouseMotion{0.f, 0.f}; +bool PlayerInput::primaryExists{false}; + PlayerInput::Listener::Listener(String positive, String negative, Node *object, String method) : actionNegative{negative} , actionPositive{positive} @@ -15,40 +19,45 @@ PlayerInput::Listener::Listener(String positive, String negative, Node *object, , object{object} , isMouseEvent{positive.begins_with("_mouse_") || negative.begins_with("_mouse_")} {} -float PlayerInput::Listener::evaluate_event(Ref const &event, String const &action) { +std::optional PlayerInput::Listener::evaluate_action(String const &action) { Input *input = Input::get_singleton(); - if(!action.begins_with("_mouse_")) { - return float(input->is_action_pressed(action)); + if(action.begins_with("_mouse_")) { + Vector2 vector = PlayerInput::get_last_mouse_motion(); + if(action.ends_with("_up")) + return vector.y > 0.f ? vector.y : 0.f; + else if(action.ends_with("_down")) + return vector.y < 0.f ? -vector.y : 0.f; + else if(action.ends_with("_right")) + return vector.x > 0.f ? vector.x : 0.f; + else if(action.ends_with("_left")) + return vector.x < 0.f ? -vector.x : 0.f; + else + return std::nullopt; + } else if(action.is_empty()) { + return 0.f; } else { - InputEventMouseMotion *motion = Object::cast_to(*event); - if(motion == nullptr) - return 0.f; - if(action.ends_with("_up") || action.ends_with("_right")) { - return motion->get_relative().x; - } if(action.ends_with("_right") || action.ends_with("_left")) { - return motion->get_relative().y; - } + return float(input->is_action_pressed(action)); } - return 0.f; } bool PlayerInput::Listener::has_changed(Ref const &event) { return ( (!event->is_class("InputEventMouseMotion") || - this->isMouseEvent - ) || + this->isMouseEvent) || event->is_action(this->actionNegative) || event->is_action(this->actionPositive) ); } float PlayerInput::Listener::evaluate(Ref const &event) { - float positive = PlayerInput::Listener::evaluate_event(event, this->actionPositive); - float negative = PlayerInput::Listener::evaluate_event(event, this->actionNegative); - float newest = positive - negative; - if(lastCached != newest) + std::optional positive = PlayerInput::Listener::evaluate_action(this->actionPositive); + std::optional negative = PlayerInput::Listener::evaluate_action(this->actionNegative); + if(!positive.has_value() || !negative.has_value()) + return 0.f; + float newest = positive.value() - negative.value(); + if(this->lastCached != newest || this->isMouseEvent) this->object->call(this->methodName, event, newest); - return (lastCached = newest); + return (this->lastCached = newest); } bool PlayerInput::Listener::operator==(godot::PlayerInput::Listener const& b) { @@ -58,8 +67,30 @@ bool PlayerInput::Listener::operator==(godot::PlayerInput::Listener const& b) { && this->actionPositive == b.actionPositive; } +Vector2 PlayerInput::get_last_mouse_motion() { + return PlayerInput::lastMouseMotion; +} + +void PlayerInput::_enter_tree() { + GDGAMEONLY(); + if(!PlayerInput::primaryExists) { + this->isPrimary = true; + PlayerInput::primaryExists = true; + } +} + +void PlayerInput::_exit_tree() { + GDGAMEONLY(); + if(this->isPrimary) { + this->isPrimary = false; + PlayerInput::primaryExists = false; + } +} + void PlayerInput::_unhandled_input(Ref const &event) { GDGAMEONLY(); + if(this->isPrimary && event->is_class("InputEventMouseMotion")) + PlayerInput::lastMouseMotion = Object::cast_to(*event)->get_relative(); for(Listener& listener: this->listeners) { if(listener.has_changed(event)) { listener.evaluate(event); @@ -67,6 +98,10 @@ void PlayerInput::_unhandled_input(Ref const &event) { } } +void PlayerInput::_process(double deltaTime) { + PlayerInput::lastMouseMotion = {0.f, 0.f}; +} + void PlayerInput::listen_to(Listener const& listener) { this->listeners.push_back(listener); } diff --git a/player_input.hpp b/player_input.hpp index 3bc1519..f300746 100644 --- a/player_input.hpp +++ b/player_input.hpp @@ -2,6 +2,7 @@ #define PLAYER_INPUT_HPP #include +#include #include "godot_cpp/classes/input.hpp" #include "godot_cpp/classes/input_event.hpp" #include "godot_cpp/classes/node.hpp" @@ -11,27 +12,61 @@ class PlayerInput : public Node { GDCLASS(PlayerInput, Node) static void _bind_methods(); public: + // a listener is a combination of a positive and negative action and a listener function. + // listener functions use godot's Object::call function. + // So they require a Node instance and a function name. + // The expected signature is void(Ref, float) + // actions can also be "special" actions prefixed with _. + // special actions include _mouse_up, _mouse_down, _mouse_left and _mouse_right + // which rather than checking action_is_down, + // will use PlayerInput::get_last_mouse_motion() to poll the current state. struct Listener { friend class PlayerInput; private: + // the two actions, evaluated as positive - negative String actionNegative{""}; String actionPositive{""}; + // the last cached action, if the newest result matches this, the event will be considered + // duplicate and ignored (not passed to listener) float lastCached{0.f}; + // name of the method to call, expected signature is void(Ref, float) String methodName{""}; + // pointer to the node to call methodName on Node *object{nullptr}; + // if either actionNegative or actionPositive is a _mouse_ event this will be true bool isMouseEvent{false}; public: Listener(String positive, String negative, Node *object, String method); - static float evaluate_event(Ref const &event, String const &action); + // evaluate the current state of an action. + static std::optional evaluate_action(String const &action); + // check if this event has any chance to result in a trigger, does not evaluate the event or + // poll current input state bool has_changed(Ref const &event); + // evaluate the event for changes to either actionPositive or actionNegative float evaluate(Ref const &event); + bool operator==(godot::PlayerInput::Listener const& b); }; private: + // the last mouse motion, updated by the primary instance + static Vector2 lastMouseMotion; + // does a primary instance exist + static bool primaryExists; + // is this the primary instance + // the primary instance is responsible for updating static + // variables like lastMouseMotion + bool isPrimary{false}; + + // current listeners for this instance std::vector listeners{}; public: + static Vector2 get_last_mouse_motion(); + + virtual void _enter_tree() override; + virtual void _exit_tree() override; virtual void _unhandled_input(Ref const &event) override; + virtual void _process(double deltaTime) override; void listen_to(Listener const& listener); void stop_listening(Node *node);