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

Last change on this file since 95509e1 was 95509e1, checked in by dportnoy <devnull@…>, 15 years ago

Removed reference Lunar Lander classes and lots of code that was originally from Lunar Lander. Fleets are now the same color as the player who sent them.

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