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

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

Each planet is colored based on the faction that controls it and the ship count is centered on the planet and increases over time.

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