fix: playerinput mouse events now report correctly

main
Sara 2024-02-19 01:04:14 +01:00
parent 460dc9a1c6
commit 2a2214b487
2 changed files with 90 additions and 20 deletions

View File

@ -4,10 +4,14 @@
#include "godot_cpp/classes/input_event.hpp" #include "godot_cpp/classes/input_event.hpp"
#include "godot_cpp/classes/input_event_mouse_motion.hpp" #include "godot_cpp/classes/input_event_mouse_motion.hpp"
#include <algorithm> #include <algorithm>
#include <optional>
namespace godot { namespace godot {
void PlayerInput::_bind_methods() {} 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) PlayerInput::Listener::Listener(String positive, String negative, Node *object, String method)
: actionNegative{negative} : actionNegative{negative}
, actionPositive{positive} , actionPositive{positive}
@ -15,40 +19,45 @@ PlayerInput::Listener::Listener(String positive, String negative, Node *object,
, object{object} , object{object}
, isMouseEvent{positive.begins_with("_mouse_") || negative.begins_with("_mouse_")} {} , isMouseEvent{positive.begins_with("_mouse_") || negative.begins_with("_mouse_")} {}
float PlayerInput::Listener::evaluate_event(Ref<InputEvent> const &event, String const &action) { std::optional<float> PlayerInput::Listener::evaluate_action(String const &action) {
Input *input = Input::get_singleton(); Input *input = Input::get_singleton();
if(!action.begins_with("_mouse_")) { if(action.begins_with("_mouse_")) {
return float(input->is_action_pressed(action)); 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 { } else {
InputEventMouseMotion *motion = Object::cast_to<InputEventMouseMotion>(*event); return float(input->is_action_pressed(action));
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 0.f;
} }
bool PlayerInput::Listener::has_changed(Ref<InputEvent> const &event) { bool PlayerInput::Listener::has_changed(Ref<InputEvent> const &event) {
return ( return (
(!event->is_class("InputEventMouseMotion") || (!event->is_class("InputEventMouseMotion") ||
this->isMouseEvent this->isMouseEvent) ||
) ||
event->is_action(this->actionNegative) || event->is_action(this->actionNegative) ||
event->is_action(this->actionPositive) event->is_action(this->actionPositive)
); );
} }
float PlayerInput::Listener::evaluate(Ref<InputEvent> const &event) { float PlayerInput::Listener::evaluate(Ref<InputEvent> const &event) {
float positive = PlayerInput::Listener::evaluate_event(event, this->actionPositive); std::optional<float> positive = PlayerInput::Listener::evaluate_action(this->actionPositive);
float negative = PlayerInput::Listener::evaluate_event(event, this->actionNegative); std::optional<float> negative = PlayerInput::Listener::evaluate_action(this->actionNegative);
float newest = positive - negative; if(!positive.has_value() || !negative.has_value())
if(lastCached != newest) return 0.f;
float newest = positive.value() - negative.value();
if(this->lastCached != newest || this->isMouseEvent)
this->object->call(this->methodName, event, newest); this->object->call(this->methodName, event, newest);
return (lastCached = newest); return (this->lastCached = newest);
} }
bool PlayerInput::Listener::operator==(godot::PlayerInput::Listener const& b) { 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; && 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<InputEvent> const &event) { void PlayerInput::_unhandled_input(Ref<InputEvent> const &event) {
GDGAMEONLY(); GDGAMEONLY();
if(this->isPrimary && event->is_class("InputEventMouseMotion"))
PlayerInput::lastMouseMotion = Object::cast_to<InputEventMouseMotion>(*event)->get_relative();
for(Listener& listener: this->listeners) { for(Listener& listener: this->listeners) {
if(listener.has_changed(event)) { if(listener.has_changed(event)) {
listener.evaluate(event); listener.evaluate(event);
@ -67,6 +98,10 @@ void PlayerInput::_unhandled_input(Ref<InputEvent> const &event) {
} }
} }
void PlayerInput::_process(double deltaTime) {
PlayerInput::lastMouseMotion = {0.f, 0.f};
}
void PlayerInput::listen_to(Listener const& listener) { void PlayerInput::listen_to(Listener const& listener) {
this->listeners.push_back(listener); this->listeners.push_back(listener);
} }

View File

@ -2,6 +2,7 @@
#define PLAYER_INPUT_HPP #define PLAYER_INPUT_HPP
#include <vector> #include <vector>
#include <optional>
#include "godot_cpp/classes/input.hpp" #include "godot_cpp/classes/input.hpp"
#include "godot_cpp/classes/input_event.hpp" #include "godot_cpp/classes/input_event.hpp"
#include "godot_cpp/classes/node.hpp" #include "godot_cpp/classes/node.hpp"
@ -11,27 +12,61 @@ class PlayerInput : public Node {
GDCLASS(PlayerInput, Node) GDCLASS(PlayerInput, Node)
static void _bind_methods(); static void _bind_methods();
public: 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<InputEvent>, 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 { struct Listener {
friend class PlayerInput; friend class PlayerInput;
private: private:
// the two actions, evaluated as positive - negative
String actionNegative{""}; String actionNegative{""};
String actionPositive{""}; 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}; float lastCached{0.f};
// name of the method to call, expected signature is void(Ref<InputEvent>, float)
String methodName{""}; String methodName{""};
// pointer to the node to call methodName on
Node *object{nullptr}; Node *object{nullptr};
// if either actionNegative or actionPositive is a _mouse_ event this will be true
bool isMouseEvent{false}; bool isMouseEvent{false};
public: public:
Listener(String positive, String negative, Node *object, String method); Listener(String positive, String negative, Node *object, String method);
static float evaluate_event(Ref<InputEvent> const &event, String const &action); // evaluate the current state of an action.
static std::optional<float> 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<InputEvent> const &event); bool has_changed(Ref<InputEvent> const &event);
// evaluate the event for changes to either actionPositive or actionNegative
float evaluate(Ref<InputEvent> const &event); float evaluate(Ref<InputEvent> const &event);
bool operator==(godot::PlayerInput::Listener const& b); bool operator==(godot::PlayerInput::Listener const& b);
}; };
private: 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<Listener> listeners{}; std::vector<Listener> listeners{};
public: public:
static Vector2 get_last_mouse_motion();
virtual void _enter_tree() override;
virtual void _exit_tree() override;
virtual void _unhandled_input(Ref<InputEvent> const &event) override; virtual void _unhandled_input(Ref<InputEvent> const &event) override;
virtual void _process(double deltaTime) override;
void listen_to(Listener const& listener); void listen_to(Listener const& listener);
void stop_listening(Node *node); void stop_listening(Node *node);