Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MonoGame reading touch gestures

I am currently writing a game using the fantastic monogame framework. I am having trouble reacting to touch input correctly. When a user drags horizontally or vertically I want to perform an action once per drag. The problem is the gesture is getting issued multiple times for each drag. Here is my code:

var gesture = default(GestureSample);

while (TouchPanel.IsGestureAvailable)
    gesture = TouchPanel.ReadGesture();

if (gesture.GestureType == GestureType.VerticalDrag)
{
    if (gesture.Delta.Y < 0)
        return new RotateLeftCommand(_gameController);

    if (gesture.Delta.Y > 0)
        return new RotateRightCommand(_gameController);
}

if (gesture.GestureType == GestureType.HorizontalDrag)
{
    if (gesture.Delta.X < 0)
        return new RotateLeftCommand(_gameController);

    if (gesture.Delta.X > 0)
        return new RotateRightCommand(_gameController);
}

I have moved all of this code into one block for the purposes of this example. The RotateRightCommand or RotateLeftCommand that is returned is then executed by the calling code which rotates the object on the screen. This whole block of code is being run in the update loop in monogame. I think I am missing something in terms of resetting the touch input and that's why I'm getting 3 or 4 RotateCommands returned for one drag. What am I doing wrong?

like image 899
Kevin Holditch Avatar asked Feb 02 '13 14:02

Kevin Holditch


2 Answers

Edit: You're not using gestures correctly here. A drag gesture is generated every time a finger moves across the screen. If you move your finger from one side of the screen to the other (Very quickly), you'll get a lot of small drag gestures. This is how XNA and MonoGame work.

This is the logic you're looking for:

var touchCol = TouchPanel.GetState();

foreach (var touch in touchCol)
{
    // You're looking for when they finish a drag, so only check
    // released touches.
    if (touch.State != TouchState.Released)
        continue;

    TouchLocation prevLoc;

    // Sometimes TryGetPreviousLocation can fail. Bail out early if this happened
    // or if the last state didn't move
    if (!touch.TryGetPreviousLocation(out prevLoc) || prevLoc.State != TouchState.Moved)
        continue;

    // get your delta
    var delta = touch.Position - prevLoc.Position ;

    // Usually you don't want to do something if the user drags 1 pixel.
    if (delta.LengthSquared() < YOUR_DRAG_TOLERANCE)
        continue;

    if (delta.X < 0 || delta.Y < 0)
        return new RotateLeftCommand(_gameController);

    if (delta.X > 0 || delta.Y > 0)
        return new RotateRightCommand(_gameController);   
}

If you'd rather do gestures, you can just do this:

var gesture = default(GestureSample);

while (TouchPanel.IsGestureAvailable)
{
    gesture = TouchPanel.ReadGesture();

    if (gesture.GestureType == GestureType.DragComplete)
    {
        if (gesture.Delta.Y < 0 || gesture.Delta.X < 0)
            return new RotateLeftCommand(_gameController);
        if (gesture.Delta.Y > 0 || gesture.Delta.X > 0)
            return new RotateRightCommand(_gameController);
    }
}

Either way will give you the behavior you're looking for. You still need to put the logic inside of the loop as I said in the original post, because you can have multiple gestures queued up, and you don't want to assume that the last one in the stack is the drag. (Thanks to multitouch you can have drags, taps, drag complete, etc).

I'm not 100% sure if this is your problem... but your logic is wrong here. The CodeFormatting boss is beating me here, but it should be more like this:

var gesture = default(GestureSample);

while (TouchPanel.IsGestureAvailable)
{
    gesture = TouchPanel.ReadGesture();

    if (gesture.GestureType == GestureType.VerticalDrag)
    {
        if (gesture.Delta.Y < 0)
            return new RotateLeftCommand(_gameController);
        if (gesture.Delta.Y > 0)
            return new RotateRightCommand(_gameController);
    }

    if (gesture.GestureType == GestureType.HorizontalDrag)
    {
        if (gesture.Delta.X < 0)
            return new RotateLeftCommand(_gameController);
        if (gesture.Delta.X > 0)
            return new RotateRightCommand(_gameController);
    }
}

By not putting the TouchPanel.ReadGesure() inside of a loop, you're only reading one gesure per frame. The available gestures are put into a queue, and only removed when ReadGesture() is called. Therefore if your framerate hiccups, or if you can't read a gesture as fast as you the OS can read it (Which is very possible), you'll be working with old information. You can see that here. For this application, I suggest taking each gesture, and combining it into one or two, then deciding what you should do based on that.

like image 164
RayBatts Avatar answered Oct 01 '22 19:10

RayBatts


I guess that depends on what you consider one drag. MonoGame and XNA consider the detection of change in touch input from one update to the next a drag. So if you are thinking that you will receive one Gesture from the time you touch your finger to the screen until you remove it, that would be incorrect in XNA and MonoGame terms. The Gesture that gets returned is the Delta from T-1 to T where T is Now and T-1 is the previous call to Update.

like image 35
borrillis Avatar answered Oct 01 '22 19:10

borrillis