Ноя
23

AndEngine. Нюансы и хитрости

logo_100В этой части расскажу о некоторых нюансах и «подводных камнях».

Как я уже говорил, движок AndEngine в своём методе onUpdate с каждым тактом вызывает метод onUpdate для Сцены.

Сцена, в свою очередь, пробегается по списку объектов, добавленных в неё, и также вызывает для каждого из них метод onUpdate.

Если объект содержит в себе другие объекты (детей) — то все они будут получать вызовы onUpdate.

Вот как выглядят методы onUpdate и  onManagedUpdate класса Entity:

@Override
public final void onUpdate(final float pSecondsElapsed) {
	if(!this.mIgnoreUpdate) {
		this.onManagedUpdate(pSecondsElapsed);
	}
}

protected void onManagedUpdate(final float pSecondsElapsed) {
	if(this.mEntityModifiers != null) {
		this.mEntityModifiers.onUpdate(pSecondsElapsed);
	}
	if(this.mUpdateHandlers != null) {
		this.mUpdateHandlers.onUpdate(pSecondsElapsed);
	}
	if(this.mChildren != null && !this.mChildrenIgnoreUpdate) {
		final ArrayList<IEntity> entities = this.mChildren;
		final int entityCount = entities.size();
		for(int i = 0; i < entityCount; i++) {
			entities.get(i).onUpdate(pSecondsElapsed);
		}
	}
}

Нюанс №1. А теперь представьте, что в вашей игре сотни неподвижных простых спрайтов, образующих атмосферу уровня или задний фон. Или у вас есть Сцена с экраном Паузы в игре, которая видна только при паузе.

Все эти спрайты и невидимые сцены, по умолчанию, будут получать вызов своих методов onUpdate и onManagedUpdate.

Понятно, что на это тратится драгоценное время. Чтобы ускорить весь этот процесс, нужно использовать всего два простых метода: setIgnoreUpdate и setChildrenIgnoreUpdate.

Правило №1. Используйте setIgnoreUpdate (true) всякий раз, когда прячете спрайт (setVisible (false)). Когда показываете спрятанный спрайт, вызывайте метод setIgnoreUpdate (false).

Или так: если вы заранее знаете, что спрайт (в общем случае — Entity) не будет поворачиваться, перемещаться, использовать Модификаторы — при его создании смело запрещайте ему получать обновления от движка методом setIgnoreUpdate (true). И тогда уже при прятании / показе спрайта про setIgnoreUpdate можно не вспоминать.

Используйте метод setChildrenIgnoreUpdate (true), чтобы запретить вызов обновлений только для детей (children) объекта. При этом сам объект обновления будет получать.

 

Нюанс №2. AndEngine работает со списками (SmartList или ArrayList). Все Модификаторы и объекты хранятся в списках. В каждом такте движок пробегается по спискам.

А теперь представьте, что во время очередного Апдейта вы удалили спрайт со Сцены (методом detachChild) или вообще удалили все спрайты (методом detachChildren ()). Или очистили список Модификаторов (метод clearEntityModifiers).

В этом случае обязательно выскочит ошибка типа «Выход за пределы списка» или «Не могу взывать метод такой-то объекта null». И произойдёт это в этом месте:

for(int i = 0; i < entityCount; i++) {
	entities.get(i).onUpdate(pSecondsElapsed);
}

Итак, Правило №2. Все методы, которые уменьшают размер списков движка AndEngine (удаление, очистка), нужно выполнять в методе BaseGameActivity.runOnUpdateThread.

Как это выглядит:

// main - указатель на ваш Activity (который расширяет BaseGameActivity)
main.runOnUpdateThread(new Runnable() {
	@Override
	public void run() {
		_myScene.detachChild(_mySprite);
		_myScene2.detachChildren();
		_mySprite2.clearEntityModifiers();
		_mySprite3.clearUpdateHandlers();
	}
});

Таким образом, все действия внутри run () будут выполнены в следующем такте движка, перед началом вызова метода onUpdate для Сцены.

 

Нюанс №3. Если хотите показать всплывающую подсказку Android'a (Toast), то нужно делать это в методе Activity.runOnUiThread. А иначе тоже возникнут проблемы.

Пример:

runOnUiThread(new Runnable() {
	@Override
	public void run() {
		Toast.makeText(getApplicationContext(), "your text here", Toast.LENGTH_LONG).show();
	}});

Как сделать градиенты плавными

Как-то я уже писал в блоге о проблемах с градиентами в Android — они отображаются с ломанными переходами между цветами.

В новой версии движка решение гораздо проще. Просто в OpenGL нужно включить Dither (не знаю как правильно перевести). Вообщем, это что-то типа шума — к изображению добавляются точки, которые не вооружённым взглядом не видны, но градиенты становятся плавными. Делается это так:

Sprite _SpriteWithGradient = new Sprite(0, 0, _TextureRegion)
{
    protected void onInitDraw(final GL10 pGL)
    {
       super.onInitDraw(pGL);
       GLHelper.enableDither(pGL); //Вот эта "волшебная" строчка
    }
};

И так нужно сделать для каждого спрайта, у которого есть проблемы с градиентом. При этом ВСЁ изображение на экране будет в точку, а не только этот спрайт.

Вот как выглядит картинка при увеличении:

Как закрыть игру / приложение полностью

Обычно, когда выходишь из приложения, оно остаётся висеть в памяти, пока система не выгрузит его или пока вручную не убьёшь каким-нибудь TaskKiller'ом.

Чтобы самому «убить» своё приложение при выходе из него нужно в методе onDestroy главного Activity написать так:

@Override
protected void onDestroy() {
	super.onDestroy();
        android.os.Process.killProcess(android.os.Process.myPid());
}

 




28 коммент. к записи “AndEngine. Нюансы и хитрости”

  • У меня возник вопрос. В функции public Scene onLoadScene () моей активити (унаследована от BaseGameActivity) у меня вызывается метод addHUD (). В этом методе выполняется getEngine ().registerUpdateHandler (new TimerHandler (timerStep, true, new ITimerCallback () {

    public void onTimePassed (final TimerHandler pTimerHandler)

    {...главный цикл...}));

    Вопрос в том выполняется ли код внутри этой функции в updatethread или нет?Внутри это цикла у меня есть вызовы attachChild и подобные, как их вызываь.Потому что вызов runOnUpdateThread внутри другой runOnUpdateThread , как известно не работает.

    Заранее спасибо.Никак не могу разобраться с этим.

    • Я надеюсь «главный цикл» это не главный цикл всей игры? Код внутри функции выполняется в Engine.onUpdateUpdateHandlers (float). Только не могу понять, зачем вам делать цикл через таймер?

      • Спасибо."Главный цикь" это часть где выполняются мои проверки, добавляются спрайты по условию(сразу не получится добавить), проверяется не конец ли игры. Делал по таймеру, потому что подумал, что без него тормозить будет. Или я ошибаюсь?Я так и не понял как мне правильно добавлять и удалять спрайты в главном цикле — в runOnUpdateThread или без?

        • Вся логика работы игры должна быть внутри главной сцены, которую вы создаете в onLoadScene (). Если что-то должно выполняться постоянно, т.е. с каждым тактом движка, то пихайте это в метод onManagedUpdate той же сцены.

          В runOnUpdateThread нужно вызывать только УДАЛЕНИЕ объектов, а добавлять их можно когда угодно.

          Если процесс ваших проверок при создании сцены долгий, то игра просто дольше будет инициализроваться. Ничего страшного в этом нет.

          Расширяйте класс Scene и создавайте в нём вашу игру. С каждым тактом будет вызываться onManagedUpdate.

          Читали статью про Скелет игры?

          • Перечитал еще раз статью про скелет, но не нашел примера где должен быть главный цикл приложения.Может есть ссылка на пример?

          • Ой, да, нету. Спутал с drderico.ru/pishem-igru-dlya-android-chast-2/ . Там самая первая картинка про Engine. Метод onUpdate. По идее у каждой сущности (Entity) можно задать свое поведение внутри её метода onManagedUpdate. Сцена — тоже сущность. Сцены можно добавлять к сценам и таким образом осуществлять логику игры или отдельных экранов (состояний игры) внутри других сцен, а не только главной. Не знаю как еще объяснить. Запишу как-нибудь видео потом

  • как сделать нормальный градиент в gles 2?

    спасибо.

    вот тут что т нашел ... но не пойму пока как этим пользоваться...

    www.andengine.org/forums/...gles2-t6190.html

    • Да без проблем! Вот так:

      @Override

      protected void preDraw (GLState pGLState, Camera pCamera) {

      pGLState.enableDither ();

      super.preDraw (pGLState, pCamera);

      }

  • кст чем отличается SimpleBaseGameActivity от BaseGameActivity.

    И пример с градиентом пойдет в SimpleBaseGameActivity?

    • В SimpleBaseGameActivity упрощено создание движка и похоже на структуру как в GLES1. В BaseGameActivity придётся самому допиливать методы.

      Градиент конечно пойдёт!

      • Извините заранее за такие вопросы, просто так интересно по разбираться этому, и радоваться любому запущенному приложению на своём телефоне... но без точных примеров приходиться тыкать всё подряд, но я уже доволен тем, приложения запускаются без ошибок)

        Но как применить вышеуказанный код? куда его вставить? и сделать бэкграунд четким)

        • Да вот же, в статье выше всё написано:

          Sprite _SpriteWithGradient = new Sprite (0, 0, _TextureRegion)

          {

          protected void onInitDraw (final GL10 pGL)

          {

          super.onInitDraw (pGL);

          GLHelper.enableDither (pGL); //Вот эта «волшебная» строчка

          }

          };

          В версии GLES2 метод onInitDraw заменён на preDraw. Вот onInitDraw и заменяйте новым кодом.

  • У меня возникла 2 ошибка но я немогу понять код который Вы превели, не могли б Вы привести пример кода?

    Заранее больное спасибо)))

    • Если честно, ничего не понял, что ты написал.

      • у меня происходит Нюанс №2 о котором Вы писали, но я не могу понять решение о котором вы написали куда этот кусок кода вставить(который Вы привели для решения), у меня нету указателя main на Activity (который расширяет BaseGameActivity),

        куда мне написать функцию runOnUpdateThread (new Runnable () {

        @Override

        public void run () {

        _myScene.detachChild (_mySprite);

        _myScene2.detachChildren ();

        _mySprite2.clearEntityModifiers ();

        _mySprite3.clearUpdateHandlers ();

        }

        });

        (простите что немогу коректно выразится)

        • Если нет указателя, так создай его! 🙂 Создай поле static public main в твоем классе Activity и в методе onCreate напиши main = this; И пиши этот код там, где тебе нужно удалить спрайты со сцены. Т.е. оборачивай все твои методы detachChild в этот код.

  • добавил код

    main.runOnUpdateThread (new Runnable () {

    public void run () {

    myScene.getLastChild ().detachChild (this);

    }

    });

    эклипс стал ругаться на detachChild (this) предлагая заменить на attachChild либо detachChildren и наоборот

  • На некоторых девайсах ( в частности мощных ) вылетает игра перед загрузкой ресурсов, тупо гаснет экран и вылетает на хоум скрин. Есть идеи?

  • А как Вы решили проблему с тем, что игра(andengine) снимается с паузы после того как был принят звонок?

    • А в чем проблема то?

      • Проблема в том, что если поставить игру на паузу, принять звонок, после этого игра снимется с паузы сама.

        • Тогда во время ухода на паузу показывай другую сцену поверх игры с надписью Paused, а активную до этого сцену делай в ignoreUpdate. При снятии с паузы ничего не делай, а просто жди, пока юзер не нажмет кнопку Continue, например. Сцену с паузой убираешь,а сцену с игрой возвращаешь к жизни

          • Понятно. Значит не сталкивались. Со сценами все решаемо просто. Баг в том, что Engine снимается с паузы.

          • this.mEngine.stop (); не подходит? зачем вообще понадобилось движок держать на паузе при активной Activity (игре)?

        • Я конечно поздновато, но периодически освежаю память уроками Дерико ). Для тех у кого возникнет вопрос подобный вопросу Олега, изучите цикл жизни активити и он отпадет сам собой, вообще это база=)

  • Вопрос: что является основным циклом программы? Как я понял, это какой то мистический onUpdate. Как в него что то написать?

    Скопировал это в код MainActivity:

    @Override

    public final void onUpdate (final float pSecondsElapsed) {

    //ну и мой код

    }

    ругается на @Override, грит, нет такого метода в BaseGameActivity

    Спасибо

    • Метод назывется onManagedUpdate. В некоторых случаях onUpdate. Он есть в классе Entity, а главная сцена является наследником от Entity. Т.е. главный цикл игры крутится в главной сцене

      • Спасибо большое, помогло. Нигде не мог найти, как ни странно) А Ваши статьи видимо прочитал невнимательно

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



ЗАДАЙ СВОЙ ВОПРОС