4
\$\begingroup\$

I've got a tile based game that has notions of moving up, down, left, right and the diagonals (by alternating horizontal/vertical movement).

I've found that if I use the default ThumbStick<Direction> buttons that XNA provides I'm often moving diagonally instead of in a straight line. E.g. moving the thumbstick left, often just triggers 'down' too (but only just).

How can I map moving a thumbstick to the 8 discrete directions so this accidental diagonal movement doesn't occur?

I'm starting to think about favouring up/down/left/right over diagonals (as you more frequently move straight in my game), however, I'm not sure how do put this into code.

\$\endgroup\$
9
  • \$\begingroup\$ that's because you're probably taking any value != 0 as a "direction trigger" for example, if the thumbstick is 99% to the left and 1% to the bottom, it will trigger both. Try to use a 15% threshold. (or some other value that you feel good with) \$\endgroup\$ Jan 23, 2012 at 21:40
  • \$\begingroup\$ The XNA does use a threshold, although I've added my own (higher) threshold on top and it seems to be doing the trick. If you post as an answer I'll accept. \$\endgroup\$ Jan 23, 2012 at 21:58
  • \$\begingroup\$ -1: For accepting the wrong answer. That'll teach you to wait more than a few minutes before clicking the "accept" tick. \$\endgroup\$ Jan 24, 2012 at 1:02
  • \$\begingroup\$ @NicolBolas: I don't see anything wrong with accepting an answer that is posted that solves my problem. I can always change that if I feel a subsequent answer is better. \$\endgroup\$ Jan 24, 2012 at 7:55
  • 1
    \$\begingroup\$ @NicolBolas: But Gtoknu's answer is more useful to me. Possibly next time I might wait a little longer, but I'd probably go for the same answer as although detailed, Jonathan's one in my case would be overkill. \$\endgroup\$ Jan 24, 2012 at 8:34

2 Answers 2

3
\$\begingroup\$

That's because you're probably taking any value != 0 as a "direction trigger". For example, if the thumbstick is 99% to the left and 1% to the bottom, it will trigger both.

Try to use a 15% (or some other value that you feel good with) as a threshold.

\$\endgroup\$
14
\$\begingroup\$

Distance-Based

You will basically need to take two things into consideration: a threshold (inversely a deadzone) and the dominant vector. Given an enum like the following:

public enum Direction { None, Up, Down, Left, Right }

You would find the dominant direction (if the deadzone isn't in play) and use that.

// You could make this user-configurable.
const float Deadzone = 0.8f;
const float DiagonalAvoidance = 0.2f;

public static Direction GetDirection(Vector2 gamepadThumbStick)
{
    // Get the length and prevent something from happening
    // if it's in our deadzone.
    var length = gamepadThumbStick.Length();
    if (length < Deadzone)
        return Direction.None;

    var absX = Math.Abs(gamepadThumbStick.X);
    var absY = Math.Abs(gamepadThumbStick.Y);
    var absDiff = Math.Abs(absX - absY);

    // We don't like values that are too close to each other
    // i.e. borderline diagonal.
    if (absDiff < length * DiagonalAvoidance)
        return Direction.None;

    if (absX > absY)
    {
        if (gamepadThumbStick.X > 0)
            return Direction.Right;
        else
            return Direction.Left;
    }
    else
    {
        if (gamepadThumbStick.Y > 0)
            return Direction.Down;
        else
            return Direction.Up;
    }
}

Angle-Based

With this one you basically ensure that, once again, we don't fall within a deadzone - but also that the angle is within certain ranges.

const float PI = (float)Math.PI;

// You could make this user-configurable.
const float Deadzone = 0.8f;
const float AliveAngle = PI / 4 - (PI / 16);

// Remember PI = 180 degrees
const float AliveAngle1Start = PI * 0;
const float AliveAngle1End = PI * 0.5f - AliveAngle;

const float AliveAngle2Start = PI * 0.5f + AliveAngle;
const float AliveAngle2End = PI * 1f - AliveAngle;

const float AliveAngle3Start = PI * 1f + AliveAngle;
const float AliveAngle3End = PI * 1.5f - AliveAngle;

const float AliveAngle4Start = PI * 1.5f + AliveAngle;
const float AliveAngle4End = PI * 2f - AliveAngle;

public static Direction GetDirection(Vector2 gamepadThumbStick)
{
    // Get the length and prevent something from happening
    // if it's in our deadzone.
    var length = gamepadThumbStick.Length();
    if (length < Deadzone)
        return Direction.None;

    // Find the angle that the stick is at. see: http://en.wikipedia.org/wiki/File:Atan2_60.svg
    var angle = (float)Math.Atan2(gamepadThumbStick.Y, gamepadThumbStick.X);
    if (angle < 0)
        angle += PI * 2; // Simpify our checks.

    if (angle > AliveAngle4End)
        return Direction.Right;
    if (angle > AliveAngle4Start)
        return Direction.None;
    if (angle > AliveAngle3End)
        return Direction.Up;
    if (angle > AliveAngle3Start)
        return Direction.None;
    if (angle > AliveAngle2End)
        return Direction.Left;
    if (angle > AliveAngle2Start)
        return Direction.None;
    if (angle > AliveAngle1End)
        return Direction.Down;
    if (angle > AliveAngle1Start)
        return Direction.None;
    return Direction.Right;
}

// Inlined in release so don't worry about it.
private static bool IsBetween(float value, float start, float end)
{
    return value > start && value < end;
}

Remarks

The way I did the distance-based one would likely emulate the (possibly more expensive) angle-based one - this is because of the absDiff < length * DiagonalAvoidance. Either way experiment with them and see which you like best.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks for going into more detail and posting after an accepted answer. In my case the first one is simple and works, so I'll keep that accepted. +1 \$\endgroup\$ Jan 24, 2012 at 7:56
  • \$\begingroup\$ @GeorgeDuckett that's fine, just sticky this answer for when you get bug reports because of the missing dead angle. \$\endgroup\$ Jan 24, 2012 at 10:29
  • \$\begingroup\$ @JonathanDickinson Using your angle-based method. I have noticed that when I press up on the left thumbstick, it returns Direction.Down and when I press down, it returns Direction.Up. This is an easy fix, I just want to let you know so you can make the changes yourself. \$\endgroup\$ Aug 17, 2012 at 3:22
  • \$\begingroup\$ @KyleUithoven oops, I will make a fix ASAP. Thanks for the feedback ;). \$\endgroup\$ Aug 17, 2012 at 14:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .