A. 项目描述

"水果连连看"是一款经典的休闲益智小游戏。游戏的目标是通过连接相同的水果图标来消除它们,最终清空整个游戏界面。它以其简单易懂的规则和有趣的玩法而受到许多玩家的喜爱。

android studio写一个小游戏_使用帮助

让我们来梳理一下这款小游戏的功能需求。

  1. 游戏规则和目标:
  • 游戏采用经典的连连看玩法,玩家需要在限定时间内连接相同的水果。
  • 主要目标是在规定时间内尽可能快地消除界面上的水果,获得更高的分数。
  1. 用户界面设计:
  • 主界面包括游戏开始按钮功能入口。
  • 游戏界面应展示游戏区域、剩余时间、帮助提示等元素。
  • 提供美观的水果图标,并在用户成功连接时提供声音和视觉反馈。
  1. 游戏流程:
  • 游戏开始时,随机生成一定数量的水果图标并铺满游戏区域。
  • 玩家可以通过点击两个相同的水果图标,若它们之间存在不超过两个直线拐点的路径,则可以连接消除。
  • 连接成功后,所选水果图标会消失。
  • 若剩余时间归零或无剩余可连接的水果,则游戏结束。
  1. 难度和帮助:
  • 游戏难度应逐渐增加,可以通过减少游戏规定时间的方式实现。
  • 提供游戏帮助提示的功能,帮助用户完成游戏,同时限定使用帮助的次数。

android studio写一个小游戏_ico_02

小结:
以上是"水果连连看"小游戏的功能需求。作为开发者,需要根据这些需求设计游戏逻辑、实现连连消除算法、开发用户界面和交互等相关功能。同时,确保游戏流畅运行、界面美观、易于上手。

B. 开发工具

  • Android Studio Dolphin | 2021.3.1 Patch 1
  • Java , JDK 11.0.13
  • Gradle , gradle-7.4

C. 代码设计

1. 游戏区域

1.1 游戏区域基类

BoardView 是游戏的自定义视图的基类,继承自 View 类,实现了游戏区域的若干通用功能:

  • 常量 xCountyCount 分别表示 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);
            }
        }
    }
  • 创建了工具函数 indextoScreenscreenToindex,分别用于将图片数组中的索引转换成屏幕上的坐标、将图标在屏幕中的坐标转成在数组上的索引。

总体而言,BoardView 类提供了游戏的最基础的同时也是通用的功能,它绘制了游戏区域内的所有水果图片、绘制了可以联通的路径以及清除图标、点击图片放大显示,以及一些通用的工具函数。

1.2 游戏区域实现

GameView 是游戏示图的实现,继承了 BoardView 类,实现了游戏的点击操作逻辑、关卡控制以及倒计时等功能。

  • GameView 类使用常量MAX_TIME设定了游戏的最大时间,使用变量totalTime表示每一局游戏的总时间,在函数 startNextPlay中通过每一局减少10秒的方式缩短游戏时间来实现提高游戏难度的目的;
  • GameView 用到了接口 OnTimerListenerOnStateListenerOnToolsChangeListener,分别用于游戏时间、游戏状态以及使用帮助提示的监听回调;
  • 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 自定义弹窗提示,呈现游戏胜利/失败的结果、得分,并且给用户选择退出游戏、重新开始以及玩下一关。

android studio写一个小游戏_ico_03

D. 项目演示


水果连连看,演示视频


下载安装包试玩

E. 项目源码

android studio写一个小游戏_ico_04