Add support for joypad vibration checking

This commit is contained in:
Nintorch 2026-02-19 11:30:18 +05:00
parent 8db94f7b5e
commit c83c672d61
5 changed files with 93 additions and 8 deletions

View file

@ -146,6 +146,9 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_connected_joypads"), &Input::get_connected_joypads);
ClassDB::bind_method(D_METHOD("get_joy_vibration_strength", "device"), &Input::get_joy_vibration_strength);
ClassDB::bind_method(D_METHOD("get_joy_vibration_duration", "device"), &Input::get_joy_vibration_duration);
ClassDB::bind_method(D_METHOD("get_joy_vibration_remaining_duration", "device"), &Input::get_joy_vibration_remaining_duration);
ClassDB::bind_method(D_METHOD("is_joy_vibrating", "device"), &Input::is_joy_vibrating);
ClassDB::bind_method(D_METHOD("has_joy_vibration", "device"), &Input::has_joy_vibration);
ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0));
ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration);
ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0));
@ -625,6 +628,33 @@ float Input::get_joy_vibration_duration(int p_device) {
}
}
float Input::get_joy_vibration_remaining_duration(int p_device) {
_THREAD_SAFE_METHOD_
const Joypad *joypad = joy_names.getptr(p_device);
if (joypad == nullptr || !joypad->has_vibration) {
return 0.f;
}
const VibrationInfo *vibration = joy_vibration.getptr(p_device);
if (vibration == nullptr || (vibration->weak_magnitude == 0.f && vibration->strong_magnitude == 0.f) || vibration->duration < 0.f) {
return 0.f;
}
float vibration_duration = vibration->duration;
if (vibration_duration > 0xFFFF / 1000.f || vibration_duration == 0.f) {
vibration_duration = 0xFFFF / 1000.f; // SDL_MAX_RUMBLE_DURATION_MS / 1000.f
}
return MAX(vibration_duration - (OS::get_singleton()->get_ticks_usec() - vibration->timestamp) / 1e6, 0.f);
}
bool Input::is_joy_vibrating(int p_device) {
return get_joy_vibration_remaining_duration(p_device) > 0.0f;
}
bool Input::has_joy_vibration(int p_device) const {
_THREAD_SAFE_METHOD_
const Joypad *joypad = joy_names.getptr(p_device);
return joypad != nullptr && joypad->has_vibration;
}
static String _hex_str(uint8_t p_byte) {
static const char *dict = "0123456789abcdef";
char ret[3];
@ -1738,6 +1768,9 @@ void Input::_update_joypad_features(int p_device) {
if (!joypad || joypad->features == nullptr) {
return;
}
if (joypad->features->has_joy_vibration()) {
joypad->has_vibration = true;
}
if (joypad->features->has_joy_light()) {
joypad->has_light = true;
}

View file

@ -91,6 +91,8 @@ public:
public:
virtual ~JoypadFeatures() {}
virtual bool has_joy_vibration() const { return false; }
virtual bool has_joy_light() const { return false; }
virtual void set_joy_light(const Color &p_color) {}
@ -212,6 +214,7 @@ private:
int hat_current = 0;
Dictionary info;
bool has_light = false;
bool has_vibration = false;
Input::JoypadFeatures *features = nullptr;
};
@ -362,7 +365,10 @@ public:
TypedArray<int> get_connected_joypads();
Vector2 get_joy_vibration_strength(int p_device);
float get_joy_vibration_duration(int p_device);
float get_joy_vibration_remaining_duration(int p_device);
uint64_t get_joy_vibration_timestamp(int p_device);
bool is_joy_vibrating(int p_device);
bool has_joy_vibration(int p_device) const;
void joy_connection_changed(int p_idx, bool p_connected, const String &p_name, const String &p_guid = "", const Dictionary &p_joypad_info = Dictionary());
Vector3 get_gravity() const;

View file

@ -221,6 +221,15 @@
<param index="0" name="device" type="int" />
<description>
Returns the duration of the current vibration effect in seconds.
[b]Note:[/b] This method returns the same value that was passed to [method start_joy_vibration], and this value does [b]not[/b] change when the joypad's vibration runs out, it only gets reset after a call to [method stop_joy_vibration].
If you want to check if a joypad is still vibrating, use [method is_joy_vibrating] instead.
</description>
</method>
<method name="get_joy_vibration_remaining_duration" experimental="">
<return type="float" />
<param index="0" name="device" type="int" />
<description>
Returns the remaining duration of the current vibration effect in seconds.
</description>
</method>
<method name="get_joy_vibration_strength">
@ -228,6 +237,8 @@
<param index="0" name="device" type="int" />
<description>
Returns the strength of the joypad vibration: x is the strength of the weak motor, and y is the strength of the strong motor.
[b]Note:[/b] This method returns the same values that were passed to [method start_joy_vibration], and these values do [b]not[/b] change when the joypad's vibration runs out, they only get reset after a call to [method stop_joy_vibration].
If you want to check if a joypad is still vibrating, use [method is_joy_vibrating] instead.
</description>
</method>
<method name="get_last_mouse_screen_velocity">
@ -285,6 +296,14 @@
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
</description>
</method>
<method name="has_joy_vibration" qualifiers="const">
<return type="bool" />
<param index="0" name="device" type="int" />
<description>
Returns [code]true[/code] if the joypad supports vibration. See also [method start_joy_vibration].
[b]Note:[/b] For macOS, vibration is only supported in macOS 11 and later. When connected via USB, vibration is only supported for major brand controllers (except Xbox One and Xbox Series X/S controllers) due to macOS limitations.
</description>
</method>
<method name="is_action_just_pressed" qualifiers="const">
<return type="bool" />
<param index="0" name="action" type="StringName" />
@ -392,6 +411,14 @@
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
</description>
</method>
<method name="is_joy_vibrating" experimental="">
<return type="bool" />
<param index="0" name="device" type="int" />
<description>
Returns [code]true[/code] if the joypad is still vibrating after a call to [method start_joy_vibration].
Unlike [method get_joy_vibration_strength] and [method get_joy_vibration_duration], this method returns [code]false[/code] after the joypad's vibration runs out.
</description>
</method>
<method name="is_key_label_pressed" qualifiers="const">
<return type="bool" />
<param index="0" name="keycode" type="int" enum="Key" />
@ -664,8 +691,13 @@
<param index="2" name="strong_magnitude" type="float" />
<param index="3" name="duration" type="float" default="0" />
<description>
Starts to vibrate the joypad. Joypads usually come with two rumble motors, a strong and a weak one. [param weak_magnitude] is the strength of the weak motor (between 0 and 1) and [param strong_magnitude] is the strength of the strong motor (between 0 and 1). [param duration] is the duration of the effect in seconds (a duration of 0 will try to play the vibration indefinitely). The vibration can be stopped early by calling [method stop_joy_vibration].
[b]Note:[/b] Not every hardware is compatible with long effect durations; it is recommended to restart an effect if it has to be played for more than a few seconds.
Starts to vibrate the joypad. See also [method has_joy_vibration] and [method is_joy_vibrating].
Joypads usually come with two rumble motors, a strong and a weak one.
[param weak_magnitude] is the strength of the weak motor (between [code]0.0[/code] and [code]1.0[/code]).
[param strong_magnitude] is the strength of the strong motor (between [code]0.0[/code] and [code]1.0[/code]).
[param duration] is the duration of the effect in seconds (a duration of [code]0.0[/code] will try to play the vibration as long as possible, which is about 65 seconds).
The vibration can be stopped early by calling [method stop_joy_vibration].
See also [method get_joy_vibration_strength] and [method get_joy_vibration_duration].
[b]Note:[/b] For macOS, vibration is only supported in macOS 11 and later. When connected via USB, vibration is only supported for major brand controllers (except Xbox One and Xbox Series X/S controllers) due to macOS limitations.
</description>
</method>

View file

@ -95,6 +95,17 @@ void JoypadSDL::process_events() {
SDL_Joystick *sdl_joy = SDL_GetJoystickFromID(joypads[i].sdl_instance_idx);
Vector2 strength = Input::get_singleton()->get_joy_vibration_strength(i);
// Invalid values for strength are filtered by Input::start_joy_vibration().
float duration = Input::get_singleton()->get_joy_vibration_duration(i);
Uint32 duration_ms = 0;
if (duration < 0.0f) {
continue; // Invalid duration.
} else if (duration == 0.0f) {
duration_ms = 0xFFFF; // SDL_MAX_RUMBLE_DURATION_MS
} else {
duration_ms = duration * 1000;
}
/*
If the vibration was requested to start, SDL_RumbleJoystick will start it.
@ -103,13 +114,10 @@ void JoypadSDL::process_events() {
Here strength.y goes first and then strength.x, because Input.get_joy_vibration_strength().x
is vibration's weak magnitude (high frequency rumble), and .y is strong magnitude (low frequency rumble),
SDL_RumbleJoystick takes low frequency rumble first and then high frequency rumble.
Rumble strength goes from 0 to 0xFFFF.
*/
SDL_RumbleJoystick(
sdl_joy,
// Rumble strength goes from 0 to 0xFFFF
strength.y * UINT16_MAX,
strength.x * UINT16_MAX,
Input::get_singleton()->get_joy_vibration_duration(i) * 1000);
SDL_RumbleJoystick(sdl_joy, strength.y * UINT16_MAX, strength.x * UINT16_MAX, duration_ms);
}
}
}
@ -333,6 +341,10 @@ void JoypadSDL::Joypad::set_joy_motion_sensors_enabled(bool p_enable) {
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, p_enable);
}
bool JoypadSDL::Joypad::has_joy_vibration() const {
return supports_force_feedback;
}
SDL_Joystick *JoypadSDL::Joypad::get_sdl_joystick() const {
return SDL_GetJoystickFromID(sdl_instance_idx);
}

View file

@ -62,6 +62,8 @@ private:
virtual bool has_joy_motion_sensors() const override;
virtual void set_joy_motion_sensors_enabled(bool p_enable) override;
virtual bool has_joy_vibration() const override;
SDL_Joystick *get_sdl_joystick() const;
SDL_Gamepad *get_sdl_gamepad() const;
};