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

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

Changed the package names from com.example.* to com.medievaltech.*

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