Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Allow getting Input axis/vector values from multiple actions #50788

Merged
merged 2 commits into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions core/input_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ List<StringName> InputMap::get_actions() const {
return actions;
}

List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength) const {
List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength) const {
ERR_FAIL_COND_V(!p_event.is_valid(), nullptr);

for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) {
Expand All @@ -136,7 +136,7 @@ List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Re

int device = e->get_device();
if (device == ALL_DEVICES || device == p_event->get_device()) {
if (e->action_match(p_event, p_pressed, p_strength, p_action.deadzone)) {
if (e->action_match(p_event, p_pressed, p_strength, p_raw_strength, p_action.deadzone)) {
return E;
}
}
Expand Down Expand Up @@ -221,7 +221,7 @@ bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName
return event_get_action_status(p_event, p_action);
}

bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed, float *p_strength) const {
bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed, float *p_strength, float *p_raw_strength) const {
Map<StringName, Action>::Element *E = input_map.find(p_action);
ERR_FAIL_COND_V_MSG(!E, false, _suggest_actions(p_action));

Expand All @@ -238,14 +238,18 @@ bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const Str

bool pressed;
float strength;
List<Ref<InputEvent>>::Element *event = _find_event(E->get(), p_event, &pressed, &strength);
float raw_strength;
List<Ref<InputEvent>>::Element *event = _find_event(E->get(), p_event, &pressed, &strength, &raw_strength);
if (event != nullptr) {
if (p_pressed != nullptr) {
*p_pressed = pressed;
}
if (p_strength != nullptr) {
*p_strength = strength;
}
if (p_raw_strength != nullptr) {
*p_raw_strength = raw_strength;
}
return true;
} else {
return false;
Expand Down
4 changes: 2 additions & 2 deletions core/input_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class InputMap : public Object {

mutable Map<StringName, Action> input_map;

List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed = nullptr, float *p_strength = nullptr) const;
List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed = nullptr, float *p_strength = nullptr, float *p_raw_strength = nullptr) const;

Array _get_action_list(const StringName &p_action);
Array _get_actions();
Expand All @@ -80,7 +80,7 @@ class InputMap : public Object {

const List<Ref<InputEvent>> *get_action_list(const StringName &p_action);
bool event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action) const;
bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed = nullptr, float *p_strength = nullptr) const;
bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed = nullptr, float *p_strength = nullptr, float *p_raw_strength = nullptr) const;

const Map<StringName, Action> &get_action_map() const;
void load_from_globals();
Expand Down
10 changes: 8 additions & 2 deletions core/os/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action"), &Input::is_action_just_pressed);
ClassDB::bind_method(D_METHOD("is_action_just_released", "action"), &Input::is_action_just_released);
ClassDB::bind_method(D_METHOD("get_action_strength", "action"), &Input::get_action_strength);
ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action"), &Input::get_action_raw_strength);
ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action"), &Input::get_axis);
ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone"), &Input::get_vector, DEFVAL(-1.0f));
ClassDB::bind_method(D_METHOD("add_joy_mapping", "mapping", "update_existing"), &Input::add_joy_mapping, DEFVAL(false));
ClassDB::bind_method(D_METHOD("remove_joy_mapping", "guid"), &Input::remove_joy_mapping);
ClassDB::bind_method(D_METHOD("joy_connection_changed", "device", "connected", "name", "guid"), &Input::joy_connection_changed);
Expand Down Expand Up @@ -125,10 +128,13 @@ void Input::_bind_methods() {
void Input::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
#ifdef TOOLS_ENABLED

const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\"";
const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";

String pf = p_function;
if (p_idx == 0 && (pf == "is_action_pressed" || pf == "action_press" || pf == "action_release" || pf == "is_action_just_pressed" || pf == "is_action_just_released" || pf == "get_action_strength")) {
if (p_idx == 0 && (pf == "is_action_pressed" || pf == "action_press" || pf == "action_release" ||
pf == "is_action_just_pressed" || pf == "is_action_just_released" ||
pf == "get_action_strength" || pf == "get_action_raw_strength" ||
pf == "get_axis" || pf == "get_vector")) {
List<PropertyInfo> pinfo;
ProjectSettings::get_singleton()->get_property_list(&pinfo);

Expand Down
4 changes: 4 additions & 0 deletions core/os/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ class Input : public Object {
virtual bool is_action_just_pressed(const StringName &p_action) const = 0;
virtual bool is_action_just_released(const StringName &p_action) const = 0;
virtual float get_action_strength(const StringName &p_action) const = 0;
virtual float get_action_raw_strength(const StringName &p_action) const = 0;

float get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const;
Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f) const;

virtual float get_joy_axis(int p_device, int p_axis) const = 0;
virtual String get_joy_name(int p_idx) = 0;
Expand Down
21 changes: 13 additions & 8 deletions core/os/input_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,17 @@ bool InputEvent::is_action_released(const StringName &p_action) const {
}

float InputEvent::get_action_strength(const StringName &p_action) const {
bool pressed;
float strength;
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, &pressed, &strength);
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, nullptr, &strength);
return valid ? strength : 0.0f;
}

float InputEvent::get_action_raw_strength(const StringName &p_action) const {
float raw_strength;
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, nullptr, nullptr, &raw_strength);
return valid ? raw_strength : 0.0f;
}

bool InputEvent::is_pressed() const {
return false;
}
Expand All @@ -83,7 +88,7 @@ String InputEvent::as_text() const {
return String();
}

bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
return false;
}

Expand Down Expand Up @@ -290,7 +295,7 @@ String InputEventKey::as_text() const {
return kc;
}

bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventKey> key = p_event;
if (key.is_null()) {
return false;
Expand Down Expand Up @@ -460,7 +465,7 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co
return mb;
}

bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_null()) {
return false;
Expand Down Expand Up @@ -709,7 +714,7 @@ bool InputEventJoypadMotion::is_pressed() const {
return Math::abs(axis_value) >= 0.5f;
}

bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventJoypadMotion> jm = p_event;
if (jm.is_null()) {
return false;
Expand Down Expand Up @@ -780,7 +785,7 @@ float InputEventJoypadButton::get_pressure() const {
return pressure;
}

bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventJoypadButton> jb = p_event;
if (jb.is_null()) {
return false;
Expand Down Expand Up @@ -999,7 +1004,7 @@ bool InputEventAction::is_action(const StringName &p_action) const {
return action == p_action;
}

bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventAction> act = p_event;
if (act.is_null()) {
return false;
Expand Down
13 changes: 7 additions & 6 deletions core/os/input_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ class InputEvent : public Resource {
bool is_action_pressed(const StringName &p_action, bool p_allow_echo = false) const;
bool is_action_released(const StringName &p_action) const;
float get_action_strength(const StringName &p_action) const;
float get_action_raw_strength(const StringName &p_action) const;

// To be removed someday, since they do not make sense for all events
virtual bool is_pressed() const;
Expand All @@ -212,7 +213,7 @@ class InputEvent : public Resource {

virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const;

virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const;
virtual bool shortcut_match(const Ref<InputEvent> &p_event) const;
virtual bool is_action_type() const;

Expand Down Expand Up @@ -298,7 +299,7 @@ class InputEventKey : public InputEventWithModifiers {
uint32_t get_scancode_with_modifiers() const;
uint32_t get_physical_scancode_with_modifiers() const;

virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const;
virtual bool shortcut_match(const Ref<InputEvent> &p_event) const;

virtual bool is_action_type() const { return true; }
Expand Down Expand Up @@ -357,7 +358,7 @@ class InputEventMouseButton : public InputEventMouse {
bool is_doubleclick() const;

virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const;

virtual bool is_action_type() const { return true; }
virtual String as_text() const;
Expand Down Expand Up @@ -414,7 +415,7 @@ class InputEventJoypadMotion : public InputEvent {

virtual bool is_pressed() const;

virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const;

virtual bool is_action_type() const { return true; }
virtual String as_text() const;
Expand All @@ -441,7 +442,7 @@ class InputEventJoypadButton : public InputEvent {
void set_pressure(float p_pressure);
float get_pressure() const;

virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const;
virtual bool shortcut_match(const Ref<InputEvent> &p_event) const;

virtual bool is_action_type() const { return true; }
Expand Down Expand Up @@ -526,7 +527,7 @@ class InputEventAction : public InputEvent {

virtual bool is_action(const StringName &p_action) const;

virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const;
virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const;

virtual bool shortcut_match(const Ref<InputEvent> &p_event) const;
virtual bool is_action_type() const { return true; }
Expand Down
40 changes: 40 additions & 0 deletions doc/classes/Input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@
[b]Note:[/b] This method only works on iOS, Android, and UWP. On other platforms, it always returns [constant Vector3.ZERO]. On Android the unit of measurement for each axis is m/s² while on iOS and UWP it's a multiple of the Earth's gravitational acceleration [code]g[/code] (~9.81 m/s²).
</description>
</method>
<method name="get_action_raw_strength" qualifiers="const">
<return type="float">
</return>
<argument index="0" name="action" type="String">
</argument>
<description>
Returns a value between 0 and 1 representing the raw intensity of the given action, ignoring the action's deadzone. In most cases, you should use [method get_action_strength] instead.
</description>
</method>
<method name="get_action_strength" qualifiers="const">
<return type="float">
</return>
Expand All @@ -63,6 +72,18 @@
Returns a value between 0 and 1 representing the intensity of the given action. In a joypad, for example, the further away the axis (analog sticks or L2, R2 triggers) is from the dead zone, the closer the value will be to 1. If the action is mapped to a control that has no axis as the keyboard, the value returned will be 0 or 1.
</description>
</method>
<method name="get_axis" qualifiers="const">
<return type="float">
</return>
<argument index="0" name="negative_action" type="String">
</argument>
<argument index="1" name="positive_action" type="String">
</argument>
<description>
Get axis input by specifying two actions, one negative and one positive.
This is a shorthand for writing [code]Input.get_action_strength("positive_action") - Input.get_action_strength("negative_action")[/code].
</description>
</method>
<method name="get_connected_joypads">
<return type="Array">
</return>
Expand Down Expand Up @@ -205,6 +226,25 @@
Returns the mouse mode. See the constants for more information.
</description>
</method>
<method name="get_vector" qualifiers="const">
<return type="Vector2">
</return>
<argument index="0" name="negative_x" type="String">
</argument>
<argument index="1" name="positive_x" type="String">
</argument>
<argument index="2" name="negative_y" type="String">
</argument>
<argument index="3" name="positive_y" type="String">
</argument>
<argument index="4" name="deadzone" type="float" default="-1.0">
</argument>
<description>
Gets an input vector by specifying four actions for the positive and negative X and Y axes.
This method is useful when getting vector input, such as from a joystick, directional pad, arrows, or WASD. The vector has its length limited to 1 and has a circular deadzone, which is useful for using vector input as movement.
By default, the deadzone is automatically calculated from the average of the action deadzones. However, you can override the deadzone to be whatever you want (on the range of 0 to 1).
</description>
</method>
<method name="is_action_just_pressed" qualifiers="const">
<return type="bool">
</return>
Expand Down
48 changes: 47 additions & 1 deletion main/input_default.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,50 @@ float InputDefault::get_action_strength(const StringName &p_action) const {
return E->get().strength;
}

float InputDefault::get_action_raw_strength(const StringName &p_action) const {
#ifdef DEBUG_ENABLED
bool has_action = InputMap::get_singleton()->has_action(p_action);
ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'.");
#endif
const Map<StringName, Action>::Element *E = action_state.find(p_action);
if (!E) {
return 0.0f;
}

return E->get().raw_strength;
}

float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const {
return get_action_strength(p_positive_action) - get_action_strength(p_negative_action);
}

Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone) const {
Vector2 vector = Vector2(
get_action_raw_strength(p_positive_x) - get_action_raw_strength(p_negative_x),
get_action_raw_strength(p_positive_y) - get_action_raw_strength(p_negative_y));

if (p_deadzone < 0.0f) {
// If the deadzone isn't specified, get it from the average of the actions.
p_deadzone = (InputMap::get_singleton()->action_get_deadzone(p_positive_x) +
InputMap::get_singleton()->action_get_deadzone(p_negative_x) +
InputMap::get_singleton()->action_get_deadzone(p_positive_y) +
InputMap::get_singleton()->action_get_deadzone(p_negative_y)) /
4;
}

// Circular length limiting and deadzone.
float length = vector.length();
if (length <= p_deadzone) {
return Vector2();
} else if (length > 1.0f) {
return vector / length;
} else {
// Inverse lerp length to map (p_deadzone, 1) to (0, 1).
return vector * (Math::inverse_lerp(p_deadzone, 1.0f, length) / length);
}
return vector;
}

float InputDefault::get_joy_axis(int p_device, int p_axis) const {
_THREAD_SAFE_METHOD_
int c = _combine_device(p_axis, p_device);
Expand Down Expand Up @@ -424,10 +468,12 @@ void InputDefault::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool
action.physics_frame = Engine::get_singleton()->get_physics_frames();
action.idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed = p_event->is_action_pressed(E->key());
action.strength = 0.f;
action.strength = 0.0f;
action.raw_strength = 0.0f;
action_state[E->key()] = action;
}
action_state[E->key()].strength = p_event->get_action_strength(E->key());
action_state[E->key()].raw_strength = p_event->get_action_raw_strength(E->key());
}
}

Expand Down
2 changes: 2 additions & 0 deletions main/input_default.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class InputDefault : public Input {
uint64_t idle_frame;
bool pressed;
float strength;
float raw_strength;
};

Map<StringName, Action> action_state;
Expand Down Expand Up @@ -224,6 +225,7 @@ class InputDefault : public Input {
virtual bool is_action_just_pressed(const StringName &p_action) const;
virtual bool is_action_just_released(const StringName &p_action) const;
virtual float get_action_strength(const StringName &p_action) const;
virtual float get_action_raw_strength(const StringName &p_action) const;

virtual float get_joy_axis(int p_device, int p_axis) const;
String get_joy_name(int p_idx);
Expand Down