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

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

Added code to connect to the game server.

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