效果图
前言
Qt 是一个用于跨平台应用程序的开发框架。
实现这个小游戏用到了QGraphicsView图形视图框架,GraphicsView框架结构主要包含三个主要的类。
QGraphicsScene(场景):用于放置图元的容器,本身不可见。必须通过与之相连的QGraphicsView视图来显示及与外界进行交互;
QGraphicsView(视图):提供一个可视的窗口,用于显示场景中的图元,一个场景中可以有多个视图。
QGraphicsItem(图元):它是场景中各个图元的基类,在它的基础上可以继承出各种图元类。
Scene(场景)就像一个屋子,里面放满各种各样的物品(Item(图元))。View(视图)就像窗户,通过窗户可以看到屋子的东西,可以对屋子里面的物品进行操作。
1、新建工程并初始视图
新建工程
首先新建一个Qt Widgets 工程。我这里是在Windows下开发的用的MSVC2019 32bit。
将用到的素材图片导入
初始化视图
首先获取一下屏幕的宽高方便设置场景和视图的宽高。
ini
QScreen *screen = QGuiApplication::primaryScreen();
QRect availableGeometry = screen->availableGeometry();
int screenWidth = availableGeometry.width();
int screenHeight = availableGeometry.height();
接下来定义并初始化一个宽高是屏幕的3/4场景和视图,用来显示游戏。主要用到下面几个方法:setSceneRect(0, 0, screenWidth * 0.75, screenHeight * 0.75)
设置了场景左上角坐标是(0,0),表示如果设置图元的坐标为(0,0)那么将显示在最左上角。同时设置了宽高为3/4的屏幕宽高。
场景的坐标系如下所示:
setWindowTitle
用于设置视图的标题,setGeometry
用于设置视图的显示位置和宽高,
(screenWidth*0.25)/2, (screenHeight*0.25)/2
表示设置视图左上角的坐标位于屏幕的中间((屏幕宽高-视图宽高)/2
)后面2个参数代表宽高是屏幕的3/4,+10是为了视图略大于场景以完整显示场景;setWindowIcon
设置窗口的icon图标。
scss
QGraphicsScene *scene;
QGraphicsView *view;
// 创建一个场景宽高是屏幕的3/4
scene = new QGraphicsScene();
scene->setSceneRect(0, 0, screenWidth * 0.75, screenHeight * 0.75);
//创建视图
view = new QGraphicsView;
view->setScene(scene);
view->setWindowTitle("财源广进");
view->setWindowIcon(QIcon(":/image/jinyuanbao.png"));
view->setGeometry((screenWidth*0.25)/2, (screenHeight*0.25)/2,
screenWidth * 0.75 + 10, screenHeight * 0.75 + 10);
view->show();
此时的效果如下是一个空白的窗口。
2、实现接元宝的兜
接下来实现一个能用键盘控制左右移动的兜用来接元宝。这个兜其实是一张图片,我们自定义一个类继承QGraphicsPixmapItem
。构造函数传入要用的图片路径和要显示的场景。
兜默认显示在场景底部的中间,所以在计算后通过setPos设置坐标。前面设置的场景左上角为(0,0);
所以(场景高度-图片高度) 可以使图片底部刚好贴住场景底部,所以y = scene.sceneRect().height()-pixmap.height();
x坐标类似。
less
//定义一个兜 用于接元宝
class Catcher :public QObject, public QGraphicsPixmapItem {
private:
QGraphicsScene &mScene;
public:
Catcher(QString imagepath, QGraphicsScene &scene) :mScene(scene) {
QPixmap pixmap = QPixmap(imagepath);
setPixmap(pixmap);
qreal x = (scene.sceneRect().width()-pixmap.width())/2;
qreal y = scene.sceneRect().height()-pixmap.height();
qDebug() << "Catcher set: " <<QString("width x height:%1x%2").arg(pixmap.width()).arg(pixmap.height()) <<QString(" xy:%1x%2").arg(x).arg(y);
setPos(x, y);
}
void keyPressEvent(QKeyEvent *event) {
// qDebug() << "Catcher event move " << event->key() << " x:" << x() <<" y:"<<y();
if (event->key() == Qt::Key_Left && x() > 0) {
//向左移动
setPos(x() - 20, y());
} else if (event->key() == Qt::Key_Right && x() < (mScene.sceneRect().width() - pixmap().width())) {
//向右移动
setPos(x() + 20, y());
}
}
};
然后重写keyPressEvent控制它的移动。重点是控制移动的边界,只需要左右移动所以Y轴的坐标不用管;
根据场景的坐标,在X轴向左移动只需要控制x坐标不大于0就行了;向右移动是控制兜的右边不要超过场景的右边界,即x() < (mScene.sceneRect().width() - pixmap().width())
。20指的是一次按键移动的距离。
效果如下
3、实现元宝的掉落
接下来实现元宝的随机掉落,每个元宝都可以看作一个图元。所以可以用一个定时器经过若干时间生成一个图元添加到场景中即可。这里要分成两步,第一步添加显示元宝,第二步元宝的掉落移动。
第一步添加显示元宝,先要随机生成元宝的初始位置,Y轴坐标为0,X轴在(0-场景宽度)之间随机一个值出来。然后添加到场景中显示出来,同时用List将每个位置的存下来,之后刷新移动会用到。然后每生成一个元宝后重新设置下次定时器触发时间。
ini
QList<QGraphicsPixmapItem*> ybList;
QTimer createTimer;
QPixmap pixmap = QPixmap(":/image/jinyuanbao.png");
QObject::connect(&createTimer, &QTimer::timeout, [&]() {
qreal xPos = QRandomGenerator::global()->
bounded(0,static_cast<int>(scene->sceneRect().width() -pixmap.width()));
int yPos = 0;
QGraphicsPixmapItem *ybItem = new QGraphicsPixmapItem(pixmap);
ybItem->setPos(xPos, yPos);
ybList.append(ybItem);
scene->addItem(ybItem);
int randomNumber = QRandomGenerator::global()->bounded(500, 1301);
// qDebug() << "createYB:" << randomNumber;
createTimer.setInterval(randomNumber);
});
createTimer.start(500);
实现效果
到这里实现的元宝都是静态的不会动,所以第二步是控制它们的移动。
这里同样用一个定时器去更新它们的位置达到模拟掉落的效果。每次刷新保持X轴坐标不变,Y轴添加一个步长即可。不过感觉用平移动画可能效果会好点。
ini
QTimer updateTimer;
QObject::connect(&updateTimer, &QTimer::timeout, [&]() {
for (QGraphicsPixmapItem* item : ybList) {
QPointF itemPos = item->scenePos();
qreal x = itemPos.x();
qreal y = itemPos.y();
y += 3;
item->setPos(x, y);
}
});
updateTimer.start(1000 / 60);
效果如下
4、计算接住的元宝
到这里就是计算图元的碰撞计分了。
collidingItems
是 QGraphicsItem 类中的一个函数,用于获取与当前图元重叠的其他图元。该函数返回一个 *QList<QGraphicsItem > ,包含与当前图元重叠的所有图元。
在每次刷新的时候获取与兜碰撞的图元,将这个元宝从场景中移除并计算分数即可。
ini
// 处理碰撞逻辑 计分
QList<QGraphicsItem*> collidingItems = catcher->collidingItems();
if (!collidingItems.isEmpty()) {
for (QGraphicsItem* item : collidingItems) {
scene->removeItem(item);
wealthValue++;
}
qDebug() << "Collision detected!" << collidingItems.size() << " wealthValue:" << wealthValue;
if(wealthValue >= 3) {
createTimer.stop();
updateTimer.stop();
qDebug() << "quit!";
scene->clear();
//结算画面
QPixmap pixmap(":/res/caiyuangungun.png");
pixmap = pixmap.scaled(scene->height(), scene->height(), Qt::KeepAspectRatio); // 缩放图片
QGraphicsPixmapItem *pixmapItem = new QGraphicsPixmapItem(pixmap);
pixmapItem->setPos(scene->width() / 2 - pixmapItem->boundingRect().width() / 2,
scene->height() / 2 - pixmapItem->boundingRect().height() / 2);
scene->addItem(pixmapItem);
return;
}
}
到这里小游戏的实现就完成了。
5、打包成exe
将Qt工程变成release版本,然后编译得到exe程序,将exe程序拷贝到一个空目录下。
打开对应的命令行工具,这里看之前用的是什么。
切换到你创建的目录,然后执行windeployqt
命令。
源码其实就一个文件加3张图片