source: advance-wars/src/com/medievaltech/advancewars/GameView.java@ b97a618

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

Touching a unit now displays its movement area. Touching a square without a unit will stop displaying any previously displayed movement area. THe movement area is now drawn under the unit itself.

  • Property mode set to 100644
File size: 18.2 KB
Line 
1package com.medievaltech.advancewars;
2
3import com.medievaltech.game.*;
4import com.medievaltech.gui.*;
5
6import android.content.Context;
7import android.graphics.*;
8import android.os.*;
9import android.view.*;
10import android.util.AttributeSet;
11import android.util.Log;
12import android.widget.TextView;
13
14/**
15 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
16 *
17 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
18 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
19 * updatePhysics() advances the physics based on realtime. draw() renders the
20 * ship, and does an invalidate() to prompt another draw() as soon as possible
21 * by the system.
22 */
23class GameView extends SurfaceView implements SurfaceHolder.Callback {
24
25 class DrawingThread extends Thread {
26 public AppState mAppState;
27 public GameState mGameState;
28
29 /*
30 * UI constants (i.e. the speed & fuel bars)
31 */
32 public static final int UI_BAR = 100; // width of the bar(s)
33 public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
34
35 /*
36 * Member (state) fields
37 */
38
39 private int mCanvasHeight = 1;
40 private int mCanvasWidth = 1;
41 public int mPlayerWins = 0, mPlayerLosses = 0;
42
43 /** Message handler used by thread to interact with TextView */
44 private Handler mHandler;
45
46 /** Used to figure out elapsed time between frames */
47 private long mLastTime;
48
49 /** Paint to draw the lines on screen. */
50 private Paint mLinePaint, mTextPaint, mButtonPaint, mTilePaint1, mTilePaint2, mSelectionPaint,
51 mUnitPaint;
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.medievaltech.gui.Window wndMainMenu, wndBattleMap;
62 private Unit selectedUnit;
63
64 public DrawingThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {
65 // get handles to some important objects
66 mSurfaceHolder = surfaceHolder;
67 mHandler = handler;
68 mContext = context;
69
70 mLinePaint = new Paint();
71 mLinePaint.setAntiAlias(true);
72 mLinePaint.setARGB(255, 0, 255, 0);
73
74 mTextPaint = new Paint();
75 mTextPaint.setAntiAlias(true);
76 mTextPaint.setARGB(255, 255, 255, 255);
77 mTextPaint.setTextSize(12);
78 mTextPaint.setTextAlign(Paint.Align.CENTER);
79
80 mButtonPaint = new Paint();
81 mButtonPaint.setAntiAlias(true);
82 mButtonPaint.setARGB(255, 0, 0, 0);
83 mButtonPaint.setTextSize(20);
84 mButtonPaint.setTextAlign(Paint.Align.CENTER);
85
86 mTilePaint1 = new Paint();
87 mTilePaint1.setAntiAlias(true);
88 mTilePaint1.setARGB(255, 0, 255, 0);
89
90 mTilePaint2 = new Paint();
91 mTilePaint2.setAntiAlias(true);
92 mTilePaint2.setARGB(255, 0, 0, 255);
93
94 mUnitPaint = new Paint();
95 mUnitPaint.setAntiAlias(true);
96 mUnitPaint.setARGB(255, 255, 0, 0);
97
98 mSelectionPaint = new Paint();
99 mSelectionPaint.setAntiAlias(true);
100 mSelectionPaint.setARGB(255, 255, 127, 0);
101
102 wndMainMenu = new com.medievaltech.gui.Window(0, 0, 320, 450);;
103 wndMainMenu.addGUIObject("txtTitle", new Text("Main Menu", 100, 30, 120, 20, mTextPaint));
104 wndMainMenu.addGUIObject("btnNewGame", new Button("New Game", 100, 90, 120, 20, mLinePaint, mButtonPaint));
105 wndMainMenu.addGUIObject("btnLoadGame", new Button("Load Game", 100, 125, 120, 20, mLinePaint, mButtonPaint));
106 wndMainMenu.addGUIObject("btnMapEditor", new Button("Map Editor", 100, 160, 120, 20, mLinePaint, mButtonPaint));
107 wndMainMenu.addGUIObject("btnQuit", new Button("Quit", 100, 195, 120, 20, mLinePaint, mButtonPaint));
108
109 Tile grassTile = new Tile(mTilePaint1);
110 Tile oceanTile = new Tile(mTilePaint2);
111
112 mMap = new Map(grassTile, 6, 8, new Point(10, 25));
113
114 boolean land = true;
115
116 for(int x=0; x<mMap.getWidth(); x++) {
117 for(int y=0; y<mMap.getHeight(); y++) {
118 if(land)
119 mMap.setTile(x, y, new Tile(grassTile, new Point(x, y)));
120 else
121 mMap.setTile(x, y, new Tile(oceanTile, new Point(x, y)));
122 land = !land;
123 }
124 land = !land;
125 }
126
127 mMap.getTile(2, 3).addUnit(new Soldier(mUnitPaint));
128 mMap.getTile(5, 6).addUnit(new Soldier(mUnitPaint));
129
130 mGameState = GameState.MAIN_MENU;
131 }
132
133 /**
134 * Starts the game, setting parameters for the current difficulty.
135 */
136 // I don't think this gets called now. maybe we should call it in the thread constructor
137 public void doStart() {
138 synchronized (mSurfaceHolder) {
139 mLastTime = System.currentTimeMillis() + 100;
140 setState(AppState.RUNNING);
141 Log.i("AdvanceWars", "Player's turn starting now");
142 mGameState = GameState.MAIN_MENU;
143 }
144 }
145
146 /**
147 * Pauses the physics update & animation.
148 */
149 public void pause() {
150 synchronized (mSurfaceHolder) {
151 if (mAppState == AppState.RUNNING) setState(AppState.PAUSE);
152 }
153 }
154
155 @Override
156 public void run() {
157 while (mRun) {
158 Canvas c = null;
159 try {
160 c = mSurfaceHolder.lockCanvas(null);
161 synchronized(mSurfaceHolder) {
162 if(mAppState == AppState.RUNNING)
163 updatePhysics();
164 doDraw(c);
165 }
166 } finally {
167 // do this in a finally so that if an exception is thrown
168 // during the above, we don't leave the Surface in an
169 // inconsistent state
170 if (c != null) {
171 mSurfaceHolder.unlockCanvasAndPost(c);
172 }
173 }
174 }
175 }
176
177 /**
178 * Used to signal the thread whether it should be running or not.
179 * Passing true allows the thread to run; passing false will shut it
180 * down if it's already running. Calling start() after this was most
181 * recently called with false will result in an immediate shutdown.
182 *
183 * @param b true to run, false to shut down
184 */
185 public void setRunning(boolean b) {
186 mRun = b;
187 }
188
189 /**
190 * Sets the game mode. That is, whether we are running, paused, in the
191 * failure state, in the victory state, etc.
192 *
193 * @see #setState(int, CharSequence)
194 * @param mode one of the STATE_* constants
195 */
196 public void setState(AppState state) {
197 synchronized (mSurfaceHolder) {
198 setState(state, null);
199 }
200 }
201
202 public void setGameState(GameState state) {
203 synchronized (mSurfaceHolder) {
204 mGameState = state;
205 }
206 }
207
208 /**
209 * Sets the game mode. That is, whether we are running, paused, in the
210 * failure state, in the victory state, etc.
211 *
212 * @param mode one of the STATE_* constants
213 * @param message string to add to screen or null
214 */
215 public void setState(AppState mode, CharSequence message) {
216 /*
217 * This method optionally can cause a text message to be displayed
218 * to the user when the mode changes. Since the View that actually
219 * renders that text is part of the main View hierarchy and not
220 * owned by this thread, we can't touch the state of that View.
221 * Instead we use a Message + Handler to relay commands to the main
222 * thread, which updates the user-text View.
223 */
224 synchronized (mSurfaceHolder) {
225 mAppState = mode;
226
227 if (mAppState == AppState.RUNNING) {
228 Message msg = mHandler.obtainMessage();
229 Bundle b = new Bundle();
230 b.putString("text", "");
231 b.putInt("viz", GameView.INVISIBLE);
232 msg.setData(b);
233 mHandler.sendMessage(msg);
234 } else {
235 CharSequence str = "";
236 str = "Mode probably changed";
237
238 if (message != null) {
239 str = message + "\n" + str;
240 }
241
242 Message msg = mHandler.obtainMessage();
243 Bundle b = new Bundle();
244 b.putString("text", str.toString());
245 b.putInt("viz", GameView.VISIBLE);
246 msg.setData(b);
247 //mHandler.sendMessage(msg);
248 }
249 }
250 }
251
252 /* Callback invoked when the surface dimensions change. */
253 public void setSurfaceSize(int width, int height) {
254 // synchronized to make sure these all change atomically
255 synchronized (mSurfaceHolder) {
256 mCanvasWidth = width;
257 mCanvasHeight = height;
258
259 Log.i("AdvanceWars", "width: "+mCanvasWidth+", height: "+mCanvasHeight);
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(AppState.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 && (mAppState == AppState.READY || mAppState == AppState.LOSE || mAppState == AppState.WIN)) {
290 // ready-to-start -> start
291 doStart();
292 return true;
293 } else if (mAppState == AppState.PAUSE && okStart) {
294 // paused -> running
295 unpause();
296 return true;
297 } else if (mAppState == AppState.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 (mAppState == AppState.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 String text;
340 Paint.FontMetrics metrics = mTextPaint.getFontMetrics();
341
342 switch(mGameState) {
343 case MAIN_MENU:
344 wndMainMenu.draw(canvas);
345 break;
346 case BATTLE_MAP:
347 mTextPaint.setTextSize(12);
348
349 mMap.draw(canvas);
350
351 if(selectedUnit != null) {
352 for(Point p : selectedUnit.getMovementRange()) {
353 canvas.drawRect(p.x*50+10, p.y*50+25, p.x*50+50+10, p.y*50+50+25, mSelectionPaint);
354 }
355 }
356
357 mMap.drawUnits(canvas);
358
359 break;
360 }
361 }
362
363 /**
364 * Figures the lander state (x, y, fuel, ...) based on the passage of
365 * realtime. Does not invalidate(). Called at the start of draw().
366 * Detects the end-of-game and sets the UI to the next state.
367 */
368 private void updatePhysics() {
369 long now = System.currentTimeMillis();
370
371 // Do nothing if mLastTime is in the future.
372 // This allows the game-start to delay the start of the physics
373 // by 100ms or whatever.
374 if (mLastTime > now) return;
375
376 // DO SHIT HERE
377
378 mLastTime = now+50;
379 }
380 }
381
382 /** Handle to the application context, used to e.g. fetch Drawables. */
383 private Context mContext;
384
385 /** Pointer to the text view to display "Paused.." etc. */
386 private TextView mStatusText;
387
388 /** The thread that actually draws the animation */
389 private DrawingThread thread;
390
391 public Game mGame;
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("AdvanceWars", "Detected touch event");
414
415 if(event.getAction() == MotionEvent.ACTION_UP) {
416 Log.i("AdvanceWars", "Detected UP touch action");
417 switch(thread.mGameState) {
418 case MAIN_MENU:
419 Log.i("AdvanceWars", "Switching to battle map");
420 if(thread.wndMainMenu.getGUIObject("btnNewGame").isClicked(event.getX(), event.getY())) {
421 thread.mGameState = GameState.BATTLE_MAP;
422 }else if(thread.wndMainMenu.getGUIObject("btnLoadGame").isClicked(event.getX(), event.getY())) {
423 thread.mGameState = GameState.BATTLE_MAP;
424 }else if(thread.wndMainMenu.getGUIObject("btnQuit").isClicked(event.getX(), event.getY())) {
425 mGame.finish();
426 }
427 break;
428 case BATTLE_MAP:
429 Log.i("AdvanceWars", "Touch event detected on battle map");
430
431 if(event.getX() >= thread.mMap.offset.x && event.getY() >= thread.mMap.offset.y) {
432 int x = ((int)event.getX() - thread.mMap.offset.x) / 50;
433 int y = ((int)event.getY() - thread.mMap.offset.y) / 50;
434
435 thread.selectedUnit = thread.mMap.getTile(x, y).currentUnit;
436 }
437
438 break;
439 }
440 }else if(event.getAction() == MotionEvent.ACTION_DOWN) {
441
442 }
443
444 return true;
445 }
446
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
504 //if(thread.mAppState == AppState.PAUSE)
505 //thread.unpause();
506 //else
507 thread.start();
508 }
509
510 /*
511 * Callback invoked when the Surface has been destroyed and must no longer
512 * be touched. WARNING: after this method returns, the Surface/Canvas must
513 * never be touched again!
514 */
515 public void surfaceDestroyed(SurfaceHolder holder) {
516 // we have to tell thread to shut down & wait for it to finish, or else
517 // it might touch the Surface after we return and explode
518 boolean retry = true;
519 thread.setRunning(false);
520 while (retry) {
521 try {
522 thread.join();
523 retry = false;
524 } catch (InterruptedException e) {
525 }
526 }
527 }
528}
Note: See TracBrowser for help on using the repository browser.