1 of 19

making paper

spaceships, timers, and some other things

2 of 19

Hello

Matthew Lim

  • Android Developer @ Spotify
  • @matthewylim

Jefferson Lee

  • Android Developer @ Insensi
  • @kimchibooty

3 of 19

okay wait, so what was PaperCraft again?

4 of 19

5 of 19

Overview

  • Game Loops
  • Drawing using the Canvas API
  • Animation Techniques

6 of 19

Loops

7 of 19

Game Loops

Most games work like this, on a per-frame basis:

  • Process any input events from the user, i.e. from a controller or from a touchscreen
  • Figure out what the next frame’s game state should be, given new user input and the current frame’s game state.
  • Draw the next frame, now that we’ve calculated its state.

Draw game

Evaluate next game state

Receive input

8 of 19

Game Loops

How we did it on Android:

  • Process inputs on the main thread, save the input data for reading later.
  • Evaluate next game state on a background TimerTask thread, taking into account input data saved from before. Use a synchronized {} block.
  • Trigger a draw from the background thread using postInvalidate();

Main thread

Draw game

Background thread

Evaluate next game state

Receive input

9 of 19

Game Loops

Set up the TimerTask:

  • Update game logic here in response to input events or existing game state.
  • Call postInvalidate() here.
  • In general, this is not cheap - fancy fast drawing is inherently expensive.

TimerTask frameTask;

int enemyIntervalTicker;

public void initFrameTask() {

frameTask = new TimerTask() {

@Override

public void run() {

// this fires every 1/60th of a second...

// 60 ticks = 1 second

// 30 ticks = 1/2 second

synchronized(frameLock) {

// update game logic

if (enemyIntervalTicker++ == 60) {

spawnEnemy();

enemyIntervalTicker = 0;

}

removeOffscreenEnemies();

}

postInvalidate(); // trigger onDraw()

}

};

}

10 of 19

Timer Management

@Override

public void onResume() { // schedule the task

super.onResume();

if (frameTimer == null) {

frameTimer = new Timer();

}

if (frameTask == null) {

initFrameTask();

} else {

frameTask.cancel();

frameTask = null;

initFrameTask();

}

frameTimer.schedule(frameTask, 0, 16);

}

@Override

public void onPause() { // stop the task

super.onPause();

if (frameTimer != null) {

frameTimer.cancel();

frameTimer.purge();

}

if (frameTask != null) {

frameTask.cancel();

frameTask = null;

frameTimer = null;

}

}

11 of 19

Drawing

12 of 19

Drawing with the Canvas API

Two main methods we used to draw everything in the game:

  • Shape/Line Drawing:
    • More expensive, but allows for more complex and dynamic drawing�
  • Bitmap Drawing:
    • Much cheaper but provides less flexibility.

@Override onDraw(Canvas canvas) {

//Shape Drawing

Path path = new Path();

path.moveTo(0,0);

path.lineTo(10,0);

path.lineTo(10,10);

path.lineTo(0,10);

path.close();

canvas.drawPath(path, pathPaint);

//Bitmap Drawing

Bitmap bitmap = generateBitmap();

canvas.drawBitmap(bitmap, transMatrix, null);

}

13 of 19

Drawing Shapes

You might draw a simple shape like this:

  • Create a path and use it to draw any shape
  • If the shape will be a more dynamic object in the game, you can create a Matrix object with the desired effects.
  • Apply the matrix transform on the path
  • Draw the path

private int tWidth, tHeight; //dimension values. could be dynamic

private int posX, posY; //values coming from user input

private Paint paint; //paint object to draw the path

private Path trianglePath = new Path();

@Override onDraw(Canvas canvas) {

//make a triangle shape with a path

trianglePath.reset();

trianglePath.moveTo(0,0);

trianglePath.lineTo(tWidth, tHeight/2);

trianglePath.lineTo(0, tHeight);

trianglePath.close();

//draw shape!

canvas.drawPath(path, paint);

}

14 of 19

Drawing Bitmaps

Matrix matrix;

Rect srcRect;

Rect destRect;

@Override

public void onDraw(Canvas canvas) {

// draw w/ x & y offset

canvas.drawBitmap(left, top, bitmap, null);

// draw using source/dest rectangles (automatic scaling)

canvas.drawBitmap(bitmap, srcRect, destRect, null);

// draw using a matrix (scale/rotate/translate)

matrix.reset();

matrix.setTranslate(tX, tY);

matrix.postRotate(rotAngle,

bitmap.getWidth()/2, bitmap.getHeight()/2);

canvas.drawBitmap(bitmap, matrix, null);

}

Not as many options here:

  • Drawing a bitmap using x, y coordinates
  • Drawing a transformed bitmap using a Matrix
  • Drawing a bitmap using source and destination rectangles

15 of 19

Creating Offscreen Bitmaps

public static Bitmap makeCircleEnemy(int enemyRadius, int blurRadius) {

//create a bitmap to draw on

Bitmap bitmap = Bitmap.createBitmap(enemyRadius*2, enemyRadius*2, Bitmap.Config.ARGB_8888);

//place the bitmap in a canvas to draw on it

Canvas canvas = new Canvas(bitmap);

//create a path to draw the shape

Path enemyPath = new Path();

enemyPath.addCircle(enemyRadius, enemyRadius, enemyRadius, Path.Direction.CW);

enemyPath.close();

canvas.drawPath(enemyPath, enemyPaint);

//bitmap now contains the circle

return bitmap;

}

16 of 19

Animation

17 of 19

Animation

Using Animators:

  • Useful for simple fixed-duration animations
  • Cancelable
  • ObjectAnimator, ValueAnimator, etc.

Using a TimerTask:

  • Better for more complex animations and ongoing/infinite animations
  • float, int, and lerp.

18 of 19

Demo

19 of 19

thanks!