Добрый день/вечер, в этой части мы будем рассматривать работу с сенсорным экраном и проверка столкновений координат касания пальца к экрану с координатами человечков (будем убивать наших славных ботов:)).
Для тех кто не читал предыдущие части предлагаю начать сперва с них, так как без знаний поданых в тех уроках Вы не будете иметь понятия о чем вообще идет речь в этом уроке. Вот здесь Вы можете просмотреть предыдущие части:
Если Вы уже прочитали эти статьи то прошу под кат, там Вы узнаете много нового (надеюсь).
Первое, что нужно сделать, это добавить метод onTouchEvent() в класс GameView, в целях обработки каждого касания по экрану. Для каждого спрайта в списке мы узнаем, существует ли событие нажатия на экран по координатам (х, у) и имеют ли они столкновения со спрайтами. Если столкновения не существует, мы удаляем спрайт из списка спрайтов и возвращаем назад список чтобы избежать ошибок в следующей итерации, когда мы удаляем спрайт.
GameView.java
Теперь метод onDraw() рисует спрайт из списка, удаленные спрайты рисоваться не будут. Результат обработки события onTouchEvent() является как бы раздавливае персонажа своим пальцем :) Мы очень жестоки…
Чтобы заставить его работать, мы должны реализовать метод isCollition() в классе Sprite. Этот метод должен вернуть true, если (х, у) координаты указывают на нашего человечка.
Sprite.java
Если х2 не больше х, это означает, что касание было слева от спрайта. если х2 больше, чем х + width, это означает, что касание было — справа от спрайта.
Если х2 > х & & 2 <х + width, что означает, что прикосновение в том же столбце, но все же мы должны проверить, если он находится в той же строке. у2> у & & 2 <у + height проверяет ряд столкновений таким же образом.
Запустим и глянем что происходит:
Как вы можете видеть при нажатии на человечка, которому соответствует координата касания на экране — удаляется .
Удаляются все человечки какие находятся по этим координатам, а мы хотим что бы удалялся только один из допустим трех. Для этого нам нужно добавить прерывании в нашу функцию определения нажатия на экран onTouchEvent().
GameView.java
Я забыл о синхронизации. События обрабатываются в потоке, не похожем на поток рисования и обновления. Это может привести к конфликту, если мы обновляем данные, которые были разработаны в одно и то же время. Решение довольно простое — обернуть весь код внутри onTouchEvent() с тем же объектом SurfaceHolder который мы использовали в классе GameManager.
Вспомним что мы использовали для синхронизации цикла:
GameManager.java
А теперь мы добавляем вместо старого кода в onTouchEvent():
GameView.java
при помощи этого мы можем избежать ошибки, которая появляется и очень раздражает. :)
Если мы запустим его снова, и увидим что проблема все еще не устранена. На этот раз проблема не в потоке и не синхронизации, проблема в том что событие вызывается сильно быстро и нам его нужно ограничить. Создаем метод который будет ограничивать количество кликов в 300 миллисекунд, тогда эта ошибка исчезнет и человечки будут убиваться по одному раз в 300 мс. Добавляем чуть выше кода в onTouchEvent().
GameView.java
Теперь запустите его снова, и вы увидите разницу.
Для тех кто не читал предыдущие части предлагаю начать сперва с них, так как без знаний поданых в тех уроках Вы не будете иметь понятия о чем вообще идет речь в этом уроке. Вот здесь Вы можете просмотреть предыдущие части:
- Пишем игру под Android: Часть 1 — Рисуем картинки на SurfaceView
- Пишем игру под Android: Часть 2 — Создаем первый спрайт
- Пишем игру под Android: Часть 3 — Спрайтовая анимация, работа с несколькими спрайтами
- Пишем игру под Android: Часть 4 — onTouchEvent и определение столкновений
- Пишем игру под Android: Часть 5 — Создание полноценной 2D игры
- Пишем игру под Android: Часть 6: Добавление звука
- Пишем игру под Android: Часть 7: Меню для игры и окно приветствия
- Пишем игру под Android: Часть 8: Фоновая музыка в игре
Если Вы уже прочитали эти статьи то прошу под кат, там Вы узнаете много нового (надеюсь).
onTouchEvent() — событие касания к экрану
Первое, что нужно сделать, это добавить метод onTouchEvent() в класс GameView, в целях обработки каждого касания по экрану. Для каждого спрайта в списке мы узнаем, существует ли событие нажатия на экран по координатам (х, у) и имеют ли они столкновения со спрайтами. Если столкновения не существует, мы удаляем спрайт из списка спрайтов и возвращаем назад список чтобы избежать ошибок в следующей итерации, когда мы удаляем спрайт.
GameView.java
/**Обработка косания по экрану*/
public boolean onTouchEvent(MotionEvent event) {
for (int i = sprites.size()-1; i >= 0; i--)
{
Sprite sprite = sprites.get(i);
if (sprite.isCollition(event.getX(),event.getY()))
{
sprites.remove(sprite);
}
}
return super.onTouchEvent(event);
}
Теперь метод onDraw() рисует спрайт из списка, удаленные спрайты рисоваться не будут. Результат обработки события onTouchEvent() является как бы раздавливае персонажа своим пальцем :) Мы очень жестоки…
Определение столкновений
Чтобы заставить его работать, мы должны реализовать метод isCollition() в классе Sprite. Этот метод должен вернуть true, если (х, у) координаты указывают на нашего человечка.
Sprite.java
/**Проверка на столкновения*/
public boolean isCollition(float x2, float y2) {
return x2 > x && x2 < x + width && y2 > y && y2 < y + height;
}
Если х2 не больше х, это означает, что касание было слева от спрайта. если х2 больше, чем х + width, это означает, что касание было — справа от спрайта.
Если х2 > х & & 2 <х + width, что означает, что прикосновение в том же столбце, но все же мы должны проверить, если он находится в той же строке. у2> у & & 2 <у + height проверяет ряд столкновений таким же образом.
Запустим и глянем что происходит:
Как вы можете видеть при нажатии на человечка, которому соответствует координата касания на экране — удаляется .
Ограничение по количеству кликов
Удаляются все человечки какие находятся по этим координатам, а мы хотим что бы удалялся только один из допустим трех. Для этого нам нужно добавить прерывании в нашу функцию определения нажатия на экран onTouchEvent().
GameView.java
for(int i = sprites.size()-1; i >= 0; i--) {
Sprite sprite = sprites.get(i);
if (sprite.isCollition(event.getX(),event.getY()))
{
sprites.remove(sprite);
break;
}
}
Я забыл о синхронизации. События обрабатываются в потоке, не похожем на поток рисования и обновления. Это может привести к конфликту, если мы обновляем данные, которые были разработаны в одно и то же время. Решение довольно простое — обернуть весь код внутри onTouchEvent() с тем же объектом SurfaceHolder который мы использовали в классе GameManager.
Вспомним что мы использовали для синхронизации цикла:
GameManager.java
canv = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(canv);
}
А теперь мы добавляем вместо старого кода в onTouchEvent():
GameView.java
synchronized (getHolder()) {
for (int i = sprites.size()-1; i > 0; i--)
{
Sprite sprite = sprites.get(i);
if (sprite.isCollision(event.getX(),event.getY()))
{
sprites.remove(sprite);
break;
}
}
}
при помощи этого мы можем избежать ошибки, которая появляется и очень раздражает. :)
Если мы запустим его снова, и увидим что проблема все еще не устранена. На этот раз проблема не в потоке и не синхронизации, проблема в том что событие вызывается сильно быстро и нам его нужно ограничить. Создаем метод который будет ограничивать количество кликов в 300 миллисекунд, тогда эта ошибка исчезнет и человечки будут убиваться по одному раз в 300 мс. Добавляем чуть выше кода в onTouchEvent().
GameView.java
if (System.currentTimeMillis() - lastClick > 300) {
lastClick = System.currentTimeMillis()
//скобочка закрывается перед return true; иф - охватывает весь метод
Теперь запустите его снова, и вы увидите разницу.
Спасибо !
ОтветитьУдалитьОчень классный мануал! Только он почему то не запускается у меня в Эклипсе...
ОтветитьУдалитьТы не могу бы выложить полный код?
Так ссылка ниже - исходные кода для чего? :)
Удалитьhttp://get.keeg.ru/OnTouchEventAndCollision.rar.html
Отличная статья, спасибо! :)
ОтветитьУдалитьСтатья помогла, спасибо! Есть небольшое конструктивное замечание :)
ОтветитьУдалитьpublic boolean isCollition(float x2, float y2) {
return x2 > x && x2 < x + width && y2 > y && y2 < y + height;
}
Не кажется ли Вам, что данный метод будет отслеживать лишь правый нижний угол спрайта? По-моему, лучше бы возвращаемое условие сделать таким:
return x2 > x - width && x2 < x + width && y2 > y - height && y2 < y + height;
Делал такую же штуку только с простым drawCircle(). Не сразу понял, почему кружочки убираются не всегда.
Спасибо за урок. Но не могли бы вы подсказать почему при проверке event.getAction() всегда выдает только ACTION_DOWN.
ОтветитьУдалитьКартинка отвалилась
ОтветитьУдалить