9
\$\begingroup\$

I've spent countless hours on reading tutorials and looking at every question related to multiTouch from here and Stackoverflow. But I just cannot figure out how to do this correctly. I use a loop to get my pointerId, I dont see alot of people doing this but its the only way I've managed to get it somewhat working.

I have two joysticks on my screen, one for moving and one for controlling my sprites rotation and the angle he shoots, like in Monster Shooter. Both these work fine.

My problem is that when I Move my sprite at the same time as Im shooting, my touchingPoint for my movement is set to the touchingPoint of my shooting, since the x and y is higher on the touchingPoint of my shooting (moving-stick on left side of screen, shooting-stick on right side), my sprite speeds up, this creates an unwanted change in speed for my sprite.

this is how I solved it with your help! this is for anyone that might run into a similar problem:

    public void update(MotionEvent event) {
    if (event == null && lastEvent == null) {
        return;
    } else if (event == null && lastEvent != null) {
        event = lastEvent;
    } else {
        lastEvent = event;
    }   

        int action = event.getAction();
        int actionCode = action & MotionEvent.ACTION_MASK;
        int pid = action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        int x = (int) event.getX(pid);
        int y = (int) event.getY(pid); 
        int index = event.getActionIndex();
        int id = event.getPointerId(index);
        String actionString = null;


        switch (actionCode)
        {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:

                actionString = "DOWN";
                try{
                    if(x > 0 && x < steeringxMesh + (joystick.get_joystickBg().getWidth() * 2)
                            && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                            movingPoint.x = x;
                            movingPoint.y = y;
                            dragging = true;
                            draggingId = id;

                        }
                    else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                            && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                            shootingPoint.x = x;
                            shootingPoint.y = y;
                            shooting=true;
                            shootingId=id;
                        }
                    }catch(Exception e){

                    }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE:            
                if(id == draggingId)
                    dragging = false;
                if(id ==  shootingId)
                    shooting = false;
                actionString = "UP";
                break;  
            case MotionEvent.ACTION_MOVE:           
                for(index=0; index<event.getPointerCount(); index++) {
                    id=event.getPointerId(index);
                    int xx = (int) event.getX(index); //pro naming of variable
                    int yy = (int) event.getY(index); 
                    if(dragging && id == draggingId) {
                        if(xx > 0 && xx < (steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                            && yy > yMesh - (joystick.get_joystickBg().getHeight()) && yy < panel.getHeight()) {
                            movingPoint.x = xx;
                            movingPoint.y = yy;
                        }
                        else
                            dragging = false;
                        }
                    if(shooting && id == shootingId){
                        if(xx > shootingxMesh - (joystick.get_joystickBg().getWidth()) && xx < panel.getWidth()
                            && yy > yMesh - (joystick.get_joystickBg().getHeight()) && yy < panel.getHeight()) {
                            shootingPoint.x = xx;
                            shootingPoint.y = yy;                            
                        }
                        else
                            shooting = false;
                        }
                    }

                    actionString = "MOVE";
                    break;

        }
    Log.d(TAG, "actionsString: " + actionString + ", pid: " + pid + ", x: " + x + ", y: " + y);

Wouldnt post this much code if I wasnt at an absolute loss of what I'm doing wrong. I simply can not get a good understanding of how multiTouching works.

basicly movingPoint changes for both my first and second finger. I bind it to a box, but aslong as I hold one finger within this box, it changes its value based on where my second finger touches. It moves in the right direction and nothing gives an error, the problem is the speed-change, its almost like it adds up the two touchingPoints.

\$\endgroup\$
0

5 Answers 5

4
\$\begingroup\$

I think this will work for you.

One mistake you made was iterating over all pointers for every event. It's only necessary for move events.

Secondly, you actually do need to put the index value into the getX and getY functions, but you get the id associated with that index to use as a reference to your game objects. You assign an id to your joystick during the Down Event and then when iterating through your pointer indices, check if the index is associated with the pointer id that you assigned to the joystick during the Down event. If it is, check if it is still in bounds and either update it or disable it.

I am not testing this code, but I know it works in concept because I use the method in my own code. Let me know if there are any problems with it that you can't figure out.

First, you will have to add the following to your joystick class.

boolean dragging=false;
int draggingId;
boolean shooting=false;
int shootingId;

Change your onTouchEvent to this.

public boolean onTouchEvent(MotionEvent event) {


    int index = event.getActionIndex();
    int id = event.getPointerId(index);
    String actionString;

    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            try{
                    if(x > 0 && x < steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.dragging) {
                            movingPoint.x = x;
                            movingPoint.y = y;
                            joystick.dragging = true;
                            joystick.draggingId = id;
                    }
                    else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.shooting) { 
                            shootingPoint.x = x;
                            shootingPoint.y = y;
                            joystick.shooting=true;
                            joystick.shootingId=id;
                    }
              }
              catch(Exception e){

              }

            actionString = "DOWN";
            break;
        case MotionEvent.ACTION_UP:
            if(id == draggingID)
                joystick.dragging = false;
            if(id ==  shootingID)
                joystick.shooting = false;
            actionString = "UP";
            break;  
        case MotionEvent.ACTION_POINTER_DOWN:
            try{
                    if(x > 0 && x < steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.dragging) {
                            movingPoint.x = x;
                            movingPoint.y = y;
                            joystick.dragging = true;
                            joystick.draggingId = id;
                    }
                    else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.shooting) { 
                            shootingPoint.x = x;
                            shootingPoint.y = y;
                            joystick.shooting=true;
                            joystick.shootingId=id;
                    }
              }
              catch(Exception e){

              }

            actionString = "PNTR DOWN";
            break;
        case MotionEvent.ACTION_POINTER_UP:
            if(id == joystick.draggingID)
                joystick.dragging = false;
            if(id ==  joystick.shootingID)
                joystick.shooting = false;
            actionString = "PNTR UP";
            break;
        case MotionEvent.ACTION_CANCEL:
            if(id == joystick.draggingID)
                joystick.dragging = false;
            if(id ==  joystick.shootingID)
                joystick.shooting = false;
            actionString = "CANCEL";
            break;
        case MotionEvent.ACTION_MOVE:
            for(index=0; index<e.getPointerCount(); index++) {
                id=e.getPointerId(index);
                int x = (int) event.getX(index);
                int y = (int) event.getY(index); 
                if(joystick.dragging && id == joystick.draggingId) {
                    if(x > 0 && x < steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()) {
                        movingPoint.x = x;
                        movingPoint.y = y;
                    }
                    else
                        dragging = false;
                    }
                }
                else if(joystick.shooting && id == joystick.shootingId){
                    if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()) {
                        shootingPoint.x = x;
                        shootingPoint.y = y;                            
                    }
                    else
                        shooting = false;
                    }
                }
            }
            actionString = "MOVE";
            break;
        }
    }
\$\endgroup\$
1
  • \$\begingroup\$ you sir, are my hero. I will add the updated code to my question so you can see how I solved it with your help! now I can finally finish my game :D \$\endgroup\$
    – Green_qaue
    Commented Sep 25, 2012 at 23:37
7
+50
\$\begingroup\$

You are almost getting it right, but you should use your pointer id to request the X/Y instead of i

    int id = event.getPointerId(i);
    int x = (int) event.getX(id);
    int y = (int) event.getY(id);

From the MotionEvent documentation:

The order in which individual pointers appear within a motion event is undefined. Thus the pointer index of a pointer can change from one event to the next but the pointer id of a pointer is guaranteed to remain constant as long as the pointer remains active. Use the getPointerId(int) method to obtain the pointer id of a pointer to track it across all subsequent motion events in a gesture. Then for successive motion events, use the findPointerIndex(int) method to obtain the pointer index for a given pointer id in that motion event.

event.getX/Y require a pointer id, not i, because there's no garantee they'll be in the same order.

In addition, there is another subtle but important problem. Notice how the getAction() family of function do not take a parameter. That's kind of weird, right? Getting X/Y requires the pointer id, but not the action performed? That is hinting at a couple of important things:

  • you get a call to you touch handler for each action of each pointer and not a single call per frame for all pointers
  • getX/Y looks at the trace of the pointer so far and returns the latest value, while getAction inquires about the current event only

So that means you get one call to your touch handler per pointer action (down/move/up). So two fingers moving = 2 calls. A nasty side effect of your code is that it applies the action of one event to all the pointers...

So, instead of looping over the traces, simply get the pid/x/y/action for the current event only (for simultaneous movement, you will get another call to your handler a tiny bit later, like I said)

Here is my code to handle events:

 public static boolean sendTouchToGameEngine (MotionEvent event)
 {
  int action = event.getAction();
  int actionCode = action & MotionEvent.ACTION_MASK;
  int pid = action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;

  [...]
  sendTouchToGameEngine(pid, actionCode, (int)event.getX(pid), (int)event.getY(pid));
  [...]

  return true;

}

To go back to your code, you should be able to simplify it in the following way. The loop is gone, otherwise, if you have other contraints that you didn't mention, you can always add it back. It works by tracking which pointer id is used for which control (move/shoot) when DOWN is triggered and reseting them on UP. Since you don't use gestures, you can restrict your switch() to DOWN, UP, MOVE and OUTSIDE.

int movePointerId = -1;
int shootingPointerId = -1;

void TouchEventHandler(MotionEvent event) {   
    // grab the pointer id 
    int pid = action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    int x = (int) event.getX(pid);
    int y = (int) event.getY(pid); 
    int action = event.getAction();
    int actionCode = action & MotionEvent.ACTION_MASK;
    int actionIndex = event.getActionIndex();
    String actionString;


    switch (actionCode)
    {
        case MotionEvent.ACTION_DOWN:
        // on DOWN, figure out whether the player used the moving or shooting control, if any.
        // if so, kept track of which pointer was used, because all following call about that
        // finger touches will use the same pointer id. Also record the current point coordinates.
            actionString = "DOWN";
            try{
                if(x > 0 && x < steeringxMesh + (joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                        movingPoint.x = x;
                        movingPoint.y = y;
                        movePointerId = pid;
                    }
                else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                        shootingPoint.x = x;
                        shootingPoint.y = y;
                        shootingPointerId = pid;
                    }
                }catch(Exception e){

                }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_OUTSIDE:
        // whether the player lift the finger or moves it out of bounds
        // figure out which pointer that was and reset it. You can add additional
        // processing here as required
           if( pid == movePointerId )
              movePointerId = -1;
           else if( pid == shootingPointerId )
              shootingPointerId = -1;
            actionString = "UP";
            break;  
        case MotionEvent.ACTION_MOVE:
        // when the player move their finger, it is simply a matter of comparing the pid
        // to know which one it is
          if( pid == movePointerId ) {
                        movingPoint.x = x;
                        movingPoint.y = y;
          } else if( pid == shootingPointerId ) {
                        shootingPoint.x = x;
                        shootingPoint.y = y;
          }
                actionString = "MOVE";

    }
}
\$\endgroup\$
9
  • \$\begingroup\$ thanks for this great answer, really clears a couple of things up. Could you just explain what this line sendTouchToGameEngine(pid, actionCode, (int)event.getX(pid), (int)event.getY(pid)); does and when u call it? \$\endgroup\$
    – Green_qaue
    Commented Sep 24, 2012 at 18:53
  • \$\begingroup\$ also when I use this line: int pid = action >> MotionEvent.ACTION_POINTER_ID_SHIFT; eclipse tells me to add a supressWarning, is that normal? Sorry for all the questions after such a detailed answer. MotionEvent is very new to me, and cant understand the logic for some reason \$\endgroup\$
    – Green_qaue
    Commented Sep 24, 2012 at 19:26
  • \$\begingroup\$ added updated code, as you can see I dont really know what to do with pid, and the loop is still there, wont work without it, since I need to get i . \$\endgroup\$
    – Green_qaue
    Commented Sep 24, 2012 at 19:33
  • \$\begingroup\$ @Max: the sendTouchToGameEngine is simply a call to add this event to my game engine queue to be processed during the next update. If you don't use a queue to poll your input event, you run the risk of your calling function messing with the state of the game engine in unexpected ways, since the TouchEvent comes from the UI thread and, presumably, your game engine update runs in a different thread \$\endgroup\$
    – ADB
    Commented Sep 24, 2012 at 23:44
  • \$\begingroup\$ @Max: I don't know about the warning unfortunately. Can you tell us which one it is? \$\endgroup\$
    – ADB
    Commented Sep 24, 2012 at 23:45
0
\$\begingroup\$

There's a little weirdness going on with this code. I don't use Android, so maybe I'm thinking this is something it's not. However I did notice that you're getting the position twice, in two different ways:

First you get it like this at the start of your pointerCount loop:

(int) event.getX(i)

Then, inside your switch statement you get it like so:

(int) event.getX(id)

Notice that you switch from using i to using id.

I'm assuming it's supposed to be the former method. I recommend replacing all of the instances of (int) event.getX(id) and use the value x you set at the beginning. Likewise swapping (int) event.getY(id) to y.

Try swapping out these parts:

int pointerCount = event.getPointerCount(); 
for (int i = 0; i < pointerCount; i++)
{       
    int x = (int) event.getX(i);
    int y = (int) event.getY(i);

Then inside your switch use this:

    try{
        if(x > 0 && x < touchingBox &&
              y > touchingBox && y < view.getHeight()){
            movingPoint.x = x;
            movingPoint.y = y;
            dragging = true;
        }
        else if(x > touchingBox && x < view.getWidth() &&
                   y > touchingBox && y < view.getHeight()){
            shootingPoint.x = x;
            shootingPoint.y = y;
            shooting=true;
        }else{
            shooting=false;
            dragging=false;
        }
\$\endgroup\$
10
  • 2
    \$\begingroup\$ Care to comment on why this isn't useful and worthy of a downvote? It's the polite thing to do. \$\endgroup\$
    – House
    Commented Sep 21, 2012 at 0:01
  • \$\begingroup\$ Agreed, if you down-vote. Let him know why. You are right its from a former method. Ive tried about a million different things so I just forgot to remove it. \$\endgroup\$
    – Green_qaue
    Commented Sep 21, 2012 at 0:26
  • \$\begingroup\$ Can you update the code in your question to reflect that? I see you removed the setting of x and y, but you are still getting the position by id later. \$\endgroup\$
    – House
    Commented Sep 21, 2012 at 0:53
  • \$\begingroup\$ aah, took me a while to understand your answer. but how will this work? if I use the same variable (x=event.getX(i)), then x will have to store 2 values whenever pointerCount is more then 1, correct? And that dosnt feel right. \$\endgroup\$
    – Green_qaue
    Commented Sep 21, 2012 at 1:09
  • 1
    \$\begingroup\$ From what I understand event is really a list of events. You access the different events by going through the list like you're doing. So to access the x position for the second event you use event.getX(1) (since we're starting at 0). There are multiple xs, you're using the parameter to tell it which on you want. You're looping through all the events with your for loop, so use that number as the current event you're interested in. See my code change suggestion. \$\endgroup\$
    – House
    Commented Sep 21, 2012 at 1:32
0
\$\begingroup\$

I had a similar problem once on a Huawei U8150. The two finger multitouch on that device was really bad, using a multitouch test app (maybe it was "Phone Tester" but I'm not sure) I was able to see that touching with the second finger moved the 1st touch spot of many pixels. If that's your problem than it's hardware related and I don't think you can do much for it :(

Sorry for my bad english

\$\endgroup\$
2
  • \$\begingroup\$ that is not the problem \$\endgroup\$
    – Green_qaue
    Commented Sep 24, 2012 at 11:15
  • \$\begingroup\$ ok, was just a thought \$\endgroup\$
    – pinco
    Commented Sep 24, 2012 at 11:34
0
\$\begingroup\$

I believe your problem is that you are assuming that, between updates, the order in which the pointers are arranged stays the same. That's most likely not going to be the case.

Imagine a situation where you touch with finger A. There will be a pointerCount() of 1, and A will be the only element you can ask about. If you add a second finger, the pointerCount() will be 2, A will be at index 0, B will be at index 1. If you raise finger A, pointerCount() will be 1 again, and B will be at index 0. If you then touch with finger A again, A will be at index 1 and B will be at index 0.

This is why the pointer ID is provided, so that you can track individual touches between updates. So if the first touch of finger B is assigned the ID 12, it will always have that ID, even when finger A is removed and re-added.

So if your code identifies a touch near your shooting joystick, it must check to see if there is already a 'shooting' touch in progress. If not, then the ID of the shooting touch should be remembered in some member variable that persists to the next update. In subsequent updates, if you have a 'shooting' touch in progress, iterate through the pointers and look for the pointer with the right ID, and it's that pointer you need to use to track updates for, and all others can be ignored. Same for the movement touch. When the touch is released, you clear the ID associated with that joystick so that a new touch can take control of it.

Even if a touch that starts near one joystick strays far away from the original touch position, by its ID you can still correctly identify what joystick it is controlling. And as a nice side effect, a second stray finger near a particular joystick won't have any effect, because the joystick will be bound to the first finger that triggered it until it is release.

\$\endgroup\$

You must log in to answer this question.

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