Qt 项目 愤怒的小鸟 游戏

最终效果

首页:

游戏界面:

动画展示:

下载地址

github:

本项目:https://github.com/yibobunengyuntian/AngryBirds

框架:https://github.com/yibobunengyuntian/Framework

百度网盘:

本项目:https://pan.baidu.com/s/1TmetjtYp4nSlCc05CusLWQ?pwd=1008 提取码: 1008

框架:https://pan.baidu.com/s/1UXiy81ceUrHRKuaCYUuaog?pwd=1008 提取码: 1008

注意!需将本项目与框架都下载,放到同级目录,qt打开本项目即可,如下

前话

         之前用Qt绘图这块儿写了个台球游戏,但其中的运动及碰撞等都是自己手搓的, 效果不怎么好,且不通用,后来了解到 box2d 物理引擎开源库,进行了相关了解并最终运用到该项目。

项目分析

        该项目使用 Qt绘图(QGraphicsScene、QGraphicsView、QGraphicsItem) 结合 box2d 物理引擎(本项目直接使用的box2d源码,未编译成库,不存在版本兼容问题,且对新手友好),实现 愤怒的小鸟 游戏。

        项目分为3个窗口:主页窗口、关卡选择窗口、游戏窗口。两个对话框:游戏暂停对话框、游戏结束对话框。关卡写入了json文件,可自行修改。

主要代码      

mainwin.h

#ifndef MAINWIN_H
#define MAINWIN_H

#include <QWidget>
#include <QPushButton>
#include <QMouseEvent>
#include <QTimer>
#include <QVariantAnimation>

#include "ui_mainwin.h"
#include "view.h"
#include "stopdlg.h"
#include "finishdlg.h"
#include "homewgt.h"
#include "maskwgt.h"
#include "levelwgt.h"


class MainWin : public QWidget, public Ui_MainWin
{
    Q_OBJECT

    enum ItemType
    {
        Box = 0,    // 地面与四周墙壁
        Bird,       // 鸟
        Pig,        // 猪
        Obstacle    // 障碍物
    };
    enum BirdType
    {
        RedBird = 0,// 红色鸟
        YellowBird, // 黄色鸟
    };
    enum PigType
    {
        Pig1 = 0,   // 猪1
    };
    enum ObstacleType
    {
        Plank_H = 0,// 木板(水平)
        Plank_V,    // 木板(竖直)
    };

public:
    explicit MainWin(QWidget *parent = nullptr);
    ~MainWin();

protected:
    // 初始化
    void initialize();

    /**
     * @brief startGame 开始游戏
     * @param level     关卡
     */
    void startGame(uint level = 0);

    void resizeEvent(QResizeEvent *event) override;

protected slots:
    // 新游戏
    void onNewGame();

    // 选择关卡
    void onSelectLevel();

    /**
     * @brief onStartLevel  开始关卡
     * @param level         关卡
     */
    void onStartLevel(uint level);

    // 退出游戏
    void onExit();

    // 主页
    void onHome();

    // 继续游戏
    void onResume();

    // 重新开始
    void onRepeat();

    // 暂停
    void onStop();

    // 下一关
    void onNext();

    /**
     * @brief onBeginContact    // 发生碰撞触发该函数
     * @param A                 // 物体A
     * @param B                 // 物体B
     * @param pos               // 碰撞点
     */
    void onBeginContact(ItemBase *A, ItemBase *B, QPointF pos);

    // 游戏每一帧触发该函数
    void onTimerEvent();

    void onViewMousePress(QMouseEvent *event);
    void onViewMouseRelease(QMouseEvent *event);
    void onViewMouseMove(QMouseEvent *event);

private:
    MaskWgt *m_pMaskWgt = nullptr;
    QPushButton *m_pBtnStop = nullptr;
    Scene *m_pScene = nullptr;
    View *m_pView = nullptr;
    StopDlg *m_pStopDlg = nullptr;
    FinishDlg *m_pFinishDlg = nullptr;
    HomeWgt *m_pHomeWgt = nullptr;
    LevelWgt *m_pLevelWgt = nullptr;

    QPointF m_p1 = QPointF(300 - 30, 240 + 347);
    QPointF m_p2 = QPointF(300 + 58, 240 + 344);
    QPointF m_launchPos;
    QGraphicsEllipseItem * m_pEllipseItem = nullptr;
    QGraphicsLineItem *m_pLineItem_1 = nullptr;
    QGraphicsLineItem *m_pLineItem_2 = nullptr;

    QPointF m_offset;
    bool m_isPressEllipseItem = false;

    QVariantList m_level;
    uint m_currLevel = 0;

    QList<ItemBase*> m_birds;
    QList<ItemBase*> m_pigs;
    QList<ItemBase*> m_obstacles;
    QList<ItemBase*> m_birdQueue;
    ItemBase *m_pCurrBird = nullptr;

    QGraphicsPixmapItem *m_pBoomItem = nullptr;
    QVariantAnimation *m_pBoomAnimation = nullptr;
};

#endif // MAINWIN_H

mainwin.cpp

#include "mainwin.h"
#include "utils.h"
#include "defines.h"

MainWin::MainWin(QWidget *parent)
    : QWidget(parent)
{
    this->setupUi(this);

    initialize();
}

MainWin::~MainWin()
{

}

void MainWin::initialize()
{
    this->setWindowTitle("愤怒的小鸟");
    QIcon icon(":/Resource/UI/icon/title.ico");
    QApplication::setWindowIcon(icon);

    // 遮罩层窗口, 用于弹出对话框时,挡住主窗口 使主窗口变暗
    m_pMaskWgt = new MaskWgt(this);
    m_pMaskWgt->hide();

    // 游戏界面
    m_pView = new View();

    // 游戏场景
    m_pScene = new Scene();
    m_pScene->setSceneRect ( 0, 0, 1800 , 967);
    m_pView->setScene(m_pScene);

    // 暂停按钮
    m_pBtnStop = new QPushButton(m_pView);
    m_pBtnStop->setProperty(DEF_QSS_TYPE, "Button Pause");
    m_pBtnStop->setToolTip("暂停");

    // 主页窗口
    m_pHomeWgt = new HomeWgt;

    // 关卡选择窗口
    m_pLevelWgt = new LevelWgt;

    // 窗口容器
    m_pStackedWgt->addWidget(m_pHomeWgt);
    m_pStackedWgt->addWidget(m_pView);
    m_pStackedWgt->addWidget(m_pLevelWgt);

    // 暂停对话框
    m_pStopDlg = new StopDlg(m_pMaskWgt);
    m_pStopDlg->hide();

    // 游戏结算对话框
    m_pFinishDlg = new FinishDlg(m_pMaskWgt);
    m_pFinishDlg->hide();

    // 设置窗口背景及圆角
    this->setObjectName("mainwin");
    this->setStyleSheet(QString("QWidget#mainwin{background-color: rgb(220, 240, 220);border-bottom-left-radius: %1px;border-bottom-right-radius: %1px;}").arg(10));
    m_pView->setObjectName("view");
    m_pView->setStyleSheet(QString("QWidget#view{background-color: rgb(220, 240, 220);border-bottom-left-radius: %1px;border-bottom-right-radius: %1px;}").arg(10));

    // 创建属性动画 用于动态改变 爆炸图形 的大小达到爆炸的效果
    m_pBoomAnimation = new QVariantAnimation();
    m_pBoomAnimation->setDuration(200);
    m_pBoomAnimation->setStartValue(0.2);
    m_pBoomAnimation->setEndValue(1.0);

    // 读取游戏关卡文件 若没有则使用默认文件
    QString levelPath = Utils::readConfig(qApp->applicationDirPath() + "/ini.cfg", "levelPath").toString();
    if(levelPath.isEmpty() || Utils::fileExsit(levelPath))
    {
        levelPath = ":/Resource/Game/json/level.json";
    }
    m_level = Utils::readJson(levelPath);

    // 将关卡文件传递到关卡选择窗口
    m_pLevelWgt->setLevel(m_level);

    // 链接相关窗口 按钮的信号槽
    connect(m_pHomeWgt, SIGNAL(newGame()), this, SLOT(onNewGame()));
    connect(m_pHomeWgt, SIGNAL(selectLevel()), this, SLOT(onSelectLevel()));
    connect(m_pHomeWgt, SIGNAL(exit()), this, SLOT(onExit()));
    connect(m_pLevelWgt, SIGNAL(selectedLevel(uint)), this, SLOT(onStartLevel(uint)));
    connect(m_pLevelWgt, SIGNAL(home()), this, SLOT(onHome()));
    connect(m_pBtnStop, SIGNAL(clicked(bool)), this, SLOT(onStop()));
    connect(m_pStopDlg, SIGNAL(home()), this, SLOT(onHome()));
    connect(m_pStopDlg, SIGNAL(resume()), this, SLOT(onResume()));
    connect(m_pStopDlg, SIGNAL(repeat()), this, SLOT(onRepeat()));
    connect(m_pFinishDlg, SIGNAL(home()), this, SLOT(onHome()));
    connect(m_pFinishDlg, SIGNAL(repeat()), this, SLOT(onRepeat()));
    connect(m_pFinishDlg, SIGNAL(next()), this, SLOT(onNext()));

    // 链接碰撞信号
    connect(m_pScene, SIGNAL(signalBeginContact(ItemBase*,ItemBase*,QPointF)), this, SLOT(onBeginContact(ItemBase*,ItemBase*,QPointF)));
    // 链接游戏每帧信号
    connect(m_pScene, SIGNAL(signalTimerEvent()), this, SLOT(onTimerEvent()));

    // 链接游戏窗口中的鼠标时间信号, 实现游戏操作
    connect(m_pView, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(onViewMousePress(QMouseEvent*)));
    connect(m_pView, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(onViewMouseRelease(QMouseEvent*)));
    connect(m_pView, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(onViewMouseMove(QMouseEvent*)));

    // 爆炸动画结束时,隐藏爆炸图形
    connect(m_pBoomAnimation, &QVariantAnimation::finished, this, [=](){
        if(m_pScene->items().contains(m_pBoomItem))
        {
            m_pBoomItem->setVisible(false);
        }
    });
    // 实现属性动画对爆炸图形缩放的改变
    connect(m_pBoomAnimation, &QVariantAnimation::valueChanged, this, [=](const QVariant &val) {
        if(m_pScene->items().contains(m_pBoomItem))
        {
            m_pBoomItem->setScale(val.toDouble());
        }
    });
}

void MainWin::startGame(uint level)
{
    // 暂停游戏并清空所有图形
    m_pScene->stop();
    m_currLevel = level;
    m_pScene->clear();
    m_birds.clear();
    m_pigs.clear();
    m_obstacles.clear();
    m_pCurrBird = nullptr;

    // 由于我在View窗口内部实现了上下翻转达到物理中的y轴上方为正方向,所以需要用transform对相关图像进行上下翻转
    QTransform transform;
    transform.reset();
    transform.scale ( 1  , -1 );
    // 游戏界面背景图
    QGraphicsPixmapItem *pBord = new QGraphicsPixmapItem(QPixmap(":/Resource/Game/icon/bord.jpg").transformed(transform, Qt::TransformationMode::SmoothTransformation));
    m_pScene->addItem(pBord);

    //m_p1 与 m_p2 为弹弓图片上系绳子的两个点, m_launchPos 为操作发射点
    m_launchPos = (m_p1 + m_p2) / 2;

    // 弹弓图片太大, 进行相应缩放
    transform.scale(0.5, 0.5);
    // 弹弓右半部分图像
    QPixmap pixSlingshot_2 = QPixmap(":/Resource/Game/icon/slingshot_2.png").transformed(transform, Qt::TransformationMode::SmoothTransformation);
    QGraphicsPixmapItem *pSlingshot_2 = new QGraphicsPixmapItem(pixSlingshot_2);
    m_pScene->addItem(pSlingshot_2);
    pSlingshot_2->setPos(300, 240);

    QPen pen(QColor::fromString("#311706"));
    pen.setWidthF(10.0);
    pen.setCapStyle(Qt::PenCapStyle::FlatCap);
    pen.setJoinStyle(Qt::RoundJoin); // 圆角连接

    // 弹弓绳子(右侧)
    m_pLineItem_2 = new QGraphicsLineItem(QLineF(m_p2, m_launchPos));
    m_pLineItem_2->setPen(pen);
    m_pScene->addItem(m_pLineItem_2);

    // 创建 上下左右四面无形的墙将游戏场景包围
    ItemRect* pBottom = m_pScene->CreateRect(QPointF(m_pView->scene()->sceneRect().width()/2.0, 240), m_pView->scene()->sceneRect().width(), 1);
    pBottom->setBrush(QColor(0, 0, 0, 0));
    pBottom->setData(0, ItemType::Box);
    ItemRect* pTop = m_pScene->CreateRect(QPointF(m_pView->scene()->sceneRect().width()/2.0, m_pView->scene()->sceneRect().height() - 1), m_pView->scene()->sceneRect().width(), 1);
    pTop->setBrush(QColor(0, 0, 0, 0));
    pTop->setData(0, ItemType::Box);
    ItemRect* pLeft = m_pScene->CreateRect(QPointF(0, m_pView->scene()->sceneRect().height() / 2.0), 1, m_pView->scene()->sceneRect().height());
    pLeft->setBrush(QColor(0, 0, 0, 0));
    pLeft->setData(0, ItemType::Box);
    ItemRect* pRight = m_pScene->CreateRect(QPointF(m_pView->scene()->sceneRect().width() - 1, m_pView->scene()->sceneRect().height()/2.0), 1, m_pView->scene()->sceneRect().height());
    pRight->setBrush(QColor(0, 0, 0, 0));
    pRight->setData(0, ItemType::Box);

    // 读取当前关卡数据
    QVariantMap levelMap = m_level[m_currLevel].toMap();
    // 鸟
    QVariantList birds = levelMap.value(DEF_BIRDS).toList();
    // 猪
    QVariantList pigs = levelMap.value(DEF_PIGS).toList();
    // 障碍物
    QVariantList obstacles = levelMap.value(DEF_OBSTACLES).toList();

    // 定义刚体数据
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.linearDamping = 0.1f;

    // 弹性系数(反弹系数),范围 [0,1]
    float restitution = 0.55f;
    // 密度(千克/平方米)
    float density = 1.0f;
    // 摩擦系数,范围通常为 [0,1]
    float friction = 0.1f;

    // 将关卡中的小鸟添加到场景
    for(int i = 0; i < birds.count(); ++i)
    {
        QVariantMap birdMap = birds[i].toMap();
        ItemBase *birdItem = nullptr;
        int type = birdMap.value("type").toInt();
        if(type == 0)
        {
            // 红色小鸟(拟为圆形)
            birdItem = m_pScene->CreateCircle(QPointF(110 * i + 100, 900), 50, bodyDef);
            birdItem->setImage(":/Resource/Game/icon/bird_red.png");
        }
        else if(type == 1)
        {
            // 黄色小鸟(拟为三角形)
            QList<QPointF> points;
            points.append(QPoint(0, 0));
            points.append(QPoint(-80/qSqrt(3), -80));
            points.append(QPoint(80/qSqrt(3), -80));
            birdItem = m_pScene->CreatePolygon(points, bodyDef);
            birdItem->setPos(QPoint(110 * i + 100, 900));
            birdItem->setImage(":/Resource/Game/icon/bird_yellow.png");
        }

        if(birdItem != nullptr)
        {
            birdItem->setBrush(QColor(0, 0, 0, 0));
            birdItem->setMaterial(friction, restitution, 0);
            birdItem->setData(0, ItemType::Bird);
            birdItem->setData(1, type);
            m_birds.append(birdItem);
        }
    }
    m_birdQueue = m_birds;

    // 将关卡中的猪添加到场景
    for(const QVariant &pig: pigs)
    {
        QVariantMap pigMap = pig.toMap();
        ItemBase *pigItem = nullptr;
        int type = pigMap.value("type").toInt();
        if(type == PigType::Pig1)
        {
            pigItem = m_pScene->CreateCircle(QPointF(pigMap.value("x").toDouble(), pigMap.value("y").toDouble()), 50, bodyDef);
            pigItem->setImage(":/Resource/Game/icon/pig.png");
        }

        if(pigItem != nullptr)
        {
            pigItem->setBrush(QColor(0, 0, 0, 0));
            pigItem->setMaterial(friction, restitution, density);
            pigItem->setData(0, ItemType::Pig);
            pigItem->setData(1, type);
            m_pigs.append(pigItem);
        }
    }

    // 将关卡中的障碍物添加到场景
    for(const QVariant &obstacle: obstacles)
    {
        QVariantMap obstacleMap = obstacle.toMap();
        ItemBase *obstacleItem = nullptr;
        int type = obstacleMap.value("type").toInt();

        if(type == 0)
        {
            // 水平木板
            obstacleItem = m_pScene->CreateRect(QPointF(obstacleMap.value("x").toDouble(), obstacleMap.value("y").toDouble()), 150, 20, bodyDef);
            obstacleItem->setImage(":/Resource/Game/icon/plank_1.png");
        }
        else if(type == 1)
        {
            // 垂直木板
            obstacleItem = m_pScene->CreateRect(QPointF(obstacleMap.value("x").toDouble(), obstacleMap.value("y").toDouble()), 20, 120, bodyDef);
            obstacleItem->setImage(":/Resource/Game/icon/plank_2.png");
            // QPixmap(":/Resource/Game/icon/plank_2.png").scaled(100, 600, Qt::IgnoreAspectRatio, Qt::SmoothTransformation).save("D:/test/AngryBirds/Resource/Game/plank_2.png");
        }


        if(obstacleItem != nullptr)
        {
            obstacleItem->setBrush(QColor(0, 0, 0, 0));
            obstacleItem->setMaterial(friction, restitution, density);
            obstacleItem->setData(0, ItemType::Obstacle);
            obstacleItem->setData(1, type);
            m_obstacles.append(obstacleItem);
        }
    }

    // 弹弓绳子(左侧)
    m_pLineItem_1 = new QGraphicsLineItem(QLineF(m_p1, m_launchPos));
    m_pLineItem_1->setPen(pen);
    m_pScene->addItem(m_pLineItem_1);

    // 发射操作器(圆形)
    m_pEllipseItem = new QGraphicsEllipseItem(QRectF(m_launchPos - QPointF(15, 15), m_launchPos + QPointF(15, 15)));
    m_pEllipseItem->setPen(pen);
    m_pEllipseItem->setBrush(pen.color());
    m_pScene->addItem(m_pEllipseItem);

    // 弹弓图像(左侧)
    QPixmap pixSlingshot_1 = QPixmap(":/Resource/Game/icon/slingshot_1.png").transformed(transform, Qt::TransformationMode::SmoothTransformation);
    QGraphicsPixmapItem *pSlingshot_1 = new QGraphicsPixmapItem(pixSlingshot_1);
    m_pScene->addItem(pSlingshot_1);
    pSlingshot_1->setPos(pSlingshot_2->pos().x() - pSlingshot_1->boundingRect().width() + 1, pSlingshot_2->pos().y());

    // 爆炸图像
    QPixmap pix(":/Resource/Game/icon/boom.png");
    m_pBoomItem = new QGraphicsPixmapItem(pix);
    m_pBoomItem->setOffset(-pix.width()/2.0, -pix.height()/2.0);
    m_pBoomItem->setScale(0.5);
    m_pScene->addItem(m_pBoomItem);
    m_pBoomItem->setVisible(false);

    m_pScene->start();
}

void MainWin::resizeEvent(QResizeEvent *event)
{
    // 使游戏场景始终保持在窗口内
    m_pView->fitInView(m_pScene->sceneRect(), Qt::KeepAspectRatio);

    // 使暂停按钮始终在游戏窗口右上角
    m_pBtnStop->move(m_pView->width() - 50, 10);

    // 调整遮罩层窗口位置与大小, 使其刚好覆盖主窗口, 且留出标题栏
    if(m_pMaskWgt){
        m_pMaskWgt->move(this->mapToGlobal(QPoint(0, 0)));
        m_pMaskWgt->resize(this->size());
        m_pStopDlg->move((m_pMaskWgt->width() - m_pStopDlg->width())/2.0, (m_pMaskWgt->height() - m_pStopDlg->height())/2.0);
        m_pFinishDlg->move((m_pMaskWgt->width() - m_pFinishDlg->width())/2.0, (m_pMaskWgt->height() - m_pFinishDlg->height())/2.0);
    }

    QWidget::resizeEvent(event);
}

void MainWin::onNewGame()
{
    // 将游戏窗口设置为当前窗口
    m_pStackedWgt->setCurrentWidget(m_pView);
    // 使游戏场景保持在窗口内
    m_pView->fitInView(m_pScene->sceneRect(), Qt::KeepAspectRatio);
    // 调整暂停按钮位置
    m_pBtnStop->move(m_pView->width() - 50, 10);

    // 开始新游戏
    startGame(0);
}

void MainWin::onSelectLevel()
{
    // 将关卡选择窗口设置为当前窗口
    m_pStackedWgt->setCurrentWidget(m_pLevelWgt);
}

void MainWin::onStartLevel(uint level)
{
    // 根据选择的关卡开始游戏

    m_pStackedWgt->setCurrentWidget(m_pView);
    m_pView->fitInView(m_pScene->sceneRect(), Qt::KeepAspectRatio);
    m_pBtnStop->move(m_pView->width() - 50, 10);

    startGame(level);
}

void MainWin::onExit()
{
    // 退出游戏
    this->window()->close();
}

void MainWin::onHome()
{
    // 隐藏所有对话框及遮罩层窗口
    m_pScene->stop();
    m_pStopDlg->hide();
    m_pFinishDlg->hide();
    m_pMaskWgt->hide();
    // 将主页设置为当前窗口
    m_pStackedWgt->setCurrentWidget(m_pHomeWgt);
}

void MainWin::onResume()
{
    // 隐藏所有对话框及遮罩层窗口
    m_pStopDlg->hide();
    m_pFinishDlg->hide();
    m_pMaskWgt->hide();

    // 继续游戏
    m_pScene->start();
    // 若爆炸动画属于暂停状况,则继续动画
    if(m_pBoomAnimation->state() == QAbstractAnimation::Paused)
    {
        m_pBoomAnimation->resume();
    }
}

void MainWin::onRepeat()
{
    // 隐藏所有对话框及遮罩层窗口
    m_pStopDlg->hide();
    m_pFinishDlg->hide();
    m_pMaskWgt->hide();

    // 重新开始当前关卡
    startGame(m_currLevel);
}

void MainWin::onStop()
{
    // 暂停游戏
    m_pScene->stop();

    // 若爆炸动画正在进行,则暂停爆炸动画
    if(m_pBoomAnimation->state() == QAbstractAnimation::Running)
    {
        m_pBoomAnimation->pause();
    }

    // 显示遮罩层窗口及暂停对话框
    m_pMaskWgt->move(this->mapToGlobal(QPoint(0, 0)));
    m_pMaskWgt->show();
    m_pStopDlg->move((m_pMaskWgt->width() - m_pStopDlg->width())/2.0, (m_pMaskWgt->height() - m_pStopDlg->height())/2.0);
    m_pStopDlg->show();
}

void MainWin::onNext()
{
    // 隐藏所有对话框及遮罩层窗口
    m_pStopDlg->hide();
    m_pFinishDlg->hide();
    m_pMaskWgt->hide();

    // 下一关
    m_currLevel++;
    if(m_currLevel >= m_level.count())
    {
        m_currLevel = 0;
    }

    // 开始下一关
    startGame(m_currLevel);
}

void MainWin::onBeginContact(ItemBase *A, ItemBase *B, QPointF pos)
{
    Q_UNUSED(pos)

    // 检查两个碰撞体是否为一个为鸟,一个为猪
    ItemBase *pPig = nullptr;
    if(A->data(0).toInt() == ItemType::Pig && B->data(0).toInt() == ItemType::Bird)
    {
        pPig = A;
    }
    if(A->data(0).toInt() == ItemType::Bird && B->data(0).toInt() == ItemType::Pig)
    {
        pPig = B;
    }
    // 鸟与猪发生了碰撞
    if(pPig != nullptr)
    {
        // 删除猪,并在猪的位置开始爆炸动画
        m_pScene->DestroyItem(pPig);
        m_pigs.removeOne(pPig);
        m_pBoomItem->setVisible(true);
        m_pBoomItem->setPos(pPig->pos());
        m_pBoomAnimation->stop();
        m_pBoomAnimation->start();

        // 若场景上的猪删除完了,则游戏胜利
        if(m_pigs.isEmpty())
        {
            // 显示遮罩层窗口及结算对话框
            m_pMaskWgt->move(this->mapToGlobal(QPoint(0, 0)));
            m_pMaskWgt->show();
            // 设置星数(目前默认全为3颗星)
            m_pFinishDlg->setStarNumber(3);
            m_pFinishDlg->move((m_pMaskWgt->width() - m_pFinishDlg->width())/2.0, (m_pMaskWgt->height() - m_pFinishDlg->height())/2.0);
            m_pFinishDlg->show();
        }
    }
}

void MainWin::onTimerEvent()
{
    auto birds = m_birds;
    auto pigs = m_pigs;
    auto obstacles = m_obstacles;

    // 场景是否处于静止
    bool isStatic = true;

    for(auto item: birds)
    {
        // 若物体运动到场景之外,则删除(目前场景设置了无形墙体,不会出现该情况)
        if(!m_pScene->sceneRect().contains(item->pos()))
        {
            m_pScene->DestroyItem(item);
            m_birds.removeOne(item);
        }
        // 若有物体的线速度大于1则认为场景非静止
        if(QVector2D(item->linearVelocity()).length() > 1)
        {
            isStatic = false;
        }
    }

    for(auto item: pigs)
    {
        // 若物体运动到场景之外,则删除(目前场景设置了无形墙体,不会出现该情况)
        if(!m_pScene->sceneRect().contains(item->pos()))
        {
            m_pScene->DestroyItem(item);
            m_pigs.removeOne(item);
        }
        // 若有物体的线速度大于1则认为场景非静止
        if(QVector2D(item->linearVelocity()).length() > 1)
        {
            isStatic = false;
        }
    }

    for(auto item: obstacles)
    {
        // 若物体运动到场景之外,则删除(目前场景设置了无形墙体,不会出现该情况)
        if(!m_pScene->sceneRect().contains(item->pos()))
        {
            m_pScene->DestroyItem(item);
            m_obstacles.removeOne(item);
        }
        // 若有物体的线速度大于1则认为场景非静止
        if(QVector2D(item->linearVelocity()).length() > 1)
        {
            isStatic = false;
        }
    }

    // 若场景静止,且鸟已经使用完,则判定游戏失败
    if(m_pCurrBird == nullptr && m_birdQueue.isEmpty() && isStatic && m_pFinishDlg->isHidden())
    {
        m_pMaskWgt->move(this->mapToGlobal(QPoint(0, 0)));
        m_pMaskWgt->show();
        // 失败则为0颗星
        m_pFinishDlg->setStarNumber(0);
        m_pFinishDlg->move((m_pMaskWgt->width() - m_pFinishDlg->width())/2.0, (m_pMaskWgt->height() - m_pFinishDlg->height())/2.0);
        m_pFinishDlg->show();
    }
}

void MainWin::onViewMousePress(QMouseEvent *event)
{
    if(m_pStackedWgt->currentWidget() != m_pView)
    {
        return;
    }
    // 得到光标在场景中的位置
    QPointF pos = m_pView->mapToScene(event->pos());
    // 判断是否点击了发射器
    m_isPressEllipseItem = m_pEllipseItem->shape().contains(pos);
    if(m_isPressEllipseItem)
    {
        // 若点击了发射器,计算一个光标位置补偿
        m_offset = m_pView->mapToScene(event->pos()) - m_launchPos;
        return;
    }

    for(ItemBase *bird: m_birdQueue)
    {
        // 若点击了上方待使用区的鸟
        if(bird->shape().contains(pos - bird->pos()))
        {
            // 若发射器上面已经有鸟,则先将该鸟移动到待使用区
            if(m_pCurrBird != nullptr)
            {
                m_birdQueue.push_back(m_pCurrBird);
            }
            // 将点击的鸟移除待使用区,并放到发射器
            m_birdQueue.removeOne(bird);
            m_pCurrBird = bird;

            // 刷新待使用区的鸟的位置及状态
            float restitution = 0.55f;
            float friction = 0.1f;
            for(int i = 0; i < m_birdQueue.count(); ++i)
            {
                m_birdQueue[i]->setPos(QPointF(110 * i + 100, 900));
                // 密度设置为0, 以保持静止状态
                m_birdQueue[i]->setMaterial(friction, restitution, 0);
            }

            // 将鸟放到发射器位置
            m_pCurrBird->setPos((m_p1 + m_p2) / 2);
            break;
        }
    }
}

void MainWin::onViewMouseRelease(QMouseEvent *event)
{
    Q_UNUSED(event)

    // 如果发射器处于按下状态
    if(m_isPressEllipseItem)
    {
        // 以发射器当前位置与初始位置的连线,计算出发射速度与方向
        QLineF line((m_p1 + m_p2) / 2, m_launchPos);
        // 若连线长度大于0则为有效发射
        if(line.length() > 0)
        {
            // 创建一个属性动画,实现弹弓绳子与发射器 发射后的运动效果
            QVariantAnimation *pBoomAnimation = new QVariantAnimation();
            // 动画时间 100 毫秒
            pBoomAnimation->setDuration(100);
            // 起点 发射器当前位置
            pBoomAnimation->setStartValue(line.p2());
            // 终点 发射器初始位置
            pBoomAnimation->setEndValue(line.p1());

            // 动画结束即销毁,防止内存泄露
            connect(pBoomAnimation, &QVariantAnimation::finished, this, [=](){
                pBoomAnimation->deleteLater();
            });
            // 根据动画属性变换过程中的值调整绳子与发射器的位置,实现动画效果
            connect(pBoomAnimation, &QVariantAnimation::valueChanged, this, [=](const QVariant &val) {
                m_launchPos = val.toPointF();
                m_pLineItem_1->setLine(QLineF(m_p1, m_launchPos));
                m_pLineItem_2->setLine(QLineF(m_p2, m_launchPos));
                m_pEllipseItem->setRect(QRectF(m_launchPos - QPointF(15, 15), m_launchPos + QPointF(15, 15)));
            });

            // 动画开始
            pBoomAnimation->start();

            float restitution = 0.55f;
            float density = 1.0f;
            float friction = 0.1f;

            // 如果发射器上面有鸟,则发射该鸟
            if(m_pCurrBird != nullptr)
            {
                m_pCurrBird->setMaterial(friction, restitution, density);
                // 根据发射器当前位置与初始位置的连线计算出一个矢量的线速度,并赋予当前发射的鸟
                m_pCurrBird->setLinearVelocity((line.p1() - line.p2()) * 5);
                m_pCurrBird = nullptr;
            }
        }
    }
    // 取消发射器的点击状态
    m_isPressEllipseItem = false;
}

void MainWin::onViewMouseMove(QMouseEvent *event)
{
    // 如果发射器被选中
    if(m_isPressEllipseItem)
    {
        // 计算出发射器随鼠标移动的位置
        m_launchPos = m_pView->mapToScene(event->pos()) - m_offset;
        QLineF line = QLineF((m_p1 + m_p2) / 2, m_launchPos);
        // 限制发射器的拉伸长度
        if(line.length() > 300)
        {
            line.setLength(300);
            m_launchPos = line.p2();
        }
        // 改变绳子与发射器的位置
        m_pLineItem_1->setLine(QLineF(m_p1, m_launchPos));
        m_pLineItem_2->setLine(QLineF(m_p2, m_launchPos));
        m_pEllipseItem->setRect(QRectF(m_launchPos - QPointF(10, 10), m_launchPos + QPointF(10, 10)));

        // 若发射器上面有鸟
        if(m_pCurrBird != nullptr)
        {
            // 改变鸟的位置
            m_pCurrBird->setPos(m_launchPos);
            // 根据拉伸角度改变鸟的角度
            m_pCurrBird->setRotation(180-line.angle());
        }
    }
}

项目中已经敲了大量注释, 这里暂时不在过多介绍。

结语

        代码中已经敲了大致注释, 欢迎提问。

        博主坚持原创与开源,主页还有其它项目,觉得还不错的可以点个关注。

       qq: 2947467985

       国际站点:https://github.com/yibobunengyuntian

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值