Android实训案例(四)——关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程!


关于2048,我看到很多大神,比如医生,郭神,所以我也研究了一段时间,还好是研究了一套逻辑,这是一整套的2048游戏从设计到逻辑再到编写的全部过程,小伙伴们看仔细咯,刚好今天是礼拜天,一天应该了一把这篇博客发表了,其实2048开发起来还是有点难度的,并且他的逻辑挺强的,我也是看了很多的资料偷学的,很适合来锻炼自己的逻辑性

我们首先先来选择开发环境,这里我们就以Eclipse为IDE,新建一个工程——Game2048

一.Score分数

既然是2048游戏,我们也就做一个简单的,他有一个分数,然后就是一个游戏的布局,我们也做一个简单的4*4的游戏,大概的设计图就是这样

二.游戏类:GameView

因为我们的游戏所使用到的布局就是GridLayout,所以我们新建一个GameView继承自GridLayout,然后通过算法动态添加方块,并且监听手势进行操作,这个重写的GridLayout就是游戏的布局了

<com.lgl.game2048.GameViewandroid:id="@+id/game_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" />

三.实现交互逻辑

我们这里铁定是手势操作啦,这里我们就得区分手势是往上,往下,往左,往右,的手势,这时候,我们就需要用到手势监听——OnTouchListener了,也为了确保是实时监听,我们直接在initView的初始方法中处理
我们其实只要知道两点,用户手指按下的坐标点和手指离开的坐标点,然后进行比对,就能识别出用户的意图了

其实关于MotionEvent的几个方法,大家估计都见怪不怪了,因为用的太多了
// 初始化private void initView() {// 识别手势setOnTouchListener(new OnTouchListener() {// 起始点和偏移点private float startX, startY, offsetX, offsetY;@Overridepublic boolean onTouch(View v, MotionEvent event) {/*** 交互逻辑 :我们其实只要知道两点,用户手指按下的坐标点和手指离开的坐标点,然后进行比对,就能识别出用户的意图了*/switch (event.getAction()) {// 手指按下case MotionEvent.ACTION_DOWN:// 记录按下的x,y坐标startX = event.getX();startY = event.getY();break;// 手指离开case MotionEvent.ACTION_UP:// 手指离开之后计算偏移量(离开的位置-按下的位置在进行判断是往哪个方向移动)offsetX = event.getX() - startX;offsetY = event.getY() - startY;// 开始识别方向// offsetX 的绝对值大于offsetY的绝对值 说明在水平方向if (Math.abs(offsetX) > Math.abs(offsetY)) {// (直接<0 会有些许误差,我们可以 <-5)if (offsetX < -5) {// 左System.out.println("左");} else if (offsetX > 5) {// 右System.out.println("右");}// 开始计算垂直方向上下的滑动} else {if (offsetY < -5) {// 上System.out.println("上");} else if (offsetY > 5) {// 下System.out.println("下");}}break;}return true;}});}
上面的逻辑是不是非常的简单,然后我们操作一下,看log

现在手势识别也是很精准了,当然,我们的代码设计也不能太过臃肿,所以,我们的操作逻辑就不在里面编写了,我们分别实现四个方向的方法
// 左private void isLeft() {}// 右private void isRight() {}// 上private void isTop() {}// 下private void isButtom() {}
然后把输出语句替换掉,监听到哪个方向就执行哪个方法
//System.out.println("上,下,左,右");private void isXXX() {}

四.实现方块类CardView

我们可以把这一个个卡片看作是一个对象,我们每次操作,他都要进行实例化
首先,我们新建一个类CardView继承自FrameLayout,再里面我们要考虑三点

1.卡片
2.卡片上的数字
卡片相同的比较

package com.lgl.game2048;import android.content.Context;
import android.widget.FrameLayout;
import android.widget.TextView;public class CardView extends FrameLayout {// 卡片数量private int num = 0;// 卡片文字private TextView tv_num;public CardView(Context context) {super(context);// 初始化TextViewtv_num = new TextView(getContext());// 卡片文字大小tv_num.setTextSize(20);// 布局控制器,填充满整个父容器LayoutParams lp = new LayoutParams(-1, -1);addView(tv_num, lp);setNum(0);}public int getNum() {return num;}public void setNum(int num) {this.num = num;// 要呈现出来的文字(这里要注意是String类型的)tv_num.setText(num + "");}// 两卡片相同的比较方法public boolean equals(CardView card) {return getNum() == card.getNum();}}

五.动态分配方块的宽高以及添加方块

1.动态分配方块的宽高

写到这里,就有一个梗了,还是Android的老毛病,屏幕的适配问题,所以我们队卡牌的宽高是不能做限定的,也就是说我们要去根据手机屏幕动态分配卡片的width和height,在这里我们就得用到我之前一篇博客
Android绘图机制(一)——自定义View的基础属性和方法
中提到的一个方法了

@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {// TODO Auto-generated method stubsuper.onSizeChanged(w, h, oldw, oldh);}

他负责我们的View在父容器的位置,所以我们动态分配高宽也是在他这里面完成,首先,位置发生改变之后,我们得到的位置是一个确定数,但是为了防止用户是横放这手机,这就蛋疼了,所以我们得进行一个设置了
我们打开AndroidManifest.xml的activity标签中加入

//禁止屏幕横屏
android:screenOrientation="portrait"    

好的,现在可以计算了
宽高求最小值 因为考虑到,我们的方阵他是正方形的,而手机屏幕是长方形的,这样,我们的正方形要设置变长就得求长方形的宽,也就是最小值了
并且我们也不需要他填满宽度,我们需要一点空隙,所以我们减去10个像素

再让他除以4,通过这种方式,我们就可以动态平分这个宽度了
int cardWidth = (Math.min(w, h)-10)/4;

2.添加方块

-1.添加卡片

    // 添加卡片,参数为卡片的宽高,因为他是正方形,所以宽高都是cardWidthprivate void addCard(int cardWidth, int cardHeight) {// 创建方块CardView c;// 循环添加for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {c = new CardView(getContext());// num为随机数c.setNum(2);addView(c, cardWidth, cardHeight);}}}
好的,我们来运行一下(换个AS2.0的模拟器感觉萌萌哒)

-2.方块换行

你会发现,并没有4*4,而且一排没有换行,我们回到GameView的initView()方法中添加
//换行
setColumnCount(4);
现在再看看

-3.文字居中

现在我们调整一下,让文字居中,在CardView中
    //文字居中tv_num.setGravity(Gravity.CENTER);

现在是不是好看多了

-4.方块颜色

既然是玩2048我们怎么能少了颜色尼,我们就根据这

Android高效率编码-细节,控件,架包,功能,工具,开源汇总

中的色彩表来,自己觉得什么颜色好看可以自行替换,我们直接来到CardView里面

//设置文字背景(暗卡其色)
tv_num.setBackgroundColor(0xffBDB76A);

-5.方块间距

既然是方块间隔,我们还是回到CardView里面,还记得我们设置的LayoutParams吗?你可能想到了吧,我们用Margins
//设置间距
lp.setMargins(10, 10, 0, 0);

-6.记忆方块

我们所操作之后,会有新生成的卡片,为了不重合,我们得做一个记忆功能
    // 记录卡片的二维数组private CardView[][] cards = new CardView[4][4];
然后在addCard()方法中
//记忆cards[j][i] = c;

六.随机数

我们先来思考一下这个随机数的逻辑,我们玩2048的时候,是不是开始新游戏的时候会随机出现两个方块,而这两个方块,他是随机出现在4*4的任意位置的,所以,我们确定下来,一开始是两个方块的随机出现,再接下来,我们会发现,他有时候是两个2,但是有时候是一个2,一个4,这个4出现的几率有点小,而且我们作为游戏规则制定者,这个也是我们控制的,这里,我不想他出现的很容易,所以我这里的逻辑就设置成1-9,这样4出现的概率会小很多,好了,基本确定了,我们就开始写代码了,我们写一个方法,在此之前,我们要对之前的代码进行调整一下,在CardView中setNum方法中,我们默认为0就占一格,

// 要呈现出来的文字(这里要注意是String类型的)if (num <= 0) {tv_num.setText("");} else {tv_num.setText(num + "");}
然后把刚才的文字设置换成0
// c.setNum(2);c.setNum(0);
然后我们就可以添加随机数了,我们新建一个方法addRandom();
    // 随机数private void addRandom() {// 我们新建一个lsit存放空的方块,操作之前清空point.clear();// 对所有的位置进行遍历for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {// 空方块才可以添加数字,有值我们就不添加if (cards[j][i].getNum() <= 0) {point.add(new Point(j, i));}}}// for循环走完之后我们要取方块Point p = point.remove((int) (Math.random() * point.size()));// 我们用Math.random()返回一个0-1的数,当大于0.1的时候是2否则就是4,也就是4出现的概率为十分之一cards[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4);}
这个时候我们就可以开始游戏了,为了方便等下我们需要重新开始游戏,我们就新建一个startGame()方法,让他在onSizeChanged()调用
// 开启游戏private void startGame() {// 既然是开始游戏,我们就要对所有的值进行清理for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {cards[j][i].setNum(0);}}// 重新添加随机数addRandom();// 我们要添加两个addRandom();}
好的,我们现在运行一下啊

这正是我们要的效果,每次进入游戏随机生成两个方块,他会出现在不同的位置,而且会出现2和4,4的概率小很多

七.实现方块滑动,递加逻辑

好的,终于到了核心的东西了,这次我们就要用到之前所写的上下左右方向方法了
// 左private void isLeft() {/*** 这里的逻辑有三种情况 1.左边为空,直接左滑到最后一格 2.左边碰到的第一个数是相等的,就相加 3.左边碰到的第一个数是不相等的,靠在旁边*/for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {// 往左滑是一行一行去遍历的for (int j2 = j + 1; j2 < 4; j2++) {// 如果说遍历到值if (cards[j2][i].getNum() > 0) {// 如果当前位置上为0,就放在这个位置上去if (cards[j][i].getNum() <= 0) {cards[j][i].setNum(cards[j2][i].getNum());// 把原来位置上的数字清除cards[j2][i].setNum(0);// 让图形继续遍历j--;// 有值,并且还相同} else if (cards[j][i].equals(cards[j2][i])) {// 合并,这里做了一个很巧妙的写法,我们相加,其实2048方块上的数字都是双倍的,所以我们只要原数据*2就可以了cards[j][i].setNum(cards[j][i].getNum() * 2);// 把原来位置上的数字清除cards[j2][i].setNum(0);}break;}}}}}// 右private void isRight() {/*** 这里的逻辑有三种情况 1.左边为空,直接左滑到最后一格 2.左边碰到的第一个数是相等的,就相加 3.左边碰到的第一个数是不相等的,靠在旁边*/for (int i = 0; i < 4; i++) {for (int j = 3; j >= 0; j--) {// 往左滑是一行一行去遍历的for (int j2 = j - 1; j2 >= 0; j2--) {// 如果说遍历到值if (cards[j2][i].getNum() > 0) {// 如果当前位置上为0,就放在这个位置上去if (cards[j][i].getNum() <= 0) {cards[j][i].setNum(cards[j2][i].getNum());// 把原来位置上的数字清除cards[j2][i].setNum(0);// 让图形继续遍历j++;// 有值,并且还相同} else if (cards[j][i].equals(cards[j2][i])) {// 合并,这里做了一个很巧妙的写法,我们相加,其实2048方块上的数字都是双倍的,所以我们只要原数据*2就可以了cards[j][i].setNum(cards[j][i].getNum() * 2);// 把原来位置上的数字清除cards[j2][i].setNum(0);}break;}}}}}// 上private void isTop() {/*** 这里的逻辑有三种情况 1.左边为空,直接左滑到最后一格 2.左边碰到的第一个数是相等的,就相加 3.左边碰到的第一个数是不相等的,靠在旁边*/for (int j = 0; j < 4; j++) {for (int i = 0; i < 4; i++) {// 往左滑是一行一行去遍历的for (int i2 = i + 1; i2 < 4; i2++) {// 如果说遍历到值if (cards[j][i2].getNum() > 0) {// 如果当前位置上为0,就放在这个位置上去if (cards[j][i].getNum() <= 0) {cards[j][i].setNum(cards[j][i2].getNum());// 把原来位置上的数字清除cards[j][i2].setNum(0);// 让图形继续遍历i--;// 有值,并且还相同} else if (cards[j][i].equals(cards[j][i2])) {// 合并,这里做了一个很巧妙的写法,我们相加,其实2048方块上的数字都是双倍的,所以我们只要原数据*2就可以了cards[j][i].setNum(cards[j][i].getNum() * 2);// 把原来位置上的数字清除cards[j][i2].setNum(0);}break;}}}}}// 下private void isButtom() {/*** 这里的逻辑有三种情况 1.左边为空,直接左滑到最后一格 2.左边碰到的第一个数是相等的,就相加 3.左边碰到的第一个数是不相等的,靠在旁边*/for (int j = 0; j < 4; j++) {for (int i = 3; i >= 0; i--) {// 往左滑是一行一行去遍历的for (int i2 = i - 1; i2 >= 0; i2--) {// 如果说遍历到值if (cards[j][i2].getNum() > 0) {// 如果当前位置上为0,就放在这个位置上去if (cards[j][i].getNum() <= 0) {cards[j][i].setNum(cards[j][i2].getNum());// 把原来位置上的数字清除cards[j][i2].setNum(0);// 让图形继续遍历i++;// 有值,并且还相同} else if (cards[j][i].equals(cards[j][i2])) {// 合并,这里做了一个很巧妙的写法,我们相加,其实2048方块上的数字都是双倍的,所以我们只要原数据*2就可以了cards[j][i].setNum(cards[j][i].getNum() * 2);// 把原来位置上的数字清除cards[j][i2].setNum(0);}break;}}}}}
四个方法的逻辑都是大同小异的,不过逻辑性还是很强的,大家可以适当的去研究一下然后我们多增加几个方块先来模拟下效果

八.计分

方块的逻辑差不多写完了,我们先来就在MainActivity里面来实现我们的Score计分

MainActivity

package com.lgl.game2048;import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;public class MainActivity extends Activity {private TextView tv_score;//外界可以访问的实例private static MainActivity mainActivity = null;//积分器private int score = 0;public MainActivity() {mainActivity = this;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv_score = (TextView) findViewById(R.id.tv_score);}public static MainActivity getMainActivity() {return mainActivity;}public static void setMainActivity(MainActivity mainActivity) {MainActivity.mainActivity = mainActivity;}//清除分数public void clearScore(){score = 0;showScore();}//分数public void showScore(){tv_score.setText("分数:"+score);}public void addScore(int s){score += s;showScore();}
}
这是我们计分的过程,我们思考一下在什么时候计分呢?想想就知道在滑动的时候俩值相加的时候开始计分,所以我们在四个滑动方法有值的判断句中添加
//开始计分                                     MainActivity.getMainActivity().addScore(cards[j][i].getNum());
同时,我们在开始游戏的时候要清零,所以我们在startGame方法中要添加
//计分清零
MainActivity.getMainActivity().clearScore();

九.滑动后增加方块

我们默认进来是两个方块,但是滑动之后我们应该也要随机增加方块才能达到游戏的逻辑,你说是吧!
所以,只要你滑动了,我们就要添加,一直到gameover结束为止,那我们依然在那四个方向方法里写
private void isxx(){// 加个判断是否可以添加boolean isAdd = false;for(....){for(....){for(....){if(....){if(....){....// 可以添加isAdd = true;}else if(....){....// 可以添加isAdd = true;}}}}}// 开始进行判断if (isAdd) {// 如果可以合并,我们添加随机数addRandom();}
}
好的,我们现在来运行一下

游戏现在大体上是OK的了

十.游戏结束

游戏有始有终,我们现在就来判断游戏结束,游戏结束有两个前提
1.16个格子都是满的
2.上下左右相邻的格子都不相同
这样的话我们就可以写一个endGame方法,然后让他在每次增加方块的时候调用了
// 游戏结束private void endGame() {// 在每次添加新的方块的时候判断一下// 是否结束?boolean isEnd = true;ALL: // 标签,让break跳出整个循环for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {// 等于0的话游戏没有结束,或者上下左右还是有相同的数if (cards[j][i].getNum() == 0// 左|| (j > 0 && cards[j][i].equals(cards[j - 1][i]))// 右|| (j < 3) && cards[j][i].equals(cards[j + 1][i])// 上|| (i > 0 && cards[j][i].equals(cards[j][i - 1]))// 下|| (i < 3 && cards[j][i].equals(cards[j][i + 1]))) {// 说明游戏没有结束isEnd = false;break ALL;}}}if (isEnd) {// 当isEnd = true的时候游戏结束new AlertDialog.Builder(getContext()).setTitle("Sorry,游戏结束!").setMessage("是否重新开始?").setPositiveButton("是",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {// 重新开始startGame();}}).setNegativeButton("否",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {}}).show();}}
好的,现在我们可以来检测一下了

十一.优化之色块

我们的数字每个数字代表一种颜色,这里我就简单的写点颜色,你们要是喜欢可以自己想改什么就改什么
在CardView中setnum方法里
        switch (num) {case 0:tv_num.setBackgroundColor(0xffBDB76A);break;case 2:tv_num.setBackgroundColor(0xffeee4da);break;case 4:tv_num.setBackgroundColor(0xffede0c8);break;case 8:tv_num.setBackgroundColor(0xfff2b179);break;case 16:tv_num.setBackgroundColor(0xfff59563);break;case 32:tv_num.setBackgroundColor(0xfff67c5f);break;case 64:tv_num.setBackgroundColor(0xfff65e3b);break;case 128:tv_num.setBackgroundColor(0xffedcf72);break;case 256:tv_num.setBackgroundColor(0xffedcc61);break;case 512:tv_num.setBackgroundColor(0xffedc850);break;case 1024:tv_num.setBackgroundColor(0xffedc53f);break;case 2048:tv_num.setBackgroundColor(0xffedc22e);break;default:tv_num.setBackgroundColor(0xff3c3a32);break;}
我们运行下

Demo下载地址:http://download.csdn.net/detail/qq_26787115/9409068

Android实训案例(四)——关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程!相关推荐

  1. Android实训案例(四)——关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程!...

    Android实训案例(四)--关于Game,2048方块的设计,逻辑,实现,编写.加上色彩.分数等深度剖析开发过程! 关于2048,我看到非常多大神,比方医生.郭神.所以我也研究了一段时间.还好是研 ...

  2. Android实训案例(九)——答题系统的思绪,自己设计一个题库的体验,一个思路清晰的答题软件制作过程

    Android实训案例(九)--答题系统的思绪,自己设计一个题库的体验,一个思路清晰的答题软件制作过程 项目也是偷师的,决心研究一下数据库.所以写的还是很详细的,各位看官,耐着性子看完,实现结果不重要 ...

  3. Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局

    Android实训案例(八)--单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局 阿法狗让围棋突然就被热议了,鸿洋大神也顺势出了篇五子棋单机游戏的视频,我看到了就像膜拜膜拜,就 ...

  4. Android实训案例(三)——实现时间轴效果的ListView,加入本地存储,实现恋爱日记的效果!

    Android实训案例(三)--实现时间轴效果的ListView,加入本地存储,实现恋爱日记的效果! 感叹离春节将至,也同时感叹时间不等人,一年又一年,可是我依然是android道路上的小菜鸟,这篇讲 ...

  5. Android实训案例(七)——四大组件之中的一个Service初步了解,实现通话录音功能,抽调接口...

    Service Service的奇妙之处.在于他不须要界面,一切的操作都在后台操作,所以非常多全局性(手机助手,语音助手)之类的应用非常长须要这个.我们今天也来玩玩 我们新建一个project--Se ...

  6. 构造中小型园区网实训案例

    构造中小型园区网实训案例 一.实验工具与实验拓扑规划 1.实验工具 2.实验拓扑 二.需求分析 三.数据规划 四.实施步骤 步骤1:配置所有终端 步骤2:配置所有接入层交换机 步骤3:配置网关路由器A ...

  7. android 实训的背景,Android实训项目作业.doc

    Android实训项目作业 2-1用整型数计算两个数的和2 2-7排列任意4个数的顺序,按从小到大顺序输出2 2.1编写显示下列图形的程序.2 3.1编写程序,当点击按钮命令后,页面标题及文本组件的文 ...

  8. Android实训课程之三 这次课老师先是提到了Android有四大组件:Activity、Service、Broadcast Receiver、Content Provider。然后重点提到Acti

    Android实训课程之三 这次课老师先是提到了Android有四大组件:Activity.Service.BroadcastReceiver.ContentProvider.然后重点提到Activi ...

  9. Python学习笔记7:实操案例四(支付密码的验证,模拟QQ账号登录,商品价格竞猜,星座看运势)

    Python学习笔记7:实操案例四(支付密码的验证,模拟QQ账号登录,商品价格竞猜,星座看运势) 1.支付密码的验证: 这个主要就是调用isdigit()函数判断字符串是不是全是数字组成. pwd=i ...

最新文章

  1. golang的mongodb操作(mgo)
  2. php round 取余,PHP round() 函数
  3. 20多年前我住的平房
  4. skimage 图像读取显示
  5. hibernate框架 最新_Java 15 个框架
  6. Think in Java ---Chapter 8 多态 [基础的混凝土大厦]
  7. GOF设计模式--简单工厂模式
  8. 游戏美术-魔兽世界查看器的正确使用方法
  9. Photoshop插件--创建中间调通道蒙板--脚本开发--PS插件
  10. 用 Dev-C++ 编写简单的平均数/中位数/众数/方差/一元线性回归方程计算器(附带控制台颜色设置,选择界面)
  11. 新手入门:Spring的一些学习方法及意见
  12. xtdpdgmm:动态面板数据模型一网打尽
  13. 我们梳理了国内外逾80家公司,发现6大关键趋势 | 36氪眼健康行业报告
  14. 「黑科技」盘点那些脑洞大开的人类设计的“异形”机器人
  15. 人工智能(AI)是如何处理数据的?
  16. Instance Tunnel 使用
  17. win10edge启用html5,Win 10 Edge浏览器极速运行的设置方法
  18. MSE(均方误差)函数和RMSE函数
  19. 青岛市城阳区2019年信息技术结业考试题目
  20. 【UPCOJ】问题 U: 打酱油

热门文章

  1. 【数学基础】德摩根定理
  2. SPSS如何进行均值比较和T检验之案例实训?
  3. onresize 事件动态获取窗口大小
  4. 猴子吃桃子问题(C语言)
  5. 多种多样的App主界面Tab实现方法(二):Fragment
  6. Lumus最新光波导模组:单目2K,PPD达60,亮度5000nit
  7. Conda虚拟环境部分操作
  8. 团队项目启动及成员分工
  9. sql2000之不足数自动补位、补零,左侧、右侧、中间 谢子圣 |2016-04-18 |3.8分(高于91.8%的文档)|414|11 |简介 |举报 手机打开...
  10. gmail的host地址_如何快速自定义您的Gmail地址