T O P

[资源分享]     qt制作连连看

  • By - 楼主

  • 2021-09-03 15:10:30
  •  

     

    主要参考:https://blog.csdn.net/zju_fish1996/article/details/50466698(一位靓仔写的连连看,基本照着他的启蒙)and http://shouce.jb51.net/qt-beginning/5.html(文档)*

    先来看看作业要求

    在连连看游戏中,会有⼀个地图,地图上有许多不同种类的⽅块,通过将相同种类的两个⽅块相连,可以将这两个⽅块消除,⽤户获得分数。

    在整个连连看的过程中,除了处理⽤户的操作之外,还有⼏个⽐较特殊的部分特别需要注意:

    • 随机地图的⽣成

    • 判断两个⽅块是否可以通过两次以内的折线进⾏连接

    • 判断剩余⽅块是否还有解

    除此之外,具体的功能要求如下:

    RPG 机制

    不同于传统的连连看,使⽤ RPG 模式进⾏,即玩家需要控制⼀个⻆⾊在地图的空地上移动(⻆⾊显示可⾃⾏选择)。

    • 激活:当⻆⾊处于⽅块旁且再次向⽅块⽅向移动,会激活该⽅块(请使⽤某种⽅式表示该⽅块被激活)。

    • 消除:如果此次激活的⽅块和上次激活的⽅块是同种类,且可以通过两次以内的折线连接,则该两个⽅块被消除,玩家获得分数。(请绘制出将两个⽅块连接在⼀起的折线) 否则,上次激活的⽅块被⾃动变为未激活状态,换句话说,每个⻆⾊在地图中只有 0 个(游戏刚开始时,或刚刚消除完⼀对⽅块时)或者 1 个激活的⽅块。

    计分

    不同种类的⽅块可以有不同的分值,具体规则可以⾃⾏制定。界⾯中应时刻显示玩家的分数。

    倒计时和游戏结束

    有两个情况可以导致游戏结束:

    1. 倒计时结束;

    2. 没有可消除的⽅块对(所有⽅块均被消除也属于这⼀种)。

    界⾯中应时刻显示游戏的倒计时。

    开始菜单

    ⾄少包括以下按钮:

    • 开始新游戏

      • 可选择游戏模式:单⼈模式、双⼈模式(具体看后⽂)

    • 载⼊游戏

    • 退出游戏

    单⼈模式

    游戏开始时,会随机⽣成地图,并随机玩家⻆⾊位置。随后玩家可控制⻆⾊移动,以激活和消除⽅块。

    道具

    道具通过随机⽅式出现在地图的空地上,当⻆⾊与道具出现在同⼀位置时,该⻆⾊触发道具效,道具消失。

    • +1s:延⻓剩余时间 30s

    • Shuffle:所有⽅块位置重排

    • Hint: 10s 内会⾼亮⼀对可能链接的⽅块,被消除后会⾼亮下⼀对,直到 10s 时间结束

    • Flash: 5s 内允许通过⿏标单击移动⻆⾊位置,⻆⾊移动到的位置必须通过空地可到达,否则点击不产⽣任何效果。如果点击到⽅块,且⻆⾊可以移动到该⽅块旁,则⻆⾊移动到该⽅块旁,且该⽅块被激活。如果⽅块四周有多个位置可以让⻆⾊停留,则⻆⾊移动到其中任何⼀个位置均可。

    双⼈模式

    两个玩家的两个⻆⾊在相同的地图上进⾏游戏,以结束游戏时双⽅的分数决定谁为赢家。道具 在单⼈模式的基础上,增加:

    • Freeze:对⼿ 3s 内⽆法移动

    • Dizzy:对⼿ 10s 内移动⽅向颠倒(上下左右颠倒) 此外,

    • Hint 道具的效果对两个玩家均可⻅;

    • +1s 道具的效果对两个玩家均有效。

    • 双⼈模式下,没有 Flash 道具

    暂停和存档

    在暂停时,可以保存游戏(Save)和载⼊游戏(Load)保存游戏会将当前游戏的所有状态以任意格式保存到磁盘上的⽂件载⼊游戏时,读取⽂件,并从中恢复状态

    我完成作业的顺序是:

    1. 做出菜单窗口,几个窗口的跳转

    2. 在主窗口上显示按钮和人物(都是通过建立button,在其上显示贴图实现)

    3. 键盘事件实现人物移动和人物激活按钮

    4. 实现按钮消除

    5. 实现绘制按钮之间的连线

    6. 实现双人模式

    7. 实现计时,道具,分数,背景音乐等

    8. 实现存档加载

    在整个做完后,回头看自己其实走了不少弯路,但整个过程还是比较有成就感,就像看见自己一步步养大一个小baby一样。

    这里总结一些自己踩过的坑:

    • button要显示必须要声明为Mainwindow的全局变量,不然显示不出来(这是刚刚接触qt的我纠结好久的问题)

    • mainwindow里面需要加入一个widget不然无法显示

    • 可能我选择的实现方式的问题,用button放置图片,导致一些操作的实现特别困难,比如绘制连线。但所幸我最后解决了这个问题,只能说车到山前必有路。或许用Qpainter绘制更简单一些,但我没有这样做也不知道是否更好些

    • 我的完成顺序还是有些问题的,把双人模式放在最后要改动的东西十分多,应该提前解决(也可能是我没想到合适的解决方法)

     

     

     

     

     

     

     

     

     

    这里就按照我的顺序逐步讲解

    做出菜单窗口,几个窗口的跳转

    先搭好框架,再往里面填充内容,这和我们的任务书推荐顺序稍有不同,但我觉得把这一步放在前面挺正确的。

    这一步我主要参考文档的内容:http://shouce.jb51.net/qt-beginning/5.html

    在编写窗口之前,首先需要理解以下概念:信号和槽函数

    在我的理解里,一个窗口就像一个人,他可以喊话,听到他话的人作出反应。在这个过程里,喊话就是一个窗口发送信号,做出反应就是另一个窗口响应槽函数,这样就完成了一个信号的传递。

     

     

     

    发送的主体和执行主体也可以是同一个窗口,就像人大脑给肢体发送信息一样。

    这里先实现三个窗口之间的互相切换(我是用到了三个窗口,一个开始窗口,一个模式选择窗口,一个主窗口)

    1. 添加窗口

    2. 添加dialog窗口(初学建议Qt设计师ui类)

    3. 进入dialog的ui编辑界面,拖入一个pushbutton

       

       

      双击pushbutton,可以修改按钮文字,修改为“开始”,右键单击,点击“转到槽”

       

       

       

      在弹出的转到槽对话框中选择clicked()信号并按下确定按钮。这时会跳转到编辑模式dialog.cpp文件的on_pushButton_clicked()函数处,这个就是自动生成的槽,它已经在dialog.h文件中进行了声明。我们只需要更改函数体即可。这里更改为:

      void Dialog::on_pushButton_clicked()
      {
          this->hide();
          emit mainShow();
      }

      我们让当前窗口隐藏,让主窗口显示,我们需要在dialog.h里面定义mainShow() 信号

       

      这样点击“开始”按钮后,dialog窗口就能发送信号并将自身隐藏,下一步我们需要让mainwindow响应信号显示自身。

    4. 链接信号和槽函数

      main.cpp创建dialog对象,并链接dialog对象的发送信号和mainwindow的相应槽函数

      QObject::connect(&dlg, SIGNAL(mainShow()), &w, SLOT(show()));

       

    这样就可以实现点击开始按钮后,dialog窗口隐藏,主窗口显示。如果想要隐藏主窗口显示dialog窗口,那就在mainwindow里创建一个按钮,像之前一样设置按键槽函数->发送信号->链接信号和槽函数->设置槽函数

    如此一来就可以实现几个界面的跳转了

    在主窗口上显示按钮和人物

    首先了解一下布局widget

     

     

    先对qt的组件层次关系有一个总体上的把握,大体的层次关系是这样,mainwindow上面需要有一个主widget,主widget上可以有若干个布局layout, 可以是水平布局(QHBoxLayout)、垂直布局(QVBoxLayout)、网格布局(QGridLayout)。布局里可以放各种组件,像按钮,对话框之类。当然这个关系不是绝对包含的,比如layout里面也可以加入widget。

    map是记录地图的int数组,0表示空地,正数表示图片的编号

    在对层次关系了解后,我们在 mainwindow.h 上依次新建widget、layout和button们

    QWidget *windows;    // 主widget
    ImageButton *image[X+2][Y+2];// 存储二态图片的按钮
    QGridLayout *gridLayout;    // 主布局

     

    当然要包含QGridLayout库,另外的ImageButton是我自己创建的一个类,目的是让按钮拥有普通态和激活态两种显示图片状态,为之后的选中后高亮显示做准备。

    先说说我的imagebutton的实现。

    实现了这些功能 {初始设置两个可以相互切换的图标(我的用途是切换图标的激活与未激活)、清空图标(把图片换成纯灰色)、设置新的图片(方便player移动到这个按钮)、交换两个按钮的图片(后续的重排shuffle功能用到)}

    imagebutton.h

    #ifndef IMAGEBUTTON_H
    #define IMAGEBUTTON_H
    #include <QToolButton>
    #include <QIcon>class ImageButton : public QToolButton
    {
        Q_OBJECT
    private:
        QIcon normalState;    // 普通状态
        QIcon activeState;    // 激活状态
        QIcon clearState;    // 空白状态
        bool currentIcon;    //当前为普通态还是激活态
    private:
    ​
    public:
        ImageButton();
        ~ImageButton(){}
        // 切换激活态和普通态
        void changeIcon();
        // 初始设置按钮
        void setButtonIcon(QIcon normal, QIcon active);
        // 设置为player图片
        void setPlayer(QIcon icon);
        // 消除图片后清空按钮,或者player移动后清空原来按钮
        void clearIcon();
        void exchangeIcon(ImageButton *);
        void flushIcon();
    };
    ​
    #endif // IMAGEBUTTON_H
    imagebutton.cpp
    
    #include "imagebutton.h"
    ​
    ImageButton::ImageButton()
    {
        setDown(false);
        setFocusPolicy(Qt::NoFocus);
        // 空白图片
        QPixmap map(":/new/prefix1/image/group1/1-1 (0).png");
        clearState = QIcon(map);
    }
    ​
    void ImageButton::changeIcon() {
        if(currentIcon) {
            setIcon(normalState);
            currentIcon = 0;
        } else {
            setIcon(activeState);
            currentIcon = 1;
        }
    }
    ​
    void ImageButton::setButtonIcon(QIcon normal, QIcon active)
    {
        normalState = normal;
        activeState = active;
        setIcon(normal);
        currentIcon = 0;
    }
    ​
    void ImageButton::setPlayer(QIcon icon) {
        setIcon(icon);
    }
    ​
    void ImageButton::clearIcon() {
        setIcon(clearState);
    }
    ​
    void ImageButton::exchangeIcon(ImageButton *m)
    {
        QIcon tmp1 = activeState;
        QIcon tmp2 = normalState;
        activeState = m->activeState;
        normalState = m->normalState;
        m->activeState = tmp1;
        m->normalState = tmp2;
    }
    ​
    void ImageButton::flushIcon()
    {
        setIcon(normalState);
    }

     

    创建完成之后,需要在mainwindow.cpp里实例化并赋值信息

    windows = new QWidget();
    gridLayout = new QGridLayout;
    ​
    for(int i = 0; i < X+2; ++i)
            for(int j = 0; j < Y+2; ++j) {
                image[i][j] = createImageButton(
                              texts1[buttonNum],
                              texts2[buttonNum]);
                gridLayout->addWidget(image[i][j], i, j, Qt::AlignCenter);

     

    这里是根据地图的大小创建了imagebutton,x+2*Y+2最外面一圈是陆地,中间是待消除的图标。这里调用了creatImageButton函数,实现如下

    ImageButton *MainWindow::createImageButton(const QString &str1,
                                               const QString &str2)
    {
        // 加载两张图片
        QPixmap img1, img2;
        img1.load(str1);
        img2.load(str2);
        ImageButton *button = new ImageButton;
        // 设置大小和图片
    //    button->setGeometry(0, 0, 0, 0);
        button->setFixedSize(int(img1.width()/2.15), int(img1.height()/2.15));
        button->setButtonIcon(QIcon(img1), QIcon(img2));
        button->setIconSize(QSize(img1.width()/2, img1.height()/2));
        button->setStyleSheet("border:1px rgba(237, 241, 255, 100);"
                              "border-radius:8px;"
                              "padding:0px;"
                              "border-style: outset;");
        return button;
    }

     

    接收两个图片地址字符串,把图片加载进来,实例化imagebutton,设置imgbutton大小和背景格式,返回一个Imagebutton类型

    button的setIcon()方法接收一个QIcon对象,我在读入图片的时候选择先用QPixmap格式读入,再用QIcon (img)转为QIcon格式

    人物图片的读入也一样,在.h文件下创建对象,在.cpp文件中读入就ok了

    键盘事件实现人物移动和人物激活按钮

    首先在.h文件声明对keyPressEvent的重写void keyPressEvent(QKeyEvent *event);

    我的实现如下

    void MainWindow::keyPressEvent(QKeyEvent *event)
    {
        
            switch (event->key()) {
            case Qt::Key_W: {
                // 到达边界就break
                if(loc_player1.x == 0) break;
                // 碰到图片就发送激活信号
                if(map[loc_player1.x - 1][loc_player1.y] > 0)
                    emit activateImg(loc_player1.x - 1, loc_player1.y);
                else if(loc_player1.x) {
                    loc_player1.x --;
                    flushPlayerLoc();
                }
                if(map[loc_player1.x][loc_player1.y] < 0)
                    propHandler(map[loc_player1.x][loc_player1.y]);
    ​
                break;
            }
            case Qt::Key_A: {
                if(loc_player1.y == 0) break;
                if(map[loc_player1.x][loc_player1.y - 1] > 0)
                    emit activateImg(loc_player1.x,loc_player1.y - 1);
                else if(loc_player1.y) {
                    loc_player1.y --;
                    flushPlayerLoc();
                }
                if(map[loc_player1.x][loc_player1.y] < 0)
                    propHandler(map[loc_player1.x][loc_player1.y]);
                break;
            }
            case Qt::Key_S: {
                if(loc_player1.x == X+1) break;
                if(map[loc_player1.x + 1][loc_player1.y] > 0)
                    emit activateImg(loc_player1.x + 1, loc_player1.y);
                else if(loc_player1.x < X+1){
                    loc_player1.x ++;
                    flushPlayerLoc();
                }
                if(map[loc_player1.x][loc_player1.y] < 0)
                    propHandler(map[loc_player1.x][loc_player1.y]);
    ​
                break;
            }
            case Qt::Key_D: {
                if(loc_player1.y == Y+1) break;
                if(map[loc_player1.x][loc_player1.y + 1] > 0)
                    emit activateImg(loc_player1.x, loc_player1.y + 1);
                else if(loc_player1.y < Y+1) {
                    loc_player1.y ++;
                    flushPlayerLoc();
                }
                if(map[loc_player1.x][loc_player1.y] < 0)
                    propHandler(map[loc_player1.x][loc_player1.y]);
    ​
                break;
            }
    }

     

    根据按键来判断运动方向:

    • 如果超出就break
    • 如果碰到图标就发送激活这个图标的信号(当然这个信号得在.h文件定义并提前链接到处理的槽函数中)
    • 如果可以移动,那就改变人物的坐标

    发送激活信号后,处理槽函数change(int, int)接收到激活的位置,实现如下

    void MainWindow::change(int x, int y)
    {
        enum{first, second};        // 第几次激活
        static int visitTime = first;
        if (visitTime == first) {        // 第一次激活直接激活
            loc_first[0] = loc(x, y);
            visitTime = second;
            image[x][y]->changeIcon();
            flushPlayerLoc();
            sound[1]->play();
        }else {    // 第二次激活
            loc_second[0] = loc(x, y);
            visitTime = first;
    ​
            if (x == loc_first[0].x && y == loc_first[0].y) {   // 如果是同一个按钮,变回去
                image[x][y]->changeIcon();
                sound[1]->play();
            }
            else{
                if(checkFeasibility(0)) {    // 如果两个按钮可消除
                    map[x][y] = 0;    //消除图片,并把map更新为空地
                    map[loc_first[0].x][loc_first[0].y] = 0;
                    image[x][y]->changeIcon();
                    flushLine(0);
                    sound[2]->play();
                    imgNum -= 2;
                    score[0] +=2;
                    QString tmp = "score:\n" + QString::number(score[0]);
                    if(mode) tmp+=":"+QString::number(score[1]);
                    scores->setText(tmp);
                    curSol->setText("可消除");
                    if (imgNum == 0) {
                        QMessageBox tmp;
                        tmp.setText("恭喜你,呱唧呱唧!");
                        tmp.exec();
                        emit gameover();
                    }
                }else {     // 不可消除就去除激活态
                    image[loc_first[0].x][loc_first[0].y]->changeIcon();
                    curSol->setText("不可消除");
                    sound[1]->play();
                }
            }
            if(checkRemain())
                remainSol->setText("有解");
            else {
                remainSol->setText("无解");
                QMessageBox tmp;
                tmp.setText("很遗憾,无解");
                tmp.exec();
                emit gameover();
            }
        }
    }

     

    通过创造一个静态变量来记录访问的次数

    实现按钮消除

    这算是游戏的核心算法,其实本质就是BFS,用dfs不行,我是参考https://www.cnblogs.com/HappyAngel/archive/2010/12/25/1916731.html,用dfs消除是可以实现的,但要记录路径就很麻烦了,上面的文章我看了好几遍才搞清楚,建议多看几遍。这里贴上检验代码。

     

    // 检查两个图标是否可消除
    bool MainWindow::checkFeasibility(int i)
    {
        // 如果两张图片不一样,不可消除
        if(map[loc_first[i].x][loc_first[i].y] !=
           map[loc_second[i].x][loc_second[i].y]) {
    ​
            return false;
        }
    ​
        //先把visited数组清零
        solvability = false;
        memset(visited, 0, sizeof(visited));
        queue.clear();
        for (int s = 0; s < X+2; ++s)
            for(int j = 0; j < Y+2; ++j)
                path[s][j] = loc(-1, -1);
        queue.enqueue(loc(loc_first[i].x, loc_first[i].y));
        while(!queue.empty()) {
            loc tmp = queue.front();
            queue.pop_front();
            bfs(tmp.x, tmp.y, visited[tmp.x][tmp.y]+1, i);
            if (solvability) return true;
        }
        return false;
    }
    ​
    // 广搜函数,x1 y1 第一个图片坐标; cnt 转折点次数;
    void MainWindow::bfs(int x1, int y1,int cnt, int i)
    {
    ​
        int tmp_x = x1, tmp_y = y1;
        int cnt_limit = 3;
        // 向上搜索
        while (-- tmp_x >=0 && !map[tmp_x][tmp_y]) {
            if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) {
                queue.enqueue(loc(tmp_x, tmp_y));
                visited[tmp_x][tmp_y] = cnt;
                path[tmp_x][tmp_y] = loc(x1, y1);
            }
        }
        if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) {
            solvability = true;
            path[tmp_x][tmp_y] = loc(x1, y1);
            return;
        }
    ​
        tmp_x = x1;
        // 向下搜索
        while (++ tmp_x < X+2 && !map[tmp_x][tmp_y]) {
            if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) {
                queue.enqueue(loc(tmp_x, tmp_y));
                visited[tmp_x][tmp_y] = cnt;
                path[tmp_x][tmp_y] = loc(x1, y1);
            }
        }
        if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) {
            solvability = true;
            path[tmp_x][tmp_y] = loc(x1,y1);
            return;
        }
    ​
        tmp_x = x1;
        while (-- tmp_y >= 0 && !map[tmp_x][tmp_y]) {
            if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) {
                queue.enqueue(loc(tmp_x, tmp_y));
                visited[tmp_x][tmp_y] = cnt;
                path[tmp_x][tmp_y] = loc(x1, y1);
            }
        }
        if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) {
            solvability = true;
            path[tmp_x][tmp_y] = loc(x1, y1);
            return;
        }
    ​
        tmp_y = y1;
        while (++ tmp_y < Y+2 && !map[tmp_x][tmp_y]) {
            if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) {
                queue.enqueue(loc(tmp_x, tmp_y));
                visited[tmp_x][tmp_y] = cnt;
                path[tmp_x][tmp_y] = loc(x1, y1);
            }
        }
        if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) {
            solvability = true;
            path[tmp_x][tmp_y] = loc(x1, y1);
            return;
        }
    ​
    }

     

    实现绘制按钮之间的连线

    这是最难为我的一步,因为我采用的方法是用button显示图片,但是Qt不允许在控件上面画,也就是不能叠加显示,我和老师交流了一下,很没有头绪

     

     

    在千思万虑后我想到了解决办法——把连线做成图片,只需要做从左到右、从上到下、从左到上下,从右到上下六种图片,就可以拼接形成路径了。但还是挺难,这也就导致画线函数成了整个程序最长的函数。方法是这样的:

     

     

    这里贴出画线的代码

    //
    // 可消除状态下的路线更新,路径上的方块都转换图像。path里记录的是拐点信息
    //
    void MainWindow::flushLine(int c)
    {
        // 从第二个img开始往回溯
        loc cur = loc_second[c];
        loc next = path[cur.x][cur.y];
        while (!(cur == loc_first[c])) {
            // 两个关键点在同一列
            if (cur.x == next.x) {
                if(cur.y < next.y) {
                    // 把关键点之间的空地改为直线
                    for (int i = cur.y + 1; i < next.y; ++i) {
                        // l2r代表left_to_right, u2d代表up_to_down
                        image[cur.x][i]->setPlayer(QIcon(pathPic[l2r]));
                        // 如果和人物的位置重合,就换图片
                        lineOnPlayer(loc(cur.x, i), p1_l2r, p2_l2r, p_l2r);
                    }
                    // 拐点部分单独处理
                    //要根据下一个拐点具体判断
                    if (!(next == loc_first[c])) {
                        if(path[next.x][next.y].x < next.x) {
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[u2l]));
                            lineOnPlayer(next, p1_u2l, p2_u2l, p_u2l);
                        }
                        else {
    //                        std::cerr << "d"  ;
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[d2l]));
                            lineOnPlayer(next, p1_d2l, p2_d2l, p_d2l);
                        }
                    }
                }else {
                    for (int i = next.y + 1; i < cur.y; ++i) {
                        image[cur.x][i]->setPlayer(QIcon(pathPic[l2r]));
                        lineOnPlayer(loc(cur.x, i), p1_l2r, p2_l2r, p_l2r);
                    }
                    if (!(next == loc_first[c])) {
                        if(path[next.x][next.y].x < next.x) {
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[u2r]));
                            lineOnPlayer(next, p1_u2r, p2_u2r, p_u2r);
                        }
                        else {
    //                        std::cerr << "d"  ;
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[d2r]));
                            lineOnPlayer(next, p1_d2r, p2_d2r, p_d2r);
                        }
    ​
                    }
    ​
                }
            // 两关键点在同一行
            }else {
                if(cur.x < next.x) {
                    for (int i = cur.x + 1; i < next.x; ++i) {
                        image[i][cur.y]->setPlayer(QIcon(pathPic[u2d]));
                        lineOnPlayer(loc(i, cur.y), p1_u2d, p2_u2d, p_u2d);
                    }
                    if (!(next == loc_first[c])) {
                        if(path[next.x][next.y].y < next.y) {
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[u2l]));
                            lineOnPlayer(next, p1_u2l, p2_u2l, p_u2l);
                        }
                        else {
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[u2r]));
                            lineOnPlayer(next, p1_u2r, p2_u2r, p_u2r);
                        }
                    }
                }else {
                    for (int i = next.x + 1; i < cur.x; ++i) {
                        image[i][cur.y]->setPlayer(QIcon(pathPic[u2d]));
                        lineOnPlayer(loc(i, cur.y), p1_u2d, p2_u2d, p_u2d);
                    }
                    if (!(next == loc_first[c])) {
                        if(path[next.x][next.y].y < next.y) {
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[d2l]));
                            lineOnPlayer(next, p1_d2l, p2_d2l, p_d2l);
                        }
                        else {
                            image[next.x][next.y]->setPlayer(QIcon(pathPic[d2r]));
                            lineOnPlayer(next, p1_d2r, p2_d2r, p_d2r);
                        }
                    }
                }
            }
            // 更新当前点和下一个点,进入下一次回溯
            cur = next;
            next = path[cur.x][cur.y];
        }
    }

     

    为了避免画线遮掉人物,还另做了6*3张图片(p1,p2,p1+p2乘以6种),另写了一个判断函数

    // 处理路径显示和人物重合
    //
    void MainWindow::lineOnPlayer(loc next,
                                  int index1,
                                  int index2,
                                  int index3)
    {
        if (next == loc_player1)
            image[next.x][next.y]->setPlayer(QIcon(pathPic[index1]));
        if (next == loc_player2)
            image[next.x][next.y]->setPlayer(QIcon(pathPic[index2]));
        if (next == loc_player1 && next == loc_player2)
            image[next.x][next.y]->setPlayer(QIcon(pathPic[index3]));
        
    }

     

    实现双人模式

    双人模式要加的东西有:

    • 键盘事件

    • 判断函数

    键盘事件没啥说的,和player1完全一致,但判断函数不能复用很麻烦。老师在实现建议里面提到将player实现为一个类,两个player对应两个实例,我一开始没那么做,后面后悔可要改的东西太多了

    最后的道具时间音乐等都比较简单,在做到这一步以后(倒装)。代码基本可以复制。文件操作也比较简单,这里就不多提了,代码我放在了github,链接:https://github.com/kenor5/project1_-

     

    小灶:

    用ps做连连看图标

    网上找不到现成成套的图标,就自己用photoshop制作了一套

    1. 在搜索引擎上找一套小图标

       

       

    1. 下载一张导入ps

       

       

       

    2. 用矩形选框工具(快捷键M)框选出一个小宝贝(按住shift不放,框选为正方形),然后ctrl+j复制一层,隐藏掉下面一层

       

       

    3. 用魔棒工具,选择宝贝周围的白色像素,按delete删除

    4. 新建一个图层2,拖动到图层1下面,用吸管工具(快捷键I)吸取宝贝的主题色

       

       

    5. 按alt+delete填充,按住CTRL同时选中图层一二,按ctrl+e合并图层,这样就得到一个正方形的宝贝了。可以右键单击导出为png

       

       

      如此重复,你就可以得到自己的宝贝们了

    界面美化

    我觉得美化的重点就是矩形倒圆角、调整背景半透明、设置好看的背景图片,这是花时间最少但最出效果的部分

    倒圆角&&设置三态按钮的方法:设置样式表,代码如下

    button->setStyleSheet("QPushButton{"
                         "border-image:url(:/new/prefix1/image/pause.png);"//填充图片
                         "border:2px rgb(237, 241, 255);"
                         "border-radius:50px;"//倒圆角尺寸
                         "padding:2px 3px;"
                         "border-style: outset;"
                          "}""QPushButton:hover{" //悬浮态
                          " "
                         "color: black;"
                         "}"
                         "QPushButton:pressed{"//点击态
                         ""
                         "border-style: inset;"
                          "}");

     

    设置背景半透明:

    scores->setStyleSheet(""//最后一位透明度
                             "border:2px rgb(237, 241, 255);"
                             "border-radius:7px;"
                             "padding:2px 3px;"
                             "font:bold 20px;"
                             "color:rgba(0,0,0,100);"
                             "border-style: outset;");

     

    还有重要的一点:在button里边设置背景图片时,想要实现半透明,图片必须是PNG格式(jpg格式不附带透明度信息),而且png的不透明度必须小于100%,否则显示出来还是不透明,改不透明度还是在ps里,方法为:在图层的右上角更改图层透明度,然后存储为PNG格式,这样得到的图片就是半透明的,设置在button上就可以隐约显示出背景了。

     

     

    还有一些小问题,比如正方形的图标放在倒过圆角的button里边不契合,还是会显示出整个方形图片。这个问题需要在ps里面对原始图片倒圆角。

    制作封面

    1. 网上找一张图片拖进ps(字体仿制官方海报的绿底白字)

    2. 标题打上去,选择合适的大小和喜欢的字体

       

       

    3. 用多边形套索工具勾勒出翅膀的形状,填充为白色

       

       

    4. 用框选工具调整字体间距,CTRL+j复制一层,填充为绿色,用自由变换(ctrl+t)变大一些

       

       

    1. 把绿色图层中部分空白框选出来填充

       

       

    1. 把绿色的图层复制一层,填充为黑色,放大一些,再填充更多

       

       

    1. 把三个图层同时选中,ctrl+e合并,然后ctrl+t自由变换,右键单击画面,选择变形

       

       

    1. 拖拽成你想要的样子

       

       

    1. 最后在画面下方放一个半透明的黑色蒙版,用来盛放之后的pushbutton,可以加上写信息,这样一张有模有样地封面就做好了

       

       

     

    本帖子中包含资源

    您需要 登录 才可以下载,没有帐号?立即注册