source: galcon-client/src/com/example/helloandroid/LunarView.java@ 9ef6f68

Last change on this file since 9ef6f68 was 7f84c20, checked in by dportnoy <devnull@…>, 14 years ago

Created a copy of the LunarLander demo to use for development. Original LunarLander files remain for reference.

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