Last active
April 4, 2025 14:03
-
-
Save slightfoot/a519bc3627f49c44a226 to your computer and use it in GitHub Desktop.
Example of using a SurfaceView to render potential 2d game content.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.your.package; | |
import java.util.Random; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.graphics.BitmapFactory; | |
import android.graphics.BitmapShader; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.Paint; | |
import android.graphics.PorterDuff; | |
import android.graphics.Rect; | |
import android.graphics.RectF; | |
import android.graphics.Shader; | |
import android.os.Bundle; | |
import android.os.SystemClock; | |
import android.view.SurfaceHolder; | |
import android.view.SurfaceView; | |
public class MainActivity extends Activity | |
{ | |
@Override | |
protected void onCreate(Bundle savedInstanceState) | |
{ | |
super.onCreate(savedInstanceState); | |
setContentView(new BallSurfaceView(this)); | |
} | |
public static class BallSurfaceView extends SurfaceView implements SurfaceHolder.Callback | |
{ | |
private RenderThread mThread; | |
public BallSurfaceView(Context context) | |
{ | |
super(context); | |
setKeepScreenOn(true); | |
getHolder().addCallback(this); | |
} | |
@Override | |
public void surfaceCreated(SurfaceHolder holder) | |
{ | |
mThread = new RenderThread(holder); | |
mThread.start(); | |
} | |
@Override | |
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) | |
{ | |
mThread.setSize(width, height); | |
} | |
@Override | |
public void surfaceDestroyed(SurfaceHolder holder) | |
{ | |
mThread.quit(); | |
} | |
private class RenderThread extends Thread | |
{ | |
private SurfaceHolder mHolder; | |
private boolean mQuit; | |
private int mWidth; | |
private int mHeight; | |
private Renderable[] mRenderables = new Renderable[6]; | |
private Paint mDebugPaint = new Paint(Paint.ANTI_ALIAS_FLAG); | |
private final boolean mDebugEnable = true; // flip me to see clip rect | |
public RenderThread(SurfaceHolder holder) | |
{ | |
super(RenderThread.class.getSimpleName()); | |
mHolder = holder; | |
Random rand = new Random(); | |
mRenderables[0] = new Background(); | |
for(int i = 1; i < mRenderables.length; i++){ | |
mRenderables[i] = new Ball(100 + rand.nextInt(1500)); | |
} | |
mDebugPaint.setColor(Color.GREEN); | |
} | |
public void setSize(int width, int height) | |
{ | |
mWidth = width; | |
mHeight = height; | |
for(int i = 0; i < mRenderables.length; i++){ | |
mRenderables[i].playfield(width, height); | |
} | |
} | |
@Override | |
public void run() | |
{ | |
mQuit = false; | |
Rect dirty = new Rect(); | |
RectF dirtyF = new RectF(); | |
double dt = 1 / 60.0; // upper-bound 60fps | |
double currentTime = SystemClock.elapsedRealtime(); | |
while(!mQuit){ | |
double newTime = SystemClock.elapsedRealtime(); | |
double frameTime = (newTime - currentTime) / 1000.0f; | |
currentTime = newTime; | |
dirtyF.setEmpty(); | |
while(frameTime > 0.0){ | |
double deltaTime = Math.min(frameTime, dt); | |
integrate(dirtyF, 1.0f * deltaTime); | |
frameTime -= deltaTime; | |
} | |
dirty.set((int)dirtyF.left, (int)dirtyF.top, | |
(int)Math.round(dirtyF.right), | |
(int)Math.round(dirtyF.bottom)); | |
render(dirty); | |
} | |
} | |
private void integrate(RectF dirty, double timeDelta) | |
{ | |
for(int i = 0; i < mRenderables.length; i++){ | |
final Renderable renderable = mRenderables[i]; | |
renderable.unionRect(dirty); | |
renderable.update(dirty, timeDelta); | |
renderable.unionRect(dirty); | |
} | |
} | |
private void render(Rect dirty) | |
{ | |
Canvas c = mHolder.lockCanvas(!mDebugEnable ? dirty : null); | |
if(c != null){ | |
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); | |
if(mDebugEnable){ | |
c.drawRect(dirty, mDebugPaint); | |
} | |
for(int i = 0; i < mRenderables.length; i++){ | |
mRenderables[i].draw(c); | |
} | |
mHolder.unlockCanvasAndPost(c); | |
} | |
} | |
public void quit() | |
{ | |
mQuit = true; | |
try{ | |
mThread.join(); | |
} | |
catch(InterruptedException e){ | |
// | |
} | |
} | |
private class Background extends Renderable | |
{ | |
private final Paint mBg = new Paint(Paint.ANTI_ALIAS_FLAG); | |
public Background() | |
{ | |
mBg.setShader(new BitmapShader( | |
BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_dialog_alert), | |
Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); | |
} | |
@Override | |
public void playfield(int width, int height) | |
{ | |
} | |
@Override | |
public void update(RectF dirty, double timeDelta) | |
{ | |
} | |
@Override | |
public void draw(Canvas c) | |
{ | |
c.drawPaint(mBg); | |
} | |
} | |
private class Ball extends Renderable | |
{ | |
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); | |
private float mXVelDir, mYVelDir; // pixels-per-second | |
private float mSize; | |
public Ball(int speed) | |
{ | |
mPaint.setStyle(Paint.Style.FILL); | |
mPaint.setColor(Color.RED); | |
mXVelDir = speed; | |
mYVelDir = speed; | |
} | |
@Override | |
public void playfield(int width, int height) | |
{ | |
mSize = width / 7.0f; | |
} | |
@Override | |
public void update(RectF dirty, double timeDelta) | |
{ | |
mRect.left += (mXVelDir * timeDelta); | |
mRect.top += (mYVelDir * timeDelta); | |
mRect.right = mRect.left + mSize; | |
mRect.bottom = mRect.top + mSize; | |
if(mRect.left <= 0){ | |
mRect.offset(-mRect.left, 0); | |
mXVelDir = -mXVelDir; | |
} | |
else if(mRect.right >= mWidth){ | |
mRect.offset(mWidth - mRect.right, 0); | |
mXVelDir = -mXVelDir; | |
} | |
if(mRect.top <= 0){ | |
mRect.offset(0, -mRect.top); | |
mYVelDir = -mYVelDir; | |
} | |
else if(mRect.bottom >= mHeight){ | |
mRect.offset(0, mHeight - mRect.bottom); | |
mYVelDir = -mYVelDir; | |
} | |
} | |
@Override | |
public void draw(Canvas c) | |
{ | |
c.drawCircle(mRect.centerX(), mRect.centerY(), | |
mRect.width() / 2.0f, mPaint); | |
} | |
} | |
private abstract class Renderable | |
{ | |
protected final RectF mRect = new RectF(); | |
public abstract void playfield(int width, int height); | |
public abstract void update(RectF dirty, double timeDelta); | |
public abstract void draw(Canvas c); | |
public final void unionRect(RectF dirty) | |
{ | |
dirty.union(mRect); | |
} | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment