AndroidGame--水果连连看的设计(实现篇)

扎扎实实学基础,开开心心拆游戏。
作为android小菜鸟,qiaoidea在这里记录自己从零开始学游戏的开发旅程,欢迎关注和批评指正。


水果连连看


传送门:
水果连连看的设计(原理篇)
水果连连看的设计(实现篇)


(二)实现篇



简单阐述了先连连看游戏的设计原理,这里就着手开始实现吧。首先展示下整个项目工程包的框架:

AndroidGame--水果连连看的设计(实现篇)_第1张图片

其中, activity包内是主activty WelActivity(游戏菜单和游戏开始) 和 游戏完成MyDialog;
listener包内是为游戏视图和activity进行交互而定义的接口,由WelActivity继承实现;
sound包内是定义游戏音效播放的play类;
view包内则是自定义游戏视图BoardView和游戏控制视图GameView;


1.自定义游戏视图BoardView

BoardView 继承view类 , 在onDraw方法中实现棋盘的绘制。

成员变量:

	/**
	 * xCount x轴方向的图标数+1
	 */
	protected  static final int xCount =10;
	/**
	 * yCount y轴方向的图标数+1
	 */
	protected static final int  yCount =12;
	/**
	 * map 连连看游戏棋盘
	 */
	protected int[][] map = new int[xCount][yCount];
	/**
	 * iconSize 图标大小
	 */
	protected int iconSize;
	/**
	 * iconCounts 图标的数目
	 */
	protected int iconCounts=19;
	/**
	 * icons 所有的图片
	 */
	protected Bitmap[] icons = new Bitmap[iconCounts];
	
	/**
	 * path 可以连通点的路径
	 */
	private Point[] path = null;
	/**
	 * selected 选中的图标
	 */
	protected List<Point> selected = new ArrayList<Point>();
在构造函数中实现对图片大小iconSize和图片资源ImageResources的初始化。

计算图标size和获取图片资源的方法

	/**
	 * 
	 * 计算图标的长宽
	 */
	private void calIconSize()
    {
        DisplayMetrics dm = new DisplayMetrics();
        ((Activity) this.getContext()).getWindowManager()
		.getDefaultDisplay().getMetrics(dm);
        iconSize = dm.widthPixels/(xCount);
    }

	/**
	 * 
	 * @param key 特定图标的标识
	 * @param d drawable下的资源
	 */
	public void loadBitmaps(int key,Drawable d){
		Bitmap bitmap = Bitmap.createBitmap(iconSize,iconSize,Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(bitmap);
		d.setBounds(0, 0, iconSize, iconSize);
		d.draw(canvas);
		icons[key]=bitmap;
	}

onDraw()方法

(1)绘制棋盘
		/**
		 * 绘制棋盘的所有图标 当这个坐标内的值大于0时绘制
		 */
		for(int x=0;x<map.length;x+=1){
			for(int y=0;y<map[x].length;y+=1){
				if(map[x][y]>0){
					Point p = indextoScreen(x, y);
					canvas.drawBitmap(icons[map[x][y]], p.x,p.y,null);
				}
			}
		}

(2)对于选中的图片放大
		/**
		 * 绘制选中图标,当选中时图标放大显示
		 */
		for(Point position:selected){
			Point p = indextoScreen(position.x, position.y);
			if(map[position.x][position.y] >= 1){
				canvas.drawBitmap(icons[map[position.x][position.y]],
						null,
						new Rect(p.x-5, p.y-5, p.x + iconSize + 5, p.y + iconSize + 5), null);
			}
		}

(3)当有连通路径时,绘制连线
		/**
		 * 绘制连通路径,然后将路径以及两个图标清除
		 */
		if (path != null && path.length >= 2) {
			for (int i = 0; i < path.length - 1; i++) {
				Paint paint = new Paint();
				paint.setColor(Color.CYAN);
				paint.setStyle(Paint.Style.STROKE);
				paint.setStrokeWidth(3);
				Point p1 = indextoScreen(path[i].x, path[i].y);
				Point p2 = indextoScreen(path[i + 1].x, path[i + 1].y);
				canvas.drawLine(p1.x + iconSize / 2, p1.y + iconSize / 2,
						p2.x + iconSize / 2, p2.y + iconSize / 2, paint);
			}
			Point p = path[0];
			map[p.x][p.y] = 0;
			p = path[path.length - 1];
			map[p.x][p.y] = 0;
			selected.clear();
			path = null;
		}

数组坐标与屏幕位置的转换:

	/**
	 * 工具方法
	 * @param x 数组中的横坐标
	 * @param y 数组中的纵坐标
	 * @return 将图标在数组中的坐标转成在屏幕上的真实坐标
	 */
	public Point indextoScreen(int x,int y){
		return new Point(x* iconSize , y * iconSize );
	}
	/**
	 * 工具方法
	 * @param x 屏幕中的横坐标
	 * @param y 屏幕中的纵坐标
	 * @return 将图标在屏幕中的坐标转成在数组上的虚拟坐标
	 */
	public Point screenToindex(int x,int y){
		int ix = x/ iconSize;
		int iy = y / iconSize;
		if(ix < xCount && iy <yCount){
			return new Point( ix,iy);
		}else{
			return new Point(0,0);
		}
	}


1.游戏基本逻辑和控制视图GameView


GameView继承 BoardView 类 , 实现游戏的基本逻辑(点选和连线)以及控制(是否消除、时间控制、帮助和重置)。

成员变量:

	//游戏状态:
	public static final int WIN = 1;
	public static final int LOSE = 2;
	public static final int PAUSE = 3;
	public static final int PLAY = 4;
	public static final int QUIT = 5;
	//帮助和重置次数:
	private int Help = 3;
	private int Refresh = 3;
	/**
	 * 第一关为100秒钟的时间
	 */
	private int totalTime = 100;
	private int leftTime;
	/**
	 * 音乐控制
	 */
	public static SoundPlay soundPlay;
	public MediaPlayer player;
	/**
	 * 计时器线程
	 */
	private RefreshTime refreshTime;
	private RefreshHandler refreshHandler = new RefreshHandler();	
	private boolean isStop;
	/**
	 * 游戏交互监听接口
	 */
	private OnTimerListener timerListener = null;
	private OnStateListener stateListener = null;
	private OnToolsChangeListener toolsChangedListener = null;
	//连线路径点
	private List<Point> path = new ArrayList<Point>();
	/**
	 * 声音id
	 */
	public static final int ID_SOUND_CHOOSE = 0;
	public static final int ID_SOUND_DISAPEAR = 1;
	public static final int ID_SOUND_WIN = 4;
	public static final int ID_SOUND_LOSE = 5;
	public static final int ID_SOUND_REFRESH = 6;
	public static final int ID_SOUND_TIP = 7;
	public static final int ID_SOUND_ERROR = 8;

两个控制线程:

/**
	 * 检查状态及更新
	 */
	private static final int REFRESH_VIEW = 1;
	class RefreshHandler extends Handler {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			if (msg.what == REFRESH_VIEW) {
				GameView.this.invalidate();
				if (win()) {
					setMode(WIN);
					soundPlay.play(ID_SOUND_WIN, 0);
					isStop = true;
				} else if (die()) {
					change();
				}
			}
		}

		public void sleep(int delayTime) {
			this.removeMessages(0);
			Message message = new Message();
			message.what = REFRESH_VIEW;
			sendMessageDelayed(message, delayTime);
		}
	}


	/**
	 * 计时线程
	 */
	class RefreshTime extends Thread {

		public void run() {
			while (leftTime >= 0 && !isStop) {
				timerListener.onTimer(leftTime);
				leftTime--;
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if(!isStop){
				setMode(LOSE);
				soundPlay.play(ID_SOUND_LOSE, 0);
			}
			
		}
	}

map数组初始化及控制

	/**
	 * 初始化map数组,
	 * 控制y=0,1来保证相同图片成偶数出现
	 * change交换图片产生随机序列。
	 */
	public void initMap() {
		int x = 1;
		int y = 0;
		for (int i = 1; i < xCount - 1; i++) {
			for (int j = 1; j < yCount - 1; j++) {
				map[i][j] = x;
				if (y == 1) {
					x++;
					y = 0;
					if (x == iconCounts) {
						x = 1;
					}
				} else {
					y = 1;
				}
			}
		}
		change();
	}

	private void change() {
		Random random = new Random();
		int tmpV, tmpX, tmpY;
		for (int x = 1; x < xCount - 1; x++) {
			for (int y = 1; y < yCount - 1; y++) {
				tmpX = 1 + random.nextInt(xCount - 2);
				tmpY = 1 + random.nextInt(yCount - 2);
				tmpV = map[x][y];
				map[x][y] = map[tmpX][tmpY];
				map[tmpX][tmpY] = tmpV;
			}
		}
		if (die()) {
			change();
		}
		GameView.this.invalidate();
	}
其中,每次结束后进行检查,是否存在无法消除 die()(即有可能是成偶出现但是连接路径均大于规则要求),若存在则重新布局排列

两点间的连接检查

原理篇已讲解,这里不再赘述。

	List<Point> p1E = new ArrayList<Point>();
	List<Point> p2E = new ArrayList<Point>();

	private boolean link(Point p1, Point p2) {
		if (p1.equals(p2)) {	//如果两点为同一点
			return false;
		}
		path.clear();
		if (map[p1.x][p1.y] == map[p2.x][p2.y]) {
			if (linkD(p1, p2)) { //如果两点可直连
				path.add(p1);
				path.add(p2);
				return true;
			}

			/**
			 * 检查这两个点的连线转折点p
			 * 看p是否能与他们直连
			 */
			Point p = new Point(p1.x, p2.y);
			if (map[p.x][p.y] == 0) {
				if (linkD(p1, p) && linkD(p, p2)) {
					path.add(p1);
					path.add(p);
					path.add(p2);
					return true;
				}
			}
			p = new Point(p2.x, p1.y);
			if (map[p.x][p.y] == 0) {
				if (linkD(p1, p) && linkD(p, p2)) {
					path.add(p1);
					path.add(p);
					path.add(p2);
					return true;
				}
			}
			
			/**
			 * 检查这两个点的可延长连通点(即为空的可扩展点list1/list2)
			 * 判断两个list上是否存在两点pt1和pt2可直连
			 */
			expandX(p1, p1E);
			expandX(p2, p2E);
			for (Point pt1 : p1E) {
				for (Point pt2 : p2E) {
					if (pt1.x == pt2.x) {
						if (linkD(pt1, pt2)) {
							path.add(p1);
							path.add(pt1);
							path.add(pt2);
							path.add(p2);
							return true;
						}
					}
				}
			}

			expandY(p1, p1E);
			expandY(p2, p2E);
			for (Point pt1 : p1E) {
				for (Point pt2 : p2E) {
					if (pt1.y == pt2.y) {
						if (linkD(pt1, pt2)) {
							path.add(p1);
							path.add(pt1);
							path.add(pt2);
							path.add(p2);
							return true;
						}
					}
				}
			}
			return false;
		}
		return false;
	}

	//判断是否可直连
	private boolean linkD(Point p1, Point p2) {
		if (p1.x == p2.x) {
			int y1 = Math.min(p1.y, p2.y);
			int y2 = Math.max(p1.y, p2.y);
			boolean flag = true;
			for (int y = y1 + 1; y < y2; y++) {
				if (map[p1.x][y] != 0) {
					flag = false;
					break;
				}
			}
			if (flag) {
				return true;
			}
		}
		if (p1.y == p2.y) {
			int x1 = Math.min(p1.x, p2.x);
			int x2 = Math.max(p1.x, p2.x);
			boolean flag = true;
			for (int x = x1 + 1; x < x2; x++) {
				if (map[x][p1.y] != 0) {
					flag = false;
					break;
				}
			}
			if (flag) {
				return true;
			}
		}
		return false;
	}

	//该方向上可扩展延伸的点
	private void expandX(Point p, List<Point> l) {
		l.clear();
		for (int x = p.x + 1; x < xCount; x++) {
			if (map[x][p.y] != 0) {
				break;
			}
			l.add(new Point(x, p.y));
		}
		for (int x = p.x - 1; x >= 0; x--) {
			if (map[x][p.y] != 0) {
				break;
			}
			l.add(new Point(x, p.y));
		}
	}

	private void expandY(Point p, List<Point> l) {
		l.clear();
		for (int y = p.y + 1; y < yCount; y++) {
			if (map[p.x][y] != 0) {
				break;
			}
			l.add(new Point(p.x, y));
		}
		for (int y = p.y - 1; y >= 0; y--) {
			if (map[p.x][y] != 0) {
				break;
			}
			l.add(new Point(p.x, y));
		}
	}

用户点击事件

	/**
	 * 用户点击事件
	 * 判断是否可点击,点击选中后声音
	 * 选中前后点可连通判断
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int x = (int) event.getX();
		int y = (int) event.getY();
		Point p = screenToindex(x, y);
		if (map[p.x][p.y] > 0) {
			if (selected.size() == 1) {
				if (link(selected.get(0), p)) {
					selected.add(p);
					drawLine(path.toArray(new Point[] {}));
					soundPlay.play(ID_SOUND_DISAPEAR, 0);
					refreshHandler.sleep(500);
				} else {
					selected.clear();
					selected.add(p);
					soundPlay.play(ID_SOUND_CHOOSE, 0);
					GameView.this.invalidate();
				}
			} else {
				selected.add(p);
				soundPlay.play(ID_SOUND_CHOOSE, 0);
				GameView.this.invalidate();
			}
		}
		return super.onTouchEvent(event);
	}

判断是否存在die()并检出一对可消除点

	private boolean die() {
		for (int y = 1; y < yCount - 1; y++) {
			for (int x = 1; x < xCount - 1; x++) {
				if (map[x][y] != 0) {
					for (int j = y; j < yCount - 1; j++) {
						if (j == y) {
							for (int i = x + 1; i < xCount - 1; i++) {
								if (map[i][j] == map[x][y]
										&& link(new Point(x, y),
												new Point(i, j))) {
									return false;
								}
							}
						} else {
							for (int i = 1; i < xCount - 1; i++) {
								if (map[i][j] == map[x][y]
										&& link(new Point(x, y),
												new Point(i, j))) {
									return false;
								}
							}
						}
					}
				}
			}
		}
		return true;
	}

自动消除和重新布局重置map选项,供activity按键调用

	//自动消除
	public void autoClear() {
		if (Help == 0) {
			soundPlay.play(ID_SOUND_ERROR, 0);
		}else{
			soundPlay.play(ID_SOUND_TIP, 0);
			Help--;
			toolsChangedListener.onTipChanged(Help);
			drawLine(path.toArray(new Point[] {}));
			refreshHandler.sleep(500);
		}
	}
	
	//重置map布局
	public void refreshChange(){
		if(Refresh == 0){
			soundPlay.play(ID_SOUND_ERROR, 0);
			return;
		}else{
			soundPlay.play(ID_SOUND_REFRESH, 0);
			Refresh--;
			toolsChangedListener.onRefreshChanged(Refresh);
			change();
		}
	}

Activity展示及soundplay控制不作为重点讲述,可自行在源码中查看。





你可能感兴趣的:(游戏,android,水果连连看)