Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android游戲——2048的設計(2)

Android游戲——2048的設計(2)

編輯:關於Android編程

在去年的時候曾經寫了一個Android小游戲——2048,也在應用商店上線了,
當初設計的時候還不覺得什麼,最近在整理代碼時卻覺得當時代碼設計得很是糟糕,代碼混亂,界面也不好看。於是就趁著假期重寫了一遍,游戲運行界面如下
這裡寫圖片描述

實現的功能有:
1.有4x4,5x5,6x6三種規則
2.記錄歷史最高分
3.使用純色塊
4.保存游戲
5.開啟音效
6.更換背景圖

開發工具用的是Android Studio
這裡寫圖片描述

游戲的思路並不復雜,甚至可以說是挺簡單的。
首先要自定義一個View,作為可滑動的方塊(其實滑動效果是通過改變數字與顏色來模擬實現的),這個View要繼承於FrameLayout
每一種不同數值的方塊有不同的顏色,通過設置“setBackgroundColor”來實現。

public class Card extends FrameLayout {

    private TextView label;

    private int num = 0;

    //用於判斷是否純色塊
    public boolean flag;

    public Card(Context context) {
        super(context);
        label = new TextView(context);
        label.setGravity(Gravity.CENTER);
        label.setTextSize(24);
        label.setBackgroundColor(Color.parseColor("#77E8E2D8"));
        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        lp.setMargins(5, 5, 0, 0);
        addView(label, lp);
    }

    public void setNum(int num) {
        this.num = num;
        if (num == 0) {
            label.setText("");
            label.setBackgroundColor(Color.parseColor("#77E8E2D8"));
        }else{
            if(!flag){
                label.setText(num + "");
            }
            changeCardColor();
        }
    }

    public int getNum() {
        return num;
    }

    public void changeCardColor() {
        switch (num) {
        case 2:
            label.setBackgroundColor(Color.parseColor("#5DB8E8"));
            break;
        case 4:
            label.setBackgroundColor(Color.parseColor("#A52812"));
            break;
        case 8:
            label.setBackgroundColor(Color.parseColor("#0E7171"));
            break;
        case 16:
            label.setBackgroundColor(Color.parseColor("#C0BB39"));
            break;
        case 32:
            label.setBackgroundColor(Color.parseColor("#623889"));
            break;
        case 64:
            label.setBackgroundColor(Color.parseColor("#5C7235"));
            break;
        case 128:
            label.setBackgroundColor(Color.parseColor("#826FA3"));
            break;
        case 256:
            label.setBackgroundColor(Color.parseColor("#355659"));
            break;
        case 512:
            label.setBackgroundColor(Color.parseColor("#BB719B"));
            break;
        case 1024:
            label.setBackgroundColor(Color.parseColor("#9B8B53"));
            break;
        case 2048:
            label.setBackgroundColor(Color.parseColor("#196A5D"));
            break;
        default:
            label.setBackgroundColor(Color.parseColor("#8A7760"));
        }
    }

    public boolean equals(Card c) {
        return this.getNum() == c.getNum();
    }
}

此外,可以看到不管是4x4規則的或者5x5,6x6的,整個可滑動區域都是一個正方形,方塊平均分布,因此可以自定義一個View,繼承於GridLayout,為之添加多個Card 。
GameView 的重點在於方塊的滑動判斷以及實現滑動效果。
SoundPool 的使用方法在Android5.0之後發生了改變,所以需要在代碼中判斷當前系統版本,從而使用不同的初始化方法。

public class GameView extends GridLayout {

    // 存儲所有方塊
    private Card[][] Cards;

    // 當前游戲的行數與列數
    private int Row;

    // 游戲記錄
    private SharedPreferences gameRecord;

    // 存儲游戲音效開關記錄
    private SharedPreferences GameSettings;

    private SharedPreferences.Editor grEditor;

    public ScoreChangeListen scoreChangeListen=null;

    private Context context;

    //當前得分
    private int Score;

    public SoundPool soundPool;

   // private HashMap soundID;

    private int soundID;;

    private boolean soundSwitch;

    private class Point {
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        int x;
        int y;
    }

    public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewSet);
        Row = mTypedArray.getInt(R.styleable.ViewSet_Row, 5);
        mTypedArray.recycle();
        super.setColumnCount(Row);
        init();
    }

    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewSet);
        Row = mTypedArray.getInt(R.styleable.ViewSet_Row, 5);
        mTypedArray.recycle();
        super.setColumnCount(Row);
        init();
    }

    public GameView(Context context) {
        super(context);
        this.context = context;
        init();
    }

    // 初始化
    private void init() {
        gameRecord = context.getSharedPreferences("GameRecord", Context.MODE_PRIVATE);
        GameSettings = context.getSharedPreferences("GameSettings", Context.MODE_PRIVATE);

        boolean flag=GameSettings.getBoolean("SolidColorSwitch",false);
        soundSwitch=GameSettings.getBoolean("SoundSwitch",false);

        //SoundPool的構建方法在5.0系統之後發生了變化
        if (Build.VERSION.SDK_INT < 21) {
            soundPool = new SoundPool(1,AudioManager.STREAM_MUSIC,0);
        }else{
            SoundPool.Builder builder = new SoundPool.Builder();
            builder.setMaxStreams(1);
            AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
            attrBuilder.setLegacyStreamType(AudioManager.STREAM_MUSIC);
            builder.setAudioAttributes(attrBuilder.build());
            soundPool = builder.build();
        }
        soundID=soundPool.load(context,R.raw.sound,1);
        grEditor = gameRecord.edit();
        Cards = new Card[Row][Row];
        for (int y = 0; y < Row; y++) {
            for (int x = 0; x < Row; x++) {
                Cards[x][y] = new Card(context);
                Cards[x][y].flag=flag;
            }
        }
        // 添加兩個初始方塊
        randomCard();
        randomCard();
    }

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 計算方塊的邊長
        int cardWidth = (Math.min(w, h) - 5) / Row;
        // 添加方塊
        addCard(cardWidth);
    }

    // 計算分數
    private void countScore(int num) {
        Score = Score + num;
        if(scoreChangeListen!=null){
            scoreChangeListen.OnNowScoreChange(Score);
            if(soundSwitch){
                soundPool.play(soundID, 1, 1, 0, 0, 1);
            }
        }
    }

    // 添加方塊
    private void addCard(int cardWidth) {
        for (int y = 0; y < Row; y++) {
            for (int x = 0; x < Row; x++) {
                addView(Cards[x][y], cardWidth, cardWidth);
            }
        }
    }

    // 生成偽隨機方塊
    private void randomCard() {
        List points = new ArrayList<>();
        for (int x = 0; x < Row; x++) {
            for (int y = 0; y < Row; y++) {
                // 如果還有空白方塊
                if (Cards[x][y].getNum() == 0) {
                    points.add(new Point(x, y));
                }
            }
        }
        if (points.size() == 0) {
            return;
        }
        int index = points.size() / 2;
        Cards[points.get(index).x][points.get(index).y].setNum(2);
    }

    // 左移
    private void moveLeftCard() {
        allMoveLeft();
        for (int y = 0; y < Row; y++) {
            for (int x = 0; x < Row - 1; x++) {
                if (Cards[x][y].getNum() != 0) {
                    if (Cards[x][y].equals(Cards[x + 1][y])) {
                        int num = Cards[x][y].getNum();
                        Cards[x][y].setNum(2 * num);
                        Cards[x + 1][y].setNum(0);
                        countScore(num);
                        allMoveLeft();
                    }
                }
            }
        }
        randomCard();
    }

    // 右移
    private void moveRightCard() {
        allMoveRight();
        for (int y = 0; y < Row; y++) {
            for (int x = Row - 1; x > 0; x--) {
                if (Cards[x][y].getNum() != 0) {
                    if (Cards[x][y].equals(Cards[x - 1][y])) {
                        int num = Cards[x][y].getNum();
                        Cards[x][y].setNum(2 * num);
                        Cards[x - 1][y].setNum(0);
                        countScore(num);
                        allMoveRight();
                    }
                }
            }
        }
        randomCard();
    }

    // 上移
    private void moveUpCard() {
        allMoveUp();
        for (int x = 0; x < Row; x++) {
            for (int y = 0; y < Row - 1; y++) {
                if (Cards[x][y].getNum() != 0) {
                    if (Cards[x][y].equals(Cards[x][y + 1])) {
                        int num = Cards[x][y].getNum();
                        Cards[x][y].setNum(2 * num);
                        Cards[x][y + 1].setNum(0);
                        countScore(num);
                        allMoveUp();
                    }
                }
            }
        }
        randomCard();
    }

    // 下移
    private void moveDownCard() {
        allMoveDown();
        for (int x = 0; x < Row; x++) {
            for (int y = Row - 1; y > 0; y--) {
                if (Cards[x][y].getNum() != 0) {
                    if (Cards[x][y].equals(Cards[x][y - 1])) {
                        int num = Cards[x][y].getNum();
                        Cards[x][y].setNum(2 * num);
                        Cards[x][y - 1].setNum(0);
                        countScore(num);
                        allMoveDown();
                    }
                }
            }
        }
        randomCard();
    }

    // 全部左移
    private void allMoveLeft() {
        for (int y = 0; y < Row; y++) {
            int i = 0;
            for (int x = 0; x < Row; x++) {
                if (Cards[x][y].getNum() != 0) {
                    int num = Cards[x][y].getNum();
                    Cards[x][y].setNum(0);
                    Cards[i++][y].setNum(num);
                }
            }
        }
    }

    // 全部右移
    private void allMoveRight() {
        for (int y = 0; y < Row; y++) {
            int i = Row - 1;
            for (int x = Row - 1; x > -1; x--) {
                if (Cards[x][y].getNum() != 0) {
                    int num = Cards[x][y].getNum();
                    Cards[x][y].setNum(0);
                    Cards[i--][y].setNum(num);
                }
            }
        }
    }

    // 全部上移
    private void allMoveUp() {
        for (int x = 0; x < Row; x++) {
            int i = 0;
            for (int y = 0; y < Row; y++) {
                if (Cards[x][y].getNum() != 0) {
                    int num = Cards[x][y].getNum();
                    Cards[x][y].setNum(0);
                    Cards[x][i++].setNum(num);
                }
            }
        }
    }

    // 全部下移
    private void allMoveDown() {
        for (int x = 0; x < Row; x++) {
            int i = Row - 1;
            for (int y = Row - 1; y > -1; y--) {
                if (Cards[x][y].getNum() != 0) {
                    int num = Cards[x][y].getNum();
                    Cards[x][y].setNum(0);
                    Cards[x][i--].setNum(num);
                }
            }
        }
    }


    // 觸屏事件監聽
    float X;
    float Y;
    float OffsetX;
    float OffsetY;
    int HintCount = 0;
    public boolean isHalfway = true;
    public boolean onTouchEvent(MotionEvent event) {
        // 為了避免當游戲結束時消息多次提示
        if (HintCount == 1) {
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                X = event.getX();
                Y = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                OffsetX = event.getX() - X;
                OffsetY = event.getY() - Y;
                if (Math.abs(OffsetX) > (Math.abs(OffsetY))) {
                    if (OffsetX < -5) {
                        moveLeftCard();
                    } else if (OffsetX > 5) {
                        moveRightCard();
                    }
                } else {
                    if (OffsetY < -5) {
                        moveUpCard();
                    } else if (OffsetY > 5) {
                        moveDownCard();
                    }
                }
                HintMessage();
                break;
        }
        return true;
    }

    // 判斷游戲是否結束
    private boolean isOver() {
        for (int y = 0; y < Row; y++) {
            for (int x = 0; x < Row; x++) {
                if ((Cards[x][y].getNum() == 0) || (x - 1 >= 0 && Cards[x - 1][y].equals(Cards[x][y]))
                        || (x + 1 <= Row - 1 && Cards[x + 1][y].equals(Cards[x][y]))
                        || (y - 1 >= 0 && Cards[x][y - 1].equals(Cards[x][y]))
                        || (y + 1 <= Row - 1 && Cards[x][y + 1].equals(Cards[x][y]))) {
                    return false;
                }
            }
        }
        return true;
    }

    // 當游戲結束時提示信息
    private void HintMessage() {
        if (isOver()) {
            Toast.makeText(getContext(), "游戲結束啦", Toast.LENGTH_SHORT).show();
            HintCount=1;
        }
    }

    //重新開始
    public void restart(){
        for (int y = 0; y < Row; y++) {
            for (int x = 0; x < Row; x++) {
                Cards[x][y].setNum(0);
            }
        }
        Score=0;
        HintCount=0;
        // 添加兩個初始方塊
        randomCard();
        randomCard();
    }

    //保存游戲
    public void saveGame(){
        grEditor.clear();
        grEditor.putInt("Row", Row);
        grEditor.putInt("Score",Score);
        int k = 0;
        for (int i = 0; i < Row; i++) {
            for (int j = 0; j < Row; j++) {
                k++;
                String str = k + "";
                grEditor.putInt(str, Cards[i][j].getNum());
            }
        }
        if( grEditor.commit()){
            Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(context, "保存失敗,請重試", Toast.LENGTH_SHORT).show();
        }
    }

    // 恢復游戲
    public void recoverGame() {
        int k = 0;
        for (int i = 0; i < Row; i++) {
            for (int j = 0; j < Row; j++) {
                k++;
                String str = k + "";
                int num = gameRecord.getInt(str, 0);
                Cards[i][j].setNum(num);
            }
        }
        Score=gameRecord.getInt("Score",0);
        scoreChangeListen.OnNowScoreChange(Score);
    }

}

要注意的是,在GameView的構造函數中,需要讀取GameView的一個自定義屬性“Row”,如果沒有指定則默認為5。該屬性的定義在values文件夾的attrs.xml文件中。



    
        
    

這樣,在布局文件中使用GameView時,先加上屬性聲明

xmlns:my="http://schemas.android.com/apk/res-auto"

然後就可以為GameView設置顯示行數了

整個游戲界面是由GameActivity呈現的,該Activity通過Bundle 攜帶的數據使用不同的布局文件。

public class GameActivity extends AppCompatActivity {

    private GameView gameView;

    private TextView text_nowScore;

    private TextView text_highestScore;

    private TextView text_restart;

    private TextView text_saveGame;

    private ScoreChangeListen scoreChangeListen;

    // 游戲設置
    private SharedPreferences gameSettings;

    private SharedPreferences.Editor gsEditor;

    // 歷史最高分
    private int highestScore;

    // 用於實現“在點擊一次返回鍵退出程序”的效果
    private boolean isExit = false;

    private boolean flag;

    private int temp;

    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            isExit = false;
        }
    };

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        Intent intent = getIntent();
        Bundle bundle = intent.getExtras();
        int row = bundle.getInt("Row", 4);
        if (row == 4) {
            setContentView(R.layout.activity_four);
        } else if (row == 5) {
            setContentView(R.layout.activity_five);
        } else {
            setContentView(R.layout.activity_six);
        }
        init();
        //判斷是否需要恢復游戲記錄
        if (bundle.getBoolean("RecoverGame", false)) {
            gameView.recoverGame();
        }
    }

    // 初始化
    public void init() {
        gameView = (GameView) findViewById(R.id.gameView_five);
        text_nowScore = (TextView) findViewById(R.id.nowScore);
        text_highestScore = (TextView) findViewById(R.id.highestScore);
        text_restart = (TextView) findViewById(R.id.restart);
        text_saveGame = (TextView) findViewById(R.id.save_game);
        gameSettings = getSharedPreferences("GameSettings", Context.MODE_PRIVATE);
        gsEditor = gameSettings.edit();

        highestScore = gameSettings.getInt("HighestScore", 0);
        text_nowScore.setText("當前得分\n" + 0);
        text_highestScore.setText("最高得分\n" + highestScore);
        flag = true;

        LinearLayout rootLayout = (LinearLayout) findViewById(R.id.rootLayout);
        int themeIndex = gameSettings.getInt("ThemeIndex", 1);
        switch (themeIndex) {
            case 1:
                rootLayout.setBackgroundResource(R.drawable.back1);
                break;
            case 2:
                rootLayout.setBackgroundResource(R.drawable.back2);
                break;
            case 3:
                rootLayout.setBackgroundResource(R.drawable.back3);
                break;
            case 4:
                rootLayout.setBackgroundResource(R.drawable.back4);
                break;
            case 5:
                rootLayout.setBackgroundResource(R.drawable.back5);
                break;
            case 6:
                rootLayout.setBackgroundResource(R.drawable.back6);
                break;
        }

        //重新開始游戲
        text_restart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this);
                builder.setMessage("確認重新開始游戲嗎?");
                builder.setTitle("提示");
                builder.setPositiveButton("確認", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        gameView.restart();
                        text_nowScore.setText("當前得分\n" + 0);
                        if (temp != 0) {
                            scoreChangeListen.OnHighestScoreChange(temp);
                            highestScore = temp;
                            flag = true;
                        }
                    }
                });
                builder.setNegativeButton("取消", null);
                builder.create().show();
            }
        });

        //保存游戲
        text_saveGame.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this);
                builder.setMessage("確認保存游戲嗎?");
                builder.setTitle("提示");
                builder.setPositiveButton("確認", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        gameView.saveGame();
                    }
                });
                builder.setNegativeButton("取消", null);
                builder.create().show();
            }
        });

        scoreChangeListen = new ScoreChangeListen() {
            @Override
            public void OnNowScoreChange(int Score) {
                text_nowScore.setText("當前得分\n" + Score);
                if (Score > highestScore) {
                    if (flag && highestScore != 0) {
                        Toast.makeText(GameActivity.this, "打破最高紀錄啦,請繼續保持", Toast.LENGTH_SHORT).show();
                        flag = false;
                    }
                    temp = Score;
                    text_highestScore.setText("最高得分\n" + temp);
                }
            }

            @Override
            public void OnHighestScoreChange(int Score) {
                gsEditor.putInt("HighestScore", Score);
                gsEditor.commit();
            }
        };
        gameView.scoreChangeListen = scoreChangeListen;
    }

    // 重寫返回鍵監聽事件
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            exit();
            return false;
        }
        return super.onKeyDown(keyCode, event);
    }

    private void exit() {
        if (!isExit) {
            isExit = true;
            if (gameView.isHalfway) {
                Toast.makeText(this, "再按一次結束游戲,建議保存游戲", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "再按一次結束游戲", Toast.LENGTH_SHORT).show();
            }
            // 利用handler延遲發送更改狀態信息
            mHandler.sendEmptyMessageDelayed(0, 2000);
        } else {
            finish();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (temp != 0) {
            scoreChangeListen.OnHighestScoreChange(temp);
        }
        gameView.soundPool.release();
    }
}

當中,可以通過mHandler實現“再按一次退出程序的效果”,這個效果需要靠boolean類型的isExit 來控制。
即如果用戶點擊了一次返回鍵後,mHandler就會在兩秒後發送一條消息改變isExit 的值,如果在這兩秒內用戶沒有再次點擊返回鍵,則就又需要連續點擊兩次返回鍵才能退出。

private void exit() {
        if (!isExit) {
            isExit = true;
            if (gameView.isHalfway) {
                Toast.makeText(this, "再按一次結束游戲,建議保存游戲", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "再按一次結束游戲", Toast.LENGTH_SHORT).show();
            }
            // 利用handler延遲發送更改狀態信息
            mHandler.sendEmptyMessageDelayed(0, 2000);
        } else {
            finish();
        }
    }

GameActivity中使用到了一個自定義接口ScoreChangeListen

/**
 * Created by ZY on 2016/7/18.
 */
public interface ScoreChangeListen {

    void OnNowScoreChange(int Score);

    void OnHighestScoreChange(int Score);
}

因為顯示當前分數以及歷史最高分的是兩個TextView,GameView無法直接控制,所以就使用回調函數來間接控制TextView的值。

MainActivity的布局也較為簡單,一共是六個ImageView,設定點擊不同的ImageView執行特定的函數

public class MainActivity extends AppCompatActivity {

    // 游戲記錄
    private SharedPreferences gameRecord;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportActionBar().hide();
        gameRecord = getSharedPreferences("GameRecord", Context.MODE_PRIVATE);
    }

    //軟件說明
    public void explain(View view){
        Intent intent=new Intent(MainActivity.this,ExplainActivity.class);
        startActivity(intent);
    }

    // 4乘4
    public void fourRow(View view) {
        Intent intent = new Intent(MainActivity.this, GameActivity.class);
        Bundle bundle=new Bundle();
        bundle.putInt("Row",4);
        intent.putExtras(bundle);
        startActivity(intent);
    }

    // 5乘5
    public void fiveRow(View view) {
        Intent intent = new Intent(MainActivity.this, GameActivity.class);
        Bundle bundle=new Bundle();
        bundle.putInt("Row",5);
        intent.putExtras(bundle);
        startActivity(intent);
    }

    // 6乘6
    public void sixRow(View view) {
        Intent intent = new Intent(MainActivity.this, GameActivity.class);
        Bundle bundle=new Bundle();
        bundle.putInt("Row",6);
        intent.putExtras(bundle);
        startActivity(intent);
    }

    //恢復游戲
    public void recoverGame(View view){
        if(gameRecord.contains("Row")){
            int row=gameRecord.getInt("Row",4);
            Bundle bundle=new Bundle();
            Intent intent = new Intent(MainActivity.this, GameActivity.class);
            if(row==4){
                bundle.putInt("Row",4);
            }else if(row==5){
                bundle.putInt("Row",5);
            }else{
                bundle.putInt("Row",6);
            }
            bundle.putBoolean("RecoverGame",true);
            intent.putExtras(bundle);
            startActivity(intent);
        }else{
            Toast.makeText(MainActivity.this,"沒有保存記錄,來一局新游戲吧",Toast.LENGTH_SHORT).show();
        }
    }

    //設置
    public void settings(View view){
        Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
        startActivity(intent);
    }
}

ExplainActivity和SettingsActivity兩個Activity較為簡單這裡就不再贅述
代碼下載地址:Android游戲——2048的設計
訪問密碼:286a
如果鏈接失效,可以留言,我會補發的~

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