Android遊戲——貪喫蛇開發實錄(改進後的源碼和詳解)

        本人大學剛畢業,進入公司後做的第一個Android入門小遊戲——貪喫蛇。從APP的構思設計到完成,差不多經歷了一個星期的時間。現在回想起來感覺挺有收穫,所以決定把源碼和理解分享出來,以此和廣大的程序員朋友們交流交流經驗。

        該遊戲實現的思路和源碼參考了Google自帶的Snake的例子,其中修改了一些個人認爲還不夠完善的地方,加入了一些新的功能,比如屏幕上的方向操作盤,暫停按鈕,開始按鈕,退出按鈕。另外,爲了稍微增加些用戶體驗,除了遊戲的主界面,本人自己新增了5個界面,分別是登陸界面,菜單界面,背景音樂設置界面,難度設置界面,還有個關於遊戲的介紹界面。個人覺得在新手階段,參考現成的思路和實現方式是難以避免的。重要的是我們需要有自己的理解,讀懂代碼之後,需要思考代碼背後的實現邏輯,形成自己的思維。這樣在下次開發工作時,就不用參考別人自己也能湧現出解決的思路。

        我覺得經過自己的構思和實踐,做出一個可操作有界面的小作品還是挺有成就感的,在探索和思考的過程中時間過的很快,這段時間基本都是自覺的加班到晚上9點以後。好了,下面切入正題,我考慮了下講述的順序,決定就以進入軟件後的界面順序來把。

        由於篇幅的關係,佈局的XML文件就不發了,而且我把導包的語句也省略了,反正像AS,eclipse這些工具都是可以智能導包的。完整的源碼和配置文件下載地址http://download.csdn.net/detail/kuaiguixs/9588233。那麼,首先是登陸界面,找了些網上的資源當背景。佈局還是比較簡單的。下圖中,左圖爲效果圖,右圖爲邏輯實現的流程圖。

// MainActivity.java
package con.example.wang.game;
public class MainActivity extends Activity implements OnClickListener{
    Button button;
    EditText edit1,edit2;
    CheckBox checkbox;
    ProgressBar bar;
    SharedPreferences pref;
    SharedPreferences.Editor editor;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button=(Button) findViewById(R.id.login_button);
        edit1=(EditText) findViewById(R.id.input1);
        edit2=(EditText) findViewById(R.id.input2);
        checkbox=(CheckBox) findViewById(R.id.remember_button);
        bar=(ProgressBar) findViewById(R.id.progress);
        pref= PreferenceManager.getDefaultSharedPreferences(this);
        boolean isRemember=pref.getBoolean("rem",false);     //獲取代表是否保存密碼的變量值,這裏初值設爲false

        if(isRemember) {
            //如果記住密碼,則將賬號和密碼自動填充到文本框中
            String account=pref.getString("account","");
            String password=pref.getString("password","");
            edit1.setText(account);
            edit2.setText(password);
            checkbox.setChecked(true);
        }
        button.setOnClickListener(this);
    }
    @Override
    public void onClick(View v){
        new Thread(new Runnable(){      //開啓線程運行進度條,減少主線程的壓力,這裏不用子線程也影響不大
            @Override
            public void run() {
                for (int i = 0; i < 25; i++) {
                    int progress = bar.getProgress();
                    progress = progress + 10;
                    bar.setProgress(progress);
                }
            }
        }).start();

        String account=edit1.getText().toString();
        String password=edit2.getText().toString();
        if(account.equals("admin") && password.equals("123456")) {
            editor = pref.edit();    //這個方法用於向SharedPreferences文件中寫數據
            if(checkbox.isChecked()) {
                editor.putBoolean("rem",true);
                editor.putString("account",account);
                editor.putString("password",password);
            }
            else {
                editor.clear();
            }
            editor.commit();    //這個方法必須要有,不然數據不會被保存。生效後,就可以從該文件中讀取數據。
            Intent intent=new Intent(MainActivity.this,SecondActivity.class);
            startActivity(intent);
        }
        else{    //如果用戶名或密碼不正確,這裏會彈出一個提示框
            Toast.makeText(MainActivity.this,"賬號或用戶名錯誤",Toast.LENGTH_SHORT).show();
        }
    }
}

        這個邏輯還算比較簡單,實現了記住密碼的功能,這裏的數據存儲使用的是SharedPreferences。點擊登陸後,會進入一個菜單界面,這裏設置幾個四個按鈕,分別做好監聽就可以了,然後用Intent在活動間跳轉就好了。效果圖也分享一下。



// SecondActivity.java
package com.example.wang.game;
public class SecondActivity extends Activity implements OnClickListener{

    ImageButton button1,button2,button3,button4;
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        button1=(ImageButton) findViewById(R.id.button_start);
        button2=(ImageButton) findViewById(R.id.button_difficulty);
        button3=(ImageButton) findViewById(R.id.button_music);
        button4=(ImageButton) findViewById(R.id.button_about);
        button4.setOnClickListener(this);
        button3.setOnClickListener(this);
        button2.setOnClickListener(this);
        button1.setOnClickListener(this);
    }
    @Override
    public void onClick(View v){
        switch(v.getId()) {        //看下Intent的用法,還是挺方便的,這裏用的都是顯式的方法
            case R.id.button_about:
                Intent intent1 = new Intent(SecondActivity.this, AboutActivity.class);
                startActivity(intent1);
                break;
            case R.id.button_music:
                Intent intent2 = new Intent(SecondActivity.this, MusicActivity.class);
                startActivity(intent2);
                break;
            case R.id.button_difficulty:
                Intent intent3 = new Intent(SecondActivity.this, DifficultyActivity.class);
                startActivity(intent3);
                break;
            case R.id.button_start:
                Intent intent4 = new Intent(SecondActivity.this, GameActivity.class);
                startActivity(intent4);
                break;
            default:
                break;
        }
    }
}



        下面先講難度設置界面把,這個和背景音樂開關其實差不多,所以以此爲例,背景音樂開關界面就不囉嗦了。這裏也是用的SharedPreferences存儲數據。這裏佈局文件裏把三個RadioButton放入RadioGroup,實現單選的效果。給三個按鈕設置監聽,觸發事件後分別返回對應的三個變量,這三個變量控制的是貪喫蛇運行的速度。參考下流程圖更好理解。


//  DifficultyActivity.java
package com.example.wang.game;
public class DifficultyActivity extends Activity implements OnClickListener{
    private SharedPreferences saved;
    private SharedPreferences.Editor editor;

    RadioButton button_jiandan,button_yiban,button_kunnan;
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_difficulty);
        saved = PreferenceManager.getDefaultSharedPreferences(this);
        int level = saved.getInt("nandu",500);

        button_jiandan = (RadioButton) findViewById(R.id.button_difficulty1);
        button_yiban = (RadioButton) findViewById(R.id.button_difficulty2);
        button_kunnan = (RadioButton) findViewById(R.id.button_difficulty3);
        button_jiandan.setOnClickListener(this);
        button_yiban.setOnClickListener(this);
        button_kunnan.setOnClickListener(this);
    }
    @Override
    public void onClick(View v){
        editor=saved.edit();
        switch(v.getId()){
            case R.id.button_difficulty1:
                if(button_jiandan.isChecked()){
                    editor.putInt("nandu",500);
                }
                break;
            case R.id.button_difficulty2:
                if(button_yiban.isChecked()){
                    editor.putInt("nandu",200);
                }
                break;
            case R.id.button_difficulty3:
                if(button_kunnan.isChecked()){
                    editor.putInt("nandu",100);
                }
                break;
        }
        editor.commit();
    }
}

        其它的兩個輔助界面比較簡單,背景音樂開關界面也是通過SharedPreferences文件存儲一個boolean的值,true的話就播放音樂,false就不播放。關於遊戲的介紹界面就加一些文字。上述這些都是輔助,下面是遊戲的主體部分。

        遊戲界面的設計思路就是將手機屏幕分爲多行多列的像素塊,以像素塊爲最小單位,確定各點的座標。這裏每個像素塊的大小設置爲32像素。我的手機模擬器的屏幕分辨率爲768*1280,由公式可算出,我的遊戲界面x軸上座標最大爲24,y軸上座標最大爲35。座標完成後,這裏會使用三種顏色不同的圖片來填充像素塊,這裏就叫磚塊把。

        根據java面向對象的邏輯,需要給各塊內容分類,蛇,蘋果,邊界的牆都是必不可少的元素。視圖的初始化也是圍繞着這三個元素展開的。其實這裏蛇,蘋果和邊界牆就是由不同顏色的磚塊表示出來的。該部分內容包含三個java文件,首先是磚塊的初始化。

//  TileView.java
package com.example.wang.game;
public class TileView extends View {
    public static int mTileSize =32;
    public static int mXTileCount;  //地圖上所能容納的格數
    public static int mYTileCount;
    public static int mXOffset;     //偏移量
    public static int mYOffset;
    Bitmap[] mTileArray;            //放置圖片的數組
    int[][] mTileGrid;              //存放各座標對應的圖片

    public TileView(Context context, AttributeSet attrs,int defStyle){
        super(context,attrs,defStyle);
    }
    public TileView(Context context, AttributeSet attrs){
        super(context,attrs);
    }
    public TileView(Context context){
        super(context);
    }
    //加載三幅小圖片
    public void loadTile(int key, Drawable tile) {
        Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        tile.setBounds(0, 0, mTileSize, mTileSize);
        tile.draw(canvas);
        mTileArray[key] = bitmap;
    }
    //給地圖數組賦值
    public void setTile(int tileindex, int x, int y) {
        mTileGrid[x][y] = tileindex;
    }
    public void resetTiles(int tilecount) {
        mTileArray = new Bitmap[tilecount];
    }
    //我的遊戲界面不是佔滿整個屏幕,所以地圖遍歷的時候,y不是從0開始
    public void clearTiles() {
        for (int x = 0; x < mXTileCount; x++) {
            for (int y = 2; y < mYTileCount-8; y++) {
                setTile(0, x, y);
            }
        }
    }
    //計算當前屏幕在X,Y軸上分別所能容納的最大磚塊數量
    //這裏輸出</span><span style="font-size:14px;">“mXTileCount"和”mYTileCount"的值後面會用到
    @Override
    public void onSizeChanged(int w, int h, int oldw, int oldh){
        //地圖數組初始化
        mXTileCount = (int) Math.floor(w / mTileSize);
        mYTileCount = (int) Math.floor(h / mTileSize);
//        System.out.println("-------"+mXTileCount+"----------");
//        System.out.println("-------"+mYTileCount+"----------");
        //可能屏幕的長寬不能整除,所以夠分成一格的分成一格, 剩下不夠一格的分成兩份,左邊一份,右邊一份
        mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
        mYOffset = ((h - (mTileSize * mYTileCount)) / 2);
//        System.out.println("-------"+mXOffset+"----------");
//        System.out.println("-------"+mYOffset+"----------");
        mTileGrid = new int[mXTileCount][mYTileCount];
        clearTiles();
    }
    @Override
    public void onDraw(Canvas canvas){
        super.onDraw(canvas);
    }
}

       

        其實上述這段程序就是實現了幾個方法,loadTile()用於加載圖片,setTile()和resetTile()是把圖片與座標聯繫起來,而onSizedChanged()是把手機屏幕像素塊化。這些方法都將爲以下這個類服務。爲了便於利用這些方法,以下這個類繼承自TileView。

        由於我加了一些按鈕,所以遊戲界面生成時沒有佔滿整個屏幕,所以在設置座標和遍歷地圖時與源碼的數據相差較多。

//  SnakeView.java
package com.example.wang.game;
public class SnakeView extends TileView{

    static int mMoveDelay = 500;
    private long mLastMove;

    private static final int RED_STAR = 1;
    private static final int YELLOW_STAR = 2;
    private static final int GREEN_STAR = 3;

    private static final int UP = 1;
    private static final int DOWN = 2;
    private static final int RIGHT = 3;
    private static final int LEFT = 4;
    static int mDirection = RIGHT;
    static int mNextDirection = RIGHT;
    // 這裏把遊戲界面分爲5種狀態,便於邏輯實現
    public static final int PAUSE = 0;
    public static final int READY = 1;
    public static final int RUNNING = 2;
    public static final int LOSE = 3;
    public static final int QUIT = 4;
    public int mMode = READY;
    public int newMode;

    private TextView mStatusText;    // 用於每個狀態下的文字提醒
    public long mScore = 0;

    private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>(); // 存儲蛇的所有座標的數組
    private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();  // 存儲蘋果的所有座標的數組

    private static final Random RNG = new Random();    //用於生成蘋果座標的隨機數
//    private static final String TAG = "SnakeView";

    //開啓線程,不斷調用更新和重繪。這裏利用了Handler類來實現異步消息處理機制
    MyHandler handler=new MyHandler();
    class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            SnakeView.this.update();         //不斷調用update()方法
            SnakeView.this.invalidate();    //請求重繪,不斷調用ondraw()方法
        }
        //調用sleep後,在一段時間後再sendmessage進行UI更新
        public void sleep(int delayMillis) {
            this.removeMessages(0);         //清空消息隊列
            sendMessageDelayed(obtainMessage(0), delayMillis);
        }
    }
    //這是三個構造方法,別忘了加上下面這個初始化方法
    public SnakeView(Context context, AttributeSet attrs, int defStyle){
        super(context,attrs,defStyle);
        initNewGame();
    }
    public SnakeView(Context context, AttributeSet attrs){
        super(context,attrs);
        setFocusable(true);
        initNewGame();
    }
    public SnakeView(Context context){
        super(context);
    }
    //添加蘋果的方法,最後將生成的蘋果座標存儲在上面定義的數組中
    private void addRandomApple() {
        Coordinate newCoord = null;
        boolean found = false;
        while (!found) {
            // 這裏設定了蘋果座標能隨機生成的範圍,並生成隨機座標。這裏Google源碼中是直接使用變量
            // mXTileCount和mYTileCount,我編譯時會報錯,因爲隨機數不能生成負數,而直接使用這兩個變量程序不能
            // 識別這個變量減去一個數後是否會是負數,所以我把TileView裏輸出的確切值放了進去
            int newX = 1 + RNG.nextInt(24-2);
            int newY = 3 + RNG.nextInt(35-12);
            newCoord = new Coordinate(newX, newY);

            boolean collision = false;
            int snakelength = mSnakeTrail.size();
            //遍歷snake, 看新添加的apple是否在snake體內, 如果是,重新生成座標
            for (int index = 0; index < snakelength; index++) {
                if (mSnakeTrail.get(index).equals(newCoord)) {
                    collision = true;
                }
            }
            found = !collision;
        }
//        if (newCoord == null) {
//            Log.e(TAG, "Somehow ended up with a null newCoord!");
//        }
        mAppleList.add(newCoord);
    }

    //繪製邊界的牆
    private void updateWalls() {
        for (int x = 0; x < mXTileCount; x++) {
            setTile(GREEN_STAR, x, 2);
            setTile(GREEN_STAR, x, mYTileCount - 8);
        }
        for (int y = 2; y < mYTileCount - 8; y++) {
            setTile(GREEN_STAR, 0, y);
            setTile(GREEN_STAR, mXTileCount - 1, y);
        }
    }
    //更新蛇的運動軌跡
    private void updateSnake(){
        boolean growSnake = false;
        Coordinate head = mSnakeTrail.get(0);
        Coordinate newHead = new Coordinate(1, 1);

        mDirection = mNextDirection;
        switch (mDirection) {
            case RIGHT: {
                newHead = new Coordinate(head.x + 1, head.y);
                break;
            }
            case LEFT: {
                newHead = new Coordinate(head.x - 1, head.y);
                break;
            }
            case UP: {
                newHead = new Coordinate(head.x, head.y - 1);
                break;
            }
            case DOWN: {
                newHead = new Coordinate(head.x, head.y + 1);
                break;
            }
        }
        //檢測是否撞牆
        if ((newHead.x < 1) || (newHead.y < 3) || (newHead.x > mXTileCount - 2)
                || (newHead.y > mYTileCount - 9)) {
            setMode(LOSE);
            return;
        }
        //檢測蛇頭是否撞到自己
        int snakelength = mSnakeTrail.size();
        for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
            Coordinate c = mSnakeTrail.get(snakeindex);
            if (c.equals(newHead)) {
                setMode(LOSE);
                return;
            }
        }
        //檢測蛇是否喫到蘋果
        int applecount = mAppleList.size();
        for (int appleindex = 0; appleindex < applecount; appleindex++) {
            Coordinate c = mAppleList.get(appleindex);
            if (c.equals(newHead)) {
                mAppleList.remove(c);
                addRandomApple();
                mScore++;
                mMoveDelay *= 0.95;    //蛇每遲到一個蘋果,延時就會減少,蛇的速度就會加快
                growSnake = true;
            }
        }
        mSnakeTrail.add(0,newHead);
        if(!growSnake) {
            mSnakeTrail.remove(mSnakeTrail.size() - 1);
        }
        //蛇頭和蛇身分別設置不同的圖片
        int index=0;
        for(Coordinate c:mSnakeTrail) {
            if(index == 0) {
                setTile(RED_STAR, c.x, c.y);
            } else {
                setTile(YELLOW_STAR,c.x,c.y);
            }
            index++;
        }
    }
    給蘋果加載對應的圖片
    private void updateApples() {
        for (Coordinate c : mAppleList) {
            setTile(YELLOW_STAR, c.x, c.y);
        }
    }
    // 該方法很重要,用於更新蛇,蘋果和牆的座標
    // 這裏設置了更新的時間間隔,我發現不加這個延時的話蛇運動時容易出現一下跳很多格的情況
     public void update(){
        if(mMode == RUNNING) {
            long now = System.currentTimeMillis();
            if (now - mLastMove > mMoveDelay) {
                clearTiles();
                updateWalls();
                updateSnake();
                updateApples();
                mLastMove = now;
            }
            handler.sleep(mMoveDelay);
        }
    }

    //圖像初始化,引入圖片資源
    private void initSnakeView() {
        setFocusable(true);     //添加焦點
        Resources r = this.getContext().getResources();
        //添加幾種不同的tile
        resetTiles(4);
        //從文件中加載圖片
        loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
        loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
        loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
        update();
    }
    // 數據初始化方法,定義蛇的起始座標,運動方向和得分變量。給數組添加座標的時候注意順序,因爲有蛇頭和蛇尾的區別
    public void initNewGame() {
        mSnakeTrail.clear();
        mAppleList.clear();
        //snake初始狀態時的個數和位置,方向
        mSnakeTrail.add(new Coordinate(8, 7));
        mSnakeTrail.add(new Coordinate(7, 7));
        mSnakeTrail.add(new Coordinate(6, 7));
        mSnakeTrail.add(new Coordinate(5, 7));
        mSnakeTrail.add(new Coordinate(4, 7));
        mSnakeTrail.add(new Coordinate(3, 7));
        mDirection = RIGHT;
        mNextDirection = RIGHT;  // 這個變量必須初始化,不然每次遊戲結束重新開始後,蛇初始的方向將不是向右,而是你遊戲結束時蛇的方向,
                                                      // 如果死的時候,蛇的方向向左,那麼再次點擊開始時會無法繪出蛇的圖像
        addRandomApple();
        mScore=0;
    }
    // 根據各個數組中的數據,遍歷地圖設置各點的圖片
    public void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint=new Paint();
        initSnakeView();
        //遍歷地圖繪製界面
        for (int x = 0; x < mXTileCount; x++) {
            for (int y = 0; y < mYTileCount; y++) {
                if (mTileGrid[x][y] > 0) {    // 被加了圖片的點mTileGird是大於0的
                    canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x * mTileSize, mYOffset + y * mTileSize, paint);
                }
            }
        }
    }

    //把蛇和蘋果各點對應的座標利用一個一維數組儲存起來
    private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
        int count = cvec.size();
        int[] rawArray = new int[count * 2];
        for (int index = 0; index < count; index++) {
            Coordinate c = cvec.get(index);
            rawArray[2 * index] = c.x;
            rawArray[2 * index + 1] = c.y;
        }
        return rawArray;
    }

    //將當前所有的遊戲數據全部保存
    public Bundle saveState() {
        Bundle map = new Bundle();
        map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
        map.putInt("mDirection", Integer.valueOf(mDirection));
        map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
        map.putInt("mMoveDelay", Integer.valueOf(mMoveDelay));
        map.putLong("mScore", Long.valueOf(mScore));
        map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
        return map;
    }
    //是coordArrayListToArray()的逆過程,用來讀取數組中的座標數據
    private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
        ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();
        int coordCount = rawArray.length;
        for (int index = 0; index < coordCount; index += 2) {
            Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
            coordArrayList.add(c);
        }
        return coordArrayList;
    }
    //saveState()的逆過程,用於恢復遊戲數據
    public void restoreState(Bundle icicle) {
        setMode(PAUSE);
        mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
        mDirection = icicle.getInt("mDirection");
        mNextDirection = icicle.getInt("mNextDirection");
        mMoveDelay = icicle.getInt("mMoveDelay");
        mScore = icicle.getLong("mScore");
        mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
    }
    // 設置鍵盤監聽,在模擬器中可以使用電腦鍵盤控制蛇的方向
    public boolean onKeyDown(int keyCode, KeyEvent event){
        if(keyCode == KeyEvent.KEYCODE_DPAD_UP){
            if(mDirection != DOWN) {
                mNextDirection = UP;
            }
            return (true);
        }
        if(keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
            if(mDirection != UP) {
                mNextDirection = DOWN;
            }
            return (true);
        }
        if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){
            if(mDirection != LEFT) {
                mNextDirection = RIGHT;
            }
            return (true);
        }
        if(keyCode == KeyEvent.KEYCODE_DPAD_LEFT){
            if(mDirection != RIGHT) {
                mNextDirection = LEFT;
            }
            return (true);
        }
        return super.onKeyDown(keyCode,event);
    }

    public void setTextView(TextView newView) {
        mStatusText = newView;
    }
    // 設置不同狀態下提示文字的顯示內容和可見狀態
    public void setMode(int newMode) {
        this.newMode=newMode;
        int oldMode = mMode;
        mMode = newMode;
        if (newMode == RUNNING & oldMode != RUNNING) {
            mStatusText.setVisibility(View.INVISIBLE);
            update();
            return;
        }
        // 這裏定義了一個空字符串,用於放入各個狀態下的提醒文字
        Resources res = getContext().getResources();
        CharSequence str = "";
        if (newMode == PAUSE) {
            str = res.getText(R.string.mode_pause);
        }
        if (newMode == READY) {
            str = res.getText(R.string.mode_ready);
        }
        if (newMode == LOSE) {
            str = res.getString(R.string.mode_lose_prefix) + mScore
                    + res.getString(R.string.mode_lose_suffix);
        }
        if (newMode == QUIT){
            str = res.getText(R.string.mode_quit);
        }
        mStatusText.setText(str);
        mStatusText.setVisibility(View.VISIBLE);
    }

    //記錄座標位置
    private class Coordinate {
        public int x;
        public int y;
        public Coordinate(int newX, int newY) {
            x = newX;
            y = newY;
        }
        //觸碰檢測,看蛇是否喫到蘋果
        public boolean equals(Coordinate other) {
            if (x == other.x && y == other.y) {
                return true;
            }
            return false;
        }
        // 這個方法沒研究過起什麼作用,我註釋掉對程序的運行沒有影響
        @Override
        public String toString() {
            return "Coordinate: [" + x + "," + y + "]";
        }
    }
}


        以上是自定義View的實現,實現了繪製遊戲界面的主邏輯。將SnakeView作爲一個UI控件插入到該界面的佈局中。下面開啓活動,顯示界面。

//  GameActivity.java
package com.example.wang.game;
public class GameActivity extends Activity implements OnClickListener{
    private SharedPreferences saved;
    private static String ICICLE_KEY = "snake-view";    //  個人認爲這個變量就是一箇中間值,在該類的最後一個方法中傳入該變量,完成操作。
    private SnakeView mSnakeView;
    private ImageButton change_stop,change_start,change_quit;
    private ImageButton mLeft;
    private ImageButton mRight;
    private ImageButton mUp;
    private ImageButton mDown;
    private static final int UP = 1;
    private static final int DOWN = 2;
    private static final int RIGHT = 3;
    private static final int LEFT = 4;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game);
        mSnakeView = (SnakeView) findViewById(R.id.snake);  //給自定義View實例化,把這個佈局當一個UI控件一樣插入進來
        mSnakeView.setTextView((TextView) findViewById(R.id.text_show));

        change_stop = (ImageButton) findViewById(R.id.game_stop);
        change_start = (ImageButton) findViewById(R.id.game_start);
        change_quit = (ImageButton) findViewById(R.id.game_quit);

        mLeft = (ImageButton) findViewById(R.id.left);
        mRight = (ImageButton) findViewById(R.id.right);
        mUp = (ImageButton) findViewById(R.id.up);
        mDown = (ImageButton) findViewById(R.id.down);

        change_start = (ImageButton) findViewById(R.id.game_start);
        change_stop = (ImageButton) findViewById(R.id.game_stop);
        change_quit = (ImageButton) findViewById(R.id.game_quit);

        saved = PreferenceManager.getDefaultSharedPreferences(this);
        boolean playMusic = saved.getBoolean("ifon" ,true);     // 獲取背景音樂開關的狀態變量,在設置開關界面存儲,在這裏讀取
        if(playMusic) {    // 如果設置背景音樂打開,則開啓服務,播放音樂
            Intent intent_service = new Intent(GameActivity.this, MusicService.class);
            startService(intent_service);
        }
        SnakeView.mMoveDelay=saved.getInt("nandu",500);     // 獲取當前設置的代表遊戲難度的變量,在難度設置界面保存,在這裏讀取

        // 判斷是否有保存數據,如果數據爲空就準備重新開始遊戲
        if (savedInstanceState == null) {
            mSnakeView.setMode(SnakeView.READY);
        } else {
            // 暫停後的恢復
            Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
            if (map != null) {
                mSnakeView.restoreState(map);
            } else {
                mSnakeView.setMode(SnakeView.PAUSE);
            }
        }
        mDown.setOnClickListener(this);
        mUp.setOnClickListener(this);
        mRight.setOnClickListener(this);
        mLeft.setOnClickListener(this);
        change_start.setOnClickListener(this);
        change_stop.setOnClickListener(this);
        change_quit.setOnClickListener(this);
    }
    @Override
    public void onDestroy(){
        super.onDestroy();
        saved = PreferenceManager.getDefaultSharedPreferences(this);
        boolean playMusic = saved.getBoolean("ifon" ,true);
        if(playMusic) {
            Intent intent_service = new Intent(GameActivity.this, MusicService.class);
            stopService(intent_service);
        }

    }
    // 給開始,暫停,退出,上下左右按鈕設置監聽。根據當前的狀態來決定界面的更新操作
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.game_start:
                 // 重新開始遊戲,這裏延時變量必須初始化,不然每次遊戲重新開始之後,蛇的運動速度不會初始化
                if ( mSnakeView.mMode == SnakeView.READY || mSnakeView.mMode == SnakeView.LOSE) {
                    SnakeView.mMoveDelay=saved.getInt("nandu",500);
                    mSnakeView.initNewGame();
                    mSnakeView.setMode(SnakeView.RUNNING);
                    mSnakeView.update();
                }
                // 暫停後開始遊戲,繼續暫停前的界面
                if ( mSnakeView.mMode == SnakeView.PAUSE) {
                    mSnakeView.setMode(SnakeView.RUNNING);
                    mSnakeView.update();
                }
                break;
            case R.id.game_stop:    //  暫停
                if(mSnakeView.mMode == SnakeView.RUNNING) {
                    mSnakeView.setMode(SnakeView.PAUSE);
                }
                break;
            case R.id.game_quit:   //  退出,返回菜單界面
                mSnakeView.setMode(SnakeView.QUIT);
                finish();
                break;
            // 使界面上的方向按鈕起作用
            case R.id.left:
                if (SnakeView.mDirection != RIGHT) {
                    SnakeView.mNextDirection = LEFT;
                }
                break;
            case R.id.right:
                if (SnakeView.mDirection != LEFT) {
                    SnakeView.mNextDirection = RIGHT;
                }
                break;
            case R.id.up:
                if (SnakeView.mDirection != DOWN) {
                    SnakeView.mNextDirection = UP;
                }
                break;
            case R.id.down:
                if (SnakeView.mDirection != UP) {
                    SnakeView.mNextDirection = DOWN;
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSnakeView.setMode(SnakeView.PAUSE);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        //保存遊戲狀態
        outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
    }
}


        下面是遊戲效果圖,運行界面和暫停界面。我把邏輯流程圖也貼出來,有什麼問題大家可以留言,多多交流!

       




很多大兄弟都問我要源碼。 以下自取哈,覺得有用記得關注下我的博客和GitHub
CSDN下載地址: http://download.csdn.net/detail/kuaiguixs/9588233
GitHub: https://github.com/AndroidWJC/SnakeGame
這個小遊戲是一年多以前寫的了,近期打算整理下。再加入應用退出後的暫停保存和排行榜功能,敬請期待。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章