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.完成的界面展示

!!


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 {// 记录的之前的数字和当前的数字相同// 加入*2dataAfterSwipe.add(recordPreviousDigital * 2);// 记录得分recordScore(recordPreviousDigital * 2);// 重置记录数字recordPreviousDigital = -1;}}}}if (recordPreviousDigital != -1) {dataAfterSwipe.add(recordPreviousDigital);}// 补0for (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 {//显示数字的TextViewprivate 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) {// 为最底下的格子加上bottomMargincell = 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小游戏代码视频一步步编写并完善实现,过程中遇到的困难主要是思考游戏思路和键盘事件的理解(即手指滑动时的事件)。游戏思路在上文中滑动事件前已有详细解释,这里给出一个不错的讲解键盘事件的链接,有遇到相同困难的同学可以查看。
  • 键盘事件讲解链接

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

Android小项目--2048小游戏相关推荐

  1. python小项目——2048小游戏(详解)

    2048游戏 原版游戏地址 第一部分 导入所需要的库 第二部分 确认游戏键位设置,并和对应的操作关联 第三部分 获取用户输入的值,并直到有效键位 第四部分 对矩阵的应用,减少代码量 第五部分 创建棋盘 ...

  2. QT小项目---2048小游戏

    目录 效果展示 游戏简介 游戏详情 操作指南 操作技巧 代码实现 QWidget.h Widget.cpp GameWidget.h GameWidget.cpp 总结 效果展示 游戏简介 游戏详情 ...

  3. java课设小迷宫含代码_Java小项目之迷宫游戏的实现方法

    项目要求: 一个网格迷宫由n行n列的单元格组成,每个大院个要么是空地(用0表示),要么是障碍物(用1表示),你的任务是找一条从起点到终点的移动序列,其中只能上下左右移动到相邻单元格.任何时候都不能在有 ...

  4. java 迷宫游戏_Java小项目之迷宫游戏的实现方法

    项目要求: 一个网格迷宫由n行n列的单元格组成,每个大院个要么是空地(用0表示),要么是障碍物(用1表示),你的任务是找一条从起点到终点的移动序列,其中只能上下左右移动到相邻单元格.任何时候都不能在有 ...

  5. c小项目,小码资金管理工具

    纯c小项目,小码资金管理工具moneykeeper 作者:流浪舟 公众号:小码之光 你小码哥回来啦!最近闷得慌,又在学java(自学,想走这条路,哎,一个人找到所爱的真的很难,说不定哪天真的挂了,就来 ...

  6. 前端项目2048小游戏

    第一章 2048游戏介绍 1.1 项目介绍 相信大家都玩过2048这个游戏,这次我们就一步步地来完成2048的开发,这个项目它需要用到html css和js的知识. 需求分析: 1 游戏是一个4x4的 ...

  7. python小项目-python 小项目

    广告关闭 2017年12月,云+社区对外发布,从最开始的技术博客到现在拥有多个社区产品.未来,我们一起乘风破浪,创造无限可能. 事先录制好一段音频,客户接通电话后,自动播放https:blog.csd ...

  8. 开源一个安信可A9g小项目微信小程序定位器项目②GPS模块如何定位经纬度并且上报到MQTT服务器,实现远程查看模块的经纬度;

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. 如何在windows10上环境搭建,编译烧录代码固件,查看运行 ...

  9. 2022-01-13 微信小程序-简历小项目(一) 小程序中的flex布局,简历首页的制作,箭头区域的制作

    文章目录 一.准备 1.目标 2.小程序中的flex布局 回顾flex布局 小程序中要把div换成view标签,别的不变 二.制作简历小程序-首页 1.利用小程序提供的独有的API来初始化高和宽 小程 ...

  10. Python Turtle 小项目 9 小黄人

    闲来没事儿,继续用turtle绘制图案,这次我们要绘制的是小黄人: 好了,现在开始代码教学: 1.导入模块 from turtle import * 2.设置属性 pensize(4) 3.绘制头部外 ...

最新文章

  1. cuda cudnn pytorch版本对应关系
  2. 利用属性封装复杂的选项
  3. php定时执行代码漏洞_【漏洞风险提示】Drupal任意PHP代码执行漏洞通告
  4. [Xcode 实际操作]七、文件与数据-(2)创建文件夹
  5. Nodejs实现WebSocket通信demo
  6. 分区界限必须调整为高于最后一个分区界限_不花一分钱,为SSD硬盘腾空间
  7. 外部表在Hive中的使用
  8. Eclipse MAT 安装及使用
  9. Windows Server 2012 R2 WSUS-5:组策略配置自动更新
  10. 从拨号到 5G :互联网登录完全指南
  11. linux 的一些实用工具,linux 命令行下的一些实用工具
  12. java的四种取整方法
  13. 机器人周志_机器人制造基础学习日志
  14. Android textAppearance的属性设置及TextView属性详解
  15. C# panel控件实现鼠标滚轮滚动拖动滚动条
  16. Nessus下载失败问题解决
  17. 小红书6.18种草拔草投放攻略
  18. 曾号称永久免费的知名国产浏览器推出 VIP 会员模式,网友吵翻天
  19. python个人所得税怎么写_Python计算个人所得税!
  20. 嘻哈说:设计模式之工厂方法模式

热门文章

  1. Github和Git的基本教程,适合新手
  2. 微信小程序4-小程序的api
  3. VS2017插件工具-VisualSVN(6.3.0)-破解之法
  4. 当客户端浏览器不支持相应版本的apple时自动下载运行环境JVM的解决办法!
  5. 华为NP课程笔记8-BGP2
  6. chrome pdf viewer 参数
  7. 类比菜鸟全国仓配网络来学习CDN
  8. WPF介绍和一些基础操作
  9. 【CSDN软考VIP资料群】让软考通过更容易,软考资料大全支持你软考!
  10. 进程管理API之get_task_mm