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.