The vertical menus in my game allow you to press up or down on the thumbstick to jump to the next (or previous) menu item. I implement this by storing the old thumbstick state. If the state was zero and is now non-zero, the next (or previous) menu item is selected.
I have never noticed any "flicking" with this implementation either on Xbox controller attached to a PC or an Xbox. I have tested with several controllers.
However when I answered the question question XNA - how to tell if a thumb stick was “twitched” in a certain direction, Andrew Russell commented:
While this is the right idea, the threshold needs to be above zero (for use in menus), and there needs to be a margin between activation and deactivation to prevent it flicking.
My understanding is that Xna already provides filtering and a "dead-zone" on the thumb-stick input and that no threshold or margin is necessary to prevent flicking. Andrew's statement worried me.
Are there controllers out there that do need a threshold or margin to prevent "flicking"?
Should I code in a threshold and margin just to be safe?
The following code is a minimal implementation of my method of input polling for XNA menus. The only difference is that my actual menus autoscroll after the control's value has been non-zero in the same direction for a short while. The logic related to this question is in the ProcessUserInput function.
The ball represents the active menu item. Press the left thumbstick up or down to jump the ball to the next/previous menu item. I can't get it to "flicker".
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace WindowsGame
{
public class Ball
{
public float RADIUS = DIAMETER * 0.5f;
const int DIAMETER = 40;
static readonly uint WHITE = Color.White.PackedValue;
static readonly uint BLACK = new Color(0, 0, 0, 0).PackedValue;
Texture2D m_texture;
public Ball(GraphicsDevice graphicsDevice)
{
m_texture = new Texture2D(graphicsDevice, DIAMETER, DIAMETER);
uint[] data = new uint[DIAMETER * DIAMETER];
for (int i = 0; i < DIAMETER; i++)
{
float iPosition = i - RADIUS;
for (int j = 0; j < DIAMETER; j++)
{
data[i * DIAMETER + j] = new Vector2(iPosition, j - RADIUS).Length() <= RADIUS ? WHITE : BLACK;
}
}
m_texture.SetData<uint>(data);
}
public Vector2 Position { get; set; }
private Rectangle DrawRectangle
{
get
{
return new Rectangle((int)Math.Round(Position.X - RADIUS), (int)Math.Round(Position.Y - RADIUS), DIAMETER, DIAMETER);
}
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(m_texture, DrawRectangle, Color.White);
}
}
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Matrix viewMatrix;
Matrix inverseViewMatrix;
Ball ball;
GamePadState m_lastGamePadState;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
ball = new Ball(GraphicsDevice);
viewMatrix = Matrix.CreateTranslation(Window.ClientBounds.Width * 0.5f, Window.ClientBounds.Height * 0.5f, 0.0f);
inverseViewMatrix = Matrix.Invert(viewMatrix);
m_lastGamePadState = GamePad.GetState(PlayerIndex.One);
base.Initialize();
}
private void ProcessUserInput()
{
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
if (m_lastGamePadState.ThumbSticks.Left.Y == 0.0f)
{
ball.Position += Vector2.UnitY * (-Math.Sign(gamePadState.ThumbSticks.Left.Y) * ball.RADIUS * 2.0f);
}
m_lastGamePadState = gamePadState;
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
ProcessUserInput();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, viewMatrix);
ball.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
You probably want
That should be all you need. See GamePadDeadZone. (Edit: Just noticed that that's the default -- You should already be fine.)
Personally, I use Buttons.LeftThumbstickUp, etc for GUI navigation, which allows you to treat the thumbstick like a Dpad.
It's basically about getting the controls to feel "nice".
Setting a threshold above zero is required (because the sticks are never exactly on zero). The dead-zone counts as a threshold, but it's very small. I think the default XNA dead-zone is about 0.24. This is way too small to use in a menu.
I suspect - but I am not in a position to test this right now - that XNA's interpret-the-thumbsticks-as-buttons code uses the same, tiny, unsatisfying dead zone as its activation threshold.
As you can see from my answer you linked, I set an activation threshold at 0.85, which is considerably higher. Admittedly, my code is not for a menu. The tuning for a menu might be a bit smaller, but still not as small as the default dead-zone.
As for the margin (eg: activate at 0.85, deactivate at 0.75). This is particularly required to get a satisfying feel when you're specifically responding to an "off-on-off" transition. It is not really necessary when you're simply detecting the "on" state.
It also prevents the situation where you're interpreting stick movement as button presses (as you might for a menu) and may, on rare occasion, receive a spurious double-press.