学习android有一段时间了,感觉android的基础知识已经差不多学会了。 现在我感觉自己遇到了一些困难,所以就找了一些小项目去实践、实践,让自己知道自己知道什么,不知道什么。 本文是个人总结很久之前制作的一款小游戏——2048,附源码和项目视频。
开发环境:windows 7 集成开发环境:eclipse/Android studio sdk:android 4.3
视频及源码链接: 链接: 提取码:z8m0
如果失败请联系我更新。 强烈建议按照源码或者视频,并以本文作为辅助学习。
2048是一款以前非常流行的小游戏。 本项目用到的主要知识点有:如何创建游戏项目、如何管理游戏布局、如何编译游戏主类GameView、如何在android平台上设计触摸交互、编写2048游戏卡Card类,向场景添加数字卡,在游戏中添加随机数,实现游戏逻辑(最重要的部分),检查游戏得分,以及如何判断游戏是否结束。
2048的玩法这里就不介绍了(必须了解玩法才能设计游戏)。 这篇博客主要是讲解游戏的算法。 通过该算法,可以方便地移植到其他平台。
为了防止文章过长,本文只介绍关键部分。 不是很关键的细节请参考源代码(有详细注释)和视频。
首先创建一个项目,然后设计2048的游戏布局(activity_main),上面是一个用于计分的LinearLayout,下面是游戏布局GameView,继承自GridLayout。 布局效果如下:
接下来,实现主游戏类 GameView(在 xml 文件中访问它)。 GameView继承自GridLayout,用于放置2048游戏的16张牌。 关于GridLayout的使用,推荐一篇博客:
下面是关于2048游戏的触摸交互设计。 无非就是测量手腕的上下运动,利用坐标的变化值来判断手腕的上、下、左、右滑动。 这里需要注意一点:解决中指斜滑如何判断时间的问题。 触摸交互的具体代码如下,相信也很容易理解:
setOnTouchListener(new View.OnTouchListener() { // 设置交互方式
// 监听上下左右滑动的这几个动作,再由这几个动作去执行特定的代码,去实现游戏的逻辑
private float startX, startY, offsetX, offsetY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
offsetX = event.getX() - startX;
offsetY = event.getY() - startY;
if (Math.abs(offsetX) > Math.abs(offsetY)) { // 加此判断是为了解决当用户向斜方向滑动时程序应如何判断的问题
if (offsetX 5) {
swipeRight();
}
} else { // 判断向上向下
if (offsetY 5) {
swipeDown();
}
}
break;
}
return true; // 此处必须返回true
}
});
接下来,实现2048游戏的卡牌类,在GameView中添加16张卡牌对象(继承自GridLayout),新建一个Card类,继承自Framelayout安卓手机小游戏源码在哪,每张卡牌必须有一个显示的数字,并且该类都有成员变量:
私有 int num = 0;
私有 TextView 标签; //卡片需要显示文字
具体代码如下:
public class Card extends FrameLayout {
private int num = 0;
private TextView label; //卡片需要呈现文字
public Card(Context context) {
super(context);
label = new TextView(getContext()); //初始化
label.setTextSize(32); //设置文本大小
label.setBackgroundColor(0x33ffffff); //设置文字背景或颜色
label.setGravity(Gravity.CENTER); //居中文字显示 否则数字都在卡片的左上角
LayoutParams lp = new LayoutParams(-1, -1); //设置布局参数 填充满整个父级容器
lp.setMargins(10, 10, 0, 0); //设置文字间的间隔 用以区分各个card
addView(label, lp);
setNum(0); //默认情况下卡片数字为0 !!!!!!!!!顺序不能错 否则会出bug
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
if(num <= 0){
label.setText(""); //如果卡片中的数字是0,则不显示
}else {
label.setText(num+""); //如果卡片中的数字不是0,则显示,此时num(int)会变成一个字符串
}
}
public boolean equals(Card card) { //判断两个卡片上的数字是否相同
return this.getNum() == card.getNum();
}
}
下一步其实就是在布局中添加卡片,在4*4的方阵中添加16张卡片。 为了解决适配不同手机帧率的问题,无法给出具体的宽高值,需要动态的。 估计卡片的宽度和高度是通过重绘onSizeChanged(int w, int h, int oldw, int oldh)来实现的。 计算布局的宽高并向GameView添加16张牌的具体代码如下:
// 只有第一次创建的时候才会执行一次 只可能会执行一次
// 手机横放的时候不会执行,因为布局宽高不会发生改变,在AndroidManifest文件中配置了横放手机布局不变的参数
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { // 动态计算卡片宽高
super.onSizeChanged(w, h, oldw, oldh);
int cardWidth = (Math.min(w, h) - 10) / 4; //留出图片和边缘的大小
int cardHeight = cardWidth;
addCards(cardWidth, cardHeight);
startGame(); // 这里开启游戏
// 因为第一次创建游戏时,onSizeChanged()会被调用,且仅被调用一次,所以在这里开启游戏很合适
}
private void addCards(int cardWidth, int cardHeight) {
Card card;
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
card = new Card(getContext());
card.setNum(0); // 刚开始全部添加0 此时并不会显示数字
addView(card, cardWidth, cardHeight); // card是一个继承自FrameLayout的View
// 在initGameView()中指明这个GridLayout是四列的方阵
cardsMap[x][y] = card; //用一个二维数组来记录下这16个卡片
}
}
}
这里需要注意的是,因为当手机水平放置时,手机的布局(宽度和高度)可能会发生变化。 这时,onSizeChanged(int w, int h, int oldw, int oldh)方法会被调用,但我不希望它被调用,所以需要配置它。 在AndroidManifest文件中设置屏幕仍处于直立状态。 代码如下:
android:screenOrientation="portrait"
这样,onSizeChanged(int w, int h, int oldw, int oldh)只会执行一次安卓手机小游戏源码在哪,并且只有在第一次创建布局时才会被调用执行,当布局被创建时才会被调用执行。手机水平放置。
接下来,分别设置GridLayout和Card的背景颜色。 这时候就需要注意给Card的背景颜色留出一定的余量。 设置卡片颜色的代码如下:
LayoutParams lp = new LayoutParams(-1, -1); //设置布局参数 填充满整个父级容器
lp.setMargins(10, 10, 0, 0); //设置文字间的间隔 用以区分各个card
addView(label, lp); //lable是一个TextView
此时每张卡都可以清晰区分。
接下来,让我们添加随机数。 首先将所有的位置(空点的位置)放在一个字段上,这样方便随机访问。 新建一个List来存储空点的位置,并将所有的位置(空点的位置)全部放在一个字段中,方便随机访问:
private List emptyPoints = new ArrayList();
添加随机数的代码如下:
private void addRandomNum() { // 添加随机数 首先需要遍历所有卡片
emptyPoints.clear(); // 添加随机数之前先清空emptyPoints,然后把每一个空点都添加进来
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
if (cardsMap[x][y].getNum() 0.1 ? 2 : 4); // 给这个空点添加一个数,2或4,概率为9:1
}
接下来就是游戏逻辑中最重要的部分了! ! ! !
以右手向左滑动为例(其他方向类似),这里需要实现游戏逻辑,包括游戏逻辑、游戏计分逻辑以及判断游戏是否结束的逻辑。 代码如下所示:
// 实现游戏逻辑 只要有位置的改变就添加新的 最重要的部分:游戏逻辑
private void swipeLeft() {
boolean merge = false; // 判断是否有合并,如果有的话就进行一些处理
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
for (int x1 = x + 1; x1 0) { // 如果往右去遍历得到的card的值(获取到的值)不是空的,则做如下逻辑判断
if (cardsMap[x][y].getNum() <= 0) { // 如果当前位置上的值是空的,则将获取到的值移动到当前位置上
cardsMap[x][y].setNum(cardsMap[x1][y].getNum());
cardsMap[x1][y].setNum(0);
x--; // 这里非常重要!!!! 可以测试理解
merge = true;
} else if (cardsMap[x][y].equals(cardsMap[x1][y])) { // 如果当前位置上的值不是空的,而且获取到的值与当前位置上的值相等,则做相加处理,并将结果放在当前位置上
cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);
cardsMap[x1][y].setNum(0);
// 合并时加分 有合并就有添加(分数)
MainActivity.getMainActivity().addScore(
cardsMap[x][y].getNum());
merge = true;
}
break; // 这个break的位置非常重要!!!!! 只能写在这里!! eg:方格最下面一行是2 32
// 64 2,然后往左滑动的情况!
}
}
}
}
if (merge) { // 在添加数字时判断游戏是否结束
addRandomNum();
checkComplete(); // 添加新项后都要检查游戏是否结束:没空位置,而且已经不能再合并
}
}
简单说一下这里实现的游戏算法:首先使用for循环逐行遍历每张卡牌,然后从当前位置向右遍历,并判断如果某个值不为空,则有两个。首先是当前位置的值为空,此时将获取到的值放到当前位置,同时清除获取到的位置上的数字; 二是当前位置的值不为空,如果得到的值与当前位置的值相同,则将两张卡合并,将当前位置的值除以二,并清除当前位置的数字获得的位置。
还有一种情况是,如果我们当前位置为空,然后把左边的值放到当前位置,继续遍历到前面(右边),此时后面的位置还是空的,然后左边如果还有另一个数字与前一个数字相同,也被放置在这个空位置上。 这时候就会出现一种情况:这两个数实际上是相同的,但是没有合并。 为了防止这种情况发生,我们让它再遍历一遍,也就是让x--,这样这个问题就解决了。
这里还有一个很关键的点:注意break的位置,写在别处是错误的,应该是只要测量值不为空,无论合并与否,都应该break ,这里可能比较难理解,最好自己测试一下。
这里还设置了一个布尔标志merge,记录卡片是否合并。 如果牌被合并,则必须添加一个随机数来确定游戏是否结束。 判断游戏是否结束的逻辑代码如下:
// 判断游戏是否结束的逻辑
private void checkComplete() {
boolean complete = true;
All: for (int y = 0; y < 4; y++) {
for (int x = 0; x 0 && cardsMap[x][y].equals(cardsMap[x - 1][y]))
|| (x 0 && cardsMap[x][y].equals(cardsMap[x][y - 1]))
|| (y < 3 && cardsMap[x][y].equals(cardsMap[x][y + 1]))) {
complete = false;
break All; // 写一个标签,跳出所有循环
}
}
}
if (complete) {
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
dialog.setTitle("你好")
.setMessage("游戏结束")
.setPositiveButton("重来",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
startGame();
}
});
dialog.setNegativeButton("关闭程序", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MainActivity.getMainActivity().finish();
}
});
dialog.show();
}
}
这样,游戏的基本逻辑代码就实现了。 此时仅实现了游戏的基本逻辑,游戏还可以继续优化、美化、添加功能。 如果你对上一篇文章不是很清楚,建议结合上一个链接中的视频和源码来理解。 如果链接失效,请联系我更新。 相信这还是一个非常适合入门的小型Android项目。
发表评论