Дек
2

AndEngine. Обработка касаний экрана

logo_100 AndEngine позволяет с лёгкостью ловить нажатия на экран и на спрайты.

Когда касаешься экрана, Android посылает уведомление (называется MotionEvent) в Activity. AndEngine ловит эти события, кэширует и отправляет дальше в Главную Сцену.

touch1

Главная Сцена рассылает событие всем объектам, которые добавлены в список слушателей этого события.

Чтобы добавить объект в этот "волшебный" список, нужно в коде Сцены написать:

registerTouchArea(_Sprite);

После этого спрайт будет получать вызов метода onAreaTouched. ВАЖНО: он будет его получать, даже если он не добавлен на Сцену.

 

MotionEvent и TouchEvent

Что между ними общего и в чём разница?

MotionEvent — объект, создаваемый самой системой Android. Не вдаваясь в глубокие подробности, скажу, что он содержит координаты касания экрана и вид касания. Координаты получаются АБСОЛЮТНЫЕ в пределах физического экрана устройства.

TouchEvent — объект, создаваемый движком AndEngine. Он также содержит координаты, вид касания и ещё ссылку на исходный MotionEvent. То есть через TouchEvent можно обратиться к нативному MotionEvent. Координаты у TouchEvent получаются в пределах игровой СЦЕНЫ и зависят от координат Камеры.

Из TouchEvent мы можем узнать вид касания. Он может быть:

  • ACTION_DOWN — произошло касание экрана — нажали пальцем на экран. Событие при первом касании экрана вызывается ВСЕГДА первым;
  • ACTION_UP — касание закончилось — убрали палец с экрана;
  • ACTION_MOVE — начали водить пальцем по экрану после нажатия, либо просто держим палец на месте. ACTION_MOVE всегда происходит между ACTION_DOWN и ACTION_UP;
  • ACTION_OUTSIDE — нажатие произошло за пределами Activity (я его ни разу его не использовал);
  • ACTION_CANCEL — произошла отмена нажатия — это не то же самое, что убрали палец с экрана. Я думаю это событие может произойти, когда вы нажали на экран и в это время кто-то вам звонит, и игра прерывается (тоже пока не использовал).

 

Примеры

Пример 1. Ловим первое нажатие на объект (спрайт):

/**
* Создаем кнопку
*/
_PauseBtn = new Sprite(755, 11, GfxAssets._Pause_BTN_TR)
{
	public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float pTouchAreaLocalX, float pTouchAreaLocalY)
	{
		if (pSceneTouchEvent.isActionDown())
		{
			//Пишем код реакции при нажатии на конпку
			...
			return true; //возвращаем TRUE, если событие отработали
		}

		return false; //возвращаем FALSE, если событие не отработали
	};
};
/**
* Добавляем кнопку в слушатели
*/
registerTouchArea(_PauseBtn);

 

Пример 2. Ловим нажатие типа КЛИКА на объект (спрайт):

// Флаг для определения того, что коснулись кнопки.
private boolean _BtnTouched = false;

...

_Play_btn = new Sprite(100, 100, GfxAssets._Play_BTN_TR)
{
	public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float pTouchAreaLocalX, float pTouchAreaLocalY)
	{
		if (pSceneTouchEvent.isActionDown())
		{
			//Касание экрана произошло на кнопке. Это хорошо
			_BtnTouched = true;
			return true;
		} else
		if (pSceneTouchEvent.isActionUp() && _BtnTouched)
		{
			//Отпускание пальца произошло на кнопке.
			//Да к тому же и нажатие произошло на кнопке.
			//Значит точно кликнули на кнопке
			_BtnTouched = false;
			...
			return true;
		}

		return false;
	};
};

/**
* Не забываем добавлять кнопку в список слушателей
*/
registerTouchArea(_Play_btn);

...
/**
* Это - слушатель нажатий для Сцены.
* При любом касании экрана сбрасываем флажок для кнопки (кнопок),
* потому что касание экрана может случиться не обязательно
* на кнопке.
* Если этого не делать, то могут случаться ложные срабатывания нажатий на кнопки
*/
@Override
public boolean onSceneTouchEvent(TouchEvent pSceneTouchEvent) {
	if (pSceneTouchEvent.isActionDown())
	{
		/**
		 * Сбрасываем нажатия кнопок
		 */
		_BtnTouched = false;
		//_BtnTouched2 = false;
		//_BtnTouched3 = false;
	}
	return super.onSceneTouchEvent(pSceneTouchEvent);
}

Переменная _BtnTouched используется для защиты от ложных нажатий, т.е. исключает такие варианты касаний, как:

  • Нажали пальцем на кнопке, а отпустили вне кнопки;
  • Нажали пальцем вне кнопки, а отпустили на кнопке.

Другие примеры обработки касаний можете посмотреть в AndEngineExamples...




24 комментария к записи “AndEngine. Обработка касаний экрана”

  • Написать код для клика, но не работает когда отпускаю палец, ActionUp ().

    Что может быть?

    • Все, что угодно. Код в студию!

  • Точнее не так.

    Я создал объект

    public class SquareButtons extends GameObject

    {

    public SquareButtons (int posX, int posY, TextureRegion region)

    {

    super (posX, posY, region);

    // TODO Auto-generated constructor stub

    }

    @Override

    void attachTo (Scene scene)

    {

    scene.getChild (0).attachChild (sprite);

    scene.registerTouchArea (sprite);

    }

    }

    public abstract class GameObject extends Entity

    {

    protected Sprite sprite;

    public GameObject (final int posX, final int posY, final TextureRegion region)

    {

    //TODO расчет координат независимо от размера экрана

    sprite = new Sprite (posX, posY, region);

    //attachChild (sprite);

    }

    ...

    в онЛоадСцен делаю вот так:

    SquareButtons buttonUndoMove = new SquareButtons (50, 50, gameTextureManager.buttonUndoMove)

    {

    public boolean onAreaTouched (TouchEvent pSceneTouchEvent, float pTouchAreaLocalX, float pTouchAreaLocalY)

    {

    if (pSceneTouchEvent.isActionDown ())

    {

    //Пишем код реакции при нажатии на конпку

    Log.d («PUSH», «buttonUndoMove»);

    return true; //возвращаем TRUE, если событие отработали

    }

    return false; //возвращаем FALSE, если событие не отработали

    };

    };

    buttonUndoMove.attachTo (scene);

    пишет ,что метод onAreaTouched не будет вызываться. Можно ли как то сделать нажатие именно по объекту унаследованному от Entity, а не по спрайту?

    • Зачем пишешь «scene.getChild (0).attachChild (sprite);»??? вместо «scene.attachChild (sprite);».

  • Как сделать, чтобы обработчик касания работал не во время прописывания спрайта... :

    final Sprite Button_Game = new Sprite (774, 50, Button_Game_spr){

    public boolean onAreaTouched ( TouchEvent pSceneTouchEvent,

    float pTouchAreaLocalX, float pTouchAreaLocalY) {

    switch (pSceneTouchEvent.getAction ()) {

    case TouchEvent.ACTION_DOWN:

    Button_Awards_spr.setVisible (false);

    break;

    }

    return true;

    final Sprite Button_Awards = new Sprite (774, 170, Button_Awards_spr);

    scene.attachChild (Button_Game);

    scene.attachChild (Button_Awards);

    scene.registerTouchArea (Button_Game);

    ... а после. После прописывания всех спрайтов. Дело в том, что я не могу между ними взаимодействовать, например сделать при нажатии на первый спрайт второй невидимым, т...к. второй ещё не прописан.

    • вместо Button_Awards_spr.setVisible (false);

      ты должен писать Button_Awards.setVisible (false);

    • Хм, странно. Я прописываю и все работает :)

      • Просто у него второй спрайт на момент создания первого не в области видимости идентификаторов.

    • VitaMin, по-моему, тебя зациклило. Если так спрайты не видно, то надо их объявить как ПОЛЯ. А то ты собрался в будущем с ними работать, а объявляешь их внутри метода.

      • Всё разобралси)))

        Sprite sprite1, sprite2;

        sprite1 = new Sprite (...){

        sprite2.setvisible ()

        }

        sprite2 = new Sprite (...)

        Спасибо за помощь)

  • Извиниясь, помогите пожалуйста, не работает код:

    this.playBttn = new Sprite (90,260,BrainGameActivity.PlayBttnTexture) {

    public boolean onAreaTouched (final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {

    this.setPosition (pSceneTouchEvent.getX () — this.getWidth () / 2, pSceneTouchEvent.getY () — this.getHeight () / 2);

    return true;

    }

    };

    this.exitBttn = new Sprite (90,360,BrainGameActivity.ExitBttnTexture);

    this.attachChild (playBttn);

    this.attachChild (exitBttn);

    this.registerTouchArea (playBttn);

    this.setTouchAreaBindingEnabled (true);

    this — это сцена с именем MainMenu, в свою очередь эта сцена является ребёнком сцены MainScene (ну а эта является главной сценой).

    Почему-то при воспроизведении этого когда, перекрытая функция onAreaTouched — не работает, в чем проблема?

    • а ты в MainScene посылаешь onScenTouchEvent в MainMenu?

      • Спасибо, я догадывался что что то такое нужно сделать, мозг не дошел))

        Вопрос не по теме: А текстуры в andengine, которые должны динамически обновляться нужнл создавать в отдельных чистых атласах?

  • А ведь на самом деле не работает isActionUp? Почему!??

    Код такой:

    Sprite playBttn = new Sprite (90,260,ResBank.playBttn) {

    @Override

    public boolean onAreaTouched (TouchEvent pSceneTouchEvent, float pTouchAreaLocalX, float pTouchAreaLocalY) {

    if (pSceneTouchEvent.isActionUp ()) {

    MainMenuScene.this.toggle ();

    return true;

    }

    return false;

    }

    };

    Почему так? Читал форум AndEngine, пробовал решать проблемы как они(добавляя условия для других событий и т.п., так ничего и не помогло, isActionDown — работает а isActionUp не хочет и все, 3ий час сижу и не могу понять в чем проблема!?

    • Покажи, как ты передаешь onSceneTouchEvent в сцены

      • В главной сцене с название MainScene, переопределяю метод:

        @Override

        public boolean onSceneTouchEvent (TouchEvent pSceneTouchEvent) {

        MainMenu.onSceneTouchEvent (pSceneTouchEvent);

        return super.onSceneTouchEvent (pSceneTouchEvent);

        }

        MainMenu — соответственно сцена для главного меню (в этой сцене и происходит, точнее не происходит isActionUp)

        • Стандартная «ловушка» для разработчика :) Вместо return super.onSceneTouchEvent (pSceneTouchEvent); нужно делать return true;

          • Спасибо огромное вам!!!)

  • Похоже, что касание работает как прямоугольная область всего спрайта. Но если спрайт содержит прозрачный фон и изображение не прямоугольное, а круглое или овальное?

    Есть ли возможность обрабатывать касания на спрайты по облостям, или может по цвету

  • нид хелп :)

    есть сцена, на неё добавляется определенное кол-во ОДИНАКОВЫХ спрайтов.

    Вопрос: каким образом нужно создавать спрайты (или отслеживать нажатия), чтобы при нажатии ОДИН спрайт выполнял действия.

    До сих пор в голову не пршило умных мыслей на этот счёт. Нажимаю на один спрайт — а действие выполняют все.

    • использовать this внутри методов в спрайте. А лучше — код в студию!

      • с этим разобрался :)

        есть пара вопросов на счёт этого абзаца: После этого спрайт будет получать вызов метода onAreaTouched. ВАЖНО: он будет его получать, даже если он не добавлен на Сцену.

        Собственно после удаления, нажатие на пустое место спрайта запускает действие + частенько выкидывает из прилаги. Any ideas?)

        Суну на всякий случай код) не судите строго, кодер из меня пока не очень)

        public Scene onLoadScene () {

        // TODO Auto-generated method stub

        Scene scene = new Scene (1);

        createSprite (scene,count);

        return scene;

        }

        public void onLoadComplete () {

        // TODO Auto-generated method stub

        }

        private void createSprite (Scene scene, int count){

        sCount=count;

        for (int i=0; i<count; i++){

        float pX = MathUtils.random (30.0f, (CAMERA_WIDTH — 30.0f));

        float pY = MathUtils.random (30.0f, (CAMERA_HEIGHT — 30.0f));

        Sprite sprite = new Sprite (pX, pY, this.mTextureRegion){

        public boolean onAreaTouched (TouchEvent pSceneTouchEvent, float pTouchAreaLocalX, float pTouchAreaLocalY){

        if (pSceneTouchEvent.isActionDown ()){

        mEngine.getScene ().getLastChild ().detachChild (this);

        sCount--;

        if (sCount == 0){

        showDialog (NEXT_STAGE_DIALOG);

        }

        return true;

        }

        return false;

        }

        };

        scene.registerTouchArea (sprite);

        scene.getLastChild ().attachChild (sprite);

        }

        }

        protected Dialog onCreateDialog (int id){

        switch (id){

        case GAME_OVER_DIALOG:

        AlertDialog.Builder mBuilder = new AlertDialog.Builder (this);

        mBuilder.setCancelable (false)

        .setMessage ("Вы проиграли, попробывать снова?")

        .setPositiveButton ("да", new DialogInterface.OnClickListener () {

        public void onClick (DialogInterface dialog, int which) {

        // TODO Auto-generated method stub

        mEngine.getScene ().reset ();

        createSprite (mEngine.getScene (),count);

        }

        })

        .setNegativeButton ("нет", new DialogInterface.OnClickListener () {

        public void onClick (DialogInterface dialog, int which) {

        // TODO Auto-generated method stub

        startActivity (new Intent (getApplicationContext (), FinalStageActivity.class));

        }

        });

        return mBuilder.create ();

        case NEXT_STAGE_DIALOG:

        AlertDialog.Builder fBuilder = new AlertDialog.Builder (this);

        fBuilder.setCancelable (false)

        .setMessage ("Уровень пройден")

        .setPositiveButton ("Далее", new DialogInterface.OnClickListener (){

        public void onClick (DialogInterface dialog, int which){

        createSprite (mEngine.getScene (),count++);

        }

        });

        return fBuilder.create ();

        };

        return null;

        }

        • Дак если есть scene.registerTouchArea (sprite); , то после удаления надо делать scene.unregisterTouchArea (sprite);

          А код mEngine.getScene ().getLastChild ().detachChild (this); надо запускать в runOnUpdateThread, я об этом писал

          • Понятно, спасибо!

            виноват, не дочитал, исправлюсь)

Прокомментировать