我编写了一款游戏,它使用了许多线条、圆圈(有些是轮廓,有些是填充的,有些是两者都有)和文本元素来创造一个吉他指板,我让用户与之互动。其中一些元素是动画的(坐标,alpha,颜色或组合),并且应用程序在大多数动画期间开始跳过许多帧,这是我想要修复的。我认为OpenGL是可行的方法,但在我开始之前,我对一些指针感兴趣。
目前我的动画是通过查找表,异步任务和动态位图创建实现的(对于文本-我将其渲染为位图,因为我使用自定义字体-所以我从不直接在画布上绘制文本)。我已经让异步任务运行了n * 1000毫秒,在线程中它等待x毫秒(通常是50毫秒),然后推出一个进度消息-助手类然后计算出动画在时间索引查找表中的位置,并基于此计算相对值。我将指板的静态部分直接绘制到画布上,其中包括绘制圆和绘制线的方法。
我不确定是什么减慢了我的应用程序目前(主要是因为我还没有配置它),但我很确定,即使我缓存位图,并已经相当明智的方式,我改变大小&透明度,使用位图直接绘制到画布是导致速度变慢的原因。
我已经遵循了一些教程,并写了一些OpenGL,但还不足以了解很多-我知道它很快,虽然这很重要。我不知道我是否可以使用同样的方法用OpenGL直接在画布上绘制线条和圆圈,我想我仍然需要为文本创建一些位图,我想我必须应用这些作为纹理来显示它们。
谁能给我点建议?一些绘制线条,圆圈和文本的示例代码将是惊人的。任何关于OpenGL动画的指示-我认为我目前的设置是相当可靠的,可以尝试移植它,但任何建议都会很棒,因为这是我第一次看动画。
* EDIT *
这是我的代码的一个基本概述——有很多部分,但我把它们包括进来是希望其他人可能能够使用其中的一些。:
-
我有一个查找表
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();
你必须在最后一行代码的行间读一点,因为那里有很多帮助函数。
这看起来是一个合理的方法吗?如果有人愿意的话,我很乐意进行代码审查——如果需要的话,我很乐意发布更多的代码或解释
我一直在用OpenGL工作,嗯…这可不是小事。
-
对于文本,你必须创建一个可变的位图,用canvas.drawText(…)在其中写入一些文本,然后将这个位图(应该是在powerOfTwo维度)转换为gl纹理,并将其应用于矩形。
-
圈…事情会很复杂。OpenGL绘制线条,三角形,圆点…恐怕不是圆圈。在OpenGL中实现圆的一种方法是有一个圆的纹理,并将其应用于矩形。
每次应用程序暂停和恢复时,你都必须重新创建每个纹理,并再次设置gl表面…
如果你需要混合,你可能会发现,有很多纹理,你的手机没有足够的内存来处理它…
既然我已经把你吓跑了:打开gl真的是一条可行的路!它将允许你的应用程序将绘图委托给手机GPU,这将让你保持CPU计算动画等。
你可以在这个网站上找到一些关于android使用OpenGL ES的信息:http://blog.jayway.com/2009/12/03/opengl-es-tutorial-for-android-part-i/
有6个教程,第6个是关于纹理。第二个教程告诉你如何绘制多边形和线条。
最后,你应该读这本书:http://my.safaribooksonline.com/book/office-and-productivity-applications/9781430226475/copyright/ii它是关于android, openGL,…
好运。编辑:
看起来我没有足够的权限来写评论,因此我将编辑这个:
我的做法可能会简单得多:
-
我有一个表面视图,用渲染器专门绘制尽可能频繁。他会绘制项目的同步列表(同步,因为是多线程),带有位置,alpha,…这里没有异步任务,只是一个普通的渲染线程。
-
表面视图也将启动一个线程(常规线程,没有异步任务),与列表(或类似的东西)。这个线程会每16毫秒左右运行一次动画列表,应用它们。(如果需要改变渲染线程使用的一些项目,当然是同步的)。
-
当动画结束时(比如,如果它在列表中超过2000毫秒,很容易用System.currentTimeMillis()等函数检查),我会从列表中删除它。
瞧!
然后你会告诉我:嘿,但是如果我做了很多动画,计算可能需要超过16毫秒的时间,而且它似乎变得更慢!我的回答是:有一个简单的解决方案:在处理动画的线程中,在应用它们之前,你做这样的事情:
long lastTime = currentTime;
// Save current time for next frame;
currentTime = System.currentTimeMillis();
// Get elapsed time since last animation calculus
long ellapsedTime = currentTime - lastTime;
animate(ellapsedTime);
在你的animate函数中,如果经过的时间越长,你就会动画更多!