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

Allow actions to provide an analog value #16902

Merged
merged 1 commit into from Apr 18, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 46 additions & 21 deletions core/input_map.cpp
Expand Up @@ -41,9 +41,10 @@ void InputMap::_bind_methods() {

ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action);
ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions);
ClassDB::bind_method(D_METHOD("add_action", "action"), &InputMap::add_action);
ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.5f));
ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action);

ClassDB::bind_method(D_METHOD("action_set_deadzone", "deadzone"), &InputMap::action_set_deadzone);
ClassDB::bind_method(D_METHOD("action_add_event", "action", "event"), &InputMap::action_add_event);
ClassDB::bind_method(D_METHOD("action_has_event", "action", "event"), &InputMap::action_has_event);
ClassDB::bind_method(D_METHOD("action_erase_event", "action", "event"), &InputMap::action_erase_event);
Expand All @@ -52,12 +53,13 @@ void InputMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_from_globals"), &InputMap::load_from_globals);
}

void InputMap::add_action(const StringName &p_action) {
void InputMap::add_action(const StringName &p_action, float p_deadzone) {

ERR_FAIL_COND(input_map.has(p_action));
input_map[p_action] = Action();
static int last_id = 1;
input_map[p_action].id = last_id;
input_map[p_action].deadzone = p_deadzone;
last_id++;
}

Expand Down Expand Up @@ -96,19 +98,21 @@ List<StringName> InputMap::get_actions() const {
return actions;
}

List<Ref<InputEvent> >::Element *InputMap::_find_event(List<Ref<InputEvent> > &p_list, const Ref<InputEvent> &p_event, bool p_action_test) const {
List<Ref<InputEvent> >::Element *InputMap::_find_event(Action p_action, const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength) const {

for (List<Ref<InputEvent> >::Element *E = p_list.front(); E; E = E->next()) {
for (List<Ref<InputEvent> >::Element *E = p_action.inputs.front(); E; E = E->next()) {

const Ref<InputEvent> e = E->get();

//if (e.type != Ref<InputEvent>::KEY && e.device != p_event.device) -- unsure about the KEY comparison, why is this here?
// continue;

int device = e->get_device();
if (device == ALL_DEVICES || device == p_event->get_device())
if (e->action_match(p_event))
if (device == ALL_DEVICES || device == p_event->get_device()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably just move this to InputEvent::action_match(), and pass deadzone, *pressed and *sterngth as optional arguments there , so we dont harcode it here.

This would add more flexibility for future events, and we could also use deadzone for InputEventJoyButton, which has pressure too on some controllers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't seen any progress on this either on this PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem as with event_is_action, since action_match is bound to GDscript I cannot use pointers in the function arguments. I could duplicate the function though, like with event_get_action_status().

if (e->action_match(p_event, p_pressed, p_strength, p_action.deadzone)) {
return E;
}
}
}

return NULL;
Expand All @@ -119,11 +123,18 @@ bool InputMap::has_action(const StringName &p_action) const {
return input_map.has(p_action);
}

void InputMap::action_set_deadzone(const StringName &p_action, float p_deadzone) {

ERR_FAIL_COND(!input_map.has(p_action));

input_map[p_action].deadzone = p_deadzone;
}

void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event) {

ERR_FAIL_COND(p_event.is_null());
ERR_FAIL_COND(!input_map.has(p_action));
if (_find_event(input_map[p_action].inputs, p_event))
if (_find_event(input_map[p_action], p_event))
return; //already gots

input_map[p_action].inputs.push_back(p_event);
Expand All @@ -132,14 +143,14 @@ void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent
bool InputMap::action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event) {

ERR_FAIL_COND_V(!input_map.has(p_action), false);
return (_find_event(input_map[p_action].inputs, p_event) != NULL);
return (_find_event(input_map[p_action], p_event) != NULL);
}

void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event) {

ERR_FAIL_COND(!input_map.has(p_action));

List<Ref<InputEvent> >::Element *E = _find_event(input_map[p_action].inputs, p_event);
List<Ref<InputEvent> >::Element *E = _find_event(input_map[p_action], p_event);
if (E)
input_map[p_action].inputs.erase(E);
}
Expand Down Expand Up @@ -168,19 +179,33 @@ const List<Ref<InputEvent> > *InputMap::get_action_list(const StringName &p_acti
}

bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action) const {
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 {
Map<StringName, Action>::Element *E = input_map.find(p_action);
if (!E) {
ERR_EXPLAIN("Request for nonexistent InputMap action: " + String(p_action));
ERR_FAIL_COND_V(!E, false);
}

Ref<InputEventAction> iea = p_event;
if (iea.is_valid()) {
return iea->get_action() == p_action;
Ref<InputEventAction> input_event_action = p_event;
if (input_event_action.is_valid()) {
return input_event_action->get_action() == p_action;
}

return _find_event(E->get().inputs, p_event, true) != NULL;
bool pressed;
float strength;
List<Ref<InputEvent> >::Element *event = _find_event(E->get(), p_event, &pressed, &strength);
if (event != NULL) {
if (p_pressed != NULL)
*p_pressed = pressed;
if (p_strength != NULL)
*p_strength = strength;
return true;
} else {
return false;
}
}

const Map<StringName, InputMap::Action> &InputMap::get_action_map() const {
Expand All @@ -202,16 +227,16 @@ void InputMap::load_from_globals() {

String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length());

add_action(name);

Array va = ProjectSettings::get_singleton()->get(pi.name);

for (int i = 0; i < va.size(); i++) {
Dictionary action = ProjectSettings::get_singleton()->get(pi.name);
float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.5f;
Array events = action["events"];

Ref<InputEvent> ie = va[i];
if (ie.is_null())
add_action(name, deadzone);
for (int i = 0; i < events.size(); i++) {
Ref<InputEvent> event = events[i];
if (event.is_null())
continue;
action_add_event(name, ie);
action_add_event(name, event);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions core/input_map.h
Expand Up @@ -46,6 +46,7 @@ class InputMap : public Object {

struct Action {
int id;
float deadzone;
List<Ref<InputEvent> > inputs;
};

Expand All @@ -54,7 +55,7 @@ class InputMap : public Object {

mutable Map<StringName, Action> input_map;

List<Ref<InputEvent> >::Element *_find_event(List<Ref<InputEvent> > &p_list, const Ref<InputEvent> &p_event, bool p_action_test = false) const;
List<Ref<InputEvent> >::Element *_find_event(Action p_action, const Ref<InputEvent> &p_event, bool *p_pressed = NULL, float *p_strength = NULL) const;

Array _get_action_list(const StringName &p_action);
Array _get_actions();
Expand All @@ -67,15 +68,17 @@ class InputMap : public Object {

bool has_action(const StringName &p_action) const;
List<StringName> get_actions() const;
void add_action(const StringName &p_action);
void add_action(const StringName &p_action, float p_deadzone = 0.5);
void erase_action(const StringName &p_action);

void action_set_deadzone(const StringName &p_action, float p_deadzone);
void action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event);
bool action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event);
void action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event);

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, add float *r_strength=NULL, bool *r_pressed=NULL as extra optional arguments to event_is_action instead of adding all the functions below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't do that, this function is exposed to GDscript which does not handles pointers.
That's why I added the event_get_action_status.

bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed = NULL, float *p_strength = NULL) const;

const Map<StringName, Action> &get_action_map() const;
void load_from_globals();
Expand Down
3 changes: 2 additions & 1 deletion core/os/input.cpp
Expand Up @@ -57,6 +57,7 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_action_pressed", "action"), &Input::is_action_pressed);
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("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 @@ -118,7 +119,7 @@ void Input::get_argument_options(const StringName &p_function, int p_idx, List<S
#ifdef TOOLS_ENABLED

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")) {
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")) {

List<PropertyInfo> pinfo;
ProjectSettings::get_singleton()->get_property_list(&pinfo);
Expand Down
1 change: 1 addition & 0 deletions core/os/input.h
Expand Up @@ -85,6 +85,7 @@ class Input : public Object {
virtual bool is_action_pressed(const StringName &p_action) const = 0;
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_joy_axis(int p_device, int p_axis) const = 0;
virtual String get_joy_name(int p_idx) = 0;
Expand Down
83 changes: 64 additions & 19 deletions core/os/input_event.cpp
Expand Up @@ -41,23 +41,36 @@ int InputEvent::get_device() const {
return device;
}

bool InputEvent::is_pressed() const {

return false;
}

bool InputEvent::is_action(const StringName &p_action) const {

return InputMap::get_singleton()->event_is_action(Ref<InputEvent>((InputEvent *)this), p_action);
}

bool InputEvent::is_action_pressed(const StringName &p_action) const {

return (is_pressed() && !is_echo() && is_action(p_action));
bool pressed;
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, &pressed);
return valid && pressed && !is_echo();
}

bool InputEvent::is_action_released(const StringName &p_action) const {

return (!is_pressed() && is_action(p_action));
bool pressed;
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, &pressed);
return valid && !pressed;
}

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);
return valid ? strength : 0.0f;
}

bool InputEvent::is_pressed() const {

return false;
}

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

bool InputEvent::action_match(const Ref<InputEvent> &p_event) const {
bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {

return false;
}
Expand All @@ -95,15 +108,16 @@ void InputEvent::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_device", "device"), &InputEvent::set_device);
ClassDB::bind_method(D_METHOD("get_device"), &InputEvent::get_device);

ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed);
ClassDB::bind_method(D_METHOD("is_action", "action"), &InputEvent::is_action);
ClassDB::bind_method(D_METHOD("is_action_pressed", "action"), &InputEvent::is_action_pressed);
ClassDB::bind_method(D_METHOD("is_action_released", "action"), &InputEvent::is_action_released);
ClassDB::bind_method(D_METHOD("get_action_strength", "action"), &InputEvent::get_action_strength);

ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed);
ClassDB::bind_method(D_METHOD("is_echo"), &InputEvent::is_echo);

ClassDB::bind_method(D_METHOD("as_text"), &InputEvent::as_text);

ClassDB::bind_method(D_METHOD("action_match", "event"), &InputEvent::action_match);
ClassDB::bind_method(D_METHOD("shortcut_match", "event"), &InputEvent::shortcut_match);

ClassDB::bind_method(D_METHOD("is_action_type"), &InputEvent::is_action_type);
Expand Down Expand Up @@ -281,7 +295,7 @@ String InputEventKey::as_text() const {
return kc;
}

bool InputEventKey::action_match(const Ref<InputEvent> &p_event) const {
bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {

Ref<InputEventKey> key = p_event;
if (key.is_null())
Expand All @@ -290,7 +304,14 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event) const {
uint32_t code = get_scancode_with_modifiers();
uint32_t event_code = key->get_scancode_with_modifiers();

return get_scancode() == key->get_scancode() && (!key->is_pressed() || (code & event_code) == code);
bool match = get_scancode() == key->get_scancode() && (!key->is_pressed() || (code & event_code) == code);
if (match) {
if (p_pressed != NULL)
*p_pressed = key->is_pressed();
if (p_strength != NULL)
*p_strength = (*p_pressed) ? 1.0f : 0.0f;
}
return match;
}

bool InputEventKey::shortcut_match(const Ref<InputEvent> &p_event) const {
Expand Down Expand Up @@ -446,13 +467,21 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co
return mb;
}

bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event) const {
bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {

Ref<InputEventMouseButton> mb = p_event;
if (mb.is_null())
return false;

return mb->button_index == button_index;
bool match = mb->button_index == button_index;
if (match) {
if (p_pressed != NULL)
*p_pressed = mb->is_pressed();
if (p_strength != NULL)
*p_strength = (*p_pressed) ? 1.0f : 0.0f;
}

return match;
}

String InputEventMouseButton::as_text() const {
Expand Down Expand Up @@ -610,23 +639,31 @@ void InputEventJoypadMotion::set_axis_value(float p_value) {

axis_value = p_value;
}

float InputEventJoypadMotion::get_axis_value() const {

return axis_value;
}

bool InputEventJoypadMotion::is_pressed() const {

return Math::abs(axis_value) > 0.5f;
return Math::abs(axis_value) >= 0.5f;
}

bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event) const {
bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {

Ref<InputEventJoypadMotion> jm = p_event;
if (jm.is_null())
return false;

return (axis == jm->axis && ((axis_value < 0) == (jm->axis_value < 0) || jm->axis_value == 0));
bool match = (axis == jm->axis && ((axis_value < 0) == (jm->axis_value < 0) || jm->axis_value == 0));
if (match) {
if (p_pressed != NULL)
*p_pressed = Math::abs(jm->get_axis_value() >= p_deadzone);
if (p_strength != NULL)
*p_strength = (*p_pressed) ? Math::inverse_lerp(p_deadzone, 1.0f, Math::abs(jm->get_axis_value())) : 0.0f;
}
return match;
}

String InputEventJoypadMotion::as_text() const {
Expand Down Expand Up @@ -681,13 +718,21 @@ float InputEventJoypadButton::get_pressure() const {
return pressure;
}

bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event) const {
bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {

Ref<InputEventJoypadButton> jb = p_event;
if (jb.is_null())
return false;

return button_index == jb->button_index;
bool match = button_index == jb->button_index;
if (match) {
if (p_pressed != NULL)
*p_pressed = jb->is_pressed();
if (p_strength != NULL)
*p_strength = (*p_pressed) ? 1.0f : 0.0f;
}

return match;
}

String InputEventJoypadButton::as_text() const {
Expand Down