个人记录!!
平台: Windows + Qt Creator 4.5
文章目录
一、项目简介
额…就是打地鼠嘛!!!!
做出来之后的界面:
![](https://i-blog.csdnimg.cn/blog_migrate/776cfb635511681df02885f81e3986f5.png)
- 点击开始:界面开始弹出地鼠,本例采用的是图片随机切换的方式,每次随机切换1~3张图片。鼠标成功单击有地鼠的图片之后右上角分数栏会增加得分。每只地鼠有且只能击中一次。
- 点击暂停:地鼠停止弹出,游戏暂停。
- 点击停止:地鼠停止弹出,游戏结束,清空分数。
需要准备:
- 背景图片1(仅洞口)
- 背景图片2(有老鼠)
- 背景图片3(老鼠被打)
- 锤子(举起与按下两张)
如下:(后两张老鼠自己瞎照着画的。。。)
![](https://i-blog.csdnimg.cn/blog_migrate/cecbb055e100d96c8a5260e8f2cd2dc5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3a0c67009fa140f8e5e33b1656c0da57.png)
![](https://i-blog.csdnimg.cn/blog_migrate/32f47f19ef3db0c8261987268575dc7c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d22a93199cb1c261f6fb8fa3d4271fa6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/87075754a18cfdcb0ed59b15826bb4bd.png)
二、准备阶段
1、动态2D显示容器:QGraphicsView
QGraphicsItem图元 ⊆ QGraphicsScene场景 ⊆ QGraphicsView容器
容器即是mainwindow,场景即是myScene,图元即是myItem。将要显示的图片封装到item里面,多个item共同组成场景scene,最后将场景scene显示到容器里。更多的知识点建议查看Qt Creator的Help文档(真的很强大,比度娘还靠谱!)
2、新建项目
继承自MainWindow
ui布局如下:ui里这块空白的区域,其实就是先新建一个QGraphicsView容器。
![](https://i-blog.csdnimg.cn/blog_migrate/35e33991c676aa070e3d73a90dfe05a6.png)
3、创建场景类myScene
右击->添加新文件->C++->C++ Class,Class name:myScene,Base class:QObjict(回头在文件里手动修改基类为QGraphicsScene)->继续->完成。
进入myscene.h,修改头文件如下:
#ifndef MYSCENE_H
#define MYSCENE_H
#include <QObject>
#include <QGraphicsScene>
class myScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit myScene(QObject *parent = nullptr);
signals:
public slots:
};
#endif // MYSCENE_H
进入myscene.cpp,修改如下:
#include "myscene.h"
myScene::myScene(QObject *parent) : QGraphicsScene(parent)
{
}
4、创建图元类myItem
右击->添加新文件->C++->C++ Class,Class name:myItem,Base class:直接在输入框输入:QGraphicsPixmapItem->继续->完成。
进入myitem.h,包含头文件:
#ifndef MYITEM_H
#define MYITEM_H
#include <QGraphicsPixmapItem>
class myItem : public QGraphicsPixmapItem
{
public:
myItem();
};
#endif // MYITEM_H
三、背景图片布局
1、新建Resources
项目右击->添加新文件->Qt->Qt Resource File(资源文件,管理外部资源)->名称:img
回到主界面,项目框新建了Resources文件夹,展开,右击img.qrc->Open in Editor:
添加->添加前缀:前缀(虚拟文件夹):/bg;添加->添加文件:添加背景图片(3张),/ 表示当前路径。
添加->添加前缀:前缀(虚拟文件夹):/mouse;添加->添加文件:添加锤子图片(2张)。
2、图元添加
进入myitem.cpp里面,修改如下:
#include "myitem.h"
#include <QPixmap>
myItem::myItem()
{
this->setPixmap(QPixmap(":/bg/bg.png"));
}
这样就成功将背景添加到一个图元里了。接下来要把图元封装到场景里面:在场景头文件myscene.h中包含图元头文件:
#include "myitem.h"
并在场景类myScene中创建一个private声明表示该类有一个私有的成员变量:
myItem *item;
进入myscene.cpp:在构造函数里添加(这样就声明了一个图元,并把该图元放入了场景里面):
this->item = new myItem;
this->addItem(this->item);
接下来就该把场景交给容器去显示,而容器在mianwindow里面。
进入mainwindow.h,首先包含场景头文件myscene.h,同时private里添加一个成员变量:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "myscene.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
myScene *sc;
};
#endif // MAINWINDOW_H
进入mianwindows.cpp:构造函数里new出场景对象并显示:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->sc = new myScene;
this->ui->graphicsView->setScene(sc);
}
MainWindow::~MainWindow()
{
delete ui;
}
此时运行就可以在界面上显示出我们的背景图片了,不过这还是远远不够滴。。。因为我们要显示的是16张一样的背景图片。
![](https://i-blog.csdnimg.cn/blog_migrate/d3864c81dc133a0f0a4336d1a44e9a2b.png)
那么怎么添加剩下的背景呢?
进入myscene.h,将场景类myScene中创建的private成员变量:
myItem *item;
修改为:
myItem *item[16];//16个item的数组
来到myscene.cpp的场景构造函数里面,使用循环将图元放入场景:
#include "myscene.h"
myScene::myScene(QObject *parent) : QGraphicsScene(parent)
{
//背景图片尺寸
int width = 160;
int height = 160;
//一共设置16张
for(int i = 0; i < 16; i++){
this->item[i] = new myItem;
this->item[i]->setPos(i/4 * width,i%4 *height);
this->addItem(this->item[i]);
}
这样一个4*4的背景布局就完成了:
![](https://i-blog.csdnimg.cn/blog_migrate/0952e2930c3835def5f6a2f8fc62b66e.png)
四、地鼠弹出
1、弹出方法
实现地鼠弹出大体思路:利用定时器触发槽函数,每隔一段时间就将背景图片切换为地鼠图片。而切换图片的方法与之前设置图片的方法类似。在图元类头文件myitem.h中,包含头文件
#include <QString>//添加路径用
并在类声明public里面定义切换图片的函数:
void setPic(QString path);//用于切换路径->切换图片
并实现:
void myItem::setPic(QString path){
this->setPixmap(QPixmap(path));
}
2、定时器设置
场景头文件myscene.h里面,先包含定时器头文件
#include "myitem.h"
场景声明里面,包含一个定时器指针以及信号设置:
public slots:
void showMouse();
private:
QTimer *ptimer;//定时器
myscene.cpp里面,new出定时器,并连接定时器与槽函数(用于显示地鼠):
this->ptimer = new QTimer;
//换图片:定时器决定隔多久换图片,随机值确定换哪个图片
connect(this->ptimer,SIGNAL(timeout()),this,SLOT(showMouse()));
myscene.cpp里面,当然也包含该函数的实现,但是该函数的实现需要随机数,所以事先需要设置随机数种子。
void myScene::showMouse()
{
//每次显示地鼠之前,都把之前显示的重置掉
for(int i = 0;i < 16;i++){
this->item[i]->setPic(":/bg/bg.png");
}
//随机一个数字0-15,换相应的图片
//随机1-3:弹出老鼠个数
int count = rand()%3 + 1;
for(int i = 0; i < count; i++){
int index = rand()%16; //取值0-15
this->item[index]->setPic(":/bg/bg1.png");//切换图片,显示地鼠
}
}
main.cpp里面,包含随机数头文件
#include <stdlib.h>
#include <time.h>
并设置随机数种子:
srand((unsigned)time(NULL));
现在就可以弹出地鼠了:
![](https://i-blog.csdnimg.cn/blog_migrate/7ddc41734abea2ca1e13918bb6bd0b57.png)
五、事件重写(实现打地鼠)
现在虽然可以弹出地鼠图片,但是点击地鼠图片程序是不会做出反应的!!!
Creator的help文档里面,找到QWidget->Protected Functions->
virtual void mousePressEvent(QMouseEvent event)
这个就是我们要重写的事件:鼠标按下事件。
鼠标点击是在图元上面的,所以要进入myitem.h,声明:
public:
void mousePressEvent(QGraphicsSceneMouseEvent *event);//鼠标按下事件重写
void setMouse(bool mouse);
bool isMouse();//判断是不是地鼠的图片,有为true;点击图片时,检测该值就知道是不是打到了地鼠
private:
bool mouse;
进入myitem.cpp,实现重写函数,同时设定一个标志位:
void myItem::mousePressEvent(QGraphicsSceneMouseEvent *event){
if(this->isMouse()){
this->setPixmap(QPixmap(":/bg/bg2.png"));//打到了地鼠,切换到第三张地鼠被打的背景图片
this->mouse = false;//一个地鼠只能打一次
}
}
void myItem::setMouse(bool mouse){
this->mouse = mouse;
}
bool myItem::isMouse(){
return mouse;
}
同时myscene.cpp中的showMouse函数中,显示地鼠时,将mouse值set为true,反之同理。
void myScene::showMouse(){
for(int i = 0;i < 16;i++){
this->item[i]->setPic(":/bg/bg.png");
this->item[i]->setMouse(false);//设置标志为假,表示么有地鼠
}
int count = rand()%3 + 1;
for(int i = 0; i < count; i++){
int index = rand()%16;
this->item[index]->setPic(":/bg/bg1.png");
this->item[index]->setMouse(true);//设置为true,表示有地鼠
}
}
六、按钮功能实现
这儿使用connect来实现按钮连接(最好不要使用右击转到槽)
connect有个缺点就是不方便设置按钮 setEnabled(true or false),而这个功能个人觉得在这个程序逻辑里还是蛮重要的。
这儿要注意:开始等按钮位于mainwindow里面,而游戏是否开始需要在场景(myscene)里操作。
所以,在myscene.h里面,声明:
public slots:
void startGame();//用于启动定时器ptimer
void pauseGame();
void stopGame();
所以,在myscene.cpp里面实现:
void myScene::startGame(){
this->ptimer->start(1000);
}
void myScene::pauseGame(){
this->ptimer->stop();
}
void myScene::stopGame(){
this->ptimer->stop();
//还原界面
for(int i = 0;i < 16;i++){
this->item[i]->setPic(":/bg/bg.png");
this->item[i]->setMouse(false);//设置标志为假,表示么有地鼠 }
}
回到mainwindow.cpp里面:
//发送者在mianwindow,接收者在scene
connect(this->ui->btn_start,SIGNAL(clicked(bool)),this->sc,SLOT(startGame()));
connect(this->ui->btn_pause,SIGNAL(clicked(bool)),this->sc,SLOT(pauseGame()));
connect(this->ui->btn_stop,SIGNAL(clicked(bool)),this->sc,SLOT(stopGame()));
但是现在存在一个问题,即使在暂停状态下,点击地鼠依然会有相应,所以需要设置一个状态量,来判断是否处于开始状态,只有处于开始状态下,才可以打地鼠!
进入myitem.h,声明:
public:
id setStart(bool start);
bool isStart();
private:
bool start;//用于设置只有在开始状态,才可以打老鼠
实现:
void myItem::setStart(bool start){
this->start = start;
}
bool myItem::isStart(){
return start;
}
在鼠标按下事件,单击开始、暂停、停止按钮实现函数中,均设置以下start状态量,这儿不再一一贴代码了。
七、分数添加实现
注意:分数显示与设置是在mainwindow里面实现的,而有没有击中地鼠是在图元myitem里进行判断的。所以要有一种方式把是否击中地鼠这个事件发送到主窗口。可以采取:
- 设置全局变量:不推荐,效率低,延迟
- 单例:永远只会产生一个对象,也只能被获取到这一个对象
这里采用单例模式实现:将构造函数设为私有, 通过调用static 成员函数生成对象
关于c++单例:C++ 单例模式总结与剖析 - 行者孙 - 博客园
单例模式的编写:
a) 构造函数设为私有,此时无法生成对象
b) 编写一个成员函数来生成对象,但是无法调用。
c) 将该函数设为static,此时可以生成对象,但是对象不唯一。
d) 添加一个static指针成员,仅当该指针为NULL(也就是第一次访问时)才去生成对象。
e) 但是此时的代码在多线程环境下存在竞态问题。。。不过这儿并不涉及
总之就是添加了一个单例,在单例里面实现分数的添加。
最后还有一个细节就是,之前设置停止游戏的时候,是没有实现分数的清零的。因为停止游戏是在场景myscene里实现的,但是分数在主窗口,是不可以在场景里面把分数清空的。可以这样设置:
mainwindow.h里面,声明:
private slots:
void clearScore();
mainwindow.cpp里面,实现:
void MainWindow::clearScore(){
this->ui->lcdNumber->display("0");
}
并连接(qt里一个按钮是可以绑定多个槽函数的):
connect(this->ui->btn_stop,SIGNAL(clicked(bool)),this,SLOT(clearScore()));
除此之外鼠标可以设置为锤子的图片,不再赘述,留下又菜又累的泪水 -_-。
![](https://i-blog.csdnimg.cn/blog_migrate/b7ff1710250aa97d9c7683c524ff683d.png)