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

Last change on this file since 6ebf60f was 6ebf60f, checked in by Zero Cool <devnull@…>, 14 years ago

Updates to fleet traveling + planet attack and fleet deletion

  • Property mode set to 100644
File size: 17.5 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 //Log.i("Gencon", "run called");
128
129 Canvas c = null;
130 try {
131 c = mSurfaceHolder.lockCanvas(null);
132 synchronized (mSurfaceHolder) {
133 //Log.i("Gencon", "about to call stuff: mode is "+mMode);
134
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 }
223
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;
230
231 Log.i("Gencon", "width: "+mCanvasWidth+", height: "+mCanvasHeight);
232
233 Random rand = new Random();
234
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 }
250 }
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) {
289 return true;
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) {
328 canvas.drawColor(Color.BLACK);
329
330 synchronized(planetsLock) {
331 for(Planet p : planets) {
332 p.draw(canvas, mLinePaint, mTextPaint);
333 }
334 }
335
336 if(planetSelected != null) {
337 planetSelected.drawSelectionCircle(canvas);
338 }
339
340 synchronized(fleetsLock) {
341 for(Fleet f : fleets) {
342 f.draw(canvas, mLinePaint);
343 }
344 }
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() {
353 //Log.i("Gencon", "updatePhysics called");
354
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
362 synchronized(planetsLock) {
363 for(Planet p : planets) {
364 p.update();
365 }
366 }
367
368 synchronized(fleetsLock) {
369 Iterator<Fleet> i = fleets.iterator();
370 Fleet f = null;
371 while(i.hasNext()){
372 f = i.next();
373 if(f.getNumShips() == 0)
374 i.remove();
375 else
376 f.update(planets);
377 }
378 }
379
380 mLastTime = now;
381 }
382 }
383
384 /** Handle to the application context, used to e.g. fetch Drawables. */
385 private Context mContext;
386
387 /** Pointer to the text view to display "Paused.." etc. */
388 private TextView mStatusText;
389
390 /** The thread that actually draws the animation */
391 private DrawingThread thread;
392
393 public GameView(Context context, AttributeSet attrs) {
394 super(context, attrs);
395
396 // register our interest in hearing about changes to our surface
397 SurfaceHolder holder = getHolder();
398 holder.addCallback(this);
399
400 // create thread only; it's started in surfaceCreated()
401 thread = new DrawingThread(holder, context, new Handler() {
402 @Override
403 public void handleMessage(Message m) {
404 mStatusText.setVisibility(m.getData().getInt("viz"));
405 mStatusText.setText(m.getData().getString("text"));
406 }
407 });
408
409 setFocusable(true); // make sure we get key events
410 }
411
412 @Override public boolean onTouchEvent(MotionEvent event) {
413 Log.i("Gencon", "Detected touch event");
414
415 if(event.getAction() != MotionEvent.ACTION_DOWN)
416 return true;
417
418 synchronized(thread.planetsLock) {
419 if(thread.planetSelected != null) {
420 Planet target = null;
421
422 for(Planet p : thread.planets) {
423 if(p.contains((int)event.getX(), (int)event.getY())) {
424 target = p;
425 break;
426 }
427 }
428
429 if(target != null && thread.planetSelected.getFaction() != 0) {
430 synchronized(thread.fleetsLock) {
431 Fleet f = new Fleet(thread.planetSelected, target, 1, 1);
432 f.setFaction(thread.planetSelected.getFaction());
433 thread.fleets.add(f);
434 }
435 }
436
437 thread.planetSelected.unselect();
438 thread.planetSelected = null;
439 }else {
440 for(Planet p : thread.planets) {
441 if(p.contains((int)event.getX(), (int)event.getY())) {
442 p.select();
443 thread.planetSelected = p;
444 break;
445 }
446 }
447 }
448 }
449
450 return true;
451 }
452
453 /**
454 * Fetches the animation thread corresponding to this LunarView.
455 *
456 * @return the animation thread
457 */
458 public DrawingThread getThread() {
459 return thread;
460 }
461
462 /**
463 * Standard override to get key-press events.
464 */
465 @Override
466 public boolean onKeyDown(int keyCode, KeyEvent msg) {
467 return thread.doKeyDown(keyCode, msg);
468 }
469
470 /**
471 * Standard override for key-up. We actually care about these, so we can
472 * turn off the engine or stop rotating.
473 */
474 @Override
475 public boolean onKeyUp(int keyCode, KeyEvent msg) {
476 return thread.doKeyUp(keyCode, msg);
477 }
478
479 /**
480 * Standard window-focus override. Notice focus lost so we can pause on
481 * focus lost. e.g. user switches to take a call.
482 */
483 @Override
484 public void onWindowFocusChanged(boolean hasWindowFocus) {
485 if (!hasWindowFocus) thread.pause();
486 }
487
488 /**
489 * Installs a pointer to the text view used for messages.
490 */
491 public void setTextView(TextView textView) {
492 mStatusText = textView;
493 }
494
495 /* Callback invoked when the surface dimensions change. */
496 public void surfaceChanged(SurfaceHolder holder, int format, int width,
497 int height) {
498 thread.setSurfaceSize(width, height);
499 }
500
501 /*
502 * Callback invoked when the Surface has been created and is ready to be
503 * used.
504 */
505 public void surfaceCreated(SurfaceHolder holder) {
506 // start the thread here so that we don't busy-wait in run()
507 // waiting for the surface to be created
508 thread.setRunning(true);
509 thread.start();
510 }
511
512 /*
513 * Callback invoked when the Surface has been destroyed and must no longer
514 * be touched. WARNING: after this method returns, the Surface/Canvas must
515 * never be touched again!
516 */
517 public void surfaceDestroyed(SurfaceHolder holder) {
518 // we have to tell thread to shut down & wait for it to finish, or else
519 // it might touch the Surface after we return and explode
520 boolean retry = true;
521 thread.setRunning(false);
522 while (retry) {
523 try {
524 thread.join();
525 retry = false;
526 } catch (InterruptedException e) {
527 }
528 }
529 }
530}
Note: See TracBrowser for help on using the repository browser.