Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android游戲 >> Android游戲開發 >> Android游戲開發教程之八:SurfaceView類的應用實例

Android游戲開發教程之八:SurfaceView類的應用實例

編輯:Android游戲開發

       SurfaceView可以說是Android游戲開發中必須學會和掌握的。本文就通過系統自帶的例子來讓大家看看SurfaceView的用法。Android SDK的Sample有有一個LunarLander游戲的例子,下面給出了其中LunarView的實現代碼。

       大家可以把此工程導入到Eclipse,然後編譯運行,試玩一下。最後對應著看代碼,這樣更容易理解。下面上代碼:

Java代碼
  1. class LunarView extends SurfaceView implements SurfaceHolder.Callback {   
  2.     class LunarThread extends Thread {   
  3.         /*  
  4.          * Difficulty setting constants  
  5.          */  
  6.         public static final int DIFFICULTY_EASY = 0;   
  7.         public static final int DIFFICULTY_HARD = 1;   
  8.         public static final int DIFFICULTY_MEDIUM = 2;   
  9.         /*  
  10.          * Physics constants  
  11.          */  
  12.         public static final int PHYS_DOWN_ACCEL_SEC = 35;   
  13.         public static final int PHYS_FIRE_ACCEL_SEC = 80;   
  14.         public static final int PHYS_FUEL_INIT = 60;   
  15.         public static final int PHYS_FUEL_MAX = 100;   
  16.         public static final int PHYS_FUEL_SEC = 10;   
  17.         public static final int PHYS_SLEW_SEC = 120; // degrees/second rotate   
  18.         public static final int PHYS_SPEED_HYPERSPACE = 180;   
  19.         public static final int PHYS_SPEED_INIT = 30;   
  20.         public static final int PHYS_SPEED_MAX = 120;   
  21.         /*  
  22.          * State-tracking constants  
  23.          */  
  24.         public static final int STATE_LOSE = 1;   
  25.         public static final int STATE_PAUSE = 2;   
  26.         public static final int STATE_READY = 3;   
  27.         public static final int STATE_RUNNING = 4;   
  28.         public static final int STATE_WIN = 5;   
  29.   
  30.         /*  
  31.          * Goal condition constants  
  32.          */  
  33.         public static final int TARGET_ANGLE = 18; // > this angle means crash   
  34.         public static final int TARGET_BOTTOM_PADDING = 17; // px below gear   
  35.         public static final int TARGET_PAD_HEIGHT = 8; // how high above ground   
  36.         public static final int TARGET_SPEED = 28; // > this speed means crash   
  37.         public static final double TARGET_WIDTH = 1.6; // width of target   
  38.         /*  
  39.          * UI constants (i.e. the speed & fuel bars)  
  40.          */  
  41.         public static final int UI_BAR = 100; // width of the bar(s)   
  42.         public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)   
  43.         private static final String KEY_DIFFICULTY = "mDifficulty";   
  44.         private static final String KEY_DX = "mDX";   
  45.   
  46.         private static final String KEY_DY = "mDY";   
  47.         private static final String KEY_FUEL = "mFuel";   
  48.         private static final String KEY_GOAL_ANGLE = "mGoalAngle";   
  49.         private static final String KEY_GOAL_SPEED = "mGoalSpeed";   
  50.         private static final String KEY_GOAL_WIDTH = "mGoalWidth";   
  51.   
  52.         private static final String KEY_GOAL_X = "mGoalX";   
  53.         private static final String KEY_HEADING = "mHeading";   
  54.         private static final String KEY_LANDER_HEIGHT = "mLanderHeight";   
  55.         private static final String KEY_LANDER_WIDTH = "mLanderWidth";   
  56.         private static final String KEY_WINS = "mWinsInARow";   
  57.   
  58.         private static final String KEY_X = "mX";   
  59.         private static final String KEY_Y = "mY";   
  60.   
  61.         /*  
  62.          * Member (state) fields  
  63.          */  
  64.         /** The drawable to use as the background of the animation canvas */  
  65.         private Bitmap mBackgroundImage;   
  66.   
  67.         /**  
  68.          * Current height of the surface/canvas.  
  69.          *   
  70.          * @see #setSurfaceSize  
  71.          */  
  72.         private int mCanvasHeight = 1;   
  73.   
  74.         /**  
  75.          * Current width of the surface/canvas.  
  76.          *   
  77.          * @see #setSurfaceSize  
  78.          */  
  79.         private int mCanvasWidth = 1;   
  80.   
  81.         /** What to draw for the Lander when it has crashed */  
  82.         private Drawable mCrashedImage;   
  83.   
  84.         /**  
  85.          * Current difficulty -- amount of fuel, allowed angle, etc. Default is  
  86.          * MEDIUM.  
  87.          */  
  88.         private int mDifficulty;   
  89.   
  90.         /** Velocity dx. */  
  91.         private double mDX;   
  92.   
  93.         /** Velocity dy. */  
  94.         private double mDY;   
  95.   
  96.         /** Is the engine burning? */  
  97.         private boolean mEngineFiring;   
  98.   
  99.         /** What to draw for the Lander when the engine is firing */  
  100.         private Drawable mFiringImage;   
  101.   
  102.         /** Fuel remaining */  
  103.         private double mFuel;   
  104.   
  105.         /** Allowed angle. */  
  106.         private int mGoalAngle;   
  107.   
  108.         /** Allowed speed. */  
  109.         private int mGoalSpeed;   
  110.   
  111.         /** Width of the landing pad. */  
  112.         private int mGoalWidth;   
  113.   
  114.         /** X of the landing pad. */  
  115.         private int mGoalX;   
  116.   
  117.         /** Message handler used by thread to interact with TextView */  
  118.         private Handler mHandler;   
  119.   
  120.         /**  
  121.          * Lander heading in degrees, with 0 up, 90 right. Kept in the range  
  122.          * 0..360.  
  123.          */  
  124.         private double mHeading;   
  125.   
  126.         /** Pixel height of lander image. */  
  127.         private int mLanderHeight;   
  128.   
  129.         /** What to draw for the Lander in its normal state */  
  130.         private Drawable mLanderImage;   
  131.   
  132.         /** Pixel width of lander image. */  
  133.         private int mLanderWidth;   
  134.   
  135.         /** Used to figure out elapsed time between frames */  
  136.         private long mLastTime;   
  137.   
  138.         /** Paint to draw the lines on screen. */  
  139.         private Paint mLinePaint;   
  140.   
  141.         /** "Bad" speed-too-high variant of the line color. */  
  142.         private Paint mLinePaintBad;   
  143.   
  144.         /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */  
  145.         private int mMode;   
  146.   
  147.         /** Currently rotating, -1 left, 0 none, 1 right. */  
  148.         private int mRotating;   
  149.   
  150.         /** Indicate whether the surface has been created & is ready to draw */  
  151.         private boolean mRun = false;   
  152.   
  153.         /** Scratch rect object. */  
  154.         private RectF mScratchRect;   
  155.   
  156.         /** Handle to the surface manager object we interact with */  
  157.         private SurfaceHolder mSurfaceHolder;   
  158.   
  159.         /** Number of wins in a row. */  
  160.         private int mWinsInARow;   
  161.   
  162.         /** X of lander center. */  
  163.         private double mX;   
  164.   
  165.         /** Y of lander center. */  
  166.         private double mY;   
  167.   
  168.         public LunarThread(SurfaceHolder surfaceHolder, Context context,   
  169.                 Handler handler) {   
  170.             // get handles to some important objects   
  171.             mSurfaceHolder = surfaceHolder;   
  172.             mHandler = handler;   
  173.             mContext = context;   
  174.   
  175.             Resources res = context.getResources();   
  176.             // cache handles to our key sprites & other drawables   
  177.             mLanderImage = context.getResources().getDrawable(   
  178.                     R.drawable.lander_plain);   
  179.             mFiringImage = context.getResources().getDrawable(   
  180.                     R.drawable.lander_firing);   
  181.             mCrashedImage = context.getResources().getDrawable(   
  182.                     R.drawable.lander_crashed);   
  183.   
  184.             // load background image as a Bitmap instead of a Drawable b/c   
  185.             // we don't need to transform it and it's faster to draw this way   
  186.             mBackgroundImage = BitmapFactory.decodeResource(res,   
  187.                     R.drawable.earthrise);   
  188.   
  189.             // Use the regular lander image as the model size for all sprites   
  190.             mLanderWidth = mLanderImage.getIntrinsicWidth();   
  191.             mLanderHeight = mLanderImage.getIntrinsicHeight();   
  192.   
  193.             // Initialize paints for speedometer   
  194.             mLinePaint = new Paint();   
  195.             mLinePaint.setAntiAlias(true);   
  196.             mLinePaint.setARGB(255, 0, 255, 0);   
  197.   
  198.             mLinePaintBad = new Paint();   
  199.             mLinePaintBad.setAntiAlias(true);   
  200.             mLinePaintBad.setARGB(255, 120, 180, 0);   
  201.   
  202.             mScratchRect = new RectF(0, 0, 0, 0);   
  203.   
  204.             mWinsInARow = 0;   
  205.             mDifficulty = DIFFICULTY_MEDIUM;   
  206.   
  207.             // initial show-up of lander (not yet playing)   
  208.             mX = mLanderWidth;   
  209.             mY = mLanderHeight * 2;   
  210.             mFuel = PHYS_FUEL_INIT;   
  211.             mDX = 0;   
  212.             mDY = 0;   
  213.             mHeading = 0;   
  214.             mEngineFiring = true;   
  215.         }   
  216.   
  217.         /**  
  218.          * Starts the game, setting parameters for the current difficulty.  
  219.          */  
  220.         public void doStart() {   
  221.             synchronized (mSurfaceHolder) {   
  222.                 // First set the game for Medium difficulty   
  223.                 mFuel = PHYS_FUEL_INIT;   
  224.                 mEngineFiring = false;   
  225.                 mGoalWidth = (int) (mLanderWidth * TARGET_WIDTH);   
  226.                 mGoalSpeed = TARGET_SPEED;   
  227.                 mGoalAngle = TARGET_ANGLE;   
  228.                 int speedInit = PHYS_SPEED_INIT;   
  229.   
  230.                 // Adjust difficulty params for EASY/HARD   
  231.                 if (mDifficulty == DIFFICULTY_EASY) {   
  232.                     mFuel = mFuel * 3 / 2;   
  233.                     mGoalWidth = mGoalWidth * 4 / 3;   
  234.                     mGoalSpeed = mGoalSpeed * 3 / 2;   
  235.                     mGoalAngle = mGoalAngle * 4 / 3;   
  236.                     speedInit = speedInit * 3 / 4;   
  237.                 } else if (mDifficulty == DIFFICULTY_HARD) {   
  238.                     mFuel = mFuel * 7 / 8;   
  239.                     mGoalWidth = mGoalWidth * 3 / 4;   
  240.                     mGoalSpeed = mGoalSpeed * 7 / 8;   
  241.                     speedInit = speedInit * 4 / 3;   
  242.                 }   
  243.   
  244.                 // pick a convenient initial location for the lander sprite   
  245.                 mX = mCanvasWidth / 2;   
  246.                 mY = mCanvasHeight - mLanderHeight / 2;   
  247.   
  248.                 // start with a little random motion   
  249.                 mDY = Math.random() * -speedInit;   
  250.                 mDX = Math.random() * 2 * speedInit - speedInit;   
  251.                 mHeading = 0;   
  252.   
  253.                 // Figure initial spot for landing, not too near center   
  254.                 while (true) {   
  255.                     mGoalX = (int) (Math.random() * (mCanvasWidth - mGoalWidth));   
  256.                     if (Math.abs(mGoalX - (mX - mLanderWidth / 2)) > mCanvasHeight / 6)   
  257.                         break;   
  258.                 }   
  259.   
  260.                 mLastTime = System.currentTimeMillis() + 100;   
  261.                 setState(STATE_RUNNING);   
  262.             }   
  263.         }   
  264.   
  265.         /**  
  266.          * Pauses the physics update & animation.  
  267.          */  
  268.         public void pause() {   
  269.             synchronized (mSurfaceHolder) {   
  270.                 if (mMode == STATE_RUNNING) setState(STATE_PAUSE);   
  271.             }   
  272.         }   
  273.   
  274.         /**  
  275.          * Restores game state from the indicated Bundle. Typically called when  
  276.          * the Activity is being restored after having been previously  
  277.          * destroyed.  
  278.          *   
  279.          * @param savedState Bundle containing the game state  
  280.          */  
  281.         public synchronized void restoreState(Bundle savedState) {   
  282.             synchronized (mSurfaceHolder) {   
  283.                 setState(STATE_PAUSE);   
  284.                 mRotating = 0;   
  285.                 mEngineFiring = false;   
  286.   
  287.                 mDifficulty = savedState.getInt(KEY_DIFFICULTY);   
  288.                 mX = savedState.getDouble(KEY_X);   
  289.                 mY = savedState.getDouble(KEY_Y);   
  290.                 mDX = savedState.getDouble(KEY_DX);   
  291.                 mDY = savedState.getDouble(KEY_DY);   
  292.                 mHeading = savedState.getDouble(KEY_HEADING);   
  293.   
  294.                 mLanderWidth = savedState.getInt(KEY_LANDER_WIDTH);   
  295.                 mLanderHeight = savedState.getInt(KEY_LANDER_HEIGHT);   
  296.                 mGoalX = savedState.getInt(KEY_GOAL_X);   
  297.                 mGoalSpeed = savedState.getInt(KEY_GOAL_SPEED);   
  298.                 mGoalAngle = savedState.getInt(KEY_GOAL_ANGLE);   
  299.                 mGoalWidth = savedState.getInt(KEY_GOAL_WIDTH);   
  300.                 mWinsInARow = savedState.getInt(KEY_WINS);   
  301.                 mFuel = savedState.getDouble(KEY_FUEL);   
  302.             }   
  303.         }   
  304.   
  305.         @Override  
  306.         public void run() {   
  307.             while (mRun) {   
  308.                 Canvas c = null;   
  309.                 try {   
  310.                     c = mSurfaceHolder.lockCanvas(null);   
  311.                     synchronized (mSurfaceHolder) {   
  312.                         if (mMode == STATE_RUNNING) updatePhysics();   
  313.                         doDraw(c);   
  314.                     }   
  315.                 } finally {   
  316.                     // do this in a finally so that if an exception is thrown   
  317.                     // during the above, we don't leave the Surface in an   
  318.                     // inconsistent state   
  319.                     if (c != null) {   
  320.                         mSurfaceHolder.unlockCanvasAndPost(c);   
  321.                     }   
  322.                 }   
  323.             }   
  324.         }   
  325.   
  326.         /**  
  327.          * Dump game state to the provided Bundle. Typically called when the  
  328.          * Activity is being suspended.  
  329.          *   
  330.          * @return Bundle with this view's state  
  331.          */  
  332.         public Bundle saveState(Bundle map) {   
  333.             synchronized (mSurfaceHolder) {   
  334.                 if (map != null) {   
  335.                     map.putInt(KEY_DIFFICULTY, Integer.valueOf(mDifficulty));   
  336.                     map.putDouble(KEY_X, Double.valueOf(mX));   
  337.                     map.putDouble(KEY_Y, Double.valueOf(mY));   
  338.                     map.putDouble(KEY_DX, Double.valueOf(mDX));   
  339.                     map.putDouble(KEY_DY, Double.valueOf(mDY));   
  340.                     map.putDouble(KEY_HEADING, Double.valueOf(mHeading));   
  341.                     map.putInt(KEY_LANDER_WIDTH, Integer.valueOf(mLanderWidth));   
  342.                     map.putInt(KEY_LANDER_HEIGHT, Integer   
  343.                             .valueOf(mLanderHeight));   
  344.                     map.putInt(KEY_GOAL_X, Integer.valueOf(mGoalX));   
  345.                     map.putInt(KEY_GOAL_SPEED, Integer.valueOf(mGoalSpeed));   
  346.                     map.putInt(KEY_GOAL_ANGLE, Integer.valueOf(mGoalAngle));   
  347.                     map.putInt(KEY_GOAL_WIDTH, Integer.valueOf(mGoalWidth));   
  348.                     map.putInt(KEY_WINS, Integer.valueOf(mWinsInARow));   
  349.                     map.putDouble(KEY_FUEL, Double.valueOf(mFuel));   
  350.                 }   
  351.             }   
  352.             return map;   
  353.         }   
  354.   
  355.         /**  
  356.          * Sets the current difficulty.  
  357.          *   
  358.          * @param difficulty  
  359.          */  
  360.         public void setDifficulty(int difficulty) {   
  361.             synchronized (mSurfaceHolder) {   
  362.                 mDifficulty = difficulty;   
  363.             }   
  364.         }   
  365.   
  366.         /**  
  367.          * Sets if the engine is currently firing.  
  368.          */  
  369.         public void setFiring(boolean firing) {   
  370.             synchronized (mSurfaceHolder) {   
  371.                 mEngineFiring = firing;   
  372.             }   
  373.         }   
  374.   
  375.         /**  
  376.          * Used to signal the thread whether it should be running or not.  
  377.          * Passing true allows the thread to run; passing false will shut it  
  378.          * down if it's already running. Calling start() after this was most  
  379.          * recently called with false will result in an immediate shutdown.  
  380.          *   
  381.          * @param b true to run, false to shut down  
  382.          */  
  383.         public void setRunning(boolean b) {   
  384.             mRun = b;   
  385.         }   
  386.   
  387.         /**  
  388.          * Sets the game mode. That is, whether we are running, paused, in the  
  389.          * failure state, in the victory state, etc.  
  390.          *   
  391.          * @see #setState(int, CharSequence)  
  392.          * @param mode one of the STATE_* constants  
  393.          */  
  394.         public void setState(int mode) {   
  395.             synchronized (mSurfaceHolder) {   
  396.                 setState(mode, null);   
  397.             }   
  398.         }   
  399.   
  400.         /**  
  401.          * Sets the game mode. That is, whether we are running, paused, in the  
  402.          * failure state, in the victory state, etc.  
  403.          *   
  404.          * @param mode one of the STATE_* constants  
  405.          * @param message string to add to screen or null  
  406.          */  
  407.         public void setState(int mode, CharSequence message) {   
  408.             /*  
  409.              * This method optionally can cause a text message to be displayed  
  410.              * to the user when the mode changes. Since the View that actually  
  411.              * renders that text is part of the main View hierarchy and not  
  412.              * owned by this thread, we can't touch the state of that View.  
  413.              * Instead we use a Message + Handler to relay commands to the main  
  414.              * thread, which updates the user-text View.  
  415.              */  
  416.             synchronized (mSurfaceHolder) {   
  417.                 mMode = mode;   
  418.   
  419.                 if (mMode == STATE_RUNNING) {   
  420.                     Message msg = mHandler.obtainMessage();   
  421.                     Bundle b = new Bundle();   
  422.                     b.putString("text", "");   
  423.                     b.putInt("viz", View.INVISIBLE);   
  424.                     msg.setData(b);   
  425.                     mHandler.sendMessage(msg);   
  426.                 } else {   
  427.                     mRotating = 0;   
  428.                     mEngineFiring = false;   
  429.                     Resources res = mContext.getResources();   
  430.                     CharSequence str = "";   
  431.                     if (mMode == STATE_READY)   
  432.                         str = res.getText(R.string.mode_ready);   
  433.                     else if (mMode == STATE_PAUSE)   
  434.                         str = res.getText(R.string.mode_pause);   
  435.                     else if (mMode == STATE_LOSE)   
  436.                         str = res.getText(R.string.mode_lose);   
  437.                     else if (mMode == STATE_WIN)   
  438.                         str = res.getString(R.string.mode_win_prefix)   
  439.                                 + mWinsInARow + " "  
  440.                                 + res.getString(R.string.mode_win_suffix);   
  441.   
  442.                     if (message != null) {   
  443.                         str = message + "\n" + str;   
  444.                     }   
  445.   
  446.                     if (mMode == STATE_LOSE) mWinsInARow = 0;   
  447.   
  448.                     Message msg = mHandler.obtainMessage();   
  449.                     Bundle b = new Bundle();   
  450.                     b.putString("text", str.toString());   
  451.                     b.putInt("viz", View.VISIBLE);   
  452.                     msg.setData(b);   
  453.                     mHandler.sendMessage(msg);   
  454.                 }   
  455.             }   
  456.         }   
  457.   
  458.         /* Callback invoked when the surface dimensions change. */  
  459.         public void setSurfaceSize(int width, int height) {   
  460.             // synchronized to make sure these all change atomically   
  461.             synchronized (mSurfaceHolder) {   
  462.                 mCanvasWidth = width;   
  463.                 mCanvasHeight = height;   
  464.   
  465.                 // don't forget to resize the background image   
  466.                 mBackgroundImage = mBackgroundImage.createScaledBitmap(   
  467.                         mBackgroundImage, width, height, true);   
  468.             }   
  469.         }   
  470.   
  471.         /**  
  472.          * Resumes from a pause.  
  473.          */  
  474.         public void unpause() {   
  475.             // Move the real time clock up to now   
  476.             synchronized (mSurfaceHolder) {   
  477.                 mLastTime = System.currentTimeMillis() + 100;   
  478.             }   
  479.             setState(STATE_RUNNING);   
  480.         }   
  481.   
  482.         /**  
  483.          * Handles a key-down event.  
  484.          *   
  485.          * @param keyCode the key that was pressed  
  486.          * @param msg the original event object  
  487.          * @return true  
  488.          */  
  489.         boolean doKeyDown(int keyCode, KeyEvent msg) {   
  490.             synchronized (mSurfaceHolder) {   
  491.                 boolean okStart = false;   
  492.                 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;   
  493.                 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;   
  494.                 if (keyCode == KeyEvent.KEYCODE_S) okStart = true;   
  495.   
  496.                 boolean center = (keyCode == KeyEvent.KEYCODE_DPAD_UP);   
  497.   
  498.                 if (okStart   
  499.                         && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {   
  500.                     // ready-to-start -> start   
  501.                     doStart();   
  502.                     return true;   
  503.                 } else if (mMode == STATE_PAUSE && okStart) {   
  504.                     // paused -> running   
  505.                     unpause();   
  506.                     return true;   
  507.                 } else if (mMode == STATE_RUNNING) {   
  508.                     // center/space -> fire   
  509.                     if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER   
  510.                             || keyCode == KeyEvent.KEYCODE_SPACE) {   
  511.                         setFiring(true);   
  512.                         return true;   
  513.                         // left/q -> left   
  514.                     } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT   
  515.                             || keyCode == KeyEvent.KEYCODE_Q) {   
  516.                         mRotating = -1;   
  517.                         return true;   
  518.                         // right/w -> right   
  519.                     } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT   
  520.                             || keyCode == KeyEvent.KEYCODE_W) {   
  521.                         mRotating = 1;   
  522.                         return true;   
  523.                         // up -> pause   
  524.                     } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {   
  525.                         pause();   
  526.                         return true;   
  527.                     }   
  528.                 }   
  529.   
  530.                 return false;   
  531.             }   
  532.         }   
  533.   
  534.         /**  
  535.          * Handles a key-up event.  
  536.          *   
  537.          * @param keyCode the key that was pressed  
  538.          * @param msg the original event object  
  539.          * @return true if the key was handled and consumed, or else false  
  540.          */  
  541.         boolean doKeyUp(int keyCode, KeyEvent msg) {   
  542.             boolean handled = false;   
  543.   
  544.             synchronized (mSurfaceHolder) {   
  545.                 if (mMode == STATE_RUNNING) {   
  546.                     if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER   
  547.                             || keyCode == KeyEvent.KEYCODE_SPACE) {   
  548.                         setFiring(false);   
  549.                         handled = true;   
  550.                     } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT   
  551.                             || keyCode == KeyEvent.KEYCODE_Q   
  552.                             || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT   
  553.                             || keyCode == KeyEvent.KEYCODE_W) {   
  554.                         mRotating = 0;   
  555.                         handled = true;   
  556.                     }   
  557.                 }   
  558.             }   
  559.   
  560.             return handled;   
  561.         }   
  562.   
  563.         /**  
  564.          * Draws the ship, fuel/speed bars, and background to the provided  
  565.          * Canvas.  
  566.          */  
  567.         private void doDraw(Canvas canvas) {   
  568.             // Draw the background image. Operations on the Canvas accumulate   
  569.             // so this is like clearing the screen.   
  570.             canvas.drawBitmap(mBackgroundImage, 0, 0, null);   
  571.   
  572.             int yTop = mCanvasHeight - ((int) mY + mLanderHeight / 2);   
  573.             int xLeft = (int) mX - mLanderWidth / 2;   
  574.   
  575.             // Draw the fuel gauge   
  576.             int fuelWidth = (int) (UI_BAR * mFuel / PHYS_FUEL_MAX);   
  577.             mScratchRect.set(4, 4, 4 + fuelWidth, 4 + UI_BAR_HEIGHT);   
  578.             canvas.drawRect(mScratchRect, mLinePaint);   
  579.   
  580.             // Draw the speed gauge, with a two-tone effect   
  581.             double speed = Math.sqrt(mDX * mDX + mDY * mDY);   
  582.             int speedWidth = (int) (UI_BAR * speed / PHYS_SPEED_MAX);   
  583.   
  584.             if (speed <= mGoalSpeed) {   
  585.                 mScratchRect.set(4 + UI_BAR + 4, 4,   
  586.                         4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);   
  587.                 canvas.drawRect(mScratchRect, mLinePaint);   
  588.             } else {   
  589.                 // Draw the bad color in back, with the good color in front of   
  590.                 // it   
  591.                 mScratchRect.set(4 + UI_BAR + 4, 4,   
  592.                         4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);   
  593.                 canvas.drawRect(mScratchRect, mLinePaintBad);   
  594.                 int goalWidth = (UI_BAR * mGoalSpeed / PHYS_SPEED_MAX);   
  595.                 mScratchRect.set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + goalWidth,   
  596.                         4 + UI_BAR_HEIGHT);   
  597.                 canvas.drawRect(mScratchRect, mLinePaint);   
  598.             }   
  599.   
  600.             // Draw the landing pad   
  601.             canvas.drawLine(mGoalX, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,   
  602.                     mGoalX + mGoalWidth, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,   
  603.                     mLinePaint);   
  604.   
  605.   
  606.             // Draw the ship with its current rotation   
  607.             canvas.save();   
  608.             canvas.rotate((float) mHeading, (float) mX, mCanvasHeight   
  609.                     - (float) mY);   
  610.             if (mMode == STATE_LOSE) {   
  611.                 mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop   
  612.                         + mLanderHeight);   
  613.                 mCrashedImage.draw(canvas);   
  614.             } else if (mEngineFiring) {   
  615.                 mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop   
  616.                         + mLanderHeight);   
  617.                 mFiringImage.draw(canvas);   
  618.             } else {   
  619.                 mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop   
  620.                         + mLanderHeight);   
  621.                 mLanderImage.draw(canvas);   
  622.             }   
  623.             canvas.restore();   
  624.         }   
  625.   
  626.         /**  
  627.          * Figures the lander state (x, y, fuel, ...) based on the passage of  
  628.          * realtime. Does not invalidate(). Called at the start of draw().  
  629.          * Detects the end-of-game and sets the UI to the next state.  
  630.          */  
  631.         private void updatePhysics() {   
  632.             long now = System.currentTimeMillis();   
  633.   
  634.             // Do nothing if mLastTime is in the future.   
  635.             // This allows the game-start to delay the start of the physics   
  636.             // by 100ms or whatever.   
  637.             if (mLastTime > now) return;   
  638.   
  639.             double elapsed = (now - mLastTime) / 1000.0;   
  640.   
  641.             // mRotating -- update heading   
  642.             if (mRotating != 0) {   
  643.                 mHeading += mRotating * (PHYS_SLEW_SEC * elapsed);   
  644.   
  645.                 // Bring things back into the range 0..360   
  646.                 if (mHeading < 0)   
  647.                     mHeading += 360;   
  648.                 else if (mHeading >= 360) mHeading -= 360;   
  649.             }   
  650.   
  651.             // Base accelerations -- 0 for x, gravity for y   
  652.             double ddx = 0.0;   
  653.             double ddy = -PHYS_DOWN_ACCEL_SEC * elapsed;   
  654.   
  655.             if (mEngineFiring) {   
  656.                 // taking 0 as up, 90 as to the right   
  657.                 // cos(deg) is ddy component, sin(deg) is ddx component   
  658.                 double elapsedFiring = elapsed;   
  659.                 double fuelUsed = elapsedFiring * PHYS_FUEL_SEC;   
  660.   
  661.                 // tricky case where we run out of fuel partway through the   
  662.                 // elapsed   
  663.                 if (fuelUsed > mFuel) {   
  664.                     elapsedFiring = mFuel / fuelUsed * elapsed;   
  665.                     fuelUsed = mFuel;   
  666.   
  667.                     // Oddball case where we adjust the "control" from here   
  668.                     mEngineFiring = false;   
  669.                 }   
  670.   
  671.                 mFuel -= fuelUsed;   
  672.   
  673.                 // have this much acceleration from the engine   
  674.                 double accel = PHYS_FIRE_ACCEL_SEC * elapsedFiring;   
  675.   
  676.                 double radians = 2 * Math.PI * mHeading / 360;   
  677.                 ddx = Math.sin(radians) * accel;   
  678.                 ddy += Math.cos(radians) * accel;   
  679.             }   
  680.   
  681.             double dxOld = mDX;   
  682.             double dyOld = mDY;   
  683.   
  684.             // figure speeds for the end of the period   
  685.             mDX += ddx;   
  686.             mDY += ddy;   
  687.   
  688.             // figure position based on average speed during the period   
  689.             mX += elapsed * (mDX + dxOld) / 2;   
  690.             mY += elapsed * (mDY + dyOld) / 2;   
  691.   
  692.             mLastTime = now;   
  693.   
  694.             // Evaluate if we have landed ... stop the game   
  695.             double yLowerBound = TARGET_PAD_HEIGHT + mLanderHeight / 2  
  696.                     - TARGET_BOTTOM_PADDING;   
  697.             if (mY <= yLowerBound) {   
  698.                 mY = yLowerBound;   
  699.   
  700.                 int result = STATE_LOSE;   
  701.                 CharSequence message = "";   
  702.                 Resources res = mContext.getResources();   
  703.                 double speed = Math.sqrt(mDX * mDX + mDY * mDY);   
  704.                 boolean onGoal = (mGoalX <= mX - mLanderWidth / 2 && mX   
  705.                         + mLanderWidth / 2 <= mGoalX + mGoalWidth);   
  706.   
  707.                 // "Hyperspace" win -- upside down, going fast,   
  708.                 // puts you back at the top.   
  709.                 if (onGoal && Math.abs(mHeading - 180) < mGoalAngle   
  710.                         && speed > PHYS_SPEED_HYPERSPACE) {   
  711.                     result = STATE_WIN;   
  712.                     mWinsInARow++;   
  713.                     doStart();   
  714.   
  715.                     return;   
  716.                     // Oddball case: this case does a return, all other cases   
  717.                     // fall through to setMode() below.   
  718.                 } else if (!onGoal) {   
  719.                     message = res.getText(R.string.message_off_pad);   
  720.                 } else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle)) {   
  721.                     message = res.getText(R.string.message_bad_angle);   
  722.                 } else if (speed > mGoalSpeed) {   
  723.                     message = res.getText(R.string.message_too_fast);   
  724.                 } else {   
  725.                     result = STATE_WIN;   
  726.                     mWinsInARow++;   
  727.                 }   
  728.   
  729.                 setState(result, message);   
  730.             }   
  731.         }   
  732.     }   
  733.   
  734.     /** Handle to the application context, used to e.g. fetch Drawables. */  
  735.     private Context mContext;   
  736.   
  737.     /** Pointer to the text view to display "Paused.." etc. */  
  738.     private TextView mStatusText;   
  739.   
  740.     /** The thread that actually draws the animation */  
  741.     private LunarThread thread;   
  742.   
  743.     public LunarView(Context context, AttributeSet attrs) {   
  744.         super(context, attrs);   
  745.   
  746.         // register our interest in hearing about changes to our surface   
  747.         SurfaceHolder holder = getHolder();   
  748.         holder.addCallback(this);   
  749.   
  750.         // create thread only; it's started in surfaceCreated()   
  751.         thread = new LunarThread(holder, context, new Handler() {   
  752.             @Override  
  753.             public void handleMessage(Message m) {   
  754.                 mStatusText.setVisibility(m.getData().getInt("viz"));   
  755.                 mStatusText.setText(m.getData().getString("text"));   
  756.             }   
  757.         });   
  758.   
  759.         setFocusable(true); // make sure we get key events   
  760.     }   
  761.   
  762.     /**  
  763.      * Fetches the animation thread corresponding to this LunarView.  
  764.      *   
  765.      * @return the animation thread  
  766.      */  
  767.     public LunarThread getThread() {   
  768.         return thread;   
  769.     }   
  770.   
  771.     /**  
  772.      * Standard override to get key-press events.  
  773.      */  
  774.     @Override  
  775.     public boolean onKeyDown(int keyCode, KeyEvent msg) {   
  776.         return thread.doKeyDown(keyCode, msg);   
  777.     }   
  778.   
  779.     /**  
  780.      * Standard override for key-up. We actually care about these, so we can  
  781.      * turn off the engine or stop rotating.  
  782.      */  
  783.     @Override  
  784.     public boolean onKeyUp(int keyCode, KeyEvent msg) {   
  785.         return thread.doKeyUp(keyCode, msg);   
  786.     }   
  787.   
  788.     /**  
  789.      * Standard window-focus override. Notice focus lost so we can pause on  
  790.      * focus lost. e.g. user switches to take a call.  
  791.      */  
  792.     @Override  
  793.     public void onWindowFocusChanged(boolean hasWindowFocus) {   
  794.         if (!hasWindowFocus) thread.pause();   
  795.     }   
  796.   
  797.     /**  
  798.      * Installs a pointer to the text view used for messages.  
  799.      */  
  800.     public void setTextView(TextView textView) {   
  801.         mStatusText = textView;   
  802.     }   
  803.   
  804.     /* Callback invoked when the surface dimensions change. */  
  805.     public void surfaceChanged(SurfaceHolder holder, int format, int width,   
  806.             int height) {   
  807.         thread.setSurfaceSize(width, height);   
  808.     }   
  809.   
  810.     /*  
  811.      * Callback invoked when the Surface has been created and is ready to be  
  812.      * used.  
  813.      */  
  814.     public void surfaceCreated(SurfaceHolder holder) {   
  815.         // start the thread here so that we don't busy-wait in run()   
  816.         // waiting for the surface to be created   
  817.         thread.setRunning(true);   
  818.         thread.start();   
  819.     }   
  820.   
  821.     /*  
  822.      * Callback invoked when the Surface has been destroyed and must no longer  
  823.      * be touched. WARNING: after this method returns, the Surface/Canvas must  
  824.      * never be touched again!  
  825.      */  
  826.     public void surfaceDestroyed(SurfaceHolder holder) {   
  827.         // we have to tell thread to shut down & wait for it to finish, or else   
  828.         // it might touch the Surface after we return and explode   
  829.         boolean retry = true;   
  830.         thread.setRunning(false);   
  831.         while (retry) {   
  832.             try {   
  833.                 thread.join();   
  834.                 retry = false;   
  835.             } catch (InterruptedException e) {   
  836.             }   
  837.         }   
  838.     }   
  839. }  

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved