вторник, 22 января 2013 г.

Ограничение количества кадров в секунду в AndEngine GLES2

Бережное отношение к ресурсам устройства на котором работает ваша программа - залог крепкого здоровья, хорошей кармы и великого счастья всем пользователям и процессорам.
Например, если вы создаете живые обои настоятельно рекомендуется минимизировать потребляемые ресурсы. Оптимизация потребления памяти - отдельная большая тема о которой поговорим в другой раз, а сегодня проделаем достаточно простое и эффективное действо по ограничение количества кадров при рендринге изображения движком.
Начинающие разработчики при поиске подходящего движка часто обращаются к движку AndEngine. Оно и не мудрено, он хорошо проработан и имеет большое комьюнити. Правда документацией автор слегка пренебрег. Но форум движка бесценен, там можно найти ответ практически на любой вопрос, да и различных туториалов в интернете достаточно много. Я постараюсь писать ориентируясь именно на новичков, тем более что специалистам эта статья будет не очень интересна.

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



Актуальная на данный момент ветка движка - GLES2, то есть версия использующая OpenGL ES 2. На самом деле основа под ограничение количества кадров в самом движке уже присутствует. Предположим что вы уже создали проект, а значит основная Activity (или WallpaperService у вас есть. В GLES2 немного изменился интерфейс родительского класса. В чистом виде имеет несколько наследуемых методов, список которых может отличаться в зависимости от того какой из какого именно класса идет наследование (SimpleBaseGameActivity, BaseGameActivity или BaseLiveWallpaperService).

SimpleBaseGameActivity имеет в девичестве следующий вид:

public class TMActivity extends SimpleBaseGameActivity {
@Override
public EngineOptions onCreateEngineOptions() {
// TODO Auto-generated method stub
return null;
}
@Override
protected void onCreateResources() {
// TODO Auto-generated method stub
}
@Override
protected Scene onCreateScene() {
// TODO Auto-generated method stub
return null;
}
}


BaseGameActivity и BaseLiveWallpaperService  немного отличаются:

public class TMActivity extends BaseGameActivity {
@Override
public EngineOptions onCreateEngineOptions() {
// TODO Auto-generated method stub
return null;
@Override
public void onCreateResources(
OnCreateResourcesCallback pOnCreateResourcesCallback)
throws Exception {
// TODO Auto-generated method stub
@Override
public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback)
throws Exception {
// TODO Auto-generated method stub
@Override
public void onPopulateScene(Scene pScene,
OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
// TODO Auto-generated method stub
}
}

Обратите внимание на метод public EngineOptions onCreateEngineOptions(). В этом месте мы создаем экземпляр опций движка и возвращаем их родителю. Однако через опции ограничить количество кадров нельзя. В GLES1 мы имели возможность в нашей активности не просто задавать некоторый набор параметров движка, а создавать сам экземпляр класса Engine, что давало немного больше гибкости. Но в GLES2 все то же достаточно просто. Экземпляр Engine создается в родительском классе в отдельном методе onCreateEngine(final EngineOptions pEngineOptions), что дает нам возможность легко подменить его в наследнике (нашем основном классе). Для этого просто добавьте в ваш класс метод (для порядка лучше сразу до или сразу после onCreateEngineOptions(), но очередность методов не принципиальна):

@Override
public org.andengine.engine.Engine onCreateEngine(EngineOptions pEngineOptions) {
Engine engine = new Engine(pEngineOptions);
return engine;
}
Специально для ограничения количества кадров, в движке существует класс LimitedFPSEngine, который является наследником класса Engine. То есть вы можете при создании заменить строку: 
Engine engine = new Engine(pEngineOptions);
На следующую:
LimitedFPSEngine engine = new LimitedFPSEngine(pEngineOptions, 25);
Где 25 это и есть требуемое количество кадров в секунду.
То есть в итоге метод будет выглядеть так:
@Override
public org.andengine.engine.Engine onCreateEngine(EngineOptions pEngineOptions) {
LimitedFPSEngine engine = new LimitedFPSEngine(pEngineOptions, 25);
return engine;
}
Не забывайте импортировать класс LimitedFPSEngine, нажав Ctrl+Shift+O.

Иногда может потребоваться динамически менять частоту кадров. Создавать engine в процессе работы нельзя, а значит необходимо немного изменить класс LimitedFPSEngine. Что бы не вмешиваться в код движка мы просто сделаем другой класс расширения класса Engine. Создайте в своем проекте класс с названием FlexibleFPSEngine. Замените
public class FlexibleFPSEngine {
}
следующим кодом:
import org.andengine.engine.Engine;
import org.andengine.engine.options.EngineOptions;
import org.andengine.util.time.TimeConstants; 
public class FlexibleFPSEngine extends Engine {
private long mPreferredFrameLengthNanoseconds;
private int mFramesPerSecond;
public FlexibleFPSEngine(final EngineOptions pEngineOptions, final int pFramesPerSecond) {
super(pEngineOptions);
this.mFramesPerSecond = pFramesPerSecond;
@Override
public void onUpdate(final long pNanosecondsElapsed) throws InterruptedException {
this.mPreferredFrameLengthNanoseconds = TimeConstants.NANOSECONDS_PER_SECOND / mFramesPerSecond;
final long preferredFrameLengthNanoseconds = this.mPreferredFrameLengthNanoseconds;
final long deltaFrameLengthNanoseconds = preferredFrameLengthNanoseconds - pNanosecondsElapsed;
if(deltaFrameLengthNanoseconds <= 0) {
super.onUpdate(pNanosecondsElapsed);
} else {
final int sleepTimeMilliseconds = (int) (deltaFrameLengthNanoseconds / TimeConstants.NANOSECONDS_PER_MILLISECOND);
Thread.sleep(sleepTimeMilliseconds);
super.onUpdate(pNanosecondsElapsed + deltaFrameLengthNanoseconds);
}
public void setFramesPerSecond (final int pFramesPerSecond) {
mFramesPerSecond = pFramesPerSecond;
}
}
Теперь при создании экземпляра движка нужно использовать вместо LimitedFPSEngine ваш новый FlexibleFPSEngine. То есть:
@Override
public org.andengine.engine.Engine onCreateEngine(EngineOptions pEngineOptions) {
FlexibleFPSEngine engine = new FlexibleFPSEngine(pEngineOptions);
return engine;
}
Для доступа из других методов необходимо вынести определение переменной engine за пределы метода:

FlexibleFPSEngine engine;

@Override
public org.andengine.engine.Engine onCreateEngine(EngineOptions pEngineOptions) {
engine = new FlexibleFPSEngine(pEngineOptions);
return engine;
}

Лучше разместить FlexibleFPSEngine engine; в начале класса, рядом с прочими переменными (что бы не потерять).

Теперь вызвав экземпляр движка мы можем обратиться к его методу setFramesPerSecond(int pFramesPerSecond).
То есть: engine.setFramesPerSecond(37) - задаст ограничение в 37 кадров в секунду.

Краткая инструкция:

1. Создайте в проекте класс FlexibleFPSEngine и скопируйте в него код:

import org.andengine.engine.Engine;
import org.andengine.engine.options.EngineOptions;
import org.andengine.util.time.TimeConstants; 
public class FlexibleFPSEngine extends Engine {
private long mPreferredFrameLengthNanoseconds;
private int mFramesPerSecond;
public FlexibleFPSEngine(final EngineOptions pEngineOptions, final int pFramesPerSecond) {
super(pEngineOptions);
this.mFramesPerSecond = pFramesPerSecond;
@Override
public void onUpdate(final long pNanosecondsElapsed) throws InterruptedException {
this.mPreferredFrameLengthNanoseconds = TimeConstants.NANOSECONDS_PER_SECOND / mFramesPerSecond;
final long preferredFrameLengthNanoseconds = this.mPreferredFrameLengthNanoseconds;
final long deltaFrameLengthNanoseconds = preferredFrameLengthNanoseconds - pNanosecondsElapsed;
if(deltaFrameLengthNanoseconds <= 0) {
super.onUpdate(pNanosecondsElapsed);
} else {
final int sleepTimeMilliseconds = (int) (deltaFrameLengthNanoseconds / TimeConstants.NANOSECONDS_PER_MILLISECOND);
Thread.sleep(sleepTimeMilliseconds);
super.onUpdate(pNanosecondsElapsed + deltaFrameLengthNanoseconds);
}
public void setFramesPerSecond (final int pFramesPerSecond) {
mFramesPerSecond = pFramesPerSecond;
}
}
2. Определите переменную движка FlexibleFPSEngine engine;
3. В классе сервиса или активности проекта добавьте метод:
@Override
public org.andengine.engine.Engine onCreateEngine(EngineOptions pEngineOptions) {
FlexibleFPSEngine engine = new FlexibleFPSEngine(pEngineOptions);
return engine;
}
4. Теперь вызвав  engine.setFramesPerSecond(int p) вы в любой момент сможете изменить ограничение по частоте кадров.

Надеюсь что статья была вам полезна.
PS: Как только переедем на новый сайт сразу сделаю в статьях подсветку кода, а пока что страдайте О_О

Комментариев нет:

Отправить комментарий