/[projects]/android/admob/samples/LunarLander/src/com/example/admob/lunarlander/LunarView.java
ViewVC logotype

Contents of /android/admob/samples/LunarLander/src/com/example/admob/lunarlander/LunarView.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 337 - (show annotations) (download)
Wed Sep 23 12:54:05 2009 UTC (14 years, 8 months ago) by torben
File size: 34792 byte(s)
import admob library
1 /*
2 * Copyright (C) 2007 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17 package com.example.admob.lunarlander;
18
19 import com.admob.android.ads.*;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.RectF;
27 import android.graphics.drawable.Drawable;
28 import android.hardware.*;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.util.AttributeSet;
33 import android.view.*;
34 import android.widget.TextView;
35
36
37 /**
38 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
39 *
40 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
41 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
42 * updatePhysics() advances the physics based on realtime. draw() renders the
43 * ship, and does an invalidate() to prompt another draw() as soon as possible
44 * by the system.
45 */
46 class LunarView extends SurfaceView implements SurfaceHolder.Callback, SensorEventListener {
47 class LunarThread extends Thread {
48 /*
49 * Difficulty setting constants
50 */
51 public static final int DIFFICULTY_EASY = 0;
52 public static final int DIFFICULTY_HARD = 1;
53 public static final int DIFFICULTY_MEDIUM = 2;
54 /*
55 * Physics constants
56 */
57 public static final int PHYS_DOWN_ACCEL_SEC = 35;
58 public static final int PHYS_FIRE_ACCEL_SEC = 80;
59 public static final int PHYS_FUEL_INIT = 60;
60 public static final int PHYS_FUEL_MAX = 100;
61 public static final int PHYS_FUEL_SEC = 10;
62 public static final int PHYS_SLEW_SEC = 120; // degrees/second rotate
63 public static final int PHYS_SPEED_HYPERSPACE = 180;
64 public static final int PHYS_SPEED_INIT = 30;
65 public static final int PHYS_SPEED_MAX = 120;
66 /*
67 * State-tracking constants
68 */
69 public static final int STATE_LOSE = 1;
70 public static final int STATE_PAUSE = 2;
71 public static final int STATE_READY = 3;
72 public static final int STATE_RUNNING = 4;
73 public static final int STATE_WIN = 5;
74
75 /*
76 * Goal condition constants
77 */
78 public static final int TARGET_ANGLE = 18; // > this angle means crash
79 public static final int TARGET_BOTTOM_PADDING = 17; // px below gear
80 public static final int TARGET_PAD_HEIGHT = 8; // how high above ground
81 public static final int TARGET_SPEED = 28; // > this speed means crash
82 public static final double TARGET_WIDTH = 1.6; // width of target
83 /*
84 * UI constants (i.e. the speed & fuel bars)
85 */
86 public static final int UI_BAR = 100; // width of the bar(s)
87 public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
88 private static final String KEY_DIFFICULTY = "mDifficulty";
89 private static final String KEY_DX = "mDX";
90
91 private static final String KEY_DY = "mDY";
92 private static final String KEY_FUEL = "mFuel";
93 private static final String KEY_GOAL_ANGLE = "mGoalAngle";
94 private static final String KEY_GOAL_SPEED = "mGoalSpeed";
95 private static final String KEY_GOAL_WIDTH = "mGoalWidth";
96
97 private static final String KEY_GOAL_X = "mGoalX";
98 private static final String KEY_HEADING = "mHeading";
99 private static final String KEY_LANDER_HEIGHT = "mLanderHeight";
100 private static final String KEY_LANDER_WIDTH = "mLanderWidth";
101 private static final String KEY_WINS = "mWinsInARow";
102
103 private static final String KEY_X = "mX";
104 private static final String KEY_Y = "mY";
105
106 /*
107 * Member (state) fields
108 */
109 /** The drawable to use as the background of the animation canvas */
110 private Bitmap mBackgroundImage;
111
112 /**
113 * Current height of the surface/canvas.
114 *
115 * @see #setSurfaceSize
116 */
117 private int mCanvasHeight = 1;
118
119 /**
120 * Current width of the surface/canvas.
121 *
122 * @see #setSurfaceSize
123 */
124 private int mCanvasWidth = 1;
125
126 /** What to draw for the Lander when it has crashed */
127 private Drawable mCrashedImage;
128
129 /**
130 * Current difficulty -- amount of fuel, allowed angle, etc. Default is
131 * MEDIUM.
132 */
133 private int mDifficulty;
134
135 /** Velocity dx. */
136 private double mDX;
137
138 /** Velocity dy. */
139 private double mDY;
140
141 /** Is the engine burning? */
142 private boolean mEngineFiring;
143
144 /** What to draw for the Lander when the engine is firing */
145 private Drawable mFiringImage;
146
147 /** Fuel remaining */
148 private double mFuel;
149
150 /** Allowed angle. */
151 private int mGoalAngle;
152
153 /** Allowed speed. */
154 private int mGoalSpeed;
155
156 /** Width of the landing pad. */
157 private int mGoalWidth;
158
159 /** X of the landing pad. */
160 private int mGoalX;
161
162 /** Message handler used by thread to interact with TextView */
163 private Handler mHandler;
164
165 /**
166 * Lander heading in degrees, with 0 up, 90 right. Kept in the range
167 * 0..360.
168 */
169 private double mHeading;
170
171 /** Pixel height of lander image. */
172 private int mLanderHeight;
173
174 /** What to draw for the Lander in its normal state */
175 private Drawable mLanderImage;
176
177 /** Pixel width of lander image. */
178 private int mLanderWidth;
179
180 /** Used to figure out elapsed time between frames */
181 private long mLastTime;
182
183 /** Paint to draw the lines on screen. */
184 private Paint mLinePaint;
185
186 /** "Bad" speed-too-high variant of the line color. */
187 private Paint mLinePaintBad;
188
189 /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
190 private int mMode;
191
192 /** Currently rotating, -1 left, 0 none, 1 right. */
193 private int mRotating;
194
195 /** Indicate whether the surface has been created & is ready to draw */
196 private boolean mRun = false;
197
198 /** Scratch rect object. */
199 private RectF mScratchRect;
200
201 /** Handle to the surface manager object we interact with */
202 private SurfaceHolder mSurfaceHolder;
203
204 /** Number of wins in a row. */
205 private int mWinsInARow;
206
207 /** X of lander center. */
208 private double mX;
209
210 /** Y of lander center. */
211 private double mY;
212
213 public LunarThread(SurfaceHolder surfaceHolder, Context context,
214 Handler handler) {
215 // get handles to some important objects
216 mSurfaceHolder = surfaceHolder;
217 mHandler = handler;
218 mContext = context;
219
220 Resources res = context.getResources();
221 // cache handles to our key sprites & other drawables
222 mLanderImage = context.getResources().getDrawable(
223 R.drawable.lander_plain);
224 mFiringImage = context.getResources().getDrawable(
225 R.drawable.lander_firing);
226 mCrashedImage = context.getResources().getDrawable(
227 R.drawable.lander_crashed);
228
229 // load background image as a Bitmap instead of a Drawable b/c
230 // we don't need to transform it and it's faster to draw this way
231 mBackgroundImage = BitmapFactory.decodeResource(res,
232 R.drawable.earthrise);
233
234 // Use the regular lander image as the model size for all sprites
235 mLanderWidth = mLanderImage.getIntrinsicWidth();
236 mLanderHeight = mLanderImage.getIntrinsicHeight();
237
238 // Initialize paints for speedometer
239 mLinePaint = new Paint();
240 mLinePaint.setAntiAlias(true);
241 mLinePaint.setARGB(255, 0, 255, 0);
242
243 mLinePaintBad = new Paint();
244 mLinePaintBad.setAntiAlias(true);
245 mLinePaintBad.setARGB(255, 120, 180, 0);
246
247 mScratchRect = new RectF(0, 0, 0, 0);
248
249 mWinsInARow = 0;
250 mDifficulty = DIFFICULTY_MEDIUM;
251
252 // initial show-up of lander (not yet playing)
253 mX = mLanderWidth;
254 mY = mLanderHeight * 2;
255 mFuel = PHYS_FUEL_INIT;
256 mDX = 0;
257 mDY = 0;
258 mHeading = 0;
259 mEngineFiring = true;
260 }
261
262 /**
263 * Starts the game, setting parameters for the current difficulty.
264 */
265 public void doStart() {
266 synchronized (mSurfaceHolder) {
267 // First set the game for Medium difficulty
268 mFuel = PHYS_FUEL_INIT;
269 mEngineFiring = false;
270 mGoalWidth = (int) (mLanderWidth * TARGET_WIDTH);
271 mGoalSpeed = TARGET_SPEED;
272 mGoalAngle = TARGET_ANGLE;
273 int speedInit = PHYS_SPEED_INIT;
274
275 // Adjust difficulty params for EASY/HARD
276 if (mDifficulty == DIFFICULTY_EASY) {
277 mFuel = mFuel * 3 / 2;
278 mGoalWidth = mGoalWidth * 4 / 3;
279 mGoalSpeed = mGoalSpeed * 3 / 2;
280 mGoalAngle = mGoalAngle * 4 / 3;
281 speedInit = speedInit * 3 / 4;
282 } else if (mDifficulty == DIFFICULTY_HARD) {
283 mFuel = mFuel * 7 / 8;
284 mGoalWidth = mGoalWidth * 3 / 4;
285 mGoalSpeed = mGoalSpeed * 7 / 8;
286 speedInit = speedInit * 4 / 3;
287 }
288
289 // pick a convenient initial location for the lander sprite
290 mX = mCanvasWidth / 2;
291 mY = mCanvasHeight - mLanderHeight / 2;
292
293 // start with a little random motion
294 mDY = Math.random() * -speedInit;
295 mDX = Math.random() * 2 * speedInit - speedInit;
296 mHeading = 0;
297
298 // Figure initial spot for landing, not too near center
299 while (true) {
300 mGoalX = (int) (Math.random() * (mCanvasWidth - mGoalWidth));
301 if (Math.abs(mGoalX - (mX - mLanderWidth / 2)) > mCanvasHeight / 6)
302 break;
303 }
304
305 mLastTime = System.currentTimeMillis() + 100;
306 setState(STATE_RUNNING);
307 }
308 }
309
310 /**
311 * Pauses the physics update & animation.
312 */
313 public void pause() {
314 synchronized (mSurfaceHolder) {
315 if (mMode == STATE_RUNNING) setState(STATE_PAUSE);
316 }
317 }
318
319 /**
320 * Restores game state from the indicated Bundle. Typically called when
321 * the Activity is being restored after having been previously
322 * destroyed.
323 *
324 * @param savedState Bundle containing the game state
325 */
326 public synchronized void restoreState(Bundle savedState) {
327 synchronized (mSurfaceHolder) {
328 setState(STATE_PAUSE);
329 mRotating = 0;
330 mEngineFiring = false;
331
332 mDifficulty = savedState.getInt(KEY_DIFFICULTY);
333 mX = savedState.getDouble(KEY_X);
334 mY = savedState.getDouble(KEY_Y);
335 mDX = savedState.getDouble(KEY_DX);
336 mDY = savedState.getDouble(KEY_DY);
337 mHeading = savedState.getDouble(KEY_HEADING);
338
339 mLanderWidth = savedState.getInt(KEY_LANDER_WIDTH);
340 mLanderHeight = savedState.getInt(KEY_LANDER_HEIGHT);
341 mGoalX = savedState.getInt(KEY_GOAL_X);
342 mGoalSpeed = savedState.getInt(KEY_GOAL_SPEED);
343 mGoalAngle = savedState.getInt(KEY_GOAL_ANGLE);
344 mGoalWidth = savedState.getInt(KEY_GOAL_WIDTH);
345 mWinsInARow = savedState.getInt(KEY_WINS);
346 mFuel = savedState.getDouble(KEY_FUEL);
347 }
348 }
349
350 @Override
351 public void run() {
352 while (mRun) {
353 Canvas c = null;
354 try {
355 c = mSurfaceHolder.lockCanvas(null);
356 synchronized (mSurfaceHolder) {
357 if (mMode == STATE_RUNNING) updatePhysics();
358 doDraw(c);
359 }
360 } finally {
361 // do this in a finally so that if an exception is thrown
362 // during the above, we don't leave the Surface in an
363 // inconsistent state
364 if (c != null) {
365 mSurfaceHolder.unlockCanvasAndPost(c);
366 }
367 }
368 }
369 }
370
371 /**
372 * Dump game state to the provided Bundle. Typically called when the
373 * Activity is being suspended.
374 *
375 * @return Bundle with this view's state
376 */
377 public Bundle saveState(Bundle map) {
378 synchronized (mSurfaceHolder) {
379 if (map != null) {
380 map.putInt(KEY_DIFFICULTY, Integer.valueOf(mDifficulty));
381 map.putDouble(KEY_X, Double.valueOf(mX));
382 map.putDouble(KEY_Y, Double.valueOf(mY));
383 map.putDouble(KEY_DX, Double.valueOf(mDX));
384 map.putDouble(KEY_DY, Double.valueOf(mDY));
385 map.putDouble(KEY_HEADING, Double.valueOf(mHeading));
386 map.putInt(KEY_LANDER_WIDTH, Integer.valueOf(mLanderWidth));
387 map.putInt(KEY_LANDER_HEIGHT, Integer
388 .valueOf(mLanderHeight));
389 map.putInt(KEY_GOAL_X, Integer.valueOf(mGoalX));
390 map.putInt(KEY_GOAL_SPEED, Integer.valueOf(mGoalSpeed));
391 map.putInt(KEY_GOAL_ANGLE, Integer.valueOf(mGoalAngle));
392 map.putInt(KEY_GOAL_WIDTH, Integer.valueOf(mGoalWidth));
393 map.putInt(KEY_WINS, Integer.valueOf(mWinsInARow));
394 map.putDouble(KEY_FUEL, Double.valueOf(mFuel));
395 }
396 }
397 return map;
398 }
399
400 /**
401 * Sets the current difficulty.
402 *
403 * @param difficulty
404 */
405 public void setDifficulty(int difficulty) {
406 synchronized (mSurfaceHolder) {
407 mDifficulty = difficulty;
408 }
409 }
410
411 /**
412 * Sets if the engine is currently firing.
413 */
414 public void setFiring(boolean firing) {
415 synchronized (mSurfaceHolder) {
416 mEngineFiring = firing;
417 }
418 }
419
420 /**
421 * Used to signal the thread whether it should be running or not.
422 * Passing true allows the thread to run; passing false will shut it
423 * down if it's already running. Calling start() after this was most
424 * recently called with false will result in an immediate shutdown.
425 *
426 * @param b true to run, false to shut down
427 */
428 public void setRunning(boolean b) {
429 mRun = b;
430 }
431
432 /**
433 * Sets the game mode. That is, whether we are running, paused, in the
434 * failure state, in the victory state, etc.
435 *
436 * @see #setState(int, CharSequence)
437 * @param mode one of the STATE_* constants
438 */
439 public void setState(int mode) {
440 synchronized (mSurfaceHolder) {
441 setState(mode, null);
442 }
443 }
444
445 /**
446 * Sets the game mode. That is, whether we are running, paused, in the
447 * failure state, in the victory state, etc.
448 *
449 * @param mode one of the STATE_* constants
450 * @param message string to add to screen or null
451 */
452 public void setState(int mode, CharSequence message) {
453 /*
454 * This method optionally can cause a text message to be displayed
455 * to the user when the mode changes. Since the View that actually
456 * renders that text is part of the main View hierarchy and not
457 * owned by this thread, we can't touch the state of that View.
458 * Instead we use a Message + Handler to relay commands to the main
459 * thread, which updates the user-text View.
460 */
461 synchronized (mSurfaceHolder) {
462 mMode = mode;
463
464 if (mMode == STATE_RUNNING) {
465 Message msg = mHandler.obtainMessage();
466 Bundle b = new Bundle();
467 b.putString("text", "");
468 b.putInt("viz", View.INVISIBLE);
469 msg.setData(b);
470 mHandler.sendMessage(msg);
471 } else {
472 mRotating = 0;
473 mEngineFiring = false;
474 Resources res = mContext.getResources();
475 CharSequence str = "";
476 if (mMode == STATE_READY)
477 str = res.getText(R.string.mode_ready);
478 else if (mMode == STATE_PAUSE)
479 str = res.getText(R.string.mode_pause);
480 else if (mMode == STATE_LOSE)
481 str = res.getText(R.string.mode_lose);
482 else if (mMode == STATE_WIN)
483 str = res.getString(R.string.mode_win_prefix)
484 + mWinsInARow + " "
485 + res.getString(R.string.mode_win_suffix);
486
487 if (message != null) {
488 str = message + "\n" + str;
489 }
490
491 if (mMode == STATE_LOSE) mWinsInARow = 0;
492
493 Message msg = mHandler.obtainMessage();
494 Bundle b = new Bundle();
495 b.putString("text", str.toString());
496 b.putInt("viz", View.VISIBLE);
497 msg.setData(b);
498 mHandler.sendMessage(msg);
499 }
500 }
501 }
502
503 /* Callback invoked when the surface dimensions change. */
504 public void setSurfaceSize(int width, int height) {
505 // synchronized to make sure these all change atomically
506 synchronized (mSurfaceHolder) {
507 mCanvasWidth = width;
508 mCanvasHeight = height;
509
510 // don't forget to resize the background image
511 mBackgroundImage = Bitmap.createScaledBitmap(
512 mBackgroundImage, width, height, true);
513 }
514 }
515
516 /**
517 * Resumes from a pause.
518 */
519 public void unpause() {
520 // Move the real time clock up to now
521 synchronized (mSurfaceHolder) {
522 mLastTime = System.currentTimeMillis() + 100;
523 }
524 setState(STATE_RUNNING);
525 }
526
527 /**
528 * Handles a key-down event.
529 *
530 * @param keyCode the key that was pressed
531 * @param msg the original event object
532 * @return true
533 */
534 boolean doKeyDown(int keyCode, KeyEvent msg) {
535 synchronized (mSurfaceHolder) {
536 boolean okStart = false;
537 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
538 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
539 if (keyCode == KeyEvent.KEYCODE_S) okStart = true;
540
541 //boolean center = (keyCode == KeyEvent.KEYCODE_DPAD_UP);
542
543 if (okStart
544 && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
545 // ready-to-start -> start
546 doStart();
547 return true;
548 } else if (mMode == STATE_PAUSE && okStart) {
549 // paused -> running
550 unpause();
551 return true;
552 } else if (mMode == STATE_RUNNING) {
553 // center/space -> fire
554 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
555 || keyCode == KeyEvent.KEYCODE_SPACE) {
556 setFiring(true);
557 return true;
558 // left/q -> left
559 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
560 || keyCode == KeyEvent.KEYCODE_Q) {
561 mRotating = -1;
562 return true;
563 // right/w -> right
564 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
565 || keyCode == KeyEvent.KEYCODE_W) {
566 mRotating = 1;
567 return true;
568 // up -> pause
569 } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
570 pause();
571 return true;
572 }
573 }
574
575 return false;
576 }
577 }
578
579 /**
580 * Handles a key-up event.
581 *
582 * @param keyCode the key that was pressed
583 * @param msg the original event object
584 * @return true if the key was handled and consumed, or else false
585 */
586 boolean doKeyUp(int keyCode, KeyEvent msg) {
587 boolean handled = false;
588
589 synchronized (mSurfaceHolder) {
590 if (mMode == STATE_RUNNING) {
591 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
592 || keyCode == KeyEvent.KEYCODE_SPACE) {
593 setFiring(false);
594 handled = true;
595 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
596 || keyCode == KeyEvent.KEYCODE_Q
597 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
598 || keyCode == KeyEvent.KEYCODE_W) {
599 mRotating = 0;
600 handled = true;
601 }
602 }
603 }
604
605 return handled;
606 }
607
608 /**
609 * Handles turning the ship left and right by tilting the phone.
610 */
611 void doOrientationChange( float tiltLeftRight )
612 {
613 mRotating = 0;
614 mHeading = Math.round( tiltLeftRight );
615 invalidate();
616 }
617
618 /**
619 * Draws the ship, fuel/speed bars, and background to the provided
620 * Canvas.
621 */
622 private void doDraw(Canvas canvas) {
623 // Draw the background image. Operations on the Canvas accumulate
624 // so this is like clearing the screen.
625 canvas.drawBitmap(mBackgroundImage, 0, 0, null);
626
627 int yTop = mCanvasHeight - ((int) mY + mLanderHeight / 2);
628 int xLeft = (int) mX - mLanderWidth / 2;
629
630 // Draw the fuel gauge
631 int fuelWidth = (int) (UI_BAR * mFuel / PHYS_FUEL_MAX);
632 mScratchRect.set(4, 4, 4 + fuelWidth, 4 + UI_BAR_HEIGHT);
633 canvas.drawRect(mScratchRect, mLinePaint);
634
635 // Draw the speed gauge, with a two-tone effect
636 double speed = Math.sqrt(mDX * mDX + mDY * mDY);
637 int speedWidth = (int) (UI_BAR * speed / PHYS_SPEED_MAX);
638
639 if (speed <= mGoalSpeed) {
640 mScratchRect.set(4 + UI_BAR + 4, 4,
641 4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);
642 canvas.drawRect(mScratchRect, mLinePaint);
643 } else {
644 // Draw the bad color in back, with the good color in front of
645 // it
646 mScratchRect.set(4 + UI_BAR + 4, 4,
647 4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);
648 canvas.drawRect(mScratchRect, mLinePaintBad);
649 int goalWidth = (UI_BAR * mGoalSpeed / PHYS_SPEED_MAX);
650 mScratchRect.set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + goalWidth,
651 4 + UI_BAR_HEIGHT);
652 canvas.drawRect(mScratchRect, mLinePaint);
653 }
654
655 // Draw the landing pad
656 canvas.drawLine(mGoalX, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,
657 mGoalX + mGoalWidth, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,
658 mLinePaint);
659
660
661 // Draw the ship with its current rotation
662 canvas.save();
663 canvas.rotate((float) mHeading, (float) mX, mCanvasHeight
664 - (float) mY);
665 if (mMode == STATE_LOSE) {
666 mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
667 + mLanderHeight);
668 mCrashedImage.draw(canvas);
669 } else if (mEngineFiring) {
670 mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
671 + mLanderHeight);
672 mFiringImage.draw(canvas);
673 } else {
674 mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
675 + mLanderHeight);
676 mLanderImage.draw(canvas);
677 }
678 canvas.restore();
679 }
680
681 /**
682 * Figures the lander state (x, y, fuel, ...) based on the passage of
683 * realtime. Does not invalidate(). Called at the start of draw().
684 * Detects the end-of-game and sets the UI to the next state.
685 */
686 private void updatePhysics() {
687 long now = System.currentTimeMillis();
688
689 // Do nothing if mLastTime is in the future.
690 // This allows the game-start to delay the start of the physics
691 // by 100ms or whatever.
692 if (mLastTime > now) return;
693
694 double elapsed = (now - mLastTime) / 1000.0;
695
696 // mRotating -- update heading
697 if (mRotating != 0) {
698 mHeading += mRotating * (PHYS_SLEW_SEC * elapsed);
699
700 // Bring things back into the range 0..360
701 if (mHeading < 0)
702 mHeading += 360;
703 else if (mHeading >= 360) mHeading -= 360;
704 }
705
706 // Base accelerations -- 0 for x, gravity for y
707 double ddx = 0.0;
708 double ddy = -PHYS_DOWN_ACCEL_SEC * elapsed;
709
710 if (mEngineFiring) {
711 // taking 0 as up, 90 as to the right
712 // cos(deg) is ddy component, sin(deg) is ddx component
713 double elapsedFiring = elapsed;
714 double fuelUsed = elapsedFiring * PHYS_FUEL_SEC;
715
716 // tricky case where we run out of fuel partway through the
717 // elapsed
718 if (fuelUsed > mFuel) {
719 elapsedFiring = mFuel / fuelUsed * elapsed;
720 fuelUsed = mFuel;
721
722 // Oddball case where we adjust the "control" from here
723 mEngineFiring = false;
724 }
725
726 mFuel -= fuelUsed;
727
728 // have this much acceleration from the engine
729 double accel = PHYS_FIRE_ACCEL_SEC * elapsedFiring;
730
731 double radians = 2 * Math.PI * mHeading / 360;
732 ddx = Math.sin(radians) * accel;
733 ddy += Math.cos(radians) * accel;
734 }
735
736 double dxOld = mDX;
737 double dyOld = mDY;
738
739 // figure speeds for the end of the period
740 mDX += ddx;
741 mDY += ddy;
742
743 // figure position based on average speed during the period
744 mX += elapsed * (mDX + dxOld) / 2;
745 mY += elapsed * (mDY + dyOld) / 2;
746
747 mLastTime = now;
748
749 // Evaluate if we have landed ... stop the game
750 double yLowerBound = TARGET_PAD_HEIGHT + mLanderHeight / 2
751 - TARGET_BOTTOM_PADDING;
752 if (mY <= yLowerBound) {
753 mY = yLowerBound;
754
755 int result = STATE_LOSE;
756 CharSequence message = "";
757 Resources res = mContext.getResources();
758 double speed = Math.sqrt(mDX * mDX + mDY * mDY);
759 boolean onGoal = (mGoalX <= mX - mLanderWidth / 2 && mX
760 + mLanderWidth / 2 <= mGoalX + mGoalWidth);
761
762 // "Hyperspace" win -- upside down, going fast,
763 // puts you back at the top.
764 if (onGoal && Math.abs(mHeading - 180) < mGoalAngle
765 && speed > PHYS_SPEED_HYPERSPACE) {
766 result = STATE_WIN;
767 mWinsInARow++;
768 doStart();
769
770 return;
771 // Oddball case: this case does a return, all other cases
772 // fall through to setMode() below.
773 } else if (!onGoal) {
774 message = res.getText(R.string.message_off_pad);
775 } else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle)) {
776 message = res.getText(R.string.message_bad_angle);
777 } else if (speed > mGoalSpeed) {
778 message = res.getText(R.string.message_too_fast);
779 } else {
780 result = STATE_WIN;
781 mWinsInARow++;
782 }
783
784 setState(result, message);
785 }
786 }
787 }
788
789 /** Handle to the application context, used to e.g. fetch Drawables. */
790 private Context mContext;
791
792 /** Pointer to the text view to display "Paused.." etc. */
793 private TextView mStatusText;
794
795 /** Pointer to the ad view shown below <code>mStatusText</code>. */
796 private AdView mAd; // TODO You will need an AdView (this one is defined in res/layout/lunar_layout.xml).
797
798 /** The thread that actually draws the animation */
799 private LunarThread thread;
800
801 public LunarView(Context context, AttributeSet attrs) {
802 super(context, attrs);
803
804 // register our interest in hearing about changes to our surface
805 SurfaceHolder holder = getHolder();
806 holder.addCallback(this);
807
808 // create thread only; it's started in surfaceCreated()
809 thread = new LunarThread(holder, context, new Handler() {
810 private boolean firstTime = true;
811
812 @Override
813 public void handleMessage(Message m) {
814 int vis = m.getData().getInt("viz");
815 mStatusText.setVisibility( vis );
816 mStatusText.setText(m.getData().getString("text"));
817
818 // TODO Here we show the ad when paused or the game is over and
819 // hide the ad when the game is being played. We choose not to
820 // show and ad when the user starts the game (the first time the
821 // screen is displayed).
822 if ( !firstTime )
823 {
824 // Make the ad disappear with the text or a new ad appear.
825 mAd.setVisibility( vis );
826 }
827
828 firstTime = false;
829 }
830 });
831
832 setFocusable(true); // make sure we get key events
833 }
834
835 /**
836 * Fetches the animation thread corresponding to this LunarView.
837 *
838 * @return the animation thread
839 */
840 public LunarThread getThread() {
841 return thread;
842 }
843
844 /**
845 * Standard override to get key-press events.
846 */
847 @Override
848 public boolean onKeyDown(int keyCode, KeyEvent msg) {
849 return thread.doKeyDown(keyCode, msg);
850 }
851
852 /**
853 * Standard override for key-up. We actually care about these, so we can
854 * turn off the engine or stop rotating.
855 */
856 @Override
857 public boolean onKeyUp(int keyCode, KeyEvent msg) {
858 return thread.doKeyUp(keyCode, msg);
859 }
860
861 /**
862 * Called when the accuracy of one of the sensors changes.
863 */
864 public void onAccuracyChanged( Sensor sensor, int accuracy )
865 {
866 // We don't care.
867 }
868
869 /**
870 * Called when the the value of a sensor changes.
871 */
872 public void onSensorChanged( SensorEvent sensor )
873 {
874 // We only use the sensor for the orientation of the phone.
875 if ( sensor.sensor.getType() == Sensor.TYPE_ORIENTATION )
876 {
877 // How much is the phone rotating to the left or right?
878 float tiltLeftRight = sensor.values[2];
879
880 // Simulate it as a left or right key press.
881 thread.doOrientationChange( tiltLeftRight );
882 }
883 }
884
885 /**
886 * Standard window-focus override. Notice focus lost so we can pause on
887 * focus lost. e.g. user switches to take a call.
888 */
889 @Override
890 public void onWindowFocusChanged(boolean hasWindowFocus) {
891 if (!hasWindowFocus) thread.pause();
892 }
893
894 /**
895 * Installs a pointer to the text view used for messages.
896 */
897 public void setTextView(TextView textView, AdView adView) {
898 mStatusText = textView;
899 mAd = adView;
900 }
901
902 /* Callback invoked when the surface dimensions change. */
903 public void surfaceChanged(SurfaceHolder holder, int format, int width,
904 int height) {
905 thread.setSurfaceSize(width, height);
906 }
907
908 /*
909 * Callback invoked when the Surface has been created and is ready to be
910 * used.
911 */
912 public void surfaceCreated(SurfaceHolder holder) {
913 // start the thread here so that we don't busy-wait in run()
914 // waiting for the surface to be created
915 thread.setRunning(true);
916 thread.start();
917 }
918
919 /*
920 * Callback invoked when the Surface has been destroyed and must no longer
921 * be touched. WARNING: after this method returns, the Surface/Canvas must
922 * never be touched again!
923 */
924 public void surfaceDestroyed(SurfaceHolder holder) {
925 // we have to tell thread to shut down & wait for it to finish, or else
926 // it might touch the Surface after we return and explode
927 boolean retry = true;
928 thread.setRunning(false);
929 while (retry) {
930 try {
931 thread.join();
932 retry = false;
933 } catch (InterruptedException e) {
934 }
935 }
936 }
937 }

  ViewVC Help
Powered by ViewVC 1.1.20