我写了一个游戏,它使用了许多线条,圆圈(有些是轮廓,有些是填充的,有些是两者)和文本元素,以创建吉他指板,我让用户与之交互。这些元素中的一些是动画的(坐标,alpha,颜色或组合),并且应用程序在我想要修复的大多数动画中开始跳过大量帧。我认为OpenGL是可行的方法,但在我加入之前,我对一些指针很感兴趣。
目前我的动画是通过查找表,异步任务和动态位图创建实现的(对于文本 - 我使用自定义字体将其渲染为位图 - 所以我从不直接在画布上绘制文本)。我有异步任务运行n * 1000毫秒,并在线程中等待x毫秒(通常为50毫秒),然后推出进度消息 - 辅助类然后计算出时间索引查找表中动画的位置并根据该值计算相对值。我使用附带的绘制圆和绘制线方法将指板的静态位直接绘制到画布上。
我不确定目前放慢我的应用程序的速度是什么(主要是因为我还没有对它进行分析)但是我很确定即使我缓存了位图并且对我的方式非常敏感m改变大小&透明度,使用直接绘制到Canvas的位图是导致速度减慢的原因。
我已经按照一些教程编写了一些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();
你需要在最后一段代码中读取一行代码,因为那里有大量辅助函数。
这看起来像一个合理的方法吗?如果有人可以打扰我,我会喜欢代码审查 - 如果需要,我很乐意发布更多代码或解释
答案 0 :(得分:1)
我一直在使用OpenGL工作,而且......这不是一件容易的事。
对于文本,你必须创建一个可变的Bitmap,用canvas.drawText(...)在其中写入一些文本,然后将这个Bitmap(应该是powerOfTwo维度)转换为gl纹理,并将其应用于矩形。
对于圈子......它会变得复杂。 OpenGL绘制线条,三角形,圆点...而不是我害怕的圆圈。在OpenGL中实现圆形的一种方法是使用圆形纹理,并将其应用于矩形。
每当你的应用程序暂停并恢复时,你将不得不重新创建每个纹理,并再次设置你的gl表面......
如果你需要混合,你可能会发现,有很多纹理,你的手机没有足够的内存来处理它...
现在我吓坏了你:开放gl真是其中一条路!它将允许您的应用程序将绘图委托给手机GPU,这样可以让您保留CPU来计算动画等。
您可以在此网站上找到有关使用OpenGL ES for android的一些信息: 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,......
祝你好运。修改:
看起来我没有足够的权限来撰写评论,因此我将对此进行编辑:
我这样做的方式可能会简单得多:
我有一个表面视图,渲染器专用于尽可能多地绘制。他会绘制同步的项目列表(Synchronized,因为多线程),位置,alpha,...这里没有异步任务,只是一个常规的渲染线程。
表面视图也会启动一个线程(常规线程,没有异步任务),带有List(或类似的东西)。这个线程每隔16毫秒或者其他东西,会在动画列表中运行,然后应用它们。 (当然,如果需要更改渲染线程使用的某些项目,则同步)。
当动画结束时(例如,如果它在列表中的时间超过2000毫秒,很容易用System.currentTimeMillis()等函数检查),我会从列表中删除它。 / p>
瞧!
然后你会告诉我:嘿,但如果我做了很多动画,计算可能需要超过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);
在你的动画功能中,如果时间过长,你会动画更多! 希望这会有所帮助。