0

After a lot of tinkering, I think I've finally come up with a good multitouch handling system for my android game. It makes use of Robert Greene's Input Pipeline, modified for use with multitouch. Right now, the code has a simple system that records which pointer ID is currently doing which action (right now just shooting and moving). Each pointer's state is kept in a Pointer class, which is just a simple encapsulation of whether it is down, and it's coordinates. The ID acts as the pointer array index.

This seems like it should work well in practice, but in game it behaves very erratic. When recording the pointer actions in LogCat, oftentimes Android will send an "UP" action when the pointer remains down, or just before a number of "MOVE" actions by the pointer. Because my code believes the pointer is up, the game doesn't respond to it.

This also happens with button presses like the shooting button. When the pointer comes down on the area (which right now is just simply the lower left region), Android will send multiple "UP" and "DOWN" actions even though the pointer remains down the whole time. I had a single touch movement system before and none of these problems happened.

Is this just an issue with how I am reacting to the events? Should I handle POINTER_DOWN and DOWN separately? Or should I detect which pointers are moving after the "UP" action to see which ones really are down despite what Android says?

Here's my current code in my thread which receives the input events from Android. Because it is a pipeline system, I have the events encapsulated in the InputObject which is somewhat similar to Robert Greene's. Maybe a new set of eyes can help me tell what's wrong? Thanks for any help!

private int inx, iny;
private int shootID;
public boolean shooting = false;
private int moveID;
public boolean moveDown = false;

private static final int MAX_POINTERS = 10;
private Pointer[] pointers = new Pointer[MAX_POINTERS];

public void inputTouch(InputObject in) {

    switch(in.action) {
    case MotionEvent.ACTION_DOWN:
    case MotionEvent.ACTION_POINTER_DOWN:
        pointers[in.pID].press(in.pX[in.actionIndex], in.pY[in.actionIndex]);
        //Log.i("D", "DOWN");
        break;
    case MotionEvent.ACTION_MOVE:
        for(int p = 0; p < in.pointCount; p++) {
            int id = in.pointerIDs[p];
            pointers[id].setCoord(in.pX[id], in.pY[id]);
        }
        //Log.i("D", "MOVE");
        break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
        pointers[in.pID].release();

        if(shootID == in.pID) {
            shooting = false;
        }

        if(moveID == in.pID) {
            moveDown = false;
        }

        //Log.i("D", "UP");
        break;
    case MotionEvent.ACTION_CANCEL:
    default:
        break;
    }

    for(int ap = 0; ap < MAX_POINTERS; ap++) {
        if(pointers[ap].down) {
            if(pointers[ap].x < world.cam.pixelWidth / 4 &&
                    pointers[ap].y > world.cam.pixelHeight - (world.cam.pixelHeight / 4)) {

                shootID = ap;
                shooting = true;
            } else {
                inx = pointers[ap].x;
                iny = pointers[ap].y;
                moveID = ap;
                moveDown = true;
            }
        }
    }

    StringBuilder sb = new StringBuilder();
    for(int j = 0; j < 3; j++) {
        sb.append("ID " + (j+1) + ": " + pointers[j].down + "[" + pointers[j].x + ", " + pointers[j].y + "]" + " | ");
    }
    //Log.i("D", sb.toString());
}

1 Answers1

0

Hope you got the answer by now, but here''s my solution:

First of all, there's a couple of things to add in the InputHolder class:

-internal public fields for mPointerIndex = event.getPointerIndex and mPointerID = event.getPointerID(mPointerIndex) (these get assigned in the useEvent/useHistory)

-if you only need to track 2 touchpoints, you need to add mPointerIndex2, x2 and y2 aswell. Add more as you need to track more points.

-add definitions for the case where MotionEvent.ACTION_POINTER_2_UP/DOWN gets passed. Turns out, POINTER_1 is the same as POINTER_UP/DOWN! This really tripped me up because I was falling through the switch case to the default. I only caught this because I changed my default to -1 and saw it logged. Depending on how you process the actions in your processInput(obj), you map these to different ints. In my case I used the obj.y to see if they where left/right touches and I only needed two points, so I mapped these to ACTION_TOUCH_POINTER_UP/DOWN instead of giving each touch it's own action int identifier.

-now, if you want to track multiple touch points, you would have to do the above and the below in a for loop over all entries in event.getPointerCount(). In my case I was only interested in the x/y of one other touchpoint, so I could get away with only doing a check after I had filled the first point and the other pointerindex was easy to deduce:

public void useEvent(MotionEvent event) {
    eventType = EVENT_TYPE_TOUCH;
    int a = event.getAction();
    switch (a) {
        case MotionEvent.ACTION_DOWN:
        action = ACTION_TOUCH_DOWN;
        break;
        case MotionEvent.ACTION_POINTER_DOWN:
        action = ACTION_TOUCH_POINTER_DOWN;
        break;
        case MotionEvent.ACTION_POINTER_2_DOWN:
        action = ACTION_TOUCH_POINTER_DOWN;
        break;
        case MotionEvent.ACTION_MOVE:
        action = ACTION_TOUCH_MOVE;
        break;
        case MotionEvent.ACTION_UP:
        action = ACTION_TOUCH_UP;
        break;
        case MotionEvent.ACTION_POINTER_UP:
        action = ACTION_TOUCH_POINTER_UP;
        break;
        case MotionEvent.ACTION_POINTER_2_UP:
        action = ACTION_TOUCH_POINTER_UP;
        break;
        default:
        action = -1;
    }
    time = event.getEventTime();

    pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
    pointerID = event.getPointerId(pointerIndex);

    x = (int) event.getX(pointerIndex);
    y = (int) event.getY(pointerIndex);

    if (event.getPointerCount() > 1)
    {
        pointerIndex2 = pointerIndex== 0 ? 1 : 0;
        x2 = (int)event.getX(pointerIndex2);
        y2 = (int)event.getY(pointerIndex2);
    }
}

If you''re tracking more points, or need more information about each touchevent, you have to extend the for loop up over the action switch case.

Anyway, now you need two variables in your thread so you can keep track of your two touches: call 'em touchOneID and touchTwoID (or index). On an ACTION_POINTER_2_UP, the obj.mPointerID refers to the obj which is going UP! This means that the other touch will change it's ID! Keep track of this change, and you're sorted. The internal obj's mPointerID/Index will always be correct, you just have to track them correctly in your surfaceview's thread so you can act accordingly to when you get a POINTER_DOWN. In my case, I did a simple x position check to determine what to do in my ACTION_MOVE event which told me enough to correctly determine which x/y or x2/y2 I should use and how to use it. This meant less code to run and limits to the things I had to keep in memory, but it all depends on what and how much information you need.

Finally: To be honest, if you handled the definitions correctly and assigned every MotionEvent and held them in the InputObject, you'd probably be fine. Hell, I think you can ignore and lose the whole switch case and just say obj.mAction = event.getAction() and handle these in your processInput(obj)! Meaning, all those static ints he's remapping to in the InputHolder seem unnecessary, unless you really only need one or two touch definitions (which explains his mysterious standalone comment of "this app is only interested in down touches"). Getting rid of those statically defined ints also means you can just test against MotionEvent.ACTION_CODE instead of doing a lookup against InputHolder.TOUCH_ACTION_CODE.

MacD
  • 783
  • 7
  • 23