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);
}
}
}