New functionality: GamePadListener with associated eventargs, listenerSettings. This addition is feature-complete and entirely documented.

This commit is contained in:
detuur
2016-01-20 21:32:18 +01:00
parent 396685471b
commit 2dfd424976
5 changed files with 672 additions and 1 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ MonoGame.Extended is designed to build on top of MonoGame, but may work with XNA
- **Tile based maps** using [Tiled](http://www.mapeditor.org/) (orthogonal only in v0.3, isometric in master)
- **Bitmap fonts** using [BMFont](http://www.angelcode.com/products/bmfont/)
- **[Sprites](http://dylanwilson.net/sprites-and-spritebatch-extensions-in-monogame-extended)** (with SpriteBatch extensions!)
- **Input listeners** for event driven input handling (Keyboard, Mouse, Touch)
- **Input listeners** for event driven input handling (Keyboard, Mouse, Touch) (GamePad in master)
- **Texture Atlases** using the JSON format in [TexturePacker](https://www.codeandweb.com/texturepacker)
- **2D Camera** with pan, zoom, and rotation
- **Viewport Adapters** for resolution independent rendering
@@ -0,0 +1,62 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace MonoGame.Extended.InputListeners
{
/// <summary>
/// This class contains all information resulting from events fired by
/// <see cref="GamePadListener"/>.
/// </summary>
public class GamePadEventArgs : EventArgs
{
internal GamePadEventArgs(GamePadState previousState, GamePadState currentState,
TimeSpan elapsedTime, PlayerIndex playerIndex, Buttons? button = null,
float triggerState = 0, Vector2? thumbStickState = null)
{
PlayerIndex = playerIndex;
PreviousState = previousState;
CurrentState = currentState;
ElapsedTime = elapsedTime;
if (button != null)
Button = button.Value;
TriggerState = triggerState;
ThumbStickState = thumbStickState ?? Vector2.Zero;
}
/// <summary>
/// The index of the controller.
/// </summary>
public PlayerIndex PlayerIndex { get; private set; }
/// <summary>
/// The state of the controller in the previous update.
/// </summary>
public GamePadState PreviousState { get; private set; }
/// <summary>
/// The state of the controller in this update.
/// </summary>
public GamePadState CurrentState { get; private set; }
/// <summary>
/// The button that triggered this event, if appliable.
/// </summary>
public Buttons Button { get; private set; }
/// <summary>
/// The time elapsed since last event.
/// </summary>
public TimeSpan ElapsedTime { get; private set; }
/// <summary>
/// If a TriggerMoved event, displays the responsible trigger's position.
/// </summary>
public float TriggerState { get; private set; }
/// <summary>
/// If a ThumbStickMoved event, displays the responsible stick's position.
/// </summary>
public Vector2 ThumbStickState { get; private set; }
}
}
@@ -0,0 +1,486 @@
using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace MonoGame.Extended.InputListeners
{
/// <summary>
/// This is a listener that exposes several events for easier handling of gamepads.
/// <para>In order to initialise it, call the <see cref="InputListenerManager.AddListener"/> method
/// with a <see cref="GamePadListenerSettings"/> object.</para>
/// </summary>
public class GamePadListener : InputListener
{
internal 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;
}
#region events
/// <summary>
/// This event fires whenever a controller connects or disconnects. <para>In order
/// for it to work, the <see cref="CheckControllerConnections"/> property must
/// be set to true.</para>
/// </summary>
public static event EventHandler<GamePadEventArgs> ControllerConnectionChanged;
/// <summary>
/// This event fires whenever a button changes from the Up
/// to the Down state.
/// </summary>
public event EventHandler<GamePadEventArgs> ButtonDown;
/// <summary>
/// This event fires whenever a button changes from the Down
/// to the Up state.
/// </summary>
public event EventHandler<GamePadEventArgs> ButtonUp;
/// <summary>
/// This event fires repeatedly whenever a button is held sufficiently
/// long. Use this for things like menu navigation.
/// </summary>
public event EventHandler<GamePadEventArgs> ButtonRepeated;
/// <summary>
/// This event fires whenever a thumbstick changes position.
/// <para>The parameter governing the sensitivity of this functionality
/// is <see cref="GamePadListenerSettings.ThumbStickDeltaTreshold"/>.</para>
/// </summary>
public event EventHandler<GamePadEventArgs> ThumbStickMoved;
/// <summary>
/// This event fires whenever a trigger changes position.
/// <para>The parameter governing the sensitivity of this functionality
/// is <see cref="GamePadListenerSettings.TriggerDeltaTreshold"/>.</para>
/// </summary>
public event EventHandler<GamePadEventArgs> TriggerMoved;
#endregion
#region public properties
/// <summary>
/// If set to true, the static event <see cref="ControllerConnectionChanged"/>
/// will fire when any controller changes in connectivity status.
/// <para>This functionality requires that you have one actively updating
/// <see cref="InputListenerManager"/>.</para>
/// </summary>
public static bool CheckControllerConnections { get; set; }
/// <summary>
/// The index of the controller.
/// </summary>
public PlayerIndex PlayerIndex { get; private set; }
/// <summary>
/// When a button is held down, the interval in which
/// ButtonRepeated fires. Value in milliseconds.
/// </summary>
public int RepeatDelay { get; private set; }
/// <summary>
/// The amount of time a button has to be held down
/// in order to fire ButtonRepeated the first time.
/// Value in milliseconds.
/// </summary>
public int RepeatInitialDelay { get; private set; }
/// <summary>
/// Whether vibration is enabled for this controller.
/// </summary>
public bool VibrationEnabled { get; set; }
/// <summary>
/// General setting for the strength of the left motor.
/// This motor has a slow, deep, powerful rumble.
/// <para>This setting will modify all future vibrations
/// through this listener.</para>
/// </summary>
public float VibrationStrengthLeft
{
get { return _vibrationStrengthLeft; }
// Clamp the value, just to be sure.
set { _vibrationStrengthLeft = MathHelper.Clamp(value, 0, 1); }
}
/// <summary>
/// General setting for the strength of the right motor.
/// This motor has a snappy, quick, high-pitched rumble.
/// <para>This setting will modify all future vibrations
/// through this listener.</para>
/// </summary>
public float VibrationStrengthRight
{
get { return _vibrationStrengthRight; }
// Clamp the value, just to be sure.
set { _vibrationStrengthRight = MathHelper.Clamp(value, 0, 1); }
}
/// <summary>
/// The treshold of movement that has to be met in order
/// for the listener to fire an event with the trigger's
/// updated position.
/// <para>In essence this defines the event's
/// resolution.</para>
/// At a value of 0 this will fire every time
/// the trigger's position is not 0f.
/// </summary>
public float TriggerDeltaTreshold { get; private set; }
/// <summary>
/// The treshold of movement that has to be met in order
/// for the listener to fire an event with the thumbstick's
/// updated position.
/// <para>In essence this defines the event's
/// resolution.</para>
/// At a value of 0 this will fire every time
/// the thumbstick's position is not {x:0, y:0}.
/// </summary>
public float ThumbStickDeltaTreshold { get; private set; }
/// <summary>
/// How deep the triggers have to be depressed in order to
/// register as a ButtonDown event.
/// </summary>
public float TriggerDownTreshold { get; private set; }
/// <summary>
/// How deep the triggers have to be depressed in order to
/// register as a ButtonDown event.
/// </summary>
public float ThumbstickDownTreshold { get; private set; }
#endregion
#region private fields
private static readonly bool[] GamePadConnections = new bool[4];
private float _vibrationStrengthLeft;
private float _vibrationStrengthRight;
private GamePadState _currentState;
private GamePadState _previousState;
//private int _lastPacketNumber;
// Implementation doesn't work, see explanation in CheckAllButtons().
private GameTime _gameTime;
private GameTime _previousGameTime;
private Buttons _lastButton;
private int _repeatedButtonTimer;
private GamePadState _lastTriggerState;
private GamePadState _lastThumbStickState;
private bool _leftTriggerDown;
private bool _rightTriggerDown;
private bool _leftStickDown;
private bool _rightStickDown;
private Buttons _lastRightStickDirection;
private Buttons _lastLeftStickDirection;
private float _leftCurVibrationStrength;
private float _rightCurVibrationStrength;
private TimeSpan _vibrationStart;
private TimeSpan _vibrationDurationLeft;
private TimeSpan _vibrationDurationRight;
private bool _leftVibrating;
private bool _rightVibrating;
// 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
};
#endregion
/// <summary>
/// Send a vibration command to the controller.
/// Returns true if the operation succeeded.
/// <para>Motor values that are unset preserve
/// their current vibration strength and duration.</para>
/// Note: Vibration currently only works on select platforms,
/// like Monogame.Windows.
/// </summary>
/// <param name="durationMs">Duration of the vibration in milliseconds.</param>
/// <param name="leftStrength">The strength of the left motor.
/// This motor has a slow, deep, powerful rumble.</param>
/// <param name="rightStrength">The strength of the right motor.
/// This motor has a snappy, quick, high-pitched rumble.</param>
/// <returns>Returns true if the operation succeeded.</returns>
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<GamePadState, float> getButtonState, Buttons button)
{
float debounce = 0.05f; // Value used to qualify a trigger as coming Up from a Down state
var curstate = getButtonState(_currentState);
bool curdown = curstate > TriggerDownTreshold;
bool 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.Raise(this, MakeArgs(button, triggerstate: curstate));
_lastTriggerState = _currentState;
}
}
else if (prevstate > TriggerDeltaTreshold)
{
TriggerMoved.Raise(this, MakeArgs(button, triggerstate: curstate));
_lastTriggerState = _currentState;
}
}
private void CheckThumbSticks(Func<GamePadState, Vector2> getButtonState, Buttons button)
{
const float debounce = 0.15f;
var curVector = getButtonState(_currentState);
bool curdown = curVector.Length() > ThumbstickDownTreshold;
bool right = button == Buttons.RightStick;
bool 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.Raise(this, MakeArgs(button, thumbStickState: curVector));
_lastThumbStickState = _currentState;
}
} else if (prevVector.Length() > ThumbStickDeltaTreshold)
{
ThumbStickMoved.Raise(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.Raise(null,
new GamePadEventArgs(GamePadState.Default, GamePad.GetState(index), TimeSpan.Zero, index));
}
}
}
private void CheckVibrate()
{
if (_leftVibrating && _vibrationStart + _vibrationDurationLeft < _gameTime.TotalGameTime)
Vibrate(0, leftStrength: 0);
if (_rightVibrating && _vibrationStart + _vibrationDurationRight < _gameTime.TotalGameTime)
Vibrate(0, rightStrength: 0);
}
internal 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.Raise(this, MakeArgs(button));
ButtonRepeated.Raise(this, MakeArgs(button));
_lastButton = button;
_repeatedButtonTimer = 0;
}
private void RaiseButtonUp(Buttons button)
{
ButtonUp.Raise(this, MakeArgs(button));
_lastButton = 0;
}
private void CheckRepeatButton()
{
_repeatedButtonTimer += _gameTime.ElapsedGameTime.Milliseconds;
if(_repeatedButtonTimer < RepeatInitialDelay || _lastButton == 0)
return;
if (_repeatedButtonTimer < RepeatInitialDelay + RepeatDelay)
{
ButtonRepeated.Raise(this, MakeArgs(_lastButton));
_repeatedButtonTimer = RepeatDelay + RepeatInitialDelay;
}
else if (_repeatedButtonTimer > RepeatInitialDelay + RepeatDelay*2)
{
ButtonRepeated.Raise(this, MakeArgs(_lastButton));
_repeatedButtonTimer = RepeatDelay + RepeatInitialDelay;
}
}
}
}
@@ -0,0 +1,121 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.InputListeners
{
/// <summary>
/// This is a class that contains settings to be used to initialise a <see cref="GamePadListener"/>.
/// </summary>
/// <seealso cref="InputListenerManager"/>
public class GamePadListenerSettings : InputListenerSettings<GamePadListener>
{
/// <summary>
/// This is a class that contains settings to be used to initialise a <see cref="GamePadListener"/>.
/// <para>Note: There are a number of extra settings that are settable properties.</para>
/// </summary>
/// <param name="playerIndex">The index of the controller the listener will be tied to.</param>
/// <param name="vibrationEnabled">Whether vibration is enabled on the controller.</param>
/// <param name="vibrationStrengthLeft">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.</param>
/// <param name="vibrationStrengthRight">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.</param>
public GamePadListenerSettings(PlayerIndex playerIndex, bool vibrationEnabled = true,
float vibrationStrengthLeft = 1.0f, float vibrationStrengthRight = 1.0f)
{
PlayerIndex = playerIndex;
VibrationEnabled = vibrationEnabled;
VibrationStrengthLeft = vibrationStrengthLeft;
VibrationStrengthRight = vibrationStrengthRight;
TriggerDownTreshold = 0.15f;
ThumbstickDownTreshold = 0.5f;
RepeatInitialDelay = 500;
RepeatDelay = 50;
}
/// <summary>
/// The index of the controller.
/// </summary>
public PlayerIndex PlayerIndex { get; set; }
/// <summary>
/// When a button is held down, the interval in which
/// ButtonRepeated fires. Value in milliseconds.
/// </summary>
public int RepeatDelay { get; set; }
/// <summary>
/// The amount of time a button has to be held down
/// in order to fire ButtonRepeated the first time.
/// Value in milliseconds.
/// </summary>
public int RepeatInitialDelay { get; set; }
/// <summary>
/// Whether vibration is enabled for this controller.
/// </summary>
public bool VibrationEnabled { get; set; }
/// <summary>
/// General setting for the strength of the left motor.
/// This motor has a slow, deep, powerful rumble.
/// <para>This setting will modify all future vibrations
/// through this listener.</para>
/// </summary>
public float VibrationStrengthLeft { get; set; }
/// <summary>
/// General setting for the strength of the right motor.
/// This motor has a snappy, quick, high-pitched rumble.
/// <para>This setting will modify all future vibrations
/// through this listener.</para>
/// </summary>
public float VibrationStrengthRight { get; set; }
/// <summary>
/// The treshold of movement that has to be met in order
/// for the listener to fire an event with the trigger's
/// updated position.
/// <para>In essence this defines the event's
/// resolution.</para>
/// At a value of 0 this will fire every time
/// the trigger's position is not 0f.
/// </summary>
public float TriggerDeltaTreshold { get; set; }
/// <summary>
/// The treshold of movement that has to be met in order
/// for the listener to fire an event with the thumbstick's
/// updated position.
/// <para>In essence this defines the event's
/// resolution.</para>
/// At a value of 0 this will fire every time
/// the thumbstick's position is not {x:0, y:0}.
/// </summary>
public float ThumbStickDeltaTreshold { get; set; }
/// <summary>
/// How deep the triggers have to be depressed in order to
/// register as a ButtonDown event.
/// </summary>
public float TriggerDownTreshold { get; set; }
/// <summary>
/// How deep the triggers have to be depressed in order to
/// register as a ButtonDown event.
/// </summary>
public float ThumbstickDownTreshold { get; private set; }
internal override GamePadListener CreateListener()
{
return new GamePadListener(this);
}
// For internal use only. This limits the user to only having
// one controller.
internal GamePadListenerSettings() : this(PlayerIndex.One) { }
}
}
@@ -62,6 +62,8 @@ namespace MonoGame.Extended.InputListeners
{
foreach (var listener in _listeners)
listener.Update(gameTime);
GamePadListener.CheckConnections();
}
}
}