在Android上创建一个无延迟的2D游戏循环

5
我花了一些时间学习如何在2016年创建Android上的2D渲染游戏循环。
我想要实现以下目标:
- 平滑动画 - 硬件加速 - 无延迟(60 fps) - 使用普通的Canvas - 简单易用(不使用OpenGL)
SurfaceView的神话:
首先,有几篇文章推荐使用SurfaceView。乍一看,这似乎是个好主意,因为它使用一个独立的渲染线程,但事实证明,从SurfaceHolder返回的Canvas不能硬件加速!!在具有QuadHD(2560x1440)分辨率的设备上使用软件渲染的SurfaceView非常低效。
因此,我的选择是扩展基本视图并覆盖onDraw()。每次更新时调用invalidate()。
平滑动画:

我的下一个挑战是平滑动画。结果发现在onDraw()内读取System.nanoTime()是一个坏主意,因为它不会在完全1/60秒的间隔内被调用,从而在我的精灵上创建抖动运动。因此,我使用Choreographer来提供每个VSYNC的帧时间。这很有效。

当前进展:

我觉得我已经接近成功了,但在旧设备上仍然会偶尔出现卡顿。内存使用率相当低,所以我不认为GC是问题的根源......似乎我的回调有时会错过/跳过一帧。

我将发布我的代码,并期待阅读您的评论和建议。

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v4.content.res.ResourcesCompat;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.View;

public class MiniGameView extends View implements Choreographer.FrameCallback
{
    private final float mDisplayDensity;

    private long mFrameTime = System.nanoTime();

    private final Drawable mBackground;
    private final Drawable mMonkey;

    public MiniGameView(Context context)
    {
        this(context, null);
    }

    public MiniGameView(Context context, AttributeSet attrs)
    {
        super(context, attrs);

        mDisplayDensity = getResources().getDisplayMetrics().density;

        // Load graphics
        mBackground = ResourcesCompat.getDrawable(getResources(), R.drawable.background, null);
        mMonkey = ResourcesCompat.getDrawable(getResources(), R.drawable.monkey, null);

        Choreographer.getInstance().postFrameCallback(this);
    }

    // Receive time in nano seconds at last VSYNC. Use this frameTime for smooth animations!
    @Override
    public void doFrame(long frameTimeNanos)
    {
        mFrameTime = frameTimeNanos;

        Choreographer.getInstance().postFrameCallback(this);
        invalidate();
    }

    // Draw game here
    @Override
    protected void onDraw(Canvas canvas)
    {
        drawBackground(canvas);
        drawSprites(canvas);
    }

    private void drawBackground(Canvas canvas)
    {
        mBackground.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        mBackground.draw(canvas);
    }

    private void drawSprites(Canvas canvas)
    {
        double t = mFrameTime * 0.00000001;

        int width = canvas.getWidth();
        int height = canvas.getHeight();

        for(int i=0;i<8;i++)
        {
            double x = width * (1 + Math.sin(-0.181 * t)) * 0.5;
            double y = height * (1 - Math.cos(0.153 * t)) * 0.5;

            int size = (int)Math.round((80 + 40 * Math.cos(0.2 * t)) * mDisplayDensity);

            drawSprite(canvas, mMonkey, (int) x, (int) y, size, size);

            t += 0.8;
        }
    }

    private void drawSprite(final Canvas canvas, final Drawable sprite, int x, int y, int w2, int h2)
    {
        sprite.setBounds(x - w2, y - h2, x + w2, y + h2);
        sprite.draw(canvas);
    }

}

我也创建了一个 systrace 文件


1
尝试查看这个仓库 :) 这是一个运行60 fps的2D游戏循环非常简单的示例 https://github.com/matthewlim/MakingPaper - kimchibooty
1个回答

6

关于SurfaceView并没有什么“神话”可言,它是高分辨率快速动画的最佳选择……但你必须使用OpenGL ES。在Surface上进行Canvas渲染——SurfaceView、TextureView或其他任何视图——都没有硬件加速,在像素数量增加时变得越来越昂贵。

SurfaceView的一个有用功能是,您可以将Surface设置为固定大小,让显示硬件对其进行缩放。对于某些类型的游戏,这可以产生足够的性能。这里有一个缩放效果示例(链接);请注意,它使用GLES进行渲染。

有关游戏循环的一般建议可以在此附录中找到。您似乎正在做正确的事情。您可能需要考虑添加一个帧丢失计数器,以查看您的动画故障是否与丢帧相关。(如果Choreographer报告的连续时间从16.7ms跳到33ms,则说明您已经丢失了一帧。)

追踪动画故障的最佳方法是使用systrace。追踪可以很容易地看到所有线程正在做什么,并为暂停建立因果关系。


感谢您的建议。我确认 onFrame 会偶尔掉帧,当它发生时,精灵动画将会抖动!我已经更新了原始帖子,并添加了系统跟踪。 - Ludde
如果更新正常工作,您在60fps下掉一帧不应该注意到太多。Grafika中的“记录GL应用程序”演示将可靠地在某些设备上丢失帧(特别是qcom芯片组,因为当您的手指没有接触屏幕时,它们会积极降低时钟),但除非停顿持续两三帧,否则您不会真正注意到任何问题。Chrome目前有些困难,所以我还没有查看跟踪。 - fadden
Win10 Chrome无法处理跟踪文件,而Linux Chrome可以。看起来事情还在继续进行,尽管在2470毫秒时,wpa_supplicant接管了核心0,你能看到一个突然的停顿。这是一个明显的故障吗?有时候你只需要一遍又一遍地抓取跟踪数据,直到你幸运地捕捉到一个大的停顿,这样你就有东西可以查看了。 - fadden
似乎Chrome 50中的systrace查看功能出现了问题... https://dev59.com/fJbfa4cB1Zd3GeqPuHcb - fadden
解决方法:在新标签页中打开chrome://tracing,然后点击“加载”。(此及其他想法来自前面的链接。) - fadden

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