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

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

Changed the width of the planet selection circle.

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