安卓:在SurfaceView中绘制多个位图时低帧率

4

我正在尝试创建一个弹幕游戏,但遇到了一些麻烦。当有大约500颗子弹时,帧率不能超过17fps。更新逻辑代码对于它们需要1-4ms,而渲染代码需要约40ms。

目前我的代码如下:

private void drawEntities(Canvas canvas) {
    for (HashMap<UUID, Spatial> h: spatialList) {
        for (Spatial spatial: h.values()) {
            spatial.render(canvas);
            if(spatial.life > 0)
                spatial.life--;
            else if (spatial.life == 0)
                engine.deleteEntity(spatial.owner);
        }
    }
}

spatialList是一个ArrayList,其中每个索引都表示一个zLevel。

显示实际子弹的空间是:

public void render(Canvas canvas) {
    float angle = (float) (vel.getAngle() * (180 / Math.PI));
    matrix.reset();
    matrix.setTranslate(pos.x - bullet.getWidth() / 2, pos.y - bullet.getHeight() / 2);
    matrix.postRotate(angle + 90,  pos.x, pos.y);
    canvas.drawBitmap(bullet, matrix, paint);
    canvas.drawCircle(pos.x, pos.y, col.getRadius(), paint);
}

我可以提供更多的代码,但这些似乎是主要问题。我已经尝试了我所能想到的一切,也没有在网上找到更多的信息。我能想到的唯一解决方法是从SurfaceView切换到GLSurfaceView,但我真的认为有更好的方法,我只是在使用糟糕的代码。
编辑:我注意到我的计时器有误,并删除了绘制圆形,重新运行后,我得到40ms左右500次,这仍然对于合理的性能来说有点太低了。
简而言之:500个实体=17fps。
3个回答

6
你可能会受到像素填充率的限制。你测试设备上的显示屏有多大(以像素为单位)?
一个简单的调整是使用 setFixedSize() 来减小 SurfaceView 的表面大小。这将减少你所触及的像素数量。示例 在这里, 视频 在这里, 博客文章 在这里
通常这是一个好主意,因为新设备似乎正在向着荒谬的像素计数竞速。在2560x1440的显示器上,全屏游戏如果所有渲染都在软件中进行,将会遇到困难,并且在4K下表现不佳。将游戏“限制”在1080p并让显示器缩放器进行重负载处理应该会有所帮助。根据游戏的性质,您甚至可以将分辨率设置得更低,而不会出现明显的质量损失。
另一种尝试的方法是消除drawBitmap()调用,检查您的时间,然后恢复它并消除drawCircle()调用,以查看其中一个是否占用了大部分时间。
您可能会发现切换到OpenGL ES并不那么糟糕。 Grafika(来自上面链接的视频)中的“硬件缩放器练习”活动显示了一些简单的位图渲染。用缩放位图替换drawCircle(),您可能已经完成了大部分工作。(请注意,在该活动中使用SurfaceView而不是GLSurfaceView进行GLES。)

仅使用drawCircle函数,即使在我的烂手机上最多有750个实体,fps也不会低于30,这太棒了。然而,我注意到即使没有任何东西被绘制,渲染时间也为16ms,这很奇怪。setFixedSize()函数并没有看到fps的微小增加。你说你可以使用surfaceview来进行gles?有趣。 - Metalith
可能是位图旋转的问题。如果你只是删除 matrix.postRotate(),会有所改善吗?GLSurfaceView 只是 SurfaceView 的包装类,它处理 EGL 设置并为您管理渲染器线程。Grafika 拥有您需要的所有 EGL 设置,并且我猜您已经在为 SurfaceView 管理自己的线程,因此可能很简单(需要一些 GLES 工作)。在 60fps 设备上,16.7ms 的“渲染时间”可能是由于队列堆积导致的,因为 SurfaceView 不会丢帧;请参阅 https://source.android.com/devices/graphics/architecture.html#loops。 - fadden
哇塞,OpenGL 真的很吓人,但是因为它能加速处理速度且转换似乎不太困难,所以我想说你回答得很好。 - Metalith
你需要的大部分内容都在Grafika或者像Android Breakout(https://github.com/fadden/android-breakout)这样的示例中。后者旨在作为一个简单但完整的GLES 2.x游戏的示例。我认为Grafika中的类组合更好。一旦你跨过了最初的学习曲线,你只需要复制位图。唯一棘手的地方是文本渲染,你可以使用字体纹理(就像Breakout使用的那样),或者对于得分叠加层,只需将其绘制在SurfaceView的View部分上即可。 - fadden

2
我遇到了同样的问题。如果您在位图帧缓冲区上进行所有绘制,然后再将帧缓冲区绘制到画布上,则问题将在很大程度上得到解决。如果您直接在画布上进行绘制,则会出现几个开销。我找到了这个教程 "http://www.kilobolt.com/day-6-the-android-game-framework-part-ii.html"。请查看AndroidGraphics和AndroidFastRenderView的实现方式,并查看他如何使用AndroidGraphics在缓冲区中进行所有实际绘制,并在AndroidFastRenderView中将该缓冲区绘制到画布上。

我在大多数手机上使用2个全屏背景图像,约200个旋转和缩放的精灵以及32位图像时,可以获得超过30帧每秒的性能。 - Aseem Vyas
是的,使用framebuffer后我可以最小降低绘制大约800。这种技术确实不容易理解,但很容易实现。 - Metalith

0

尝试从您的render()方法中删除rotate和/或drawCircle部分时的性能。它们两个可能会非常耗时,因为它们可能包含sin() / cos()计算。

如果这有所帮助,您将不得不想办法用更快的东西替换它们,如果没有,那么...


我已经尝试过这两种方法,虽然它们可以稍微加快速度,但旋转是必要的。图像需要在每一帧动态旋转以匹配射击角度。 - Metalith
我明白了,很抱歉它没有帮到你。我自己不是游戏开发者,所以恐怕这就是我能提供的全部了 :-/ - Ridcully
我自己不是游戏开发者,但对于弹幕游戏来说,250颗子弹相当有限。这也暗示着我的代码存在某些严重的减速问题。 - Metalith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接