source: galcon-client/src/com/example/helloandroid/GameView.java@ 76819ac

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