diff --git a/core/input/input.cpp b/core/input/input.cpp index 38da1c6e85ac..f1b7879f8179 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -239,6 +239,45 @@ void Input::_bind_methods() { ADD_SIGNAL(MethodInfo("joy_connection_changed", PropertyInfo(Variant::INT, "device"), PropertyInfo(Variant::BOOL, "connected"))); } +void Input::_process(double p_delta) { + if (joy_echo_send_events && joy_echo_count_per_second > 0) { + const float delay_between_sending = 1.0 / joy_echo_count_per_second; + HashMap::Iterator iter = joy_echo.begin(); + while (iter != joy_echo.end()) { + JoyEchoInfo &echo = iter->value; + JoyEchoId echo_id; + echo_id.value = iter->key; + if (echo_id.is_axis && !joy_echo_send_axis_events) { + continue; + } + + int times_to_send = 0; + echo.time += p_delta; + + if (echo.waiting && echo.time > joy_echo_wait_time) { + echo.waiting = false; + echo.time -= joy_echo_wait_time; + times_to_send = 1; + } + + if (!echo.waiting && echo.time > delay_between_sending) { + times_to_send += (int)Math::floor(echo.time / delay_between_sending); + echo.time = Math::fmod(echo.time, delay_between_sending); + } + + for (int i = 0; i < times_to_send; i++) { + if (echo_id.is_axis) { + _axis_event(echo_id.device, (JoyAxis)echo_id.input_id, get_joy_axis(echo_id.device, (JoyAxis)echo_id.input_id), true); + } else { + _button_event(echo_id.device, (JoyButton)echo_id.input_id, true, true); + } + } + + ++iter; + } + } +} + #ifdef TOOLS_ENABLED void Input::get_argument_options(const StringName &p_function, int p_idx, List *r_options) const { const String pf = p_function; @@ -734,9 +773,11 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ for (int i = 0; i < (int)JoyButton::MAX; i++) { JoyButton c = _combine_device((JoyButton)i, p_idx); joy_buttons_pressed.erase(c); + joy_echo.erase(JoyEchoId(p_idx, (JoyButton)i).value); } for (int i = 0; i < (int)JoyAxis::MAX; i++) { set_joy_axis(p_idx, (JoyAxis)i, 0.0f); + joy_echo.erase(JoyEchoId(p_idx, (JoyAxis)i).value); } MotionInfo *motion = joy_motion.getptr(p_idx); if (motion != nullptr && motion->gamepad_motion != nullptr) { @@ -976,7 +1017,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em Ref jb = p_event; - if (jb.is_valid()) { + if (jb.is_valid() && !jb->is_echo()) { JoyButton c = _combine_device(jb->get_button_index(), jb->get_device()); if (jb->is_pressed()) { @@ -984,12 +1025,31 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em } else { joy_buttons_pressed.erase(c); } + + if (joy_echo_send_events) { + uint16_t echo_id = JoyEchoId(jb->get_device(), jb->get_button_index()).value; + if (jb->is_pressed()) { + joy_echo.insert(echo_id, JoyEchoInfo()); + } else { + joy_echo.erase(echo_id); + } + } } Ref jm = p_event; - if (jm.is_valid()) { + if (jm.is_valid() && !jm->is_echo()) { set_joy_axis(jm->get_device(), jm->get_axis(), jm->get_axis_value()); + if (joy_echo_send_events && joy_echo_send_axis_events) { + uint16_t echo_id = JoyEchoId(jm->get_device(), jm->get_axis()).value; + if (Math::abs(jm->get_axis_value()) >= joy_echo_axis_deadzone) { + if (!joy_echo.has(echo_id)) { + joy_echo.insert(echo_id, JoyEchoInfo()); + } + } else { + joy_echo.erase(echo_id); + } + } } Ref ge = p_event; @@ -1823,22 +1883,24 @@ void Input::joy_motion_sensors(int p_device, const Vector3 &p_accelerometer, con motion->gamepad_motion->ProcessMotion(gyro_degrees.x, gyro_degrees.y, gyro_degrees.z, accel_g.x, accel_g.y, accel_g.z, delta_time); } -void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed) { +void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed, bool p_echo) { Ref ievent; ievent.instantiate(); ievent->set_device(p_device); ievent->set_button_index(p_index); ievent->set_pressed(p_pressed); + ievent->set_echo(p_echo); parse_input_event(ievent); } -void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) { +void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value, bool p_echo) { Ref ievent; ievent.instantiate(); ievent->set_device(p_device); ievent->set_axis(p_axis); ievent->set_axis_value(p_value); + ievent->set_echo(p_echo); parse_input_event(ievent); } @@ -2372,6 +2434,11 @@ Input::Input() { gyroscope_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gyroscope", false); magnetometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_magnetometer", false); ignore_joypad_on_unfocused_application = GLOBAL_DEF_RST_BASIC("input_devices/joypads/ignore_joypad_on_unfocused_application", false); + joy_echo_send_events = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/send", false); + joy_echo_wait_time = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/wait_time", 0.5f); + joy_echo_count_per_second = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/count_per_second", 20); + joy_echo_send_axis_events = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/axis_echo_events/send", true); + joy_echo_axis_deadzone = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/axis_echo_events/deadzone", InputMap::DEFAULT_TOGGLE_DEADZONE); } Input::~Input() { diff --git a/core/input/input.h b/core/input/input.h index 6d732877efba..ad91d690f084 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -192,6 +192,43 @@ class Input : public Object { HashMap joy_motion; + union JoyEchoId { + struct { + uint16_t input_id : 7; // 0-128 to allow both axes and buttons + uint16_t device : 4; // 16 joypad devices + uint16_t is_axis : 1; + }; + uint16_t value; + + JoyEchoId() {} + + JoyEchoId(int p_device, JoyButton p_button) { + device = p_device; + input_id = (uint16_t)p_button; + is_axis = 0; + } + + JoyEchoId(int p_device, JoyAxis p_axis) { + device = p_device; + input_id = (uint16_t)p_axis; + is_axis = 1; + } + }; + + static_assert(sizeof(JoyEchoId) == sizeof(JoyEchoId::value), "Bitpacking doesn't work properly on this platform."); + + struct JoyEchoInfo { + bool waiting = true; + float time = 0.0; + }; + + HashMap joy_echo; + bool joy_echo_send_events = false; + float joy_echo_wait_time = 0.0; + int joy_echo_count_per_second = 0; + bool joy_echo_send_axis_events = false; + float joy_echo_axis_deadzone = 0.0; + struct VelocityTrack { uint64_t last_tick = 0; Vector2 velocity; @@ -297,8 +334,8 @@ class Input : public Object { void _get_mapped_hat_events(const JoyDeviceMapping &mapping, HatDir p_hat, JoyEvent r_events[(size_t)HatDir::MAX]); JoyButton _get_output_button(const String &output); JoyAxis _get_output_axis(const String &output); - void _button_event(int p_device, JoyButton p_index, bool p_pressed); - void _axis_event(int p_device, JoyAxis p_axis, float p_value); + void _button_event(int p_device, JoyButton p_index, bool p_pressed, bool p_echo = false); + void _axis_event(int p_device, JoyAxis p_axis, float p_value, bool p_echo = false); void _update_action_cache(const StringName &p_action_name, ActionState &r_action_state); void _update_joypad_features(int p_device); @@ -349,6 +386,8 @@ class Input : public Object { void get_argument_options(const StringName &p_function, int p_idx, List *r_options) const override; #endif + void _process(double p_delta); + static Input *get_singleton(); bool is_anything_pressed() const; diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 2d6c293c4155..9d321e01a52c 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1130,6 +1130,15 @@ float InputEventJoypadMotion::get_axis_value() const { return axis_value; } +void InputEventJoypadMotion::set_echo(bool p_enable) { + echo = p_enable; + emit_changed(); +} + +bool InputEventJoypadMotion::is_echo() const { + return echo; +} + bool InputEventJoypadMotion::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref jm = p_event; if (jm.is_null()) { @@ -1200,7 +1209,8 @@ String InputEventJoypadMotion::as_text() const { } String InputEventJoypadMotion::_to_string() { - return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f", axis, axis_value); + String e = is_echo() ? "true" : "false"; + return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f, echo=%s", axis, axis_value, e); } Ref InputEventJoypadMotion::create_reference(JoyAxis p_axis, float p_value, int p_device) { @@ -1220,8 +1230,11 @@ void InputEventJoypadMotion::_bind_methods() { ClassDB::bind_method(D_METHOD("set_axis_value", "axis_value"), &InputEventJoypadMotion::set_axis_value); ClassDB::bind_method(D_METHOD("get_axis_value"), &InputEventJoypadMotion::get_axis_value); + ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventJoypadMotion::set_echo); + ADD_PROPERTY(PropertyInfo(Variant::INT, "axis"), "set_axis", "get_axis"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "axis_value"), "set_axis_value", "get_axis_value"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo"); } /////////////////////////////////// @@ -1247,6 +1260,15 @@ float InputEventJoypadButton::get_pressure() const { return pressure; } +void InputEventJoypadButton::set_echo(bool p_enable) { + echo = p_enable; + emit_changed(); +} + +bool InputEventJoypadButton::is_echo() const { + return echo; +} + bool InputEventJoypadButton::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref jb = p_event; if (jb.is_null()) { @@ -1320,7 +1342,8 @@ String InputEventJoypadButton::as_text() const { String InputEventJoypadButton::_to_string() { String p = is_pressed() ? "true" : "false"; - return vformat("InputEventJoypadButton: button_index=%d, pressed=%s, pressure=%.2f", button_index, p, pressure); + String e = is_echo() ? "true" : "false"; + return vformat("InputEventJoypadButton: button_index=%d, pressed=%s, pressure=%.2f, echo=%s", button_index, p, pressure, e); } Ref InputEventJoypadButton::create_reference(JoyButton p_btn_index, int p_device) { @@ -1341,9 +1364,12 @@ void InputEventJoypadButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventJoypadButton::set_pressed); + ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventJoypadButton::set_echo); + ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pressure"), "set_pressure", "get_pressure"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo"); } /////////////////////////////////// diff --git a/core/input/input_event.h b/core/input/input_event.h index ec00dfdc5896..8fa263764b1f 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -314,6 +314,7 @@ class InputEventJoypadMotion : public InputEvent { JoyAxis axis = (JoyAxis)0; ///< Joypad axis float axis_value = 0; ///< -1 to 1 + bool echo = false; /// true if this is an echo key protected: static void _bind_methods(); @@ -324,6 +325,9 @@ class InputEventJoypadMotion : public InputEvent { void set_axis_value(float p_value); float get_axis_value() const; + void set_echo(bool p_enable); + virtual bool is_echo() const override; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; @@ -342,6 +346,8 @@ class InputEventJoypadButton : public InputEvent { JoyButton button_index = (JoyButton)0; float pressure = 0; //0 to 1 + + bool echo = false; /// true if this is an echo key protected: static void _bind_methods(); @@ -354,6 +360,9 @@ class InputEventJoypadButton : public InputEvent { void set_pressure(float p_pressure); float get_pressure() const; + void set_echo(bool p_enable); + virtual bool is_echo() const override; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml index 584b31f8fab0..36dce9107213 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -80,8 +80,10 @@ - Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey]). An echo event is a repeated key event sent when the user is holding down the key. Any other event type returns [code]false[/code]. - [b]Note:[/b] The rate at which echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior. + Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey] and [InputEventJoypadButton]). An echo event is a repeated event sent when the user is holding down the input. Any other event type returns [code]false[/code]. + [b]Note:[/b] The rate at which keyboard echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior. + [b]Note:[/b] To allow the engine to send joypad echo events, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/send] is set to [code]true[/code]. + To control when joypad echo events are sent and how often, see [member ProjectSettings.input_devices/joypads/joypad_echo_events/wait_time] and [member ProjectSettings.input_devices/joypads/joypad_echo_events/count_per_second]. diff --git a/doc/classes/InputEventJoypadButton.xml b/doc/classes/InputEventJoypadButton.xml index 3d1842fa77c1..90e9f8f56b83 100644 --- a/doc/classes/InputEventJoypadButton.xml +++ b/doc/classes/InputEventJoypadButton.xml @@ -13,6 +13,11 @@ Button identifier. One of the [enum JoyButton] button constants. + + If [code]true[/code], the button was already pressed before this event. An echo event is a repeated button event sent when the user is holding down the button. + [b]Note:[/b] To allow the engine to send joypad echo events, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/send] is set to [code]true[/code]. + To control when echo events are sent and how often, see [member ProjectSettings.input_devices/joypads/joypad_echo_events/wait_time] and [member ProjectSettings.input_devices/joypads/joypad_echo_events/count_per_second]. + If [code]true[/code], the button's state is pressed. If [code]false[/code], the button's state is released. diff --git a/doc/classes/InputEventJoypadMotion.xml b/doc/classes/InputEventJoypadMotion.xml index 2107c4ba686f..cfa96bf6c083 100644 --- a/doc/classes/InputEventJoypadMotion.xml +++ b/doc/classes/InputEventJoypadMotion.xml @@ -16,5 +16,11 @@ Current position of the joystick on the given axis. The value ranges from [code]-1.0[/code] to [code]1.0[/code]. A value of [code]0[/code] means the axis is in its resting position. + + If [code]true[/code], the axis was already in the same position before this event. An echo event is a repeated axis event sent when the user is keeping the axis over the deadzone specified by [member ProjectSettings.input_devices/joypads/joypad_echo_events/axis_echo_events/deadzone]. + [b]Note:[/b] To allow the engine to send joypad echo events, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/send] is set to [code]true[/code]. + To control when echo events are sent and how often, see [member ProjectSettings.input_devices/joypads/joypad_echo_events/wait_time] and [member ProjectSettings.input_devices/joypads/joypad_echo_events/count_per_second]. + To allow the engine to send joypad echo events for the axes, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/axis_echo_events/send] is set to [code]true[/code]. + diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ddcef3340e09..533cb72c503f 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1668,6 +1668,23 @@ If [code]true[/code], joypad input (including motion sensors) and LED light changes will be ignored and joypad vibration will be stopped when the application is not focused. + + The deadzone that is used by the engine to determine if joypad axis echo events should be sent. + + + If [code]true[/code], the engine will send joypad echo events for axis movement after the user has moved the joypad axes for long enough. + [b]Note:[/b] This setting has no effect if [member input_devices/joypads/joypad_echo_events/count_per_second] is set to [code]0[/code] or [member input_devices/joypads/joypad_echo_events/send] is set to [code]false[/code]. + + + The number of times per second joypad echo events should be sent by the engine after the user has pressed the buttons or kept the axes over the deadzone for long enough. + + + If [code]true[/code], the engine will send joypad echo events after the user has pressed the joypad buttons or moved the joypad axes for long enough. + [b]Note:[/b] This setting has no effect if [member input_devices/joypads/joypad_echo_events/count_per_second] is set to [code]0[/code]. + + + The amount of time of pressing the joypad buttons or keeping the joypad axes over the deadzone required for the engine to start sending echo events, in seconds. + Specifies the tablet driver to use. If left empty, the default driver will be used. [b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 33362c5114b9..0703c658a381 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -8269,6 +8269,7 @@ HashMap EditorNode::get_initial_settings() { HashMap settings; settings["physics/3d/physics_engine"] = "Jolt Physics"; settings["rendering/rendering_device/driver.windows"] = "d3d12"; + settings["input_devices/joypads/joypad_echo_events/send"] = true; return settings; } diff --git a/main/main.cpp b/main/main.cpp index 760ba81f12bb..11a797a83680 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -5002,6 +5002,7 @@ bool Main::iteration() { if (Input::get_singleton()->is_agile_input_event_flushing()) { Input::get_singleton()->flush_buffered_events(); } + Input::get_singleton()->_process(process_step * time_scale); uint64_t process_begin = OS::get_singleton()->get_ticks_usec();