着眼于在应用程序中实现OpenGL



我编写了一款游戏,它使用了许多线条、圆圈(有些是轮廓,有些是填充的,有些是两者都有)和文本元素来创造一个吉他指板,我让用户与之互动。其中一些元素是动画的(坐标,alpha,颜色或组合),并且应用程序在大多数动画期间开始跳过许多帧,这是我想要修复的。我认为OpenGL是可行的方法,但在我开始之前,我对一些指针感兴趣。

目前我的动画是通过查找表,异步任务和动态位图创建实现的(对于文本-我将其渲染为位图,因为我使用自定义字体-所以我从不直接在画布上绘制文本)。我已经让异步任务运行了n * 1000毫秒,在线程中它等待x毫秒(通常是50毫秒),然后推出一个进度消息-助手类然后计算出动画在时间索引查找表中的位置,并基于此计算相对值。我将指板的静态部分直接绘制到画布上,其中包括绘制圆和绘制线的方法。

我不确定是什么减慢了我的应用程序目前(主要是因为我还没有配置它),但我很确定,即使我缓存位图,并已经相当明智的方式,我改变大小&透明度,使用位图直接绘制到画布是导致速度变慢的原因。

我已经遵循了一些教程,并写了一些OpenGL,但还不足以了解很多-我知道它很快,虽然这很重要。我不知道我是否可以使用同样的方法用OpenGL直接在画布上绘制线条和圆圈,我想我仍然需要为文本创建一些位图,我想我必须应用这些作为纹理来显示它们。

谁能给我点建议?一些绘制线条,圆圈和文本的示例代码将是惊人的。任何关于OpenGL动画的指示-我认为我目前的设置是相当可靠的,可以尝试移植它,但任何建议都会很棒,因为这是我第一次看动画。

* EDIT *

这是我的代码的一个基本概述——有很多部分,但我把它们包括进来是希望其他人可能能够使用其中的一些。:

  1. 我有一个查找表

    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();
    }
    
  2. 我已经将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);
                }
            }
        }
        }
    }
    
  3. 我有一个类,我的动画值计算:

    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;
        }
    }
    
  4. 在我的游戏代码中,我通过指定要使用的查找表并为每个不同的状态创建回调,创建带有构建器类的计时器并启动它,将它们联系在一起:

    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();
    
  5. 在我的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函数中,如果经过的时间越长,你就会动画更多!

最新更新