Smooth Operator

We have our game together and it’s actually playable. To discover the various ways of improving it I suggest you spend some time playing it. If something is less than perfect now, then it will be a fatal frustration for the end user. Happily, we’re still at the glaring omissions stage so I have plenty of material for new posts.

The first interface problem is nothing seems to happen when you make a move until the move is complete, then the piece snaps to position. A more intuitive way of working would be for the piece to follow your finger as you move it, then snap to the grid when you lift your finger. There are two parts to this problem and we’ll deal with the easy one first.

We will fill in the handleMoveAction() stub we wrote before to handle gathering the required information on the move the user is making. We’ll also need some temporary storage to go with it.

//Board.java
private float mMovingX = 0; 
private float mMovingY = 0; 
	
private boolean handleMoveAction(MotionEvent event) {

	if (mMovingPiece != 0) { 
		mMovingX = event.getX(); 
		mMovingY = event.getY(); 
		invalidate(); 
	}
	return true;
}

Now we know what the user is doing with the piece we need to draw it in it’s new location. Extend the onDraw() function with the bold code below and copy the drawMovingPiece() function into the class.

 
@Override public void onDraw(Canvas canvas) { 
	
	drawBoard(canvas);
	
	drawPieces(canvas);
	
	drawMovingPiece(canvas);
}

private void drawMovingPiece(Canvas canvas){
	if (mMovingPiece != 0) {
		canvas.drawBitmap(getBitmap(mMovingPiece), mMovingX - mTileWidth / 2, mMovingY - mTileHeight / 2, null);
	}
}

Give it a go and you should find the piece follows your finger quite well. Note the piece is drawn offset by half a tile to centre the piece on the move.

I say quite well, but if you do the maths suddenly that 118 milliseconds we were so proud of just doesn’t cut it. Eight and a half frames per second does not portray sleek, next generation technology. Everyone who doubted frame rate could be important in a board game, please collect your “I told you so” from the front desk after today’s session. Put bluntly, the piece lags behind your finger during a move.

In case you were wondering, we are now done with the easy bit. We need to cut down the amount of work our onDraw() is doing, so first we need to find out what it’s doing.

Run your app in an emulator and click back to the home screen. Then open the apps menu and open “Dev tools”. Check the option that says “Show Screen Updates” and your screen will start flickering. These flickers are used to show the area of the screen that is being updated.

Now look at your app again. As you drag a piece around the screen the whole board flickers, letting you know the whole board is being redrawn from scratch. But we know the only area of the board that is changing is the area directly surrounding the moving piece. Fortunately invalidate() can take parameters so you can let your app know that these is only a relatively small area needing to be redrawn at any one time.

Replace the invalidate on your handleMoveAction() function with the following line.

private boolean handleMoveAction(MotionEvent event) {

	if (mMovingPiece != 0) { 
		mMovingX = event.getX(); 
		mMovingY = event.getY(); 
		invalidate((int)(event.getX() - mTileWidth) , (int)(event.getY() - mTileHeight) , (int)(event.getX() + mTileWidth) , (int)(event.getY() + mTileHeight));
	}
	return true;
}

Now when you drag a piece it should settle into redrawing an area the size of two tiles around your piece. Checking the log gives an average of 20 milliseconds to complete the draw. All of the draw requests that fall outside the invalid area are ignored, resulting in a more respectable 50 frames per second.

Now really drag the piece around. Seriously, go nuts! You may end up with ghost pieces littering your board. This is because between one frame and the next you have moved further than the redraw area we defined. It’s not considered invalid so it stays put until you lift your finger and the whole board is redrawn on the ACTION_UP of course.

The way to deal with that is to invalidate the area where the piece was when it was last drawn. Information we have at the start of the move in the mMovingX and mMovingY variables so we invalidate that area before we concern ourselves with where the piece is now.

private boolean handleMoveAction(MotionEvent event) {

	if (mMovingPiece != 0) { 
		invalidate((int)(mMovingX - mTileWidth/2) , (int)(mMovingY - mTileHeight/2) , (int)(mMovingX + mTileWidth/2) , (int)(mMovingY + mTileHeight/2));
		mMovingX = event.getX(); 
		mMovingY = event.getY(); 
		invalidate((int)(event.getX() - mTileWidth) , (int)(event.getY() - mTileHeight) , (int)(event.getX() + mTileWidth) , (int)(event.getY() + mTileHeight));
	}
	return true;
}

Note that we are only invalidating the area around the previous and current locations. With the the screen updates still showing we can see Android updates the union of these two areas to form a rectangle that neatly encompasses exactly what needs to be redrawn. We can then apply the same process of invalidating where the piece was and where it is now to the down and up actions to finish the job.

Without compiling rigorous statistics, I’m going to estimate an average redraw rate of 30 milliseconds, or 33 frames per second on the emulator. Moving to real hardware we find it would require better than millisecond timing to get accurate figures, so we’ll call it 1 millisecond and much smoother animation.

The RPS chess game mid move

Mid Move

This entry was posted in android and tagged . Bookmark the permalink.

2 Responses to Smooth Operator

  1. Wails says:

    Cool ..can you share the complete code, my project is some thing similar to this….it will help me a lot if you share the code …thanks in advance….

Comments are closed.