source: advance-wars/src/com/example/advancewars/GameView.java@ 26a9fd6

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

Added a Window class that draws all GUI objects that are added to it.

  • Property mode set to 100644
File size: 17.2 KB
Line 
1package com.example.advancewars;
2
3import java.util.*;
4
5import com.example.gui.*;
6
7import android.content.Context;
8import android.graphics.*;
9import android.os.*;
10import android.view.*;
11import android.util.AttributeSet;
12import android.util.Log;
13import android.widget.TextView;
14
15/**
16 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
17 *
18 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
19 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
20 * updatePhysics() advances the physics based on realtime. draw() renders the
21 * ship, and does an invalidate() to prompt another draw() as soon as possible
22 * by the system.
23 */
24class GameView extends SurfaceView implements SurfaceHolder.Callback {
25
26 class DrawingThread extends Thread {
27 public AppState mAppState;
28 public GameState mGameState;
29
30 /*
31 * UI constants (i.e. the speed & fuel bars)
32 */
33 public static final int UI_BAR = 100; // width of the bar(s)
34 public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
35
36 /*
37 * Member (state) fields
38 */
39
40 private int mCanvasHeight = 1;
41 private int mCanvasWidth = 1;
42 public int mPlayerWins = 0, mPlayerLosses = 0;
43
44 /** Message handler used by thread to interact with TextView */
45 private Handler mHandler;
46
47 /** Used to figure out elapsed time between frames */
48 private long mLastTime;
49
50 /** Paint to draw the lines on screen. */
51 private Paint mLinePaint, mTextPaint, mButtonPaint, mTilePaint1, mTilePaint2;
52
53 private Map mMap;
54
55 /** Indicate whether the surface has been created & is ready to draw */
56 private boolean mRun = false;
57
58 /** Handle to the surface manager object we interact with */
59 private SurfaceHolder mSurfaceHolder;
60
61 private com.example.gui.Window wndMainMenu, wndBattleMap;
62
63 public DrawingThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {
64 // get handles to some important objects
65 mSurfaceHolder = surfaceHolder;
66 mHandler = handler;
67 mContext = context;
68
69 mLinePaint = new Paint();
70 mLinePaint.setAntiAlias(true);
71 mLinePaint.setARGB(255, 0, 255, 0);
72
73 mTextPaint = new Paint();
74 mTextPaint.setAntiAlias(true);
75 mTextPaint.setARGB(255, 255, 255, 255);
76 mTextPaint.setTextSize(12);
77 mTextPaint.setTextAlign(Paint.Align.CENTER);
78
79 mButtonPaint = new Paint();
80 mButtonPaint.setAntiAlias(true);
81 mButtonPaint.setARGB(255, 0, 0, 0);
82 mButtonPaint.setTextSize(20);
83 mButtonPaint.setTextAlign(Paint.Align.CENTER);
84
85 mTilePaint1 = new Paint();
86 mTilePaint1.setAntiAlias(true);
87 mTilePaint1.setARGB(255, 0, 255, 0);
88
89 mTilePaint2 = new Paint();
90 mTilePaint2.setAntiAlias(true);
91 mTilePaint2.setARGB(255, 0, 0, 255);
92
93 wndMainMenu = new com.example.gui.Window(0, 0, 320, 450);;
94 wndMainMenu.addGUIObject("txtTitle", new Text("Main Menu", 100, 30, 120, 20, mTextPaint));
95 wndMainMenu.addGUIObject("btnNewGame", new Button("New Game", 100, 90, 120, 20, mLinePaint, mButtonPaint));
96 wndMainMenu.addGUIObject("btnLoadGame", new Button("Load Game", 100, 125, 120, 20, mLinePaint, mButtonPaint));
97 wndMainMenu.addGUIObject("btnMapEditor", new Button("Map Editor", 100, 160, 120, 20, mLinePaint, mButtonPaint));
98 wndMainMenu.addGUIObject("btnQuit", new Button("Quit", 100, 195, 120, 20, mLinePaint, mButtonPaint));
99
100 Tile grassTile = new Tile(mTilePaint1);
101 Tile oceanTile = new Tile(mTilePaint2);
102
103 mMap = new Map(grassTile, 6, 8);
104
105 boolean land = true;
106
107 for(int x=0; x<mMap.getWidth(); x++) {
108 for(int y=0; y<mMap.getHeight(); y++) {
109 if(land)
110 mMap.setTile(x, y, grassTile);
111 else
112 mMap.setTile(x, y, oceanTile);
113 land = !land;
114 }
115 land = !land;
116 }
117
118 mGameState = GameState.MAIN_MENU;
119 }
120
121 /**
122 * Starts the game, setting parameters for the current difficulty.
123 */
124 // I don't think this gets called now. maybe we should call it in the thread constructor
125 public void doStart() {
126 synchronized (mSurfaceHolder) {
127 mLastTime = System.currentTimeMillis() + 100;
128 setState(AppState.RUNNING);
129 Log.i("AdvanceWars", "Player's turn starting now");
130 mGameState = GameState.MAIN_MENU;
131 }
132 }
133
134 /**
135 * Pauses the physics update & animation.
136 */
137 public void pause() {
138 synchronized (mSurfaceHolder) {
139 if (mAppState == AppState.RUNNING) setState(AppState.PAUSE);
140 }
141 }
142
143 @Override
144 public void run() {
145 while (mRun) {
146 Canvas c = null;
147 try {
148 c = mSurfaceHolder.lockCanvas(null);
149 synchronized(mSurfaceHolder) {
150 if(mAppState == AppState.RUNNING)
151 updatePhysics();
152 doDraw(c);
153 }
154 } finally {
155 // do this in a finally so that if an exception is thrown
156 // during the above, we don't leave the Surface in an
157 // inconsistent state
158 if (c != null) {
159 mSurfaceHolder.unlockCanvasAndPost(c);
160 }
161 }
162 }
163 }
164
165 /**
166 * Used to signal the thread whether it should be running or not.
167 * Passing true allows the thread to run; passing false will shut it
168 * down if it's already running. Calling start() after this was most
169 * recently called with false will result in an immediate shutdown.
170 *
171 * @param b true to run, false to shut down
172 */
173 public void setRunning(boolean b) {
174 mRun = b;
175 }
176
177 /**
178 * Sets the game mode. That is, whether we are running, paused, in the
179 * failure state, in the victory state, etc.
180 *
181 * @see #setState(int, CharSequence)
182 * @param mode one of the STATE_* constants
183 */
184 public void setState(AppState state) {
185 synchronized (mSurfaceHolder) {
186 setState(state, null);
187 }
188 }
189
190 public void setGameState(GameState state) {
191 synchronized (mSurfaceHolder) {
192 mGameState = state;
193 }
194 }
195
196 /**
197 * Sets the game mode. That is, whether we are running, paused, in the
198 * failure state, in the victory state, etc.
199 *
200 * @param mode one of the STATE_* constants
201 * @param message string to add to screen or null
202 */
203 public void setState(AppState mode, CharSequence message) {
204 /*
205 * This method optionally can cause a text message to be displayed
206 * to the user when the mode changes. Since the View that actually
207 * renders that text is part of the main View hierarchy and not
208 * owned by this thread, we can't touch the state of that View.
209 * Instead we use a Message + Handler to relay commands to the main
210 * thread, which updates the user-text View.
211 */
212 synchronized (mSurfaceHolder) {
213 mAppState = mode;
214
215 if (mAppState == AppState.RUNNING) {
216 Message msg = mHandler.obtainMessage();
217 Bundle b = new Bundle();
218 b.putString("text", "");
219 b.putInt("viz", GameView.INVISIBLE);
220 msg.setData(b);
221 mHandler.sendMessage(msg);
222 } else {
223 CharSequence str = "";
224 str = "Mode probably changed";
225
226 if (message != null) {
227 str = message + "\n" + str;
228 }
229
230 Message msg = mHandler.obtainMessage();
231 Bundle b = new Bundle();
232 b.putString("text", str.toString());
233 b.putInt("viz", GameView.VISIBLE);
234 msg.setData(b);
235 //mHandler.sendMessage(msg);
236 }
237 }
238 }
239
240 /* Callback invoked when the surface dimensions change. */
241 public void setSurfaceSize(int width, int height) {
242 // synchronized to make sure these all change atomically
243 synchronized (mSurfaceHolder) {
244 mCanvasWidth = width;
245 mCanvasHeight = height;
246
247 Log.i("AdvanceWars", "width: "+mCanvasWidth+", height: "+mCanvasHeight);
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(AppState.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 && (mAppState == AppState.READY || mAppState == AppState.LOSE || mAppState == AppState.WIN)) {
278 // ready-to-start -> start
279 doStart();
280 return true;
281 } else if (mAppState == AppState.PAUSE && okStart) {
282 // paused -> running
283 unpause();
284 return true;
285 } else if (mAppState == AppState.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 (mAppState == AppState.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 String text;
328 Paint.FontMetrics metrics = mTextPaint.getFontMetrics();
329
330 switch(mGameState) {
331 case MAIN_MENU:
332 wndMainMenu.draw(canvas);
333 break;
334 case BATTLE_MAP:
335 mTextPaint.setTextSize(12);
336
337 mMap.draw(canvas, 10, 25);
338
339 text = "Advance Wars grid test";
340 canvas.drawText(text, 0, 450-(metrics.ascent+metrics.descent)/2, mTextPaint);
341 break;
342 }
343 }
344
345 /**
346 * Figures the lander state (x, y, fuel, ...) based on the passage of
347 * realtime. Does not invalidate(). Called at the start of draw().
348 * Detects the end-of-game and sets the UI to the next state.
349 */
350 private void updatePhysics() {
351 long now = System.currentTimeMillis();
352
353 // Do nothing if mLastTime is in the future.
354 // This allows the game-start to delay the start of the physics
355 // by 100ms or whatever.
356 if (mLastTime > now) return;
357
358 // DO SHIT HERE
359
360 mLastTime = now+50;
361 }
362 }
363
364 /** Handle to the application context, used to e.g. fetch Drawables. */
365 private Context mContext;
366
367 /** Pointer to the text view to display "Paused.." etc. */
368 private TextView mStatusText;
369
370 /** The thread that actually draws the animation */
371 private DrawingThread thread;
372
373 public Game mGame;
374
375 public GameView(Context context, AttributeSet attrs) {
376 super(context, attrs);
377
378 // register our interest in hearing about changes to our surface
379 SurfaceHolder holder = getHolder();
380 holder.addCallback(this);
381
382 // create thread only; it's started in surfaceCreated()
383 thread = new DrawingThread(holder, context, new Handler() {
384 @Override
385 public void handleMessage(Message m) {
386 mStatusText.setVisibility(m.getData().getInt("viz"));
387 mStatusText.setText(m.getData().getString("text"));
388 }
389 });
390
391 setFocusable(true); // make sure we get key events
392 }
393
394 @Override public boolean onTouchEvent(MotionEvent event) {
395 Log.i("AdvanceWars", "Detected touch event");
396
397 if(event.getAction() == MotionEvent.ACTION_UP) {
398 Log.i("AdvanceWars", "Detected UP touch action");
399 switch(thread.mGameState) {
400 case MAIN_MENU:
401 Log.i("AdvanceWars", "Switching to battle map");
402 if(thread.wndMainMenu.getGUIObject("btnNewGame").isClicked(event.getX(), event.getY())) {
403 thread.mGameState = GameState.BATTLE_MAP;
404 }else if(thread.wndMainMenu.getGUIObject("btnLoadGame").isClicked(event.getX(), event.getY())) {
405 thread.mGameState = GameState.BATTLE_MAP;
406 }else if(thread.wndMainMenu.getGUIObject("btnQuit").isClicked(event.getX(), event.getY())) {
407 mGame.finish();
408 }
409 break;
410 case BATTLE_MAP:
411 Log.i("AdvanceWars", "Touch event detected on battle map");
412 thread.mGameState = GameState.MAIN_MENU;
413 break;
414 }
415 }else if(event.getAction() == MotionEvent.ACTION_DOWN) {
416
417 }
418
419 return true;
420 }
421
422 /**
423 * Fetches the animation thread corresponding to this LunarView.
424 *
425 * @return the animation thread
426 */
427 public DrawingThread getThread() {
428 return thread;
429 }
430
431 /**
432 * Standard override to get key-press events.
433 */
434 @Override
435 public boolean onKeyDown(int keyCode, KeyEvent msg) {
436 return thread.doKeyDown(keyCode, msg);
437 }
438
439 /**
440 * Standard override for key-up. We actually care about these, so we can
441 * turn off the engine or stop rotating.
442 */
443 @Override
444 public boolean onKeyUp(int keyCode, KeyEvent msg) {
445 return thread.doKeyUp(keyCode, msg);
446 }
447
448 /**
449 * Standard window-focus override. Notice focus lost so we can pause on
450 * focus lost. e.g. user switches to take a call.
451 */
452 @Override
453 public void onWindowFocusChanged(boolean hasWindowFocus) {
454 if (!hasWindowFocus) thread.pause();
455 }
456
457 /**
458 * Installs a pointer to the text view used for messages.
459 */
460 public void setTextView(TextView textView) {
461 mStatusText = textView;
462 }
463
464 /* Callback invoked when the surface dimensions change. */
465 public void surfaceChanged(SurfaceHolder holder, int format, int width,
466 int height) {
467 thread.setSurfaceSize(width, height);
468 }
469
470 /*
471 * Callback invoked when the Surface has been created and is ready to be
472 * used.
473 */
474 public void surfaceCreated(SurfaceHolder holder) {
475 // start the thread here so that we don't busy-wait in run()
476 // waiting for the surface to be created
477 thread.setRunning(true);
478
479 //if(thread.mAppState == AppState.PAUSE)
480 //thread.unpause();
481 //else
482 thread.start();
483 }
484
485 /*
486 * Callback invoked when the Surface has been destroyed and must no longer
487 * be touched. WARNING: after this method returns, the Surface/Canvas must
488 * never be touched again!
489 */
490 public void surfaceDestroyed(SurfaceHolder holder) {
491 // we have to tell thread to shut down & wait for it to finish, or else
492 // it might touch the Surface after we return and explode
493 boolean retry = true;
494 thread.setRunning(false);
495 while (retry) {
496 try {
497 thread.join();
498 retry = false;
499 } catch (InterruptedException e) {
500 }
501 }
502 }
503}
Note: See TracBrowser for help on using the repository browser.