Я написал игру, в которой используется множество линий, кругов (некоторые обведены, некоторые заполнены, некоторые с обоими) и текстовых элементов, чтобы создать гриф гитары, с которым я взаимодействую. Некоторые из этих элементов являются анимированными (координата, альфа, цвет или комбинация), и приложение начинает пропускать много кадров во время большинства анимаций, которые я хотел бы исправить. Я думаю, что OpenGL - это путь, но я заинтересован в некоторых указателях, прежде чем прыгну.
В настоящее время моя анимация достигается с помощью справочных таблиц, асинхронных задач и динамического создания растровых изображений (для текста - я отображаю его в растровых изображениях, когда использую пользовательские шрифты - поэтому я никогда не рисую текст непосредственно на холсте). У меня есть асинхронная задача, выполняемая в течение n * 1000 мс, и в потоке она ждет x мс (обычно 50 мс), а затем выталкивает сообщение о ходе выполнения - вспомогательные классы затем работают, где в индексированной по времени таблице поиска анимация и рассчитывает относительные значения на основе этого. Я рисую статические кусочки грифа прямо на холсте с помощью включенных методов рисования круга и линии.
Я не уверен, что в настоящее время замедляет работу моего приложения (в основном потому, что я его еще не профилировал), но я почти уверен, что, несмотря на то, что я кеширую растровые изображения, я довольно разумно относился к тому, как Изменяя размер и прозрачность, замедление вызывает использование растровых изображений, отображаемых непосредственно на холсте.
Я следовал некоторым урокам и написал несколько OpenGL, но этого недостаточно, чтобы вообще что-то знать - я знаю, что это быстро, хотя и важно. Я не знаю, смогу ли я использовать те же методы рисования линий и окружностей непосредственно на холсте с помощью OpenGL, и я думаю, что мне все равно придется создавать некоторые растровые изображения для текста, и я думаю, что я должен применять их в качестве текстур в чтобы показать их.
Так кто-нибудь может дать мне несколько советов? Некоторый пример кода рисования линий, кругов и текста был бы удивительным. Любые указания на анимацию в OpenGL - я думаю, что моя текущая установка довольно надежна и может портировать ее, но любой совет был бы полезен, так как это мой первый взгляд на анимацию.
* РЕДАКТИРОВАТЬ *
Вот основной обзор моего кода - есть много частей, но я включаю их в надежде, что кто-то еще сможет использовать некоторые из них .:
У меня есть справочная таблица
public enum LookUpTable {
COUNT_DOWN {
@Override
public float[][] getLookUpTable() {
/*
* 1 = total time left
* 2 = font alpha
* 3 = font size
*/
float[][] lookUpTable = {
{ 0f, 0f, 400f },
{ 400f, 255f, 200f },
{ 700f, 255f, 150f },
{ 1000f, 0f, 5f }
};
return lookUpTable;
}
@Override
public LookUpType getLookUpType() {
return LookUpType.REPEAT;
}
};
// does the timer loop around, or is it a one off run
private enum LookUpType {
REPEAT, SINGLE
}
abstract public LookUpType getLookUpType();
abstract public float[][] getLookUpTable();
}
Я расширил задачу AsyncTask до функции построителя:
public class CountDownTimerBuilder {
// callbacks - instantiated in the view
protected CountDownEndEvent countDownEndEvent;
protected CountDownProgressEvent countDownProgressEvent;
protected CountDownInitEvent countDownInitEvent;
protected int updatePeriod;
protected float runTime;
public CountDownTimerBuilder withCountDownEndEvent(CountDownEndEvent countDownEndEvent) {
this.countDownEndEvent = countDownEndEvent;
return this;
}
public CountDownTimerBuilder withCountDownProgressEvent(CountDownProgressEvent countDownProgressEvent) {
this.countDownProgressEvent = countDownProgressEvent;
return this;
}
public CountDownTimerBuilder withCountDownInitEvent(CountDownInitEvent countDownInitEvent) {
this.countDownInitEvent = countDownInitEvent;
return this;
}
public CountDownTimerBuilder withUpdatePeriod(int updatePeriod) {
this.updatePeriod = updatePeriod;
return this;
}
public CountDownTimerBuilder withRunTime(float runTime) {
this.runTime = runTime;
return this;
}
public CountDownTimer build() {
return new CountDownTimer();
}
public static interface CountDownEndEvent {
public abstract void dispatch(Long... endResult);
}
public static interface CountDownInitEvent {
public abstract void dispatch();
}
public static interface CountDownProgressEvent {
public abstract void dispatch(Long... progress);
}
public class CountDownTimer {
AsyncTask<Void, Long, Long> genericTimerTask;
/**
* Starts the internal timer
*/
public void start() {
genericTimerTask = new GenericCountDownTimer().execute(new Void[] {});
}
public void cancel() {
if (genericTimerTask != null) {
genericTimerTask.cancel(true);
genericTimerTask = null;
}
}
private class GenericCountDownTimer extends AsyncTask<Void, Long, Long> {
@Override
protected Long doInBackground(Void... params) {
long startTime = System.currentTimeMillis();
long currentTime;
long countDown;
Log.i(ApplicationState.getLogTag(getClass()), "Timer running for " + runTime + " ms, updating every " + updatePeriod + " ms");
do {
try {
Thread.sleep(updatePeriod);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (this.isCancelled()) {
Log.i(ApplicationState.getLogTag(getClass()), "Timer Cancelled");
break;
}
currentTime = System.currentTimeMillis();
countDown = currentTime - startTime;
publishProgress((long)runTime - countDown);
} while (countDown <= runTime);
return 0l;
}
@Override
protected void onPreExecute() {
if (countDownInitEvent != null) {
countDownInitEvent.dispatch();
}
}
@Override
protected void onProgressUpdate(Long... progress) {
Log.v(ApplicationState.getLogTag(getClass()), "Timer progress " + progress[0] + " ms");
if (countDownProgressEvent != null) {
countDownProgressEvent.dispatch(progress);
}
}
@Override
protected void onPostExecute(Long endresult) {
if (countDownEndEvent != null) {
countDownEndEvent.dispatch(endresult);
}
}
}
}
}
У меня есть класс, в котором вычисляются значения моей анимации:
public class AnimationHelper {
private LookUpTable lookUpTable;
private float[][] lookUpTableData;
private float currentTime = -1;
private float multiplier;
private int sourceIndex;
public void setLookupTableData(LookUpTable lookUpTable) {
if (this.lookUpTable != lookUpTable) {
this.lookUpTableData = lookUpTable.getLookUpTable();
this.currentTime = -1;
this.multiplier = -1;
this.sourceIndex = -1;
}
}
private void setCurrentTime(float currentTime) {
this.currentTime = currentTime;
}
public float calculate(float currentTime, int index) {
if (this.currentTime == -1 || this.currentTime != currentTime) {
setCurrentTime(currentTime);
getCurrentLookupTableIndex();
getMultiplier();
}
return getCurrentValue(index);
}
private void getCurrentLookupTableIndex() {
sourceIndex = -1;
for (int scanTimeRange = 0; scanTimeRange < (lookUpTableData.length - 1); scanTimeRange++) {
if (currentTime < lookUpTableData[scanTimeRange + 1][0]) {
sourceIndex = scanTimeRange;
break;
}
}
}
private void getMultiplier() {
if ((lookUpTableData[sourceIndex][0] - lookUpTableData[sourceIndex + 1][0]) == 0.0f) {
multiplier = 0.0f;
} else {
multiplier = (currentTime - lookUpTableData[sourceIndex][0]) / (lookUpTableData[sourceIndex + 1][0] - lookUpTableData[sourceIndex][0]);
}
}
public float getCurrentValue(int index) {
float currentValue = lookUpTableData[sourceIndex][index] + ((lookUpTableData[sourceIndex + 1][index] - lookUpTableData[sourceIndex][index]) * multiplier);
return currentValue > 0 ? currentValue : 0;
}
}
В своем игровом коде я связываю все это, определяя таблицу поиска для использования и создавая обратные вызовы для каждого из различных состояний, создавая таймер с классом построителя и запуская его:
AnimationHelper animHelper = new AnimationHelper();
animHelper.setLookupTableData(LookUpTable.COUNT_DOWN);
CountDownInitEvent animationInitEvent = new CountDownInitEvent() {
public void dispatch() {
genericTimerState = TimerState.NOT_STARTED;
}
};
CountDownProgressEvent animationProgressEvent = new CountDownProgressEvent() {
public void dispatch(Long... progress) {
genericTimerState = TimerState.IN_PROGRESS;
// update the generic timer - we'll use this in all animations
genericTimerCountDown = progress[0];
invalidate();
}
};
CountDownEndEvent animationEndEvent = new CountDownEndEvent() {
public void dispatch(Long... endValue) {
genericTimerState = TimerState.FINISHED;
startGame();
}
};
CountDownTimer timer = new CountDownTimerBuilder()
.withRunTime(getCountDownPeriod(countDownTimePeriod)) // getCountDownPeriod() is used for handling screen rotation - esentially returns the run time for the timer in ms
.withUpdatePeriod(TIMER_UPDATE_PERIOD) // currently set at 50
.withCountDownInitEvent(animationInitEvent)
.withCountDownProgressEvent(animationProgressEvent)
.withCountDownEndEvent(animationEndEvent)
.build();
timer.start();
в моем onDraw я получаю конкретные значения из справочной таблицы и воздействую на них:
private int IFTL = 0; // total time left
private int IFY1 = 1; // initial instructions y offset
private int IFY2 = 2; // start message y offset
private int IFA1 = 3; // note to guess alpha
float yPosition1 = animHelper.calculate(genericTimerCountDown, IFY1);
float yPosition2 = animHelper.calculate(genericTimerCountDown, IFY2);
float alpha1 = animHelper.calculate(genericTimerCountDown, IFA1);
// getScreenDrawData() returns the coordinates and other positioning info for the bitmap
final ScreenDrawData guessNoteTitleDrawValues = FretBoardDimensionHelper.getScreenDrawData(AssetId.GUESS_NOTE_TITLE);
//change the y position of the bitmap being drawn to screen
guessNoteTitleDrawValues.withAlteredCoordinate(Constants.Y_COORDINATE, 0-yPosition1);
//
DrawBitmapBuilder.createInstance()
.withCanvas(getCanvas())
.withBitmap(bitmapCacheGet(AssetId.GUESS_NOTE_TITLE))
.withBitmapDrawValues(guessNoteTitleDrawValues)
.draw();
Paint paint = new Paint();
paint.setAlpha((int)alpha1);
final ScreenDrawData initialNoteDrawValues = FretBoardDimensionHelper.getScreenDrawData(AssetId.GUESS_NOTE_INITIAL_NOTE);
// draw to screen with specified alpha
DrawBitmapBuilder
.createInstance()
.withCanvas(getCanvas())
.withBitmap(bitmapCacheGet(AssetId.GUESS_NOTE_INITIAL_NOTE))
.withBitmapDrawValues(initialNoteDrawValues)
.withPaint(paint)
.draw();
В последнем бите кода вам придется читать между строк немного, поскольку там есть множество вспомогательных функций.
Это похоже на разумный подход? Я хотел бы пересмотреть код, если кто-то может быть обеспокоен - более чем счастлив выложить больше кода или объяснений, если требуется