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

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

Changed something really minor, but I forget what.

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