Android游戏开发之八:SurfaceView类的应用实例

移动开发 Android 游戏开发
SurfaceView可以说是Android游戏开发中必须学会和掌握的。本文就通过系统自带的例子来让大家看看SurfaceView的用法。Android SDK的Sample有有一个LunarLander游戏的例子,下面给出了其中LunarView的实现代码。

大家可以把此工程导入到Eclipse,然后编译运行,试玩一下。最后对应着看代码,这样更容易理解。下面上代码:

  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(25502550);    
  197.    
  198.             mLinePaintBad = new Paint();    
  199.             mLinePaintBad.setAntiAlias(true);    
  200.             mLinePaintBad.setARGB(2551201800);    
  201.    
  202.             mScratchRect = new RectF(0000);    
  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, 00null);    
  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(444 + 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 + 44,    
  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 + 44,    
  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 + 444 + 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. }   

 

责任编辑:闫佳明 来源: jizhuomi
相关推荐

2013-05-20 17:04:09

2013-05-21 09:56:15

2013-05-20 17:48:20

2013-05-20 17:13:17

Android游戏开发CanvasPaint

2013-05-21 11:26:49

Android游戏开发Sensor感应

2013-05-20 15:42:22

2013-05-20 17:21:34

2013-05-21 14:10:11

Android游戏开发SoundPool类同时多音效

2011-09-08 13:11:07

Android Wid实例

2009-04-02 15:58:12

AndroidEclipseSqlite

2011-04-12 08:40:23

IMFAndroid

2021-08-25 07:43:17

AndroidSurfaceViewTextureView

2013-05-21 10:42:48

Android游戏开发Bitmap位图旋转

2013-08-01 14:03:49

JavaScript

2013-06-07 13:20:16

Android开发开源游戏引擎游戏开发

2014-07-17 11:10:19

Android开源游戏引擎

2011-05-31 15:45:38

Android 游戏引擎 开源

2013-05-21 14:22:29

Android游戏开发捕获屏幕双击事件

2009-12-16 10:41:47

Android日程表

2011-08-03 16:01:24

iPhone应用开发 自动登陆
点赞
收藏

51CTO技术栈公众号