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

Multi-focus, player-specific input events. #29989

Closed
wants to merge 17 commits into from
Closed
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
72 changes: 41 additions & 31 deletions core/input_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ void InputMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action);

ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "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);
ClassDB::bind_method(D_METHOD("action_add_event", "action", "event", "player"), &InputMap::action_add_event, DEFVAL(PLAYER_1));
ClassDB::bind_method(D_METHOD("action_has_event", "action", "event", "player"), &InputMap::action_has_event, DEFVAL(PLAYER_ALL));
ClassDB::bind_method(D_METHOD("action_erase_event", "action", "event", "player"), &InputMap::action_erase_event, DEFVAL(PLAYER_1));
ClassDB::bind_method(D_METHOD("action_erase_events", "action"), &InputMap::action_erase_events);
ClassDB::bind_method(D_METHOD("get_action_list", "action"), &InputMap::_get_action_list);
ClassDB::bind_method(D_METHOD("event_is_action", "event", "action"), &InputMap::event_is_action);
ClassDB::bind_method(D_METHOD("get_action_list", "action", "player"), &InputMap::_get_action_list, DEFVAL(PLAYER_ALL));
ClassDB::bind_method(D_METHOD("event_is_action", "event", "action", "player"), &InputMap::event_is_action, DEFVAL(PLAYER_ALL));
ClassDB::bind_method(D_METHOD("load_from_globals"), &InputMap::load_from_globals);
}

Expand Down Expand Up @@ -99,18 +99,18 @@ 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<InputMap::ActionInput>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, ActionPlayer p_player, bool *p_pressed, float *p_strength) const {

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

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

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

int device = e->get_device();
int device = a.event->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 (a.event->action_match(p_event, p_pressed, p_strength, p_action.deadzone)) {
return E;
}
}
Expand All @@ -131,27 +131,32 @@ void InputMap::action_set_deadzone(const StringName &p_action, float p_deadzone)
input_map[p_action].deadzone = p_deadzone;
}

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

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

input_map[p_action].inputs.push_back(p_event);
ActionInput a;
a.player = p_player;
a.event = p_event;
input_map[p_action].inputs.push_back(a);
}

bool InputMap::action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event) {
bool InputMap::action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event, ActionPlayer p_player) {

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

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

ERR_FAIL_COND(p_player == PLAYER_ALL);
ERR_FAIL_COND(!input_map.has(p_action));

List<Ref<InputEvent> >::Element *E = _find_event(input_map[p_action], p_event);
List<ActionInput>::Element *E = _find_event(input_map[p_action], p_event, p_player);
if (E)
input_map[p_action].inputs.erase(E);
}
Expand All @@ -163,21 +168,24 @@ void InputMap::action_erase_events(const StringName &p_action) {
input_map[p_action].inputs.clear();
}

Array InputMap::_get_action_list(const StringName &p_action) {
Array InputMap::_get_action_list(const StringName &p_action, ActionPlayer p_player) {

Array ret;
const List<Ref<InputEvent> > *al = get_action_list(p_action);
const List<ActionInput> *al = get_action_list(p_action);
if (al) {
for (const List<Ref<InputEvent> >::Element *E = al->front(); E; E = E->next()) {
for (const List<ActionInput>::Element *E = al->front(); E; E = E->next()) {

ret.push_back(E->get());
ActionInput a = E->get();
if (p_player != a.player)
continue;
ret.push_back(a.event);
}
}

return ret;
}

const List<Ref<InputEvent> > *InputMap::get_action_list(const StringName &p_action) {
const List<InputMap::ActionInput> *InputMap::get_action_list(const StringName &p_action, ActionPlayer p_player) {

const Map<StringName, Action>::Element *E = input_map.find(p_action);
if (!E)
Expand All @@ -186,11 +194,11 @@ const List<Ref<InputEvent> > *InputMap::get_action_list(const StringName &p_acti
return &E->get().inputs;
}

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_is_action(const Ref<InputEvent> &p_event, const StringName &p_action, ActionPlayer p_player) const {
return event_get_action_status(p_event, p_action, NULL, NULL, p_player);
}

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, ActionPlayer p_player) const {
Map<StringName, Action>::Element *E = input_map.find(p_action);
if (!E) {
ERR_EXPLAIN("Request for nonexistent InputMap action: " + String(p_action));
Expand All @@ -208,8 +216,8 @@ 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);
if (event != NULL) {
List<ActionInput>::Element *a = _find_event(E->get(), p_event, p_player, &pressed, &strength);
if (a != NULL) {
if (p_pressed != NULL)
*p_pressed = pressed;
if (p_strength != NULL)
Expand Down Expand Up @@ -245,10 +253,12 @@ void InputMap::load_from_globals() {

add_action(name, deadzone);
for (int i = 0; i < events.size(); i++) {
Ref<InputEvent> event = events[i];
Dictionary ev = events[i];
Ref<InputEvent> event = ev["event"];
if (event.is_null())
continue;
action_add_event(name, event);
int player = ev["player"];
action_add_event(name, event, (ActionPlayer)player);
}
}
}
Expand Down
45 changes: 36 additions & 9 deletions core/input_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,50 @@ class InputMap : public Object {
GDCLASS(InputMap, Object);

public:
enum ActionPlayer {
PLAYER_ALL = 0,
PLAYER_1, // 1
PLAYER_2,
PLAYER_3,
PLAYER_4,
PLAYER_5,
PLAYER_6,
PLAYER_7,
PLAYER_8,
PLAYER_9,
PLAYER_10,
PLAYER_11,
PLAYER_12,
PLAYER_13,
PLAYER_14,
PLAYER_15,
PLAYER_16
};

/**
* A special value used to signify that a given Action can be triggered by any device
*/
static int ALL_DEVICES;

struct ActionInput {
ActionPlayer player;
Copy link
Contributor

@LikeLakers2 LikeLakers2 Jun 22, 2019

Choose a reason for hiding this comment

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

Just out of curiousity, will this being the ActionPlayer enum type limit the number of players to 16 (since the enum itself only goes up to 16)? Or will it still allow arbitrary numbers to be input here?

And since I'm here, I should also ask, what counts as a "player" to the engine?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It somehow limits the number of split-screen players, or local players with same input actions but different input events for such actions. It does not in any way the limit the number of players in a broader sense.

And since I'm here, I should also ask, what counts as a "player" to the engine?

It's just a number used to filter input events.
So you can have the same action names with different input events.

# Check if ui_left is pressed with the key associated to player 1 in InputMap
InputMap.is_action_pressed("ui_left", 1)

# Check if ui_left is pressed with the key associated to player 2 in InputMap
InputMap.is_action_pressed("ui_left", 2)

# Check if ui_left is pressed with any key configured in InputMap
InputMap.is_action_pressed("ui_left", 0)

Copy link
Contributor

@LikeLakers2 LikeLakers2 Jun 22, 2019

Choose a reason for hiding this comment

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

It somehow limits the number of split-screen players, or local players with same input actions but different input events for such actions. It does not in any way the limit the number of players in a broader sense.

Perhaps I worded my question wrong. I was asking if that number could be a number higher than 16, without having to edit and recompile the engine. Although I'll admit that the needs for a number higher than 16 are few and far between (if one even exists), so chances are that this question is moot.

It's just a number used to filter input events.

What does that number refer to? A controller index?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Perhaps I worded my question wrong. I was asking if that number could be a number higher than 16

Yes, it does limit it to 16, but as you said, it's very unlikely you need more that that.

What does that number refer to? A controller index?

No, the local order you would give your players. And the value you set in InputMap or pass to action_add_event

Ref<InputEvent> event;
};

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

private:
static InputMap *singleton;

mutable Map<StringName, Action> input_map;

List<Ref<InputEvent> >::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed = NULL, float *p_strength = NULL) const;
List<ActionInput>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, ActionPlayer p_player = PLAYER_ALL, bool *p_pressed = NULL, float *p_strength = NULL) const;

Array _get_action_list(const StringName &p_action);
Array _get_action_list(const StringName &p_action, ActionPlayer player = PLAYER_ALL);
Array _get_actions();

protected:
Expand All @@ -72,14 +97,14 @@ class InputMap : public Object {
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);
void action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event, ActionPlayer player = PLAYER_1);
bool action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event, ActionPlayer player = PLAYER_ALL);
void action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event, ActionPlayer player = PLAYER_1);
void action_erase_events(const StringName &p_action);

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 = NULL, float *p_strength = NULL) const;
const List<ActionInput> *get_action_list(const StringName &p_action, ActionPlayer player = PLAYER_ALL);
bool event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action, ActionPlayer player = PLAYER_ALL) const;
bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed = NULL, float *p_strength = NULL, ActionPlayer player = PLAYER_ALL) const;

const Map<StringName, Action> &get_action_map() const;
void load_from_globals();
Expand All @@ -88,4 +113,6 @@ class InputMap : public Object {
InputMap();
};

VARIANT_ENUM_CAST(InputMap::ActionPlayer);

#endif // INPUT_MAP_H
24 changes: 12 additions & 12 deletions core/os/input_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,30 @@ int InputEvent::get_device() const {
return device;
}

bool InputEvent::is_action(const StringName &p_action) const {
bool InputEvent::is_action(const StringName &p_action, int p_player) const {

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

bool InputEvent::is_action_pressed(const StringName &p_action) const {
bool InputEvent::is_action_pressed(const StringName &p_action, int p_player) const {

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

bool InputEvent::is_action_released(const StringName &p_action) const {
bool InputEvent::is_action_released(const StringName &p_action, int p_player) const {

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

float InputEvent::get_action_strength(const StringName &p_action) const {
float InputEvent::get_action_strength(const StringName &p_action, int p_player) 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, &pressed, &strength, (InputMap::ActionPlayer)p_player);
return valid ? strength : 0.0f;
}

Expand Down Expand Up @@ -111,10 +111,10 @@ 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_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_action", "action"), &InputEvent::is_action, DEFVAL(0));
ClassDB::bind_method(D_METHOD("is_action_pressed", "action"), &InputEvent::is_action_pressed, DEFVAL(0));
ClassDB::bind_method(D_METHOD("is_action_released", "action"), &InputEvent::is_action_released, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_action_strength", "action"), &InputEvent::get_action_strength, DEFVAL(0));

ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed);
ClassDB::bind_method(D_METHOD("is_echo"), &InputEvent::is_echo);
Expand Down
8 changes: 4 additions & 4 deletions core/os/input_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,10 @@ class InputEvent : public Resource {
void set_device(int p_device);
int get_device() const;

bool is_action(const StringName &p_action) const;
bool is_action_pressed(const StringName &p_action) const;
bool is_action_released(const StringName &p_action) const;
float get_action_strength(const StringName &p_action) const;
bool is_action(const StringName &p_action, int p_player = 0) const;
bool is_action_pressed(const StringName &p_action, int p_player = 0) const;
bool is_action_released(const StringName &p_action, int p_player = 0) const;
float get_action_strength(const StringName &p_action, int p_player = 0) const;

// To be removed someday, since they do not make sense for all events
virtual bool is_pressed() const;
Expand Down
Loading