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

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

Added some buttons to the main menu. Added a new package to the project to make gui creation easier.

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