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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 337 - (hide annotations) (download)
Wed Sep 23 12:54:05 2009 UTC (14 years, 9 months ago) by torben
File size: 34792 byte(s)
import admob library
1 torben 337 /*
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