A. 项目描述
"水果连连看"是一款经典的休闲益智小游戏。游戏的目标是通过连接相同的水果图标来消除它们,最终清空整个游戏界面。它以其简单易懂的规则和有趣的玩法而受到许多玩家的喜爱。
让我们来梳理一下这款小游戏的功能需求。
- 游戏规则和目标:
- 游戏采用经典的连连看玩法,玩家需要在限定时间内连接相同的水果。
- 主要目标是在规定时间内尽可能快地消除界面上的水果,获得更高的分数。
- 用户界面设计:
- 主界面包括游戏开始按钮功能入口。
- 游戏界面应展示游戏区域、剩余时间、帮助提示等元素。
- 提供美观的水果图标,并在用户成功连接时提供声音和视觉反馈。
- 游戏流程:
- 游戏开始时,随机生成一定数量的水果图标并铺满游戏区域。
- 玩家可以通过点击两个相同的水果图标,若它们之间存在不超过两个直线拐点的路径,则可以连接消除。
- 连接成功后,所选水果图标会消失。
- 若剩余时间归零或无剩余可连接的水果,则游戏结束。
- 难度和帮助:
- 游戏难度应逐渐增加,可以通过减少游戏规定时间的方式实现。
- 提供游戏帮助提示的功能,帮助用户完成游戏,同时限定使用帮助的次数。
小结:
以上是"水果连连看"小游戏的功能需求。作为开发者,需要根据这些需求设计游戏逻辑、实现连连消除算法、开发用户界面和交互等相关功能。同时,确保游戏流畅运行、界面美观、易于上手。
B. 开发工具
- Android Studio Dolphin | 2021.3.1 Patch 1
- Java , JDK 11.0.13
- Gradle , gradle-7.4
C. 代码设计
1. 游戏区域
1.1 游戏区域基类
BoardView
是游戏的自定义视图的基类,继承自 View
类,实现了游戏区域的若干通用功能:
- 常量
xCount
和yCount
分别表示 x、y轴方向的图标数+2,这个多出来的2用来表示游戏界面与屏幕边缘的间距; -
BoardView
类包含了各种成员变量,包括用于表示游戏棋盘的数组、所有的水果图片、选中的水果图片以及可以连通点的路径 ; -
BoardView
构造函数初始化示图,计算每个水果图标的长宽、初始化所有的水果图片资源; -
onDraw
方法:绘制可以连通的路径,然后将路径以及两个图标清除;绘制棋盘的所有图标;绘制选中图标,当选中时图标放大显示;
@Override
protected void onDraw(Canvas canvas) {
/*
* 绘制连通路径,然后将路径以及两个图标清除
*/
if (path != null && path.length >= 2) {
final float halfIconSize = (float) iconSize / 2;
for (int i = 0; i < path.length - 1; i++) {
Paint paint = new Paint();
paint.setColor(Color.YELLOW); // Color.CYAN
paint.setStyle(Paint.Style.FILL); // STROKE
paint.setStrokeWidth(4);
Point p1 = indextoScreen(path[i].x, path[i].y);
Point p2 = indextoScreen(path[i + 1].x, path[i + 1].y);
// iconSize / 2
canvas.drawLine(p1.x + halfIconSize, p1.y + halfIconSize,
p2.x + halfIconSize, p2.y + halfIconSize, 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;
}
/*
* 绘制棋盘的所有图标 当这个坐标内的值大于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);
}
}
}
/*
* 绘制选中图标,当选中时图标放大显示
*/
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);
}
}
}
- 创建了工具函数
indextoScreen
和screenToindex
,分别用于将图片数组中的索引转换成屏幕上的坐标、将图标在屏幕中的坐标转成在数组上的索引。
总体而言,BoardView
类提供了游戏的最基础的同时也是通用的功能,它绘制了游戏区域内的所有水果图片、绘制了可以联通的路径以及清除图标、点击图片放大显示,以及一些通用的工具函数。
1.2 游戏区域实现
GameView
是游戏示图的实现,继承了 BoardView
类,实现了游戏的点击操作逻辑、关卡控制以及倒计时等功能。
-
GameView
类使用常量MAX_TIME
设定了游戏的最大时间,使用变量totalTime
表示每一局游戏的总时间,在函数startNextPlay
中通过每一局减少10秒的方式缩短游戏时间来实现提高游戏难度的目的; -
GameView
用到了接口OnTimerListener
、OnStateListener
、OnToolsChangeListener
,分别用于游戏时间、游戏状态以及使用帮助提示的监听回调; -
GameView
使用内部类RefreshHandler
继承自Handler
,用来定时刷新界面,并判断是否结束;使用内部类RefreshTime
继承自Thread
,实现了游戏倒计时的逻辑; -
startPlay
方法表示开启新一局游戏,初始化包括游戏界面排布、使用帮助提示次数限制、剩余时间、重启倒计时等状态; -
reStartPlay
方法用来继续之前的游戏,之前的游戏状态保持不变; -
startNextPlay
方法用来开启下一关游戏,提高游戏难度; -
onTouchEvent
函数用来处理用户的点击操作,用户点击了水果图片时,判断是否可以消除,以及点击效果的实现; -
win
方法用于判断游戏是否获胜; -
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;
}
-
link
方法用于判断两个点是否可以连线
// 判断两个点是否可以连线
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;
}
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;
}
}
expandX(p1, p1E); // 左右扫描X轴
expandX(p2, p2E); // 左右扫描X轴
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); // 上下扫描Y轴
expandY(p2, p2E); // 上下扫描Y轴
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;
}
change
方法用于重新排布水果图片,使用Random
随机生成位置索引;getScore
方法实现了用户得分的计算,剩余时间越多、关卡越高、使用帮助的次数越少,得分越高;
3. 游戏结果
游戏结果使用 MyDialog
自定义弹窗提示,呈现游戏胜利/失败的结果、得分,并且给用户选择退出游戏、重新开始以及玩下一关。
D. 项目演示
水果连连看,演示视频
下载安装包试玩
E. 项目源码