用qt开发捕鱼达人

您所在的位置:网站首页 捕鱼鱼网图片 用qt开发捕鱼达人

用qt开发捕鱼达人

#用qt开发捕鱼达人| 来源: 网络整理| 查看: 265

前言

整理电脑文件的时候发现了自己以前的作业,顺便就整理传了一下。在那之前我只用过unity开发过游戏,对qt要怎么做游戏还不是很了解,想要系统的学习也找不到特别多的资源,所以学习的过程花费了比较多的时间。

做出来了两个不一样的关卡,鱼有游泳的动画,发射炮弹可以捕捉鱼,并统计分数。

一、游戏效果

游戏主界面

关卡选择

游戏界面

发射子弹和渔网展开

游戏中,玩家可以用鼠标控制大炮的方向,点击鼠标左键,即可朝着鼠标方向发射子弹,子弹在接触到鱼后会展开渔网,渔网中的鱼的生命中会减少,当鱼的生命值为0时,鱼的位置会刷新,出现在窗口之外,以达到消灭鱼的效果,然后重新进入窗口,同时玩家的分数会增加,在屏幕右上方显示分数。不同鱼的生命不同,可以取得的分数也不同。

二、用户交互 1.进入游戏界面

在主窗体上点击“开始游戏”按钮,进入关卡选择界面。点击“退出按钮”,退出游戏。

2.关卡选择界面

在关卡选择界面,共有三个按钮,其中有一个返回按钮,两个选择关卡的按钮。点击返回,则会回到上一级界面,点击不同的关卡,即可进入不同的海域开始捕鱼。共有两个关卡,一个是“绿色珊瑚海”,一个是“蓝色鲸落地”。两个场景有不同的背景图片和鱼供玩家抓捕。

3.捕鱼界面

  在这个界面,玩家可以用鼠标移动来控制大炮方向,并点击鼠标左键来发射子弹,如果子弹触碰到了鱼,则会展开渔网,被触碰到的鱼会重新刷新位置,供玩家继续捕捉。点击返回按钮,玩家会回到关卡选择界面。

三、UI设计和实现

   这个项目用户界面所用UI的资源,均采用的是爱给网(捕鱼达人 - 游戏 免费下载 - 爱给网 (aigei.com))上的免费资源,尽量符合原游戏,但有些资源难以找到,或者原游戏没有,所以用其他图片代替。

本项目下载的资源

  共设计了两个窗口,一个mainwindow和一个levelselect窗口,以及level1和level2两个QGraphicsView场景。

1.Mainwindow窗口

  Mainwindow继承自QMainWindow,用代码生成界面。有一个label,两个Qpushbutton,并为窗口和它们设置了背景图片,让界面更加生动美观。

#include "mainwindow.h" #include "ui_mainwindow.h" #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { //https://www.aigei.com/view/63321.html 图片资源网站,本程序的图片来源 //设置软件窗口的小图标 ui->setupUi(this); this->setFixedSize(1280,720); this->setWindowTitle("捕鱼达人"); this->setWindowIcon(QIcon("../getFish/images/net.png")); //设置背景 QPixmap pixmap = QPixmap(QString("../getFish/images/map4.jpg")).scaled(this->size()); QPalette palette; palette.setBrush(backgroundRole(), QBrush(pixmap)); setPalette(palette); //创建标签 this->label=new QLabel(this); label->setPixmap(QPixmap("../getFish/images/logo.png")); this->label->setFixedSize(319,142); this->label->move(1280/2-319/2,100); //添加开始按钮 this->levelSelectbtn=new QPushButton(this); this->levelSelectbtn->setIcon(QIcon("../getFish/images/start.png")); this->levelSelectbtn->setFixedSize(234,82); this->levelSelectbtn->setIconSize(QSize(234,82)); this->levelSelectbtn->setFlat(true); this->levelSelectbtn->setFocusPolicy(Qt::NoFocus); this->levelSelectbtn->move(1280/2-234/2,340); //用代码创建的按钮,所以要用connect关联,只用按钮名字命名槽函数无效 connect(this->levelSelectbtn, &QPushButton::clicked, this, &MainWindow::levelSelectbtn_clicked); //创建退出按钮 this->quitbtn=new QPushButton(this); this->quitbtn->setIcon(QIcon("../getFish/images/quit.png")); this->quitbtn->setFixedSize(187,83); this->quitbtn->setIconSize(QSize(187,83)); this->quitbtn->setFlat(true); this->quitbtn->setFocusPolicy(Qt::NoFocus); this->quitbtn->move(1280/2-187/2,540); connect(this->quitbtn, &QPushButton::clicked, this, &MainWindow::close); } MainWindow::~MainWindow() { delete ui; } void MainWindow::levelSelectbtn_clicked() { emit showLevelSelect(); /* 显示主窗口 */ this->hide(); /* 隐藏登录对话框 */ } void MainWindow::showThis() { this->show(); } void MainWindow::dohelp() { } 2.levelselect窗口

levelselect继承自QWidget,主要用ui文件编辑样式,并使用了部分代码。

#include "levelselect.h" #include "ui_levelselect.h" levelSelect::levelSelect(QWidget *parent) : QWidget(parent), ui(new Ui::levelSelect) { ui->setupUi(this); //设置背景 this->setFixedSize(1280,720); this->setWindowTitle("捕鱼达人"); this->setWindowIcon(QIcon("../getFish/images/net.png")); QPixmap pixmap = QPixmap(QString("../getFish/images/map4.jpg")).scaled(this->size()); QPalette palette; palette.setBrush(backgroundRole(), QBrush(pixmap)); setPalette(palette); timer=new QTimer; connect(timer,SIGNAL(timeout()),this,SLOT(remake())); timer->start(1); } levelSelect::~levelSelect() { delete ui; } void levelSelect::on_toLevel1btn_clicked() { level1=new Level1; level1needremake=level1->islevle1live; QObject::connect ( level1, SIGNAL (showSelect()), this, SLOT (showThis()) ); level1->show(); //emit showLevel1(); /* 显示主窗口 */ this->hide(); /* 隐藏登录对话框 */ } void levelSelect::on_toLevel2btn_clicked() { level2=new Level2; level2needremake=level2->islevle2live; QObject::connect ( level2, SIGNAL (showSelect()), this, SLOT (showThis()) ); level2->show(); //emit showLevel1(); /* 显示主窗口 */ this->hide(); /* 隐藏登录对话框 */ } void levelSelect::showThis() { this->show(); } void levelSelect::on_quitToMainbtn_clicked() { emit quitToMain(); this->hide(); } void levelSelect::remake() { if(level1->islevle1live==0&&level1needremake==1) { this->show(); level1needremake=0; } } 3.两个QGraphicsView场景

由于游戏中枪需要移动、要点击发射子弹,还要判断渔网和鱼是否有接触,用原本的weiget制作很困难,并且鱼在游动时会出现锯齿,因此使用GraphicsView框架来实现。

Graphics View框架主要特点:

1、 GraphicsView框架结构中可以利用QT绘图系统的反锯齿、OpenGL工具改善绘图性能。

2、GraphicsView框架支持事件传播体系结构,使场景内的图元交互能力提高一倍。图元处理鼠标键盘事件,如鼠标按下、移动、释放、点击和双击事件,也跟踪鼠标移动。

3、在GraphicsView框架中通过二元空间划分树,提供快速的图元查找,这样能实时的显示大场景。

在这两个场景中实现了游戏的主要功能,分别代表了两个不同的关卡。这里以一个为例。

#include "level1.h" Level1::Level1() { //设置大小 //this->resize(1280,720); this->setFixedSize(1280,720); this->setWindowTitle("捕鱼达人"); this->setWindowIcon(QIcon("../getFish/images/net.png")); //设置背景 this->setAutoFillBackground(true); this->setBackgroundBrush(QBrush(QPixmap("../getFish/images/map2.png"))); //添加返回按钮 this->returnbtn=new QPushButton(this); this->returnbtn->setIcon(QIcon("../getFish/images/return.png")); this->returnbtn->setFixedSize(234,82); this->returnbtn->setIconSize(QSize(234,82)); this->returnbtn->setFlat(true); this->returnbtn->setFocusPolicy(Qt::NoFocus); this->returnbtn->move(0,0); //用代码创建的按钮,所以要用connect关联,只用按钮名字命名槽函数无效 connect(this->returnbtn, &QPushButton::clicked, this, &Level1::returnbtn_clicked); //创建分数显示框 this->moneytext=new QTextEdit(this); this->moneytext->setFixedSize(130,50); this->moneytext->setFontPointSize(18); this->moneytext->move(1150,0); this->setMouseTracking(true); scence=new QGraphicsScene; //场景的坐标系统以中心为原点,图片无法正常显示,因此需要重新设置原点 scence->setSceneRect(0,0,this->width()-2,this->height()-2); this->setScene(scence); //new一个大炮 gun=new Gun("../getFish/images/gun1.png",scence); gun->setPos(this->width()/2,this->height()); //类里始终在改变图片,因此外部的图片可以随便设置 //参数分别是初始图片,场景,是否在左侧,生命值和价格 sardine1=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine2=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine3=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine4=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine5=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine6=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine7=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine8=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine9=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine10=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine11=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); sardine12=new Sardine("../getFish/images/bluefish.png",scence,1,3,1); redfish1=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish2=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish3=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish4=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish5=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish6=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish7=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish8=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish9=new Redfish("../getFish/images/bluefish.png",scence,-1,6,2); redfish10=new Redfish("../getFish/images/bluefish.png",scence,-1,2,2); shark1=new Shark("../getFish/images/bluefish.png",scence,-1,9,3); shark2=new Shark("../getFish/images/bluefish.png",scence,-1,9,3); shark3=new Shark("../getFish/images/bluefish.png",scence,-1,9,3); shark4=new Shark("../getFish/images/bluefish.png",scence,-1,9,3); shark5=new Shark("../getFish/images/bluefish.png",scence,-1,9,3); //定时器 timer=new QTimer; connect(timer,SIGNAL(timeout()),scence,SLOT(advance())); connect(timer,&QTimer::timeout,this,&Level1::upmoney); //connect(this->returnbtn, &QPushButton::clicked, this, &Level1::returnbtn_clicked); timer->start(50); } void Level1::resizeEvent(QResizeEvent *event) { this->setBackgroundBrush(QBrush(QPixmap("../getFish/images/map2.png").scaled(event->size()))); } void Level1::mouseMoveEvent(QMouseEvent *event) { QPoint p; p=event->pos(); //获取鼠标位置 //画线 QLine line(this->width()/2,this->height(),p.x(),p.y()); QLineF linef(line); //将大炮角度设置为线的角度 gun->setRotation(90-linef.angle()); //角度要进行特定的变换,才能跟随鼠标旋转 } void Level1::mousePressEvent(QMouseEvent *event) { QPoint p; p=event->pos(); //获取鼠标位置 //画线 //首先找到子弹起始位置,在大炮口,是大炮原点加上半径 QLine line(this->width()/2,this->height(),p.x(),p.y()); QLineF linef(line); if(this->canshoot==1) { Bullet *bullet=new Bullet("../getFish/images/bullet.png",scence,linef.angle(),this); this->canshoot=0; } //bullet->setPos(this->width()/2,this->height()); //将子弹角度设置为线的角度 } void Level1::returnbtn_clicked() { islevle1live=0; delete this; //this->hide(); //emit showSelect(); /* 显示主窗口 */ } void Level1::upmoney() { this->bullettime+=1; int a=this->bullettime; qDebug()canshoot=1; this->bullettime=0; } QString s1="分数:"; QString s2=QString::number(this->money); s1.append(s2); // int a=this->money; // qDebug()drawPixmap(-pixmap.width()/2,-pixmap.height(),pixmap); } 3.枪(Gun)类

继承自QpixmapItem类,主要用来在场景生成枪的图像,没有特殊的函数。

#ifndef GUN_H #define GUN_H #include #include #include #include #include class Gun:public QpixmapItem { public: Gun(const QString & fileName,QGraphicsScene *scence); }; #endif // GUN_H #include "gun.h" Gun::Gun(const QString & fileName,QGraphicsScene *scence):QpixmapItem(fileName,scence) { } 4.子弹(Bullet)类

继承自QpixmapItem类。构造函数可以接受鼠标角度angle和生成的场景level,来决定子弹生成时方向和位置,由于设计了两关,level可以在net消灭了鱼后,改变当前关卡得到的分数。

docolliding()用来进行碰撞处理,在子弹(bullet)触碰到鱼的时候生成一张渔网(net),并delete掉子弹(bullet)。

advance()重载,每个timeout都会执行,如果子弹(bullet)的位置没有超过我设定的边界,就继续朝着子弹方向移动,(方向在构造函数中就已经得到),否则delete,以免占用内存。

paint()重载,重新绘图,并在这里判断子弹是否产生碰撞,如果碰撞,运行碰撞处理docolliding()。

#ifndef BULLET_H #define BULLET_H #include "qpixmapitem.h" #include #include #include #include "net.h" #include #include "level.h" class Bullet:public QpixmapItem { public: Bullet(const QString & fileName,QGraphicsScene *scence,qreal angle,Level *level); void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget); void advance(int phase); void docolliding(); //碰撞处理 Level *level; //level,传给neu,用来给场景返回分数使用 int bullettime=0; //用与设定子弹的发射间隔 private: QGraphicsScene *scence; //让渔网可以加入到场景 }; #endif // BULLET_H #include "bullet.h" Bullet::Bullet(const QString & fileName,QGraphicsScene *scence,qreal angle,Level *level):QpixmapItem(fileName,scence) { this->level=level; this->scence=scence; qreal dx,dy; qreal rad; rad=angle*3.14/180; dx=90*cos(rad); dy=90*sin(rad); this->setPos(scence->width()/2+dx,scence->height()-dy); this->setRotation(90-angle); //角度要进行特定的变换,才能跟随鼠标旋转 } void Bullet::advance(int phase) { if(mapToScene(0,0).x()=1200||mapToScene(0,0).y()setPos(mapToScene(0,-10)); } } void Bullet::docolliding() { Net *net=new Net("../getFish/images/net.png",this->scence,this->level); net->setPos(mapToScene(0,0));//设置渔网生成坐标,为子弹当前坐标 delete this; } void Bullet::paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget) { //位置设置为-pixmap.width()/2,-pixmap.height(),以便在场景绘图时不在场景之外 painter->drawPixmap(-pixmap.width()/2,-pixmap.height(),pixmap); //检测子弹是否有碰撞 if(this->collidingItems().count()>0) { //运行碰撞函数 docolliding(); } } 5 渔网(net)类

继承自QpixmapItem类。构造函数中接收了渔网(net)产生的场景,用来修改对应场景的分数。

advance()重载,每个timeout都执行一次advance()。渔网(net)接触到的所有Item都被放在了链表里,每执行一次advance()都遍历一次链表,让链表中的鱼类(fishes)生命值减少1,当鱼(fishes)的当前生命值(life)为0时,场景level的money增加鱼类(fishs)的price,以实现分数增加的效果,然后执行场景level的upmoney()函数,实现分数显示的更新,最后执行鱼类(fishs)的死亡函数。

在这里,如果直接delete掉渔网(net),渔网会一闪而过,看起来很突兀,为了让渔网多存在一会再消失,每次执行advance()时让变量time+1,加到3时,再delete渔网。这样造成的后果是后面进入渔网的鱼也会减少生命值,但是这样的情况出现的比较少,而且也没有那么不合理。为了适应渔网(net)存在时间的改变,我在创建(鱼类)fishs时让生命增加两倍,这样不影响消灭鱼类需要的打击次数。

#ifndef NET_H #define NET_H #include #include "fishs.h" #include "level.h" #include class Net:public QpixmapItem { public: Net(const QString & fileName,QGraphicsScene *scence,Level *level); void advance(int phase); int time=0; //设置一个time,让渔网存在3个advance后再消失 Level *level; }; #endif // NET_H #include "net.h" Net::Net(const QString & fileName,QGraphicsScene *scence,Level *level):QpixmapItem(fileName,scence) { this->level=level; } void Net::advance(int phase) { if(this->collidingItems().count()>0) //collidingItems返回所有被碰到的鱼,放入链表 { //让接触到渔网的鱼的当前生命值减1,如果生命值为0,分数增加,运行死亡函数,重新刷新位置。 QListlist=this->collidingItems(); QList::Iterator i; Fishs *fishs; i=list.begin(); while(i!=list.end()) { fishs=(Fishs *)(*i); fishs->life-=1; //减少一点当前生命值 if(fishs->life==0) { this->level->money+=fishs->price; //level的分数增加 this->level->upmoney(); // int a=this->level->money; // qDebug()life=life; this->price=price; //初始化位置 this->isLeft=isLeft; if(isLeft==1) //如果是左侧的鱼,出生点设置在左侧 { setPos(-200+rand()%800,0+rand()%500); //用随机数增加出生点的随机性,让开始游戏时鱼分布的比较散 } if(isLeft==-1) //如果是右侧的鱼,出生点设置在右侧 { setPos(100+rand()%900,0+rand()%500); } } void Fishs::advance(int phase) //随着时间的增加,刷新鱼的位置,制造出动画的效果 { //超出边界,则重新刷新位置,刷新位置要在屏幕之外,否则凭空出现在屏幕中央会很突然 if(mapToScene(0,0).y()=1400) { if(this->isLeft==1) //如果是左侧的鱼,刷新在左侧 { setPos(-50-rand()%300,200+rand()%500); //用随机数增加刷新点的随机性,让鱼分开出现 } if(isLeft==-1) //如果是右侧的鱼,刷新在右侧 { setPos(1400+rand()%100,200+rand()%400); } } int speed=rand()%10; //让速度具有随机性,各个鱼移动速度不同 this->setPos(mapToScene(this->isLeft*speed/2.0,upordown*1.0)); //用isLeft乘速度,实现如果是左侧的鱼,向右移动;如果是右侧的鱼,向左移动的效果 } void Fishs::paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget) { painter->drawPixmap(-pixmap.width()/2,-pixmap.height(),pixmap); } void Fishs::fish_death() { //鱼死亡之后不会消失,而是重新刷新在屏幕外,制造出死亡的效果 if(this->isLeft==1) //如果是左侧的鱼,刷新在左侧 { setPos(-50-rand()%300,200+rand()%500); //用随机数增加刷新点的随机性,让鱼分开出现 } if(isLeft==-1) //如果是右侧的鱼,刷新在右侧 { setPos(1400+rand()%150,200+rand()%500); } this->life=this->fulllife; } 7. 其他鱼类(以沙丁鱼为例)

继承自Fishs类,构造函数主要用来向父类传参。

paint()用来实现不同鱼的动画。动画的原理是图片的更换,只要让下载到的资源图片按顺序显示,就可以有比较顺滑的动画效果。刷新不能太快也不能太慢,否则都会不合适,所以为了找出几个timeout换一次图片,我试了几个值,认为5次比较合适。其他鱼也类似,但是由于下载到的图片数量不一样,因此有一些差别。

#ifndef SARDINE_H #define SARDINE_H #include "fishs.h" class Sardine:public Fishs { public: Sardine(const QString & fileName,QGraphicsScene *scence,int isLeft,int life,int price); void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget); //重载动画 }; #endif // SARDINE_H #include "sardine.h" Sardine::Sardine(const QString & fileName,QGraphicsScene *scence,int isLeft,int life,int price):Fishs(fileName,scence,isLeft,life,price) { } void Sardine::paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget) { char filename[50]="0"; //只会初始化一次 //图片每5个timeout刷新一下,以制造出动画效果,经多次尝试取这个值,个人认为不快也不慢正合适 static int i=5; if(i==34) i=5; sprintf(filename,"../getFish/images/sardiner%d.png",i++/5); pixmap.load(filename); painter->drawPixmap(-pixmap.width()/2,-pixmap.height(),pixmap); } 总结

用qt做游戏还是比较麻烦的,写了不少东西,用游戏引擎的话会简单很多,而且效果更好。

但是当时毕竟是要做一个完成,做出来后提升了不少对qt和c++的理解和熟练度。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3