Reputation: 537
I have spent some time learning how to create a 2D rendering game loop on Android anno 2016.
I would like achieve the following:
The myth about SurfaceView:
First of all there is several posts recommending SurfaceView. At first glance this seems like a good idea since it uses a seperate rendering thread, but it turns out that the Canvas returned from a SurfaceHolder cannot be hardware accelerated!! Using a SurfaceView with software rendering on a device with QuadHD (2560x1440) resolution is simply horribly inefficient.
Therefore my choice was to extend a basic View and override onDraw(). Calling invalidate() for each update.
Smooth animations:
My next challenge was smooth animations. Turns out reading System.nanoTime() inside onDraw() was a bad idea since it will not be called at excatly 1/60 seconds intervals creating jerky motions on my sprites. Therefore I have used the Choreographer to provide me with the frame time of each VSYNC. This works well.
Current progress:
I feel that I have come pretty close, but I still experience some occasional lagging on older devices. Memory usage is pretty low so I dont think GC is behind this... Seems like my callbacks miss/jumps aoad frame once in a while.
I will post my code and I'm looking forward to read you comments and suggestions.
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);
}
}
I have also created a systrace file.
Upvotes: 5
Views: 3864
Reputation: 52343
There really isn't a "myth" about SurfaceView. It is the best choice for high-resolution fast animation... but you have to use OpenGL ES. Canvas rendering on Surfaces -- SurfaceView, TextureView, whatever -- is not hardware-accelerated, and becomes increasingly expensive as pixel counts increase.
One useful feature of SurfaceView is that you can set the Surface to be a fixed size, and let the display hardware scale it up. For some types of games this can yield adequate performance. An example of what the scaling looks like is here; note the rendering uses GLES.
The general advice about game loops can be found in this appendix. You seem to be doing the right things. You may want to consider adding a frame-drop counter to see if your animation glitches correlate with dropped frames. (If consecutive times reported by Choreographer jump from 16.7ms to 33ms, you know you've dropped one.)
The best way to track down animation glitches is with systrace. The traces make it very easy to see exactly what all of your threads are doing, and establish cause and effect for pauses.
Upvotes: 6