安卓手机小游戏源码在哪-Android项目开发-2048小游戏

学习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项目。