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

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

Framerate is now set to 20 fps and planets spawn ships at different rates based on their size. However, fleets now don't leave a planet they're going around at the proper time.

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