Android 带你玩转实现游戏2048 其实2048只是个普通的控件

mBgColor = “#ECB04D”;

break;

case 1024:

mBgColor = “#EB9437”;

break;

case 2048:

mBgColor = “#EA7821”;

break;

default:

mBgColor = “#EA7821”;

break;

}

mPaint.setColor(Color.parseColor(mBgColor));

mPaint.setStyle(Style.FILL);

canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);

if (mNumber != 0)

drawText(canvas);

}

/**

  • 绘制文字

  • @param canvas

*/

private void drawText(Canvas canvas)

{

mPaint.setColor(Color.BLACK);

float x = (getWidth() - mBound.width()) / 2;

float y = getHeight() / 2 + mBound.height() / 2;

canvas.drawText(mNumberVal, x, y, mPaint);

}

}

很简单,基本就一个onDraw通过number来绘制背景和数字;number通过调用setNumer进行设置;它的宽和高都是固定值,所以我们并不需要自己进行测量~~

2、Game2048Layout


1、成员变量

这就是我们最主要的一个类了,首先我们看看这个类的成员变量,先看看各个成员变量的作用:

/**

  • 设置Item的数量n*n;默认为4

*/

private int mColumn = 4;

/**

  • 存放所有的Item

*/

private Game2048Item[] mGame2048Items;

/**

  • Item横向与纵向的边距

*/

private int mMargin = 10;

/**

  • 面板的padding

*/

private int mPadding;

/**

  • 检测用户滑动的手势

*/

private GestureDetector mGestureDetector;

// 用于确认是否需要生成一个新的值

private boolean isMergeHappen = true;

private boolean isMoveHappen = true;

/**

  • 记录分数

*/

private int mScore;

主要的成员变量就这些,直接看注释也比较容易理解~~

了解了成员变量,接下来我们需要在构造方法里面得到一些值和初始化一些变量

2、构造方法

public Game2048Layout(Context context, AttributeSet attrs, int defStyle)

{

super(context, attrs, defStyle);

mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,

mMargin, getResources().getDisplayMetrics());

// 设置Layout的内边距,四边一致,设置为四内边距中的最小值

mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(),

getPaddingBottom());

mGestureDetector = new GestureDetector(context , new MyGestureDetector());

}

我们在构造方法里面得到Item间的边距(margin)和我们容器的内边距(padding,),这个值应该四边一致,于是我们取四边的最小值;这两个属性可以抽取为自定义的属性;然后初始化了我们的mGestureDetector

有了margin和padding,我们就可以计算我们item的边长了。这个计算过程肯定在onMeasure里面,因为我们需要在onMeasure获取容器的宽和高

3、onMeasure

private boolean once;

/**

  • 测量Layout的宽和高,以及设置Item的宽和高,这里忽略wrap_content 以宽、高之中的最小值绘制正方形

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

{

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// 获得正方形的边长

int length = Math.min(getMeasuredHeight(), getMeasuredWidth());

// 获得Item的宽度

int childWidth = (length - mPadding * 2 - mMargin * (mColumn - 1))

/ mColumn;

if (!once)

{

if (mGame2048Items == null)

{

mGame2048Items = new Game2048Item[mColumn * mColumn];

}

// 放置Item

for (int i = 0; i < mGame2048Items.length; i++)

{

Game2048Item item = new Game2048Item(getContext());

mGame2048Items[i] = item;

item.setId(i + 1);

RelativeLayout.LayoutParams lp = new LayoutParams(childWidth,

childWidth);

// 设置横向边距,不是最后一列

if ((i + 1) % mColumn != 0)

{

lp.rightMargin = mMargin;

}

// 如果不是第一列

if (i % mColumn != 0)

{

lp.addRule(RelativeLayout.RIGHT_OF,//

mGame2048Items[i - 1].getId());

}

// 如果不是第一行,//设置纵向边距,非最后一行

if ((i + 1) > mColumn)

{

lp.topMargin = mMargin;

lp.addRule(RelativeLayout.BELOW,//

mGame2048Items[i - mColumn].getId());

}

addView(item, lp);

}

generateNum();

}

once = true;

setMeasuredDimension(length, length);

}

首先设置容器的边长为宽高中的最小值;然后(length - mPadding * 2 - mMargin * (mColumn - 1)) / mColumn ; 去计算Item的边长;

拿到以后,根据我们的mColumn初始化我们的Item数组,然后遍历生成Item,设置Item的LayoutParams以及Rule(RIGHT_OF , BELOW),最后添加到我们的容器中;

最后我们通过setMeasuredDimension(length, length);改变我们布局占据的空间;

到此,我们整个面板绘制完成了;

接下来,就是根据用户的手势,去进行游戏逻辑操作了,手势那么肯定是onTouchEvent了:

4、onTouchEvent

@Override

public boolean onTouchEvent(MotionEvent event)

{

mGestureDetector.onTouchEvent(event);

return true;

}

我们把触摸事件交给了mGestureDetector,我们去看看我们的mGestureDetector,在构造方法中有这么一句:

mGestureDetector = new GestureDetector(context , new MyGestureDetector());

so,我们需要去看看MyGestureDetector:

class MyGestureDetector extends GestureDetector.SimpleOnGestureListener

{

final int FLING_MIN_DISTANCE = 50;

@Override

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,

float velocityY)

{

float x = e2.getX() - e1.getX();

float y = e2.getY() - e1.getY();

if (x > FLING_MIN_DISTANCE

&& Math.abs(velocityX) > Math.abs(velocityY))

{

action(ACTION.RIGHT);

} else if (x < -FLING_MIN_DISTANCE

&& Math.abs(velocityX) > Math.abs(velocityY))

{

action(ACTION.LEFT);

} else if (y > FLING_MIN_DISTANCE

&& Math.abs(velocityX) < Math.abs(velocityY))

{

action(ACTION.DOWM);

} else if (y < -FLING_MIN_DISTANCE

&& Math.abs(velocityX) < Math.abs(velocityY))

{

action(ACTION.UP);

}

return true;

}

}

很简单,就是判读用户上、下、左、右滑动;然后去调用action(ACTION)方法;ACTION是个枚举:

/**

  • 运动方向的枚举

  • @author zhy

*/

private enum ACTION

{

LEFT, RIGHT, UP, DOWM

}

这么看,核心代码都在action方法里面了:

5、根据用户手势重绘Item

看代码前,先考虑下,用户从右向左滑动时,面板应该如何变化;取其中一行,可能性为:

0 0 0 2 -> 2 0 0 0

2 0 4 0 -> 2 4 0 0

2 2 4 0 -> 4 4 0 0

大概就这3中可能;

我们算法是这么做的:

拿2 2 4 0 来说:

1、首先把每行有数字的取出来,临时存储下来,即[ 2, 2, 4 ];

2、然后遍历合并第一个相遇的相同的,即[ 4, 4 ,0 ]

3、然后直接放置到原行,不足补0,即[ 4, 4, 0 ,0 ];

中间还有几个操作:

1、生成一个新的数字,游戏在每次用户滑动时,可能会生成一个数字;我们的生成策略:如果发生移动或者合并,则生成一个数字;

移动的判断,拿原数据,即【 2 ,2,4,0】和我们第一步临时存储的做比较,一一对比(遍历临时表),发现不同,则认为移动了;

合并的判断,在合并的时候会设置合并的标志位为true;

2、加分,如果发生合并,则加分,分值为合并得到的数字,比如 4,4 -> 8 ,即加8分 ; 也只需要在合并的时候进行相加就行了;

介绍完了,来看我们的代码:

/**

  • 根据用户运动,整体进行移动合并值等

*/

private void action(ACTION action)

{

// 行|列

for (int i = 0; i < mColumn; i++)

{

List row = new ArrayList();

// 行|列

for (int j = 0; j < mColumn; j++)

{

// 得到下标

int index = getIndexByAction(action, i, j);

Game2048Item item = mGame2048Items[index];

// 记录不为0的数字

if (item.getNumber() != 0)

{

row.add(item);

}

}

for (int j = 0; j < mColumn && j < row.size(); j++)

{

int index = getIndexByAction(action, i, j);

Game2048Item item = mGame2048Items[index];

if (item.getNumber() != row.get(j).getNumber())

{

isMoveHappen = true;

}

}

// 合并相同的

mergeItem(row);

// 设置合并后的值

for (int j = 0; j < mColumn; j++)

{

int index = getIndexByAction(action, i, j);

if (row.size() > j)

{

mGame2048Items[index].setNumber(row.get(j).getNumber());

} else

{

mGame2048Items[index].setNumber(0);

}

}

}

generateNum();

}

大体上是两层循环,外层循环代码循环次数,内层有3个for循环;

第一个for循环,对应上述:首先把每行有数字的取出来,临时存储下来,即[ 2, 2, 4 ];

第二个for循环,判断是否发生移动;

// 合并相同的

mergeItem(row); 是去进行合并操作,对应上述:然后遍历合并第一个相遇的相同的,即[ 4, 4 ,0 ];以及加分和设置合并标志位都在方法中;

第三个for循环:设置合并后的值,对应上述:然后直接放置到原行,不足补0,即[ 4, 4, 0 ,0 ];

最后生成数字,方法内部会进行判断游戏是否结束,是否需要生成数字;

那么先看mergeItem的代码:

/**

  • 合并相同的Item

  • @param row

*/

private void mergeItem(List row)

{

if (row.size() < 2)

return;

for (int j = 0; j < row.size() - 1; j++)

{

Game2048Item item1 = row.get(j);

Game2048Item item2 = row.get(j + 1);

if (item1.getNumber() == item2.getNumber())

{

isMergeHappen = true;

int val = item1.getNumber() + item2.getNumber();

item1.setNumber(val);

// 加分

mScore += val;

if (mGame2048Listener != null)

{

mGame2048Listener.onScoreChange(mScore);

}

// 向前移动

for (int k = j + 1; k < row.size() - 1; k++)

{

row.get(k).setNumber(row.get(k + 1).getNumber());

}

row.get(row.size() - 1).setNumber(0);

return;

}

}

}

也比较简单,循环查找相同的number,发现合并数字,加分;

加分我们设置了一个回调,把分数回调出去:

if (mGame2048Listener != null)

{

mGame2048Listener.onScoreChange(mScore);

}

最后看我们生成数字的代码:

/**

  • 产生一个数字

*/

public void generateNum()

{

if (checkOver())

{

Log.e(“TAG”, “GAME OVER”);

if (mGame2048Listener != null)

{

mGame2048Listener.onGameOver();

}

return;

}

if (!isFull())

{

if (isMoveHappen || isMergeHappen)

{

Random random = new Random();

int next = random.nextInt(16);

Game2048Item item = mGame2048Items[next];

while (item.getNumber() != 0)

{

next = random.nextInt(16);

item = mGame2048Items[next];

}

item.setNumber(Math.random() > 0.75 ? 4 : 2);

isMergeHappen = isMoveHappen = false;

}

}

}

首先判断是否结束,如果结束了,依然是回调出去,得让玩的人知道结束了;

然后判断当然面板是有木有空的格子,如果没有,在判断需要生成新的数字么,需要则随机生成一个新的2或4;

那么如何判断是否结束呢?

首先肯定是没有空格了,然后四个方向上没有相同的数字就结束了:

/**

  • 检测当前所有的位置都有数字,且相邻的没有相同的数字

  • @return

*/

private boolean checkOver()

{

// 检测是否所有位置都有数字

if (!isFull())

{

return false;

}

for (int i = 0; i < mColumn; i++)

{

for (int j = 0; j < mColumn; j++)

{

int index = i * mColumn + j;

// 当前的Item

Game2048Item item = mGame2048Items[index];

// 右边

if ((index + 1) % mColumn != 0)

{

Log.e(“TAG”, “RIGHT”);

// 右边的Item

Game2048Item itemRight = mGame2048Items[index + 1];

if (item.getNumber() == itemRight.getNumber())

return false;

}

// 下边

if ((index + mColumn) < mColumn * mColumn)

{

Log.e(“TAG”, “DOWN”);

Game2048Item itemBottom = mGame2048Items[index + mColumn];

if (item.getNumber() == itemBottom.getNumber())

return false;

}

// 左边

if (index % mColumn != 0)

{

Log.e(“TAG”, “LEFT”);

Game2048Item itemLeft = mGame2048Items[index - 1];

if (itemLeft.getNumber() == item.getNumber())

return false;

}

// 上边

if (index + 1 > mColumn)

{

Log.e(“TAG”, “UP”);

Game2048Item itemTop = mGame2048Items[index - mColumn];

if (item.getNumber() == itemTop.getNumber())

return false;

}

}

}

return true;

}

/**

  • 是否填满数字

  • @return

*/

private boolean isFull()

{

// 检测是否所有位置都有数字

for (int i = 0; i < mGame2048Items.length; i++)

{

if (mGame2048Items[i].getNumber() == 0)

{

return false;

}

}

return true;

}

到此,我们的代码介绍完毕~~~完成了我们的Game2048Layout ; 接下来看如何使用呢?

写游戏的过程很艰辛,但是用起来,看看什么叫so easy ; 当成普通的View即可:

4、实践

====

1、布局文件:


<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:tools=“http://schemas.android.com/tools”

android:layout_width=“fill_parent”

android:layout_height=“fill_parent” >

<com.zhy.game2048.view.Game2048Layout

android:id=“@+id/id_game2048”

android:layout_width=“fill_parent”

android:layout_height=“fill_parent”

android:layout_centerInParent=“true”

android:background=“#ffffff”

android:padding=“10dp” >

</com.zhy.game2048.view.Game2048Layout>

<LinearLayout

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_above=“@id/id_game2048”

android:layout_centerHorizontal=“true”

android:layout_marginBottom=“20dp”

android:background=“#EEE4DA”

android:orientation=“horizontal” >

<TextView

android:id=“@+id/id_score”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:padding=“4dp”

android:text=“Score: 0”

android:textColor=“#EA7821”

android:textSize=“30sp”

android:textStyle=“bold” />

2、MainActivity


package com.zhy.game2048;

import android.app.Activity;

import android.app.AlertDialog;

import android.content.DialogInterface;

import android.content.DialogInterface.OnClickListener;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的百度、腾讯、网易、字节跳动、阿里等公司2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

【Android高级架构视频学习资源】

**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-H5pV7U1F-1713755581706)]

[外链图片转存中…(img-tmMi1sFB-1713755581708)]

[外链图片转存中…(img-YWWHGDFO-1713755581709)]

[外链图片转存中…(img-R89q6Zde-1713755581710)]

[外链图片转存中…(img-phAjyUUS-1713755581711)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

[外链图片转存中…(img-nu81gKMb-1713755581712)]

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的百度、腾讯、网易、字节跳动、阿里等公司2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-AQlNVVFy-1713755581713)]

【Android高级架构视频学习资源】

**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 28
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值