Android小项目–2048小游戏

文章目录

  • Android小项目--2048小游戏
    • 1.摘要
    • 2.实现的功能
    • 3.完成的界面展示
    • 4.功能具体实现的过程
      • 1. 游戏面板GameView的设计(采用GridLayout布局)
      • 2. Cell类的设计:Cell用来表示游戏中的小格子
      • 3. 将cell添加到GameView中:
      • 4. 游戏模式的改变:
      • 5. 记录当前分数及历史最高分数
      • 6. 判断游戏是否结束
    • 5. 总结

1.摘要

现如今,电子游戏已慢慢渗透进人们生活中,并在扮演着越来越重的角色。2048小游戏属于益智类小游戏,它做到了娱乐性、趣味性、教育性相统一。益智类的游戏即是需要去开动大脑去思考从而获得游戏的胜利。简单的益智类游戏可以使玩家在娱乐中不断的开发大脑。这样一来就实现了在娱乐中学习。

这篇文章主要为大家介绍了Android实现2048小游戏的相关内容,感兴趣的小伙伴们可以参考一下!

2.实现的功能

  1. 确认布局
  2. UI界面
  3. 2048游戏逻辑的实现
  4. 游戏界面:
    • 基本的4×4格子
    • 扩展的5×5格子
    • 扩展的6×6格子
  5. 记录当前得分和历史最高分数
  6. 游戏模式:
    • 经典模式:达到2048即为“成功”,游戏结束。
    • 无限模式:没有2048的上限,在没有失败的情况下,玩家可以一直玩下去.
  7. 最后游戏结束时“You Win!”或“You Lose!”的判定

3.完成的界面展示

!!Android小项目--2048小游戏_第1张图片
Android小项目--2048小游戏_第2张图片
Android小项目--2048小游戏_第3张图片
Android小项目--2048小游戏_第4张图片

4.功能具体实现的过程

1. 游戏面板GameView的设计(采用GridLayout布局)

  • 先自定义一个GameView类,继承GridLayout,添加两个构造方法(GridLayout布局是Android 4.0新增的布局。引入该布局极大地方便了Grid类型的布局开发,不熟悉该布局的读者朋友可以在Android开发者网站上寻找相关的开发资料进行学习)
public class GameView extends GridLayout {      //两个必要的构造方法 public GameView(Context context) {        super(context);   initView(); } public GameView(Context context, AttributeSet attrs) {        super(context, attrs);   initView(); }}
  • 在initView()中定义格子的宽和高,并且添加触摸事件监听
 public void initView(int mode) {             gameMode = mode;        canSwipe = true;        // 移除所有视图,以便更改游戏难度        removeAllViews();        // 初始化格子        if (mode == Constant.MODE_CLASSIC) {                 // 经典模式            gridColumnCount = Config.GRIDColumnCount;        } else if (mode == Constant.MODE_INFINITE) {                 // 无限模式            gridColumnCount = 6;        }        cells = new Cell[gridColumnCount][gridColumnCount];        // 设置界面大小        setColumnCount(gridColumnCount);        // 获取格子的宽        int cellWidth = getCellSize();        // 获取格子的高        int cellHeight = getCellSize();        addCell(cellWidth, cellHeight);        startGame();        setOnTouchListener((v, event) -> {                 // 通知父控件不要拦截此控件的onTouch事件            v.getParent().requestDisallowInterceptTouchEvent(true);            if (canSwipe) {                     switch (event.getAction()) {                         case MotionEvent.ACTION_DOWN:                        setX = event.getX();                        setY = event.getY();                        break;                    case MotionEvent.ACTION_UP:                        offsetX = event.getX() - setX;                        offsetY = event.getY() - setY;                        // 判断滑动方向                        int orientation = getOrientation(offsetX, offsetY);                        switch (orientation) {                                 case 0:                                // 向右滑动                                swipeRight();                                break;                            case 1:                                // 向左滑动                                swipeLeft();                                break;                            case 2:                                // 向下滑动                                swipeDown();                                break;                            case 3:                                // 向上滑动                                swipeUp();                                break;                            default:                            break;                        }                    default:                    break;                }            }            return true;        });    }
  • 添加滑动事件(这里举例上滑):
  • 简要说一下此处实现的游戏玩法算法:首先用for循环一行一行地去遍历每一个cell,然后从当前的位置往右去遍历,判断如果获取到了某一个值不是空的,此时有两种情况,一是当前位置上的值是空的,此时把获取到的值放到当前位置上,同时把获取到的位置上的数字清掉;二是当前位置上的值不是空的,并且获取到的值和当前位置上的值相同,则把合并这两个卡片,把当前位置上的值乘以二,同时把获取到的位置上的数字清掉。
  • 还有一种情况是,如果我们当前位置上是空的,然后把右边的值放到当前的位置上去了,此时继续往后边(右边)去遍历,后边的位置还是空的,然后右边又有一个数字和之前放过去的数字是一样的情况的话,也是把它放到这个空位置上去了,这时会发生一个状况:这两张数字实际是一样的,但是它们并不合并。为了避免这种情况的发生,我们再让它去遍历一次,即让x-- ,这样这个问题就解决了。
    //上滑事件    private void swipeUp() {             // 判断是否需要添加数字        boolean needAddDigital = false;        for (int i = 0; i < gridColumnCount; i++) {                 for (int j = 0; j < gridColumnCount; j++) {                     // 获取当前位置数字                int currentDigital = cells[j][i].getDigital();                someData.add(currentDigital);                if (currentDigital != 0) {                         // 记录数字                    if (recordPreviousDigital == -1) {                             recordPreviousDigital = currentDigital;                    } else {                             // 记录的之前的数字和当前数字不同                        if (recordPreviousDigital != currentDigital) {                                 // 加入记录的数字                            dataAfterSwipe.add(recordPreviousDigital);                            recordPreviousDigital = currentDigital;                        } else {     // 记录的之前的数字和当前的数字相同                            // 加入*2                            dataAfterSwipe.add(recordPreviousDigital * 2);                            // 记录得分                            recordScore(recordPreviousDigital * 2);                            // 重置记录数字                            recordPreviousDigital = -1;                        }                    }                }            }            if (recordPreviousDigital != -1) {                     dataAfterSwipe.add(recordPreviousDigital);            }            // 补0            for (int p = dataAfterSwipe.size(); p < gridColumnCount; p++) {                     dataAfterSwipe.add(0);            }            // 若原始数据和移动后的数据不同,视为界面发生改变            if (!someData.equals(dataAfterSwipe)) {                     needAddDigital = true;            }            someData.clear();            // 重新设置格子数据            for (int k = 0; k < dataAfterSwipe.size(); k++) {                     cells[k][i].setDigital(dataAfterSwipe.get(k));            }            // 重置数据            recordPreviousDigital = -1;            dataAfterSwipe.clear();        }        if (needAddDigital) {                 // 添加一个随机数字(2或4)            addDigital(false);            playSound();        }        judgeOverOrAccomplish();    }
  • 判断滑动方向:
 /**     * 注:先依据在轴上滑动距离的大小,判断在哪个轴上滑动     * @param offsetX 在X轴上的移动距离     * @param offsetY 在Y轴上的移动距离     * @return 滑动方向     * 注:0右滑、1左滑、2下滑、3上滑、-1未构成滑动     */    private int getOrientation(float offsetX, float offsetY) {             // X轴移动        if (Math.abs(offsetX) > Math.abs(offsetY)) {                 if (offsetX > MIN_DIS) {                     return 0;            } else if (offsetX < -MIN_DIS) {                     return 1;            } else {                     return -1;            }        } else {     // Y轴移动            if (offsetY > MIN_DIS) {                     return 2;            } else if (offsetY < -MIN_DIS) {                     return 3;            } else {                     return -1;            }        }    }

2. Cell类的设计:Cell用来表示游戏中的小格子

  • Cell表示游戏中移动的小格子,格子的颜色、显示数字等属性都在对象中进行设置,Cell类如下:
public class Cell extends FrameLayout {         //显示数字的TextView    private TextView cellShowText;    //显示的数字    private int digital;    public Cell(Context context) {             super(context);    }    public Cell(@NonNull Context context, int leftMargin, int topMargin, int bottomMargin) {             super(context);        init(context, leftMargin, topMargin, bottomMargin);    }    //初始化    private void init(@NonNull Context context, int leftMargin, int topMargin, int bottomMargin) {             cellShowText = new TextView(context);        // 不同难度设置不同字体大小        switch (Config.GRIDColumnCount) {                 case 4:                cellShowText.setTextSize(36);                break;            case 5:                cellShowText.setTextSize(28);                break;            case 6:                cellShowText.setTextSize(20);                break;            default:                cellShowText.setTextSize(36);                break;        }        cellShowText.setGravity(Gravity.CENTER);        // 抗锯齿        cellShowText.getPaint().setAntiAlias(true);        // 粗体        cellShowText.getPaint().setFakeBoldText(true);        // 颜色        cellShowText.setTextColor(ContextCompat.getColor(context, R.color.colorWhite));        // 填充整个父容器        LayoutParams params = new LayoutParams(-1, -1);        params.setMargins(leftMargin, topMargin, 0, bottomMargin);        addView(cellShowText, params);        setDigital(0);    }    //获取卡片    public TextView getItemCell() {             return cellShowText;    }    //获取数字    public int getDigital() {             return digital;    }    //设置数字    public void setDigital(int digital) {             this.digital = digital;        cellShowText.setBackgroundResource(getBackgroundResource(digital));        if (digital <= 0) {                 cellShowText.setText("");        } else {                 cellShowText.setText(String.valueOf(digital));        }    }    //根据数字获取相应的背景    private int getBackgroundResource(int number) {             switch (number) {                 case 0:                return R.drawable.bg_cell_0;            case 2:                return R.drawable.bg_cell_2;            case 4:                return R.drawable.bg_cell_4;            case 8:                return R.drawable.bg_cell_8;            case 16:                return R.drawable.bg_cell_16;            case 32:                return R.drawable.bg_cell_32;            case 64:                return R.drawable.bg_cell_64;            case 128:                return R.drawable.bg_cell_128;            case 256:                return R.drawable.bg_cell_256;            case 512:                return R.drawable.bg_cell_512;            case 1024:                return R.drawable.bg_cell_1024;            case 2048:                return R.drawable.bg_cell_2048;            default:                return R.drawable.bg_cell_default;        }    }}

3. 将cell添加到GameView中:

  • 游戏初始化需要根据难度向GameView添加所有的Cell
    /**     * 初始化向布局中添加空格子     * @param cellWidth  格子宽     * @param cellHeight 格子高     */    private void addCell(int cellWidth, int cellHeight) {             Cell cell;        for (int i = 0; i < gridColumnCount; i++) {                 for (int j = 0; j < gridColumnCount; j++) {                     if (i == gridColumnCount - 1) {                         // 为最底下的格子加上bottomMargin                    cell = new Cell(getContext(), 16, 16, 16);                } else {                         cell = new Cell(getContext(), 16, 16, 0);                }                cell.setDigital(0);                addView(cell, cellWidth, cellHeight);                cells[i][j] = cell;            }        }    }
  • 所有格子需要获取数字,最初全部设为0,即所有格子为空
    //获取空格子    private void getEmptyCell() {             // 清空        emptyCellPoint.clear();        // 遍历所有格子,记录所有空格子的坐标位置        for (int i = 0; i < gridColumnCount; i++) {                 for (int j = 0; j < gridColumnCount; j++) {                     // 空格子                if (cells[i][j].getDigital() <= 0) {                         emptyCellPoint.add(new Point(i, j));                }            }        }    }
  • 以4:6的概率随机获取一个数字2或4
    public void addDigital(boolean isCheat) {             getEmptyCell();        if (emptyCellPoint.size() > 0) {                 // 随机取出一个空格子的坐标位置            Point point = emptyCellPoint.get((int) (Math.random() * emptyCellPoint.size()));            cells[point.x][point.y].setDigital(Math.random() > 0.4 ? 2 : 4);                        // 设置动画            setAppearAnim(cells[point.x][point.y]);        }    }

4. 游戏模式的改变:

  • 扩展功能:无限模式和经典模式的切换:
    //打开切换模式对话框    private void showChangeModeDialog() {             String subject = "";        if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {                 subject = "无限模式";        } else if (Config.CurrentGameMode == Constant.MODE_INFINITE) {                 subject = "经典模式";        }        CommonDialog dialog = new CommonDialog(this, R.style.CustomDialog);        dialog.setCancelable(true);        dialog.setTitle(getResources().getString(R.string.tip))                .setMessage("是否要切换到" + subject)                .setOnPositiveClickedListener("", v -> {                         if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {                             Toast.makeText(GameActivity.this, "已进入无限模式", Toast.LENGTH_SHORT).show();                        enterInfiniteMode();                    } else {                             Toast.makeText(GameActivity.this, "已进入经典模式", Toast.LENGTH_SHORT).show();                        enterClassicsMode();                    }                    dialog.cancel();                })                .setOnNegativeClickListener("", v -> dialog.cancel())                .show();    }    // 进入无限模式    private void enterInfiniteMode() {             Config.haveCheat = false;        Config.CurrentGameMode = Constant.MODE_INFINITE;        // 保存游戏模式        ConfigManager.putCurrentGameMode(this, Constant.MODE_INFINITE);        titleDescribe.setText(getResources().getString(R.string.game_mode_infinite));        bestScores.setText(String.valueOf(ConfigManager.getBestScoreWithinInfinite(this)));        bestScoresRank.setText(getResources().getText(R.string.tv_best_score_infinite));        currentScores.setText(String.valueOf(ConfigManager.getCurrentScoreWithinInfinite(this)));        modeDescribe.setText(getResources().getString(R.string.tv_describe_infinite));        setTextStyle(titleDescribe);        gameView.initView(Constant.MODE_INFINITE);    }    //进入经典模式    private void enterClassicsMode() {             Config.haveCheat = false;        Config.CurrentGameMode = Constant.MODE_CLASSIC;        // 保存游戏模式        ConfigManager.putCurrentGameMode(this, Constant.MODE_CLASSIC);        titleDescribe.setText(getResources().getString(R.string.game_mode_classics));        // 读取到历史最高分        bestScores.setText(String.valueOf(Config.BestScore));        bestScoresRank.setText(getString(R.string.best_score_rank, Config.GRIDColumnCount));        currentScores.setText(String.valueOf(ConfigManager.getCurrentScore(this)));        modeDescribe.setText(getResources().getString(R.string.tv_describe));        setTextStyle(titleDescribe);        gameView.initView(Constant.MODE_CLASSIC);    }

5. 记录当前分数及历史最高分数

    //记录得分    private void recordScore(int score) {             currentScores.setText(String.valueOf(score));        // 当前分数大于最高分        if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {                 if (score > ConfigManager.getBestScore(this)) {                     updateBestScore(score);            }        } else if (Config.CurrentGameMode == Constant.MODE_INFINITE) {                 if (score > ConfigManager.getBestScoreWithinInfinite(this)) {                     updateBestScore(score);            }        }    }    //更新历史最高分    private void updateBestScore(int newScore) {             bestScores.setText(String.valueOf(newScore));        if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {                 Config.BestScore = newScore;            ConfigManager.putBestScore(this, newScore);        } else if (Config.CurrentGameMode == Constant.MODE_INFINITE) {                 Config.BestScoreWithinInfinite = newScore;            ConfigManager.putBestScoreWithinInfinite(this, newScore);        }    }

6. 判断游戏是否结束

  • 每次获取数字时需要判断游戏是否结束
    private void judgeOverOrAccomplish() {             // 判断游戏结束的标识        boolean isOver = true;        // 判断游戏是否结束:格子都不为空且相邻的格子数字不同        over:        for (int i = 0; i < gridColumnCount; i++) {                 for (int j = 0; j < gridColumnCount; j++) {                     // 有空格子,游戏还可以继续                if (cells[i][j].getDigital() == 0) {                         isOver = false;                    break over;                }                // 判断左右上下有没有相同的                if (j < gridColumnCount - 1) {                         if (cells[i][j].getDigital() == cells[i][j + 1].getDigital()) {                             isOver = false;                        break over;                    }                }                if (i < gridColumnCount - 1) {                         if (cells[i][j].getDigital() == cells[i + 1][j].getDigital()) {                             isOver = false;                        break over;                    }                }            }        }        // 游戏结束,弹出提示框        if (isOver) {                 canSwipe = false;            sendGameOverMsg(ACTION_LOSE);        }        // 经典模式下才判赢        if (gameMode == 0) {                 // 判断是否达成游戏目标            for (int i = 0; i < gridColumnCount; i++) {                     for (int j = 0; j < gridColumnCount; j++) {                         // 有一个格子数字到达2048则视为达成目标                    if (cells[i][j].getDigital() == 2048) {                             canSwipe = false;                        int currentTime = ConfigManager.getGoalTime(getContext()) + 1;                        ConfigManager.putGoalTime(getContext(), currentTime);                        Config.GetGoalTime = currentTime;                        sendGameOverMsg(ACTION_WIN);                    }                }            }        }    }

5. 总结

  • 涉及到的知识点汇总:
    • Sqlite
    • SharedPreferences
    • GestureOverlayView
    • Animation
    • Spannable
    • Handler
    • BroadcastReceiver
    • Timer
    • 自定义View
  • 本文代码是根据极客学院2048小游戏代码视频一步步编写并完善实现,过程中遇到的困难主要是思考游戏思路和键盘事件的理解(即手指滑动时的事件)。游戏思路在上文中滑动事件前已有详细解释,这里给出一个不错的讲解键盘事件的链接,有遇到相同困难的同学可以查看。
  • 键盘事件讲解链接

作者:宋迎新
参考文章:参考文章
原文链接:原文链接

更多相关文章

  1. html5游戏移植到android并打包成apk,加广告《一》
  2. 简单的Android游戏测试
  3. Chapter3-运行cocos2dx游戏在android设备上
  4. 十五开源的Andr​​oid(2D或3D)Android开发游戏引擎
  5. 为什么没有好用的Android游戏引擎?

随机推荐

  1. 在用户组件中使用自定义标签
  2. 手把手教你逆向分析 Android(安卓)程序
  3. Android(安卓)之图片本地缓存解决方案
  4. android中的多线程编程及消息机制
  5. Android(安卓)解析启动白屏原理及解决方
  6. Android(安卓)任务和回退堆栈---管理任务
  7. 一次重拾Android(安卓)Studio开发的经历
  8. [原] Android持续优化 - 提高流畅度
  9. Android(安卓)UI 之CheckBox的妙用
  10. 【Android】Android的与服务器端传送信息