Method updatePositions
(Fig. 6.12) is called by the CannonThread
’s run
method (Section 6.8.14) to update the on-screen elements’ positions and to perform simple collision detection. The new locations of the game elements are calculated based on the elapsed time in milliseconds between the previous and current animation frames. This enables the game to update the amount by which each game element moves based on the device’s refresh rate. We discuss this in more detail when we cover game loops in Section 6.8.14.
209 // called repeatedly by the CannonThread to update game elements
210 private void updatePositions(double elapsedTimeMS)
211 {
212 double interval = elapsedTimeMS / 1000.0; // convert to seconds
213
214 if (cannonballOnScreen) // if there is currently a shot fired
215 {
216 // update cannonball position
217 cannonball.x += interval * cannonballVelocityX;
218 cannonball.y += interval * cannonballVelocityY;
219
220 // check for collision with blocker
221 if (cannonball.x + cannonballRadius > blockerDistance &&
222 cannonball.x - cannonballRadius < blockerDistance &&
223 cannonball.y + cannonballRadius > blocker.start.y &&
224 cannonball.y - cannonballRadius < blocker.end.y)
225 {
226 cannonballVelocityX *= -1; // reverse cannonball's direction
227 timeLeft -= MISS_PENALTY; // penalize the user
228
229 // play blocker sound
230 soundPool.play(soundMap.get(BLOCKER_SOUND_ID), 1, 1, 1, 0, 1f);
231 }
232 // check for collisions with left and right walls
233 else if (cannonball.x + cannonballRadius > screenWidth ||
234 cannonball.x - cannonballRadius < 0)
235 {
236 cannonballOnScreen = false; // remove cannonball from screen
237 }
238 // check for collisions with top and bottom walls
239 else if (cannonball.y + cannonballRadius > screenHeight ||
240 cannonball.y - cannonballRadius < 0)
241 {
242 cannonballOnScreen = false; // remove cannonball from screen
243 }
244 // check for cannonball collision with target
245 else if (cannonball.x + cannonballRadius > targetDistance &&
246 cannonball.x - cannonballRadius < targetDistance &&
247 cannonball.y + cannonballRadius > target.start.y &&
248 cannonball.y - cannonballRadius < target.end.y)
249 {
250 // determine target section number (0 is the top)
251 int section =
252 (int) ((cannonball.y - target.start.y) / pieceLength);
253
254 // check if the piece hasn't been hit yet
255 if ((section >= 0 && section < TARGET_PIECES ) &&
256 !hitStates[section])
257 {
258 hitStates[section] = true; // section was hit
259 cannonballOnScreen = false ; // remove cannonball
260 timeLeft += HIT_REWARD; // add reward to remaining time
261
262 // play target hit sound
263 soundPool.play(soundMap.get(TARGET_SOUND_ID), 1,
264 1, 1, 0, 1f);
265
266 // if all pieces have been hit
267 if (++targetPiecesHit == TARGET_PIECES)
268 {
269 cannonThread.setRunning(false); // terminate thread
270 showGameOverDialog(R.string.win); // show winning dialog
271 gameOver = true;
272 }
273 }
274 }
275 }
276
277 // update the blocker's position
278 double blockerUpdate = interval * blockerVelocity;
279 blocker.start.y += blockerUpdate;
280 blocker.end.y += blockerUpdate;
281
282 // update the target's position
283 double targetUpdate = interval * targetVelocity;
284 target.start.y += targetUpdate;
285 target.end.y += targetUpdate;
286
287 // if the blocker hit the top or bottom, reverse direction
288 if (blocker.start.y < 0 || blocker.end.y > screenHeight)
289 blockerVelocity *= -1;
290
291 // if the target hit the top or bottom, reverse direction
292 if (target.start.y < 0 || target.end.y > screenHeight)
293 targetVelocity *= -1;
294
295 timeLeft -= interval; // subtract from time left
296
297 // if the timer reached zero
298 if (timeLeft <= 0.0)
299 {
300 timeLeft = 0.0;
301 gameOver = true; // the game is over
302 cannonThread.setRunning(false); // terminate thread
303 showGameOverDialog(R.string.lose); // show the losing dialog
304 }
305 } // end method updatePositions
306
Line 212 converts the elapsed time since the last animation frame from milliseconds to seconds. This value is used to modify the positions of various game elements.
Line 214 checks whether the cannonball is on the screen. If it is, we update its position by adding the distance it should have traveled since the last timer event. This is calculated by multiplying its velocity by the amount of time that passed (lines 217–218). Lines 221–224 check whether the cannonball has collided with the blocker. We perform simple collision detection, based on the rectangular boundary of the cannonball. There are four conditions that must be met if the cannonball is in contact with the blocker:
• The cannonball’s x-coordinate plus the cannonball’s radius must be greater than the blocker’s distance from the left edge of the screen (blockerDistance
) (line 221). This means that the cannonball has reached the blocker’s distance from the left edge of the screen.
• The cannonball’s x-coordinate minus the cannonball’s radius must also be less than the blocker’s distance from the left edge of the screen (line 222). This ensures that the cannonball has not yet passed the blocker.
• Part of the cannonball must be lower than the top of the blocker (line 223).
• Part of the cannonball must be higher than the bottom of the blocker (line 224).
If all these conditions are met, we reverse the cannonball’s direction on the screen (line 226), penalize the user by subtracting MISS_PENALTY
from timeLeft
, then call soundPool
’s play method to play the blocker hit sound—BLOCKER_SOUND_ID
is used as the soundMap
key to locate the sound’s ID in the SoundPool
.
We remove the cannonball if it reaches any of the screen’s edges. Lines 233–237 test whether the cannonball has collided with the left or right wall and, if it has, remove the cannonball from the screen. Lines 239–243 remove the cannonball if it collides with the top or bottom of the screen.
We then check whether the cannonball has hit the target
(lines 245–248). These conditions are similar to those used to determine whether the cannonball collided with the blocker
. If the cannonball hit the target
, lines 251–252 determine which section has been hit—dividing the distance between the cannonball and the bottom of the target
by the length of a piece. This expression evaluates to 0
for the topmost section and 6
for the bottommost. We check whether that section was previously hit, using the hitStates
array (line 256). If it wasn’t, we set the corresponding hitStates
element to true
and remove the cannonball from the screen. We then add HIT_REWARD
to timeLeft
, increasing the game’s time remaining, and play the target hit sound (TARGET_SOUND_ID
). We increment targetPiecesHit
, then determine whether it’s equal to TARGET_PIECES
(line 267). If so, the game is over, so we terminate the CannonThread
by calling its setRunning
method with the argument false
, invoke method showGameOverDialog
with the String
resource ID representing the winning message and set gameOver
to true
.
Now that all possible cannonball collisions have been checked, the blocker
and target
positions must be updated. Lines 278–280 change the blocker
’s position by multiplying blockerVelocity
by the amount of time that has passed since the last update, and adding that value to the current x- and y-coordinates. Lines 283–285 do the same for the target
. If the blocker
has collided with the top or bottom wall, its direction is reversed by multiplying its velocity by -1
(lines 288–289). Lines 292–293 perform the same check and adjustment for the full length of the target
, including any sections that have already been destroyed.
We decrease timeLeft
by the time that has passed since the prior animation frame (line 295). If timeLeft
has reached zero, the game is over—we set timeLeft
to 0.0 just in case it was negative; otherwise, sometimes a negative final time would display on the screen). Then we set gameOver
to true
, terminate the CannonThread
by calling its setRunning
method with the argument false
and call method showGameOverDialog
with the String
resource ID representing the losing message.