@tool class_name VibrationComponent extends Node ## A small helper node to quickly perform controller vibrations. #signal started_vibration # TODO: Add vibrations on top of one another if they play at the same- or similar time. #static var total_weak_vibration: float = 0.0 #static var total_strong_vibration: float = 0.0 #static var _vibration_handled: bool = false static var min_weak_magnitude_threshold: float = 0.0 static var min_strong_magnitude_threshold: float = 0.0 static var min_duration_threshold: float = 0.0 ## If [code]false[/code], calling [method vibrate] will not start a controller vibration. @export var enabled: bool = true: get = is_enabled, set = set_enabled ## [param duration] is the duration of the effect in seconds ## (a duration of [code]0[/code] will try to play the vibration indefinitely).[br] ## The vibration can be stopped early by calling [method stop_vibration]. @export var duration: float = 0.15: get = get_duration, set = set_duration ## The device index to perform the vibration on. @export var device: int = 0: get = get_device, set = set_device ## Delay the vibration by this amount. @export var delay: float = 0.0 @export var sync_to_audio: bool = false #region Editor tooling @warning_ignore_start("unused_private_class_variable") @export_tool_button("Test Vibration", "InputEventJoypadMotion") var _editor_test_vibration: Callable = vibrate @export_tool_button("Stop Vibration", "MissingNode") var _editor_stop_test_vibration: Callable = stop_vibration @warning_ignore_restore("unused_private_class_variable") #endregion @export_group("Magnitude") ## [param weak_magnitude] is the strength of the weak motor ## (between [code]0[/code] and [code]1[/code]). @export_range(0.0, 1.0) var weak_magnitude: float = 0.1: get = get_weak_magnitude, set = set_weak_magnitude ## [param strong_magnitude] is the strength of the strong motor ## (between [code]0[/code] and [code]1[/code]). @export_range(0.0, 1.0) var strong_magnitude: float = 0.2: get = get_strong_magnitude, set = set_strong_magnitude ## Multiplies [member weak_magnitude] and [member strong_magnitude] by this amount. @export var magnitude_multiplier: float = 1.0: get = get_magnitude_multiplier, set = set_magnitude_multiplier var animated_weak_magnitude: float = 0.0: set(value): animated_weak_magnitude = clampf(value, 0.0, 1.0) var animated_strong_magnitude: float = 0.0: set(value): animated_strong_magnitude = clampf(value, 0.0, 1.0) func _process(delta: float) -> void: if ( can_vibrate() and not is_zero_approx(animated_weak_magnitude + animated_strong_magnitude) ): var _weak_magnitude: float = animated_weak_magnitude var _strong_magnitude: float = animated_strong_magnitude _weak_magnitude = remap(_weak_magnitude, 0.0, 1.0, min_weak_magnitude_threshold * float(_weak_magnitude > 0.0), 1.0) _strong_magnitude = remap(_strong_magnitude, 0.0, 1.0, min_strong_magnitude_threshold * float(_strong_magnitude > 0.0), 1.0) Input.start_joy_vibration.call_deferred(device, _weak_magnitude, _strong_magnitude, delta) animated_weak_magnitude = 0.0 animated_strong_magnitude = 0.0 ## Performs the controller vibration based on given parameters of the component.[br] ## [b]Info[/b]: The [member enabled] parameter must be [code]true[/code]. func vibrate() -> void: if not can_vibrate(): return if delay > 0.0: await get_tree().create_timer(delay).timeout if sync_to_audio: var last_mix_time: float = AudioServer.get_time_since_last_mix() var output_latency: float = AudioServer.get_output_latency() #SPrint.print_msg("Audio Delay: %s" % (last_mix_time + output_latency)) await get_tree().create_timer(last_mix_time + output_latency).timeout var _weak_magnitude: float = clampf(weak_magnitude * magnitude_multiplier, 0.0, 1.0) var _strong_magnitude: float = clampf(strong_magnitude * magnitude_multiplier, 0.0, 1.0) _weak_magnitude = remap(_weak_magnitude, 0.0, 1.0, min_weak_magnitude_threshold * float(_weak_magnitude > 0.0), 1.0) _strong_magnitude = remap(_strong_magnitude, 0.0, 1.0, min_strong_magnitude_threshold * float(_strong_magnitude > 0.0), 1.0) duration = maxf(duration, min_duration_threshold) Input.start_joy_vibration(device, _weak_magnitude, _strong_magnitude, duration) #started_vibration.emit() ## Stops the vibration of the joypad, based on the [member device] parameter, ## started with [method vibrate]. func stop_vibration() -> void: Input.stop_joy_vibration(device) ## Don't vibrate if the user has it turned off. func can_vibrate() -> bool: if not Engine.is_editor_hint(): if ( not InputManager.using_controller #or not ProjectSettings.get_setting("game/input/controller_vibrations", true) ): return false return enabled func set_enabled(value: bool) -> void: enabled = value func is_enabled() -> bool: return enabled func set_device(device_id: int) -> void: device = device_id func get_device() -> int: return device func set_duration(length: float) -> void: duration = length func get_duration() -> float: return duration func set_magnitude_multiplier(multiplier_value: float) -> void: magnitude_multiplier = multiplier_value func get_magnitude_multiplier() -> float: return magnitude_multiplier func set_weak_magnitude(weak_value: float) -> void: weak_magnitude = weak_value func get_weak_magnitude() -> float: return weak_magnitude func set_strong_magnitude(strong_value: float) -> void: strong_magnitude = strong_value func get_strong_magnitude() -> float: return strong_magnitude