using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace MonoGame.Extended.Input.InputListeners
{
///
/// This is a listener that exposes several events for easier handling of gamepads.
///
public class GamePadListener : InputListener
{
private static readonly bool[] _gamePadConnections = new bool[4];
// These buttons are not to be evaluated normally, but with the debounce filter
// in their respective methods.
private readonly Buttons[] _excludedButtons =
{
Buttons.LeftTrigger, Buttons.RightTrigger,
Buttons.LeftThumbstickDown, Buttons.LeftThumbstickUp, Buttons.LeftThumbstickRight,
Buttons.LeftThumbstickLeft,
Buttons.RightThumbstickLeft, Buttons.RightThumbstickRight, Buttons.RightThumbstickUp,
Buttons.RightThumbstickDown
};
private GamePadState _currentState;
//private int _lastPacketNumber;
// Implementation doesn't work, see explanation in CheckAllButtons().
private GameTime _gameTime;
private Buttons _lastButton;
private Buttons _lastLeftStickDirection;
private Buttons _lastRightStickDirection;
private GamePadState _lastThumbStickState;
private GamePadState _lastTriggerState;
private float _leftCurVibrationStrength;
private bool _leftStickDown;
private bool _leftTriggerDown;
private bool _leftVibrating;
private GameTime _previousGameTime;
private GamePadState _previousState;
private int _repeatedButtonTimer;
private float _rightCurVibrationStrength;
private bool _rightStickDown;
private bool _rightTriggerDown;
private bool _rightVibrating;
private TimeSpan _vibrationDurationLeft;
private TimeSpan _vibrationDurationRight;
private TimeSpan _vibrationStart;
private float _vibrationStrengthLeft;
private float _vibrationStrengthRight;
public GamePadListener()
: this(new GamePadListenerSettings())
{
}
public GamePadListener(GamePadListenerSettings settings)
{
PlayerIndex = settings.PlayerIndex;
VibrationEnabled = settings.VibrationEnabled;
VibrationStrengthLeft = settings.VibrationStrengthLeft;
VibrationStrengthRight = settings.VibrationStrengthRight;
ThumbStickDeltaTreshold = settings.ThumbStickDeltaTreshold;
ThumbstickDownTreshold = settings.ThumbstickDownTreshold;
TriggerDeltaTreshold = settings.TriggerDeltaTreshold;
TriggerDownTreshold = settings.TriggerDownTreshold;
RepeatInitialDelay = settings.RepeatInitialDelay;
RepeatDelay = settings.RepeatDelay;
_previousGameTime = new GameTime();
_previousState = GamePadState.Default;
}
///
/// If set to true, the static event
/// will fire when any controller changes in connectivity status.
///
/// This functionality requires that you have one actively updating
/// .
///
///
public static bool CheckControllerConnections { get; set; }
///
/// The index of the controller.
///
public PlayerIndex PlayerIndex { get; }
///
/// When a button is held down, the interval in which
/// ButtonRepeated fires. Value in milliseconds.
///
public int RepeatDelay { get; }
///
/// The amount of time a button has to be held down
/// in order to fire ButtonRepeated the first time.
/// Value in milliseconds.
///
public int RepeatInitialDelay { get; }
///
/// Whether vibration is enabled for this controller.
///
public bool VibrationEnabled { get; set; }
///
/// General setting for the strength of the left motor.
/// This motor has a slow, deep, powerful rumble.
///
/// This setting will modify all future vibrations
/// through this listener.
///
///
public float VibrationStrengthLeft
{
get { return _vibrationStrengthLeft; }
// Clamp the value, just to be sure.
set { _vibrationStrengthLeft = MathHelper.Clamp(value, 0, 1); }
}
///
/// General setting for the strength of the right motor.
/// This motor has a snappy, quick, high-pitched rumble.
///
/// This setting will modify all future vibrations
/// through this listener.
///
///
public float VibrationStrengthRight
{
get { return _vibrationStrengthRight; }
// Clamp the value, just to be sure.
set { _vibrationStrengthRight = MathHelper.Clamp(value, 0, 1); }
}
///
/// The treshold of movement that has to be met in order
/// for the listener to fire an event with the trigger's
/// updated position.
///
/// In essence this defines the event's
/// resolution.
///
/// At a value of 0 this will fire every time
/// the trigger's position is not 0f.
///
public float TriggerDeltaTreshold { get; }
///
/// The treshold of movement that has to be met in order
/// for the listener to fire an event with the thumbstick's
/// updated position.
///
/// In essence this defines the event's
/// resolution.
///
/// At a value of 0 this will fire every time
/// the thumbstick's position is not {x:0, y:0}.
///
public float ThumbStickDeltaTreshold { get; }
///
/// How deep the triggers have to be depressed in order to
/// register as a ButtonDown event.
///
public float TriggerDownTreshold { get; }
///
/// How deep the triggers have to be depressed in order to
/// register as a ButtonDown event.
///
public float ThumbstickDownTreshold { get; }
///
/// This event fires whenever a controller connects or disconnects.
///
/// In order
/// for it to work, the property must
/// be set to true.
///
///
public static event EventHandler ControllerConnectionChanged;
///
/// This event fires whenever a button changes from the Up
/// to the Down state.
///
public event EventHandler ButtonDown;
///
/// This event fires whenever a button changes from the Down
/// to the Up state.
///
public event EventHandler ButtonUp;
///
/// This event fires repeatedly whenever a button is held sufficiently
/// long. Use this for things like menu navigation.
///
public event EventHandler ButtonRepeated;
///
/// This event fires whenever a thumbstick changes position.
///
/// The parameter governing the sensitivity of this functionality
/// is .
///
///
public event EventHandler ThumbStickMoved;
///
/// This event fires whenever a trigger changes position.
///
/// The parameter governing the sensitivity of this functionality
/// is .
///
///
public event EventHandler TriggerMoved;
///
/// Send a vibration command to the controller.
/// Returns true if the operation succeeded.
///
/// Motor values that are unset preserve
/// their current vibration strength and duration.
///
/// Note: Vibration currently only works on select platforms,
/// like Monogame.Windows.
///
/// Duration of the vibration in milliseconds.
///
/// The strength of the left motor.
/// This motor has a slow, deep, powerful rumble.
///
///
/// The strength of the right motor.
/// This motor has a snappy, quick, high-pitched rumble.
///
/// Returns true if the operation succeeded.
public bool Vibrate(int durationMs, float leftStrength = float.NegativeInfinity,
float rightStrength = float.NegativeInfinity)
{
if (!VibrationEnabled)
return false;
var lstrength = MathHelper.Clamp(leftStrength, 0, 1);
var rstrength = MathHelper.Clamp(rightStrength, 0, 1);
if (float.IsNegativeInfinity(leftStrength))
lstrength = _leftCurVibrationStrength;
if (float.IsNegativeInfinity(rightStrength))
rstrength = _rightCurVibrationStrength;
var success = GamePad.SetVibration(PlayerIndex, lstrength*VibrationStrengthLeft,
rstrength*VibrationStrengthRight);
if (success)
{
_leftVibrating = true;
_rightVibrating = true;
if (leftStrength > 0)
_vibrationDurationLeft = new TimeSpan(0, 0, 0, 0, durationMs);
else
{
if (lstrength > 0)
_vibrationDurationLeft -= _gameTime.TotalGameTime - _vibrationStart;
else
_leftVibrating = false;
}
if (rightStrength > 0)
_vibrationDurationRight = new TimeSpan(0, 0, 0, 0, durationMs);
else
{
if (rstrength > 0)
_vibrationDurationRight -= _gameTime.TotalGameTime - _vibrationStart;
else
_rightVibrating = false;
}
_vibrationStart = _gameTime.TotalGameTime;
_leftCurVibrationStrength = lstrength;
_rightCurVibrationStrength = rstrength;
}
return success;
}
private void CheckAllButtons()
{
// PacketNumber only and always changes if there is a difference between GamePadStates.
// ...At least, that's the theory. It doesn't seem to be implemented. Disabled for now.
//if (_lastPacketNumber == _currentState.PacketNumber)
// return;
foreach (Buttons button in Enum.GetValues(typeof(Buttons)))
{
if (_excludedButtons.Contains(button))
break;
if (_currentState.IsButtonDown(button) && _previousState.IsButtonUp(button))
RaiseButtonDown(button);
if (_currentState.IsButtonUp(button) && _previousState.IsButtonDown(button))
RaiseButtonUp(button);
}
// Checks triggers as buttons and floats
CheckTriggers(s => s.Triggers.Left, Buttons.LeftTrigger);
CheckTriggers(s => s.Triggers.Right, Buttons.RightTrigger);
// Checks thumbsticks as vector2s
CheckThumbSticks(s => s.ThumbSticks.Right, Buttons.RightStick);
CheckThumbSticks(s => s.ThumbSticks.Left, Buttons.LeftStick);
}
private void CheckTriggers(Func getButtonState, Buttons button)
{
var debounce = 0.05f; // Value used to qualify a trigger as coming Up from a Down state
var curstate = getButtonState(_currentState);
var curdown = curstate > TriggerDownTreshold;
var prevdown = button == Buttons.RightTrigger ? _rightTriggerDown : _leftTriggerDown;
if (!prevdown && curdown)
{
RaiseButtonDown(button);
if (button == Buttons.RightTrigger)
_rightTriggerDown = true;
else
_leftTriggerDown = true;
}
else
{
if (prevdown && (curstate < debounce))
{
RaiseButtonUp(button);
if (button == Buttons.RightTrigger)
_rightTriggerDown = false;
else
_leftTriggerDown = false;
}
}
var prevstate = getButtonState(_lastTriggerState);
if (curstate > TriggerDeltaTreshold)
{
if (Math.Abs(prevstate - curstate) >= TriggerDeltaTreshold)
{
TriggerMoved?.Invoke(this, MakeArgs(button, curstate));
_lastTriggerState = _currentState;
}
}
else
{
if (prevstate > TriggerDeltaTreshold)
{
TriggerMoved?.Invoke(this, MakeArgs(button, curstate));
_lastTriggerState = _currentState;
}
}
}
private void CheckThumbSticks(Func getButtonState, Buttons button)
{
const float debounce = 0.15f;
var curVector = getButtonState(_currentState);
var curdown = curVector.Length() > ThumbstickDownTreshold;
var right = button == Buttons.RightStick;
var prevdown = right ? _rightStickDown : _leftStickDown;
var prevdir = button == Buttons.RightStick ? _lastRightStickDirection : _lastLeftStickDirection;
Buttons curdir;
if (curVector.Y > curVector.X)
{
if (curVector.Y > -curVector.X)
curdir = right ? Buttons.RightThumbstickUp : Buttons.LeftThumbstickUp;
else
curdir = right ? Buttons.RightThumbstickLeft : Buttons.LeftThumbstickLeft;
}
else
{
if (curVector.Y < -curVector.X)
curdir = right ? Buttons.RightThumbstickDown : Buttons.LeftThumbstickDown;
else
curdir = right ? Buttons.RightThumbstickRight : Buttons.LeftThumbstickRight;
}
if (!prevdown && curdown)
{
if (right)
_lastRightStickDirection = curdir;
else
_lastLeftStickDirection = curdir;
RaiseButtonDown(curdir);
if (button == Buttons.RightStick)
_rightStickDown = true;
else
_leftStickDown = true;
}
else
{
if (prevdown && (curVector.Length() < debounce))
{
RaiseButtonUp(prevdir);
if (button == Buttons.RightStick)
_rightStickDown = false;
else
_leftStickDown = false;
}
else
{
if (prevdown && curdown && (curdir != prevdir))
{
RaiseButtonUp(prevdir);
if (right)
_lastRightStickDirection = curdir;
else
_lastLeftStickDirection = curdir;
RaiseButtonDown(curdir);
}
}
}
var prevVector = getButtonState(_lastThumbStickState);
if (curVector.Length() > ThumbStickDeltaTreshold)
{
if (Vector2.Distance(curVector, prevVector) >= ThumbStickDeltaTreshold)
{
ThumbStickMoved?.Invoke(this, MakeArgs(button, thumbStickState: curVector));
_lastThumbStickState = _currentState;
}
}
else
{
if (prevVector.Length() > ThumbStickDeltaTreshold)
{
ThumbStickMoved?.Invoke(this, MakeArgs(button, thumbStickState: curVector));
_lastThumbStickState = _currentState;
}
}
}
internal static void CheckConnections()
{
if (!CheckControllerConnections)
return;
foreach (PlayerIndex index in Enum.GetValues(typeof(PlayerIndex)))
{
if (GamePad.GetState(index).IsConnected ^ _gamePadConnections[(int) index])
// We need more XORs in this world
{
_gamePadConnections[(int) index] = !_gamePadConnections[(int) index];
ControllerConnectionChanged?.Invoke(null,
new GamePadEventArgs(GamePadState.Default, GamePad.GetState(index), TimeSpan.Zero, index));
}
}
}
private void CheckVibrate()
{
if (_leftVibrating && (_vibrationStart + _vibrationDurationLeft < _gameTime.TotalGameTime))
Vibrate(0, 0);
if (_rightVibrating && (_vibrationStart + _vibrationDurationRight < _gameTime.TotalGameTime))
Vibrate(0, rightStrength: 0);
}
public override void Update(GameTime gameTime)
{
_gameTime = gameTime;
_currentState = GamePad.GetState(PlayerIndex);
CheckVibrate();
if (!_currentState.IsConnected)
return;
CheckAllButtons();
CheckRepeatButton();
//_lastPacketNumber = _currentState.PacketNumber;
_previousGameTime = gameTime;
_previousState = _currentState;
}
private GamePadEventArgs MakeArgs(Buttons? button,
float triggerstate = 0, Vector2? thumbStickState = null)
{
var elapsedTime = _gameTime.TotalGameTime - _previousGameTime.TotalGameTime;
return new GamePadEventArgs(_previousState, _currentState,
elapsedTime, PlayerIndex, button, triggerstate, thumbStickState);
}
private void RaiseButtonDown(Buttons button)
{
ButtonDown?.Invoke(this, MakeArgs(button));
ButtonRepeated?.Invoke(this, MakeArgs(button));
_lastButton = button;
_repeatedButtonTimer = 0;
}
private void RaiseButtonUp(Buttons button)
{
ButtonUp?.Invoke(this, MakeArgs(button));
_lastButton = 0;
}
private void CheckRepeatButton()
{
_repeatedButtonTimer += _gameTime.ElapsedGameTime.Milliseconds;
if ((_repeatedButtonTimer < RepeatInitialDelay) || (_lastButton == 0))
return;
if (_repeatedButtonTimer < RepeatInitialDelay + RepeatDelay)
{
ButtonRepeated?.Invoke(this, MakeArgs(_lastButton));
_repeatedButtonTimer = RepeatDelay + RepeatInitialDelay;
}
else
{
if (_repeatedButtonTimer > RepeatInitialDelay + RepeatDelay*2)
{
ButtonRepeated?.Invoke(this, MakeArgs(_lastButton));
_repeatedButtonTimer = RepeatDelay + RepeatInitialDelay;
}
}
}
}
}