using UnityEngine; using UnityEngine.InputSystem; namespace ProjectM.Client { /// /// Gamepad rumble for combat feel — a static bridge (mirrors / ). /// sets the motors and stamps a stop time; (called once per frame from /// CombatFeedbackSystem) stops them when the pulse elapses OR the app loses focus, so a rumble never sticks. /// A no-op when no pad is connected; the CALLER gates to the Gamepad scheme. Statics survive fast-enter-playmode /// reloads, so re-arms clean on play-enter and stops any leaked motor (the AimPresentation /// reset idiom). Presentation-only, main-thread, never touches the simulation. /// public static class RumbleUtil { static float s_StopTime; static bool s_Active; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void ResetState() { s_Active = false; s_StopTime = 0f; Stop(); } /// Pulse both motors at the given low/high strengths for durSec, then auto-stop. No-op without a pad. public static void Pulse(float low, float high, float durSec) { var pad = Gamepad.current; if (pad == null) return; pad.SetMotorSpeeds(Mathf.Clamp01(low), Mathf.Clamp01(high)); s_StopTime = Time.unscaledTime + Mathf.Max(0.02f, durSec); s_Active = true; } /// Call once per frame: stops the motors when the pulse elapses or focus is lost. public static void Tick() { if (!s_Active) return; if (!Application.isFocused || Time.unscaledTime >= s_StopTime) { Stop(); s_Active = false; } } static void Stop() { var pad = Gamepad.current; if (pad != null) pad.ResetHaptics(); } } }