Android Multitouch Странное поведение

После долгих раздумий я думаю, что наконец-то нашел хорошую мультитач-систему для своей игры на андроид. Он использует входной конвейер Роберта Грина, модифицированный для использования с мультитач. Прямо сейчас в коде есть простая система, которая записывает, какой идентификатор указателя в данный момент выполняет какое действие (сейчас просто стрельба и перемещение). Состояние каждого указателя хранится в классе Pointer, который представляет собой простую инкапсуляцию того, является ли он неактивным, и его координаты. Идентификатор действует как индекс массива указателей.

Кажется, что на практике это должно хорошо работать, но в игре это ведет себя очень хаотично. При записи действий указателя в LogCat часто Android отправляет действие "ВВЕРХ", когда указатель остается в нижнем положении, или непосредственно перед несколькими действиями "ПЕРЕМЕЩЕНИЕ" указателя. Поскольку мой код считает, что указатель работает, игра не отвечает на него.

Это также происходит с нажатиями кнопок, такими как кнопка съемки. Когда указатель опускается на область (которая сейчас является просто нижней левой областью), Android будет отправлять несколько действий "ВВЕРХ" и "ВНИЗ", даже если указатель остается выключенным все время. Раньше у меня была система движения одним касанием, и ни одна из этих проблем не возникала.

Это просто проблема с тем, как я реагирую на события? Должен ли я обрабатывать POINTER_DOWN и DOWN отдельно? Или я должен определить, какие указатели движутся после действия "ВВЕРХ", чтобы увидеть, какие из них действительно не работают, несмотря на то, что говорит Android?

Вот мой текущий код в моей теме, который получает входные события от Android. Поскольку это конвейерная система, у меня есть события, инкапсулированные в InputObject, что несколько похоже на Роберт Грин. Может быть, новый набор глаз может помочь мне сказать, что не так? Спасибо за любую помощь!

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 ответ

Надеюсь, вы уже получили ответ, но вот мое решение:

Прежде всего, есть несколько вещей, которые нужно добавить в класс InputHolder:

внутренние открытые поля для mPointerIndex = event.getPointerIndex и mPointerID = event.getPointerID(mPointerIndex) (они назначаются в useEvent / useHistory)

-Если вам нужно отслеживать только 2 точки касания, вам нужно также добавить mPointerIndex2, x2 и y2. Добавляйте больше по мере необходимости, чтобы отслеживать больше очков.

-добавить определения для случая, когда передается MotionEvent.ACTION_POINTER_2_UP/DOWN. Оказывается, POINTER_1 совпадает с POINTER_UP/DOWN! Это действительно сбило меня с толку, потому что я падал через корпус переключателя на значение по умолчанию. Я только поймал это, потому что я изменил свое значение по умолчанию на -1 и увидел, что он зарегистрирован. В зависимости от того, как вы обрабатываете действия в вашем processInput(obj), вы отображаете их в разные целые числа. В моем случае я использовал объект obj.y, чтобы увидеть, касаются ли они места влево / вправо, и мне потребовались только две точки, поэтому я сопоставил их с ACTION_TOUCH_POINTER_UP/DOWN вместо того, чтобы назначать каждому касанию собственный идентификатор int действия.

-в настоящее время, если вы хотите отслеживать несколько точек касания, вы должны выполнить выше и ниже в цикле for для всех записей в event.getPointerCount(). В моем случае меня интересовал только x/y еще одной точки касания, поэтому я мог обойтись без выполнения только проверки после заполнения первой точки, и другой указатель указателя было легко вывести:

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

Если вы отслеживаете больше точек или вам нужна дополнительная информация о каждом прикосновении, вам нужно расширить цикл for на случай переключателя действия.

В любом случае, теперь вам нужны две переменные в вашей ветке, чтобы вы могли отслеживать ваши два касания: вызывать их touchOneID и touchTwoID (или индекс). На ACTION_POINTER_2_UP obj.mPointerID ссылается на объект, который идет вверх! Это означает, что другое прикосновение изменит его ID! Следите за этими изменениями, и вы отсортированы. MPointerID/Index внутреннего объекта всегда будет корректным, вам просто нужно правильно отследить их в потоке вашего вида поверхности, чтобы вы могли действовать в соответствии с моментом получения POINTER_DOWN. В моем случае я выполнил простую проверку положения x, чтобы определить, что делать в моем событии ACTION_MOVE, и этого достаточно, чтобы правильно определить, какой x/y или x2/y2 я должен использовать и как его использовать. Это означало меньше кода для выполнения и ограничения для вещей, которые я должен был хранить в памяти, но все это зависит от того, какой объем информации вам нужен и сколько вам нужно.

Наконец: если честно, если вы правильно обработали определения и присвоили каждое MotionEvent и удерживали их в InputObject, у вас, вероятно, все будет в порядке. Черт, я думаю, что вы можете проигнорировать и потерять весь регистр переключателя и просто сказать obj.mAction = event.getAction() и обработать их в своем processInput(obj)! Это означает, что все эти статические целочисленные значения, в которые он переназначается в InputHolder, кажутся ненужными, если только вам действительно не нужны только одно или два определения касания (что объясняет его таинственный отдельный комментарий "это приложение заинтересовано только в касаниях"). Избавление от этих статически определенных целых также означает, что вы можете просто протестировать MotionEvent.ACTION_CODE вместо поиска по InputHolder.TOUCH_ACTION_CODE.

Другие вопросы по тегам