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

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

Merge with 422c8ef5772e988da2065d1c4b95e6d6ab6192e7

  • Property mode set to 100644
File size: 18.6 KB
RevLine 
[7f84c20]1package com.example.helloandroid;
2
[9b87c8d]3import java.util.ArrayList;
[6ebf60f]4import java.util.Iterator;
[9b87c8d]5import java.util.Random;
6
[7f84c20]7import android.content.Context;
8import android.content.res.Resources;
9import android.graphics.Canvas;
[95509e1]10import android.graphics.Color;
[7f84c20]11import android.graphics.Paint;
[69f6f01]12import android.graphics.RectF;
13import android.graphics.Paint.FontMetrics;
[7f84c20]14import android.os.Bundle;
15import android.os.Handler;
16import android.os.Message;
17import android.util.AttributeSet;
[9b87c8d]18import android.util.Log;
[7f84c20]19import android.view.KeyEvent;
[5053d90]20import android.view.MotionEvent;
[7f84c20]21import android.view.SurfaceHolder;
22import android.view.SurfaceView;
23import android.widget.TextView;
24
25/**
26 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
27 *
28 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
29 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
30 * updatePhysics() advances the physics based on realtime. draw() renders the
31 * ship, and does an invalidate() to prompt another draw() as soon as possible
32 * by the system.
33 */
34class GameView extends SurfaceView implements SurfaceHolder.Callback {
35 class DrawingThread extends Thread {
36 /*
37 * State-tracking constants
38 */
39 public static final int STATE_LOSE = 1;
40 public static final int STATE_PAUSE = 2;
41 public static final int STATE_READY = 3;
42 public static final int STATE_RUNNING = 4;
43 public static final int STATE_WIN = 5;
44
45 /*
46 * UI constants (i.e. the speed & fuel bars)
47 */
48 public static final int UI_BAR = 100; // width of the bar(s)
49 public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
50
51 /*
52 * Member (state) fields
53 */
54
55 private int mCanvasHeight = 1;
56 private int mCanvasWidth = 1;
57
58 /** Message handler used by thread to interact with TextView */
59 private Handler mHandler;
60
61 /** Used to figure out elapsed time between frames */
62 private long mLastTime;
63
64 /** Paint to draw the lines on screen. */
[8a4e64f]65 private Paint mLinePaint, mTextPaint;
[7f84c20]66
67 /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
68 private int mMode;
69
70 /** Indicate whether the surface has been created & is ready to draw */
71 private boolean mRun = false;
72
73 /** Handle to the surface manager object we interact with */
74 private SurfaceHolder mSurfaceHolder;
[9b87c8d]75
[61c4fbc]76 public Object planetsLock, fleetsLock;
[5053d90]77
78 public ArrayList<Planet> planets;
[61c4fbc]79 public ArrayList<Fleet> fleets;
[5053d90]80 public Planet planetSelected;
[69f6f01]81
82 int mFleetSize;
[7f84c20]83
84 public DrawingThread(SurfaceHolder surfaceHolder, Context context,
85 Handler handler) {
86 // get handles to some important objects
87 mSurfaceHolder = surfaceHolder;
88 mHandler = handler;
89 mContext = context;
90
91 // Initialize paints for speedometer
92 mLinePaint = new Paint();
93 mLinePaint.setAntiAlias(true);
94 mLinePaint.setARGB(255, 0, 255, 0);
95
[8a4e64f]96 mTextPaint = new Paint();
97 mTextPaint.setAntiAlias(true);
[b6a9b5f]98 mTextPaint.setARGB(255, 255, 255, 255);
[5053d90]99
100 planetsLock = new Object();
[61c4fbc]101 fleetsLock = new Object();
[9b87c8d]102
103 planets = new ArrayList<Planet>();
[5053d90]104 planetSelected = null;
[61c4fbc]105
106 fleets = new ArrayList<Fleet>();
[69f6f01]107
108 mFleetSize = 50;
[7f84c20]109 }
110
111 /**
112 * Starts the game, setting parameters for the current difficulty.
113 */
114 public void doStart() {
115 synchronized (mSurfaceHolder) {
116 mLastTime = System.currentTimeMillis() + 100;
117 setState(STATE_RUNNING);
118 }
119 }
120
121 /**
122 * Pauses the physics update & animation.
123 */
124 public void pause() {
125 synchronized (mSurfaceHolder) {
126 if (mMode == STATE_RUNNING) setState(STATE_PAUSE);
127 }
128 }
129
130 @Override
131 public void run() {
132 while (mRun) {
133 Canvas c = null;
134 try {
135 c = mSurfaceHolder.lockCanvas(null);
[04a9a00]136 synchronized(mSurfaceHolder) {
137 if(mMode == STATE_RUNNING)
138 updatePhysics();
[7f84c20]139 doDraw(c);
140 }
141 } finally {
142 // do this in a finally so that if an exception is thrown
143 // during the above, we don't leave the Surface in an
144 // inconsistent state
145 if (c != null) {
146 mSurfaceHolder.unlockCanvasAndPost(c);
147 }
148 }
149 }
150 }
151
152 /**
153 * Used to signal the thread whether it should be running or not.
154 * Passing true allows the thread to run; passing false will shut it
155 * down if it's already running. Calling start() after this was most
156 * recently called with false will result in an immediate shutdown.
157 *
158 * @param b true to run, false to shut down
159 */
160 public void setRunning(boolean b) {
161 mRun = b;
162 }
163
164 /**
165 * Sets the game mode. That is, whether we are running, paused, in the
166 * failure state, in the victory state, etc.
167 *
168 * @see #setState(int, CharSequence)
169 * @param mode one of the STATE_* constants
170 */
171 public void setState(int mode) {
172 synchronized (mSurfaceHolder) {
173 setState(mode, null);
174 }
175 }
176
177 /**
178 * Sets the game mode. That is, whether we are running, paused, in the
179 * failure state, in the victory state, etc.
180 *
181 * @param mode one of the STATE_* constants
182 * @param message string to add to screen or null
183 */
184 public void setState(int mode, CharSequence message) {
185 /*
186 * This method optionally can cause a text message to be displayed
187 * to the user when the mode changes. Since the View that actually
188 * renders that text is part of the main View hierarchy and not
189 * owned by this thread, we can't touch the state of that View.
190 * Instead we use a Message + Handler to relay commands to the main
191 * thread, which updates the user-text View.
192 */
193 synchronized (mSurfaceHolder) {
194 mMode = mode;
195
196 if (mMode == STATE_RUNNING) {
197 Message msg = mHandler.obtainMessage();
198 Bundle b = new Bundle();
199 b.putString("text", "");
200 b.putInt("viz", GameView.INVISIBLE);
201 msg.setData(b);
202 mHandler.sendMessage(msg);
203 } else {
204 Resources res = mContext.getResources();
205 CharSequence str = "";
206 if (mMode == STATE_READY)
207 str = res.getText(R.string.mode_ready);
208 else if (mMode == STATE_PAUSE)
209 str = res.getText(R.string.mode_pause);
210 else if (mMode == STATE_LOSE)
211 str = res.getText(R.string.mode_lose);
212
213 if (message != null) {
214 str = message + "\n" + str;
215 }
216
217 Message msg = mHandler.obtainMessage();
218 Bundle b = new Bundle();
219 b.putString("text", str.toString());
220 b.putInt("viz", GameView.VISIBLE);
221 msg.setData(b);
222 mHandler.sendMessage(msg);
223 }
224 }
225 }
[5053d90]226
[7f84c20]227 /* Callback invoked when the surface dimensions change. */
228 public void setSurfaceSize(int width, int height) {
229 // synchronized to make sure these all change atomically
230 synchronized (mSurfaceHolder) {
231 mCanvasWidth = width;
232 mCanvasHeight = height;
[9b87c8d]233
[b6a9b5f]234 Log.i("Gencon", "width: "+mCanvasWidth+", height: "+mCanvasHeight);
[9b87c8d]235
236 Random rand = new Random();
[7f84c20]237
[5053d90]238 synchronized(planetsLock) {
239 for(int x=0; x<15; x++) {
240 Planet p = new Planet(rand.nextInt(45)+5, rand.nextInt(mCanvasWidth), rand.nextInt(mCanvasHeight));
241
242 if(Planet.collisionDetected(p, planets)) {
243 x--;
[69f6f01]244 }else if(p.getX()-p.getRadius() < 0 || mCanvasWidth-20<=p.getX()+p.getRadius() ||
245 p.getY()-p.getRadius() < 0 || mCanvasHeight-20<=p.getY()+p.getRadius()) {
[5053d90]246 x--;
247 }else {
248 p.setNumShips(rand.nextInt(150));
249 p.setFaction(rand.nextInt(5));
250 planets.add(p);
251 }
252 }
[9b87c8d]253 }
[7f84c20]254 }
255 }
256
257 /**
258 * Resumes from a pause.
259 */
260 public void unpause() {
261 // Move the real time clock up to now
262 synchronized (mSurfaceHolder) {
263 mLastTime = System.currentTimeMillis() + 100;
264 }
265 setState(STATE_RUNNING);
266 }
267
268 /**
269 * Handles a key-down event.
270 *
271 * @param keyCode the key that was pressed
272 * @param msg the original event object
273 * @return true
274 */
275 boolean doKeyDown(int keyCode, KeyEvent msg) {
276 synchronized (mSurfaceHolder) {
277 boolean okStart = false;
278 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
279 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
280 if (keyCode == KeyEvent.KEYCODE_S) okStart = true;
281
282 if (okStart
283 && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
284 // ready-to-start -> start
285 doStart();
286 return true;
287 } else if (mMode == STATE_PAUSE && okStart) {
288 // paused -> running
289 unpause();
290 return true;
291 } else if (mMode == STATE_RUNNING) {
[8a4e64f]292 return true;
[7f84c20]293 }
294
295 return false;
296 }
297 }
298
299 /**
300 * Handles a key-up event.
301 *
302 * @param keyCode the key that was pressed
303 * @param msg the original event object
304 * @return true if the key was handled and consumed, or else false
305 */
306 boolean doKeyUp(int keyCode, KeyEvent msg) {
307 boolean handled = false;
308
309 synchronized (mSurfaceHolder) {
310 if (mMode == STATE_RUNNING) {
311 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
312 || keyCode == KeyEvent.KEYCODE_SPACE) {
313 handled = true;
314 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
315 || keyCode == KeyEvent.KEYCODE_Q
316 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
317 || keyCode == KeyEvent.KEYCODE_W) {
318 handled = true;
319 }
320 }
321 }
322
323 return handled;
324 }
325
326 /**
327 * Draws the ship, fuel/speed bars, and background to the provided
328 * Canvas.
329 */
330 private void doDraw(Canvas canvas) {
[95509e1]331 canvas.drawColor(Color.BLACK);
332
[5053d90]333 synchronized(planetsLock) {
334 for(Planet p : planets) {
335 p.draw(canvas, mLinePaint, mTextPaint);
336 }
337 }
[9b87c8d]338
[5053d90]339 if(planetSelected != null) {
340 planetSelected.drawSelectionCircle(canvas);
341 }
342
[61c4fbc]343 synchronized(fleetsLock) {
344 for(Fleet f : fleets) {
345 f.draw(canvas, mLinePaint);
346 }
347 }
[69f6f01]348
349 float textSize = mTextPaint.getTextSize();
350 mTextPaint.setTextSize(24);
351 FontMetrics metrics = mTextPaint.getFontMetrics();
352 mTextPaint.setColor(Color.WHITE);
353
354 canvas.drawText(mFleetSize+"%", mCanvasWidth-mTextPaint.measureText(mFleetSize+"%"), mCanvasHeight-20-(metrics.ascent+metrics.descent), mTextPaint);
355
356 mTextPaint.setTextSize(textSize);
357
358 mLinePaint.setColor(Color.YELLOW);
359 canvas.drawRoundRect(new RectF(70, mCanvasHeight-15, mCanvasWidth-70, mCanvasHeight-5), 5, 5, mLinePaint);
360
361 mLinePaint.setColor(Color.GREEN);
362 canvas.drawRoundRect(new RectF(70, mCanvasHeight-15, 70+(mCanvasWidth-140)*mFleetSize/100, mCanvasHeight-5), 5, 5, mLinePaint);
[7f84c20]363 }
364
365 /**
366 * Figures the lander state (x, y, fuel, ...) based on the passage of
367 * realtime. Does not invalidate(). Called at the start of draw().
368 * Detects the end-of-game and sets the UI to the next state.
369 */
370 private void updatePhysics() {
371 long now = System.currentTimeMillis();
372
373 // Do nothing if mLastTime is in the future.
374 // This allows the game-start to delay the start of the physics
375 // by 100ms or whatever.
376 if (mLastTime > now) return;
[04a9a00]377
[5053d90]378 synchronized(planetsLock) {
379 for(Planet p : planets) {
380 p.update();
381 }
[8a4e64f]382 }
383
[61c4fbc]384 synchronized(fleetsLock) {
[6ebf60f]385 Iterator<Fleet> i = fleets.iterator();
386 Fleet f = null;
387 while(i.hasNext()){
388 f = i.next();
[69f6f01]389 if(f.getNumShips() == 0) {
[6ebf60f]390 i.remove();
[69f6f01]391 }else
[6ebf60f]392 f.update(planets);
[61c4fbc]393 }
394 }
[7f84c20]395
[04a9a00]396 mLastTime = now+50;
[7f84c20]397 }
398 }
399
400 /** Handle to the application context, used to e.g. fetch Drawables. */
401 private Context mContext;
402
403 /** Pointer to the text view to display "Paused.." etc. */
404 private TextView mStatusText;
405
406 /** The thread that actually draws the animation */
407 private DrawingThread thread;
408
409 public GameView(Context context, AttributeSet attrs) {
410 super(context, attrs);
411
412 // register our interest in hearing about changes to our surface
413 SurfaceHolder holder = getHolder();
414 holder.addCallback(this);
415
416 // create thread only; it's started in surfaceCreated()
417 thread = new DrawingThread(holder, context, new Handler() {
418 @Override
419 public void handleMessage(Message m) {
420 mStatusText.setVisibility(m.getData().getInt("viz"));
421 mStatusText.setText(m.getData().getString("text"));
422 }
423 });
424
425 setFocusable(true); // make sure we get key events
426 }
427
[5053d90]428 @Override public boolean onTouchEvent(MotionEvent event) {
429 Log.i("Gencon", "Detected touch event");
430
[69f6f01]431 if(event.getAction() == MotionEvent.ACTION_UP) {
432 if(70 <= event.getX() && event.getX() <= thread.mCanvasWidth-70 &&
433 thread.mCanvasHeight-15 <= event.getY() && event.getY() <= thread.mCanvasHeight-5) {
434 thread.mFleetSize = ((int)event.getX()-70)*100/(thread.mCanvasWidth-140);
[5053d90]435 }
[69f6f01]436 }else if(event.getAction() == MotionEvent.ACTION_DOWN) {
437 synchronized(thread.planetsLock) {
438 if(thread.planetSelected != null) {
439 Planet target = null;
440
441 for(Planet p : thread.planets) {
442 if(p.contains((int)event.getX(), (int)event.getY())) {
443 target = p;
444 break;
445 }
446 }
447
448 if(target != null && target != thread.planetSelected && thread.planetSelected.getFaction() != 0) {
449 synchronized(thread.fleetsLock) {
450 Fleet f = new Fleet(thread.planetSelected, target, thread.planetSelected.getNumShips()*thread.mFleetSize/100, thread.planetSelected.getFaction());
451 f.setFaction(thread.planetSelected.getFaction());
452 thread.fleets.add(f);
453 }
454 }
455
456 thread.planetSelected.unselect();
457 thread.planetSelected = null;
458 }else {
459 for(Planet p : thread.planets) {
460 if(p.contains((int)event.getX(), (int)event.getY())) {
461 p.select();
462 thread.planetSelected = p;
463 break;
464 }
465 }
466 }
467 }
[5053d90]468 }
469
470 return true;
471 }
472
[7f84c20]473 /**
474 * Fetches the animation thread corresponding to this LunarView.
475 *
476 * @return the animation thread
477 */
478 public DrawingThread getThread() {
479 return thread;
480 }
481
482 /**
483 * Standard override to get key-press events.
484 */
485 @Override
486 public boolean onKeyDown(int keyCode, KeyEvent msg) {
487 return thread.doKeyDown(keyCode, msg);
488 }
489
490 /**
491 * Standard override for key-up. We actually care about these, so we can
492 * turn off the engine or stop rotating.
493 */
494 @Override
495 public boolean onKeyUp(int keyCode, KeyEvent msg) {
496 return thread.doKeyUp(keyCode, msg);
497 }
498
499 /**
500 * Standard window-focus override. Notice focus lost so we can pause on
501 * focus lost. e.g. user switches to take a call.
502 */
503 @Override
504 public void onWindowFocusChanged(boolean hasWindowFocus) {
505 if (!hasWindowFocus) thread.pause();
506 }
507
508 /**
509 * Installs a pointer to the text view used for messages.
510 */
511 public void setTextView(TextView textView) {
512 mStatusText = textView;
513 }
514
515 /* Callback invoked when the surface dimensions change. */
516 public void surfaceChanged(SurfaceHolder holder, int format, int width,
517 int height) {
518 thread.setSurfaceSize(width, height);
519 }
520
521 /*
522 * Callback invoked when the Surface has been created and is ready to be
523 * used.
524 */
525 public void surfaceCreated(SurfaceHolder holder) {
526 // start the thread here so that we don't busy-wait in run()
527 // waiting for the surface to be created
528 thread.setRunning(true);
529 thread.start();
530 }
531
532 /*
533 * Callback invoked when the Surface has been destroyed and must no longer
534 * be touched. WARNING: after this method returns, the Surface/Canvas must
535 * never be touched again!
536 */
537 public void surfaceDestroyed(SurfaceHolder holder) {
538 // we have to tell thread to shut down & wait for it to finish, or else
539 // it might touch the Surface after we return and explode
540 boolean retry = true;
541 thread.setRunning(false);
542 while (retry) {
543 try {
544 thread.join();
545 retry = false;
546 } catch (InterruptedException e) {
547 }
548 }
549 }
550}
Note: See TracBrowser for help on using the repository browser.