用qt开发捕鱼达人

该文详细介绍了使用Qt进行游戏开发的过程,特别是如何构建图形界面、实现用户交互和游戏逻辑。作者通过创建MainWindow、LevelSelect窗口和两个QGraphicsView场景来模拟游戏的不同阶段,利用QGraphicsView框架处理鱼的动画和碰撞检测。文章还提到了资源获取、窗口间跳转以及自定义鱼和子弹类的设计,展示了游戏开发中的问题解决和技巧应用。
摘要由CSDN通过智能技术生成

前言

整理电脑文件的时候发现了自己以前的作业,顺便就整理传了一下。在那之前我只用过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 <QPalette>
#include <QBrush>

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()<<a;
    if(this->bullettime==15)
    {
        this->canshoot=1;
        this->bullettime=0;
    }
    QString s1="分数:";
    QString s2=QString::number(this->money);
    s1.append(s2);

//    int a=this->money;
//    qDebug()<<a<<"!";
    this->moneytext->setText(s1);
}

在这两个场景,除了设置背景,还添加了显示分数的文本框,返回按钮,并添加了自己设计的各种鱼、炮,还完成了定时器和槽函数的关联,鼠标点击的响应、分数更新等操作。

4.窗口间的跳转

主窗口和关卡选择的跳转主要用信号槽机制实现,点击按钮可以相互跳转。

#include "mainwindow.h"
#include <QTextCodec>
#include <QApplication>
#include <levelselect.h>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    levelSelect levelSelect;
    QObject::connect ( &w, SIGNAL ( showLevelSelect() ), &levelSelect, SLOT ( showThis() ) );
    QObject::connect ( &levelSelect, SIGNAL ( quitToMain() ), &w, SLOT ( showThis() ) );
    w.show();
    return a.exec();
}

  在关卡选择,本来打算继续使用信号槽机制实现关卡选择和关卡间的切换,但总是报错,修改多次仍然无法解决,推测可能和关卡继承自QGraphicsView有关,于是切换了思路。在关卡里增加两个变量,一个用来判断是否生成过关卡,一个用来判断关卡是否还存在,如果都满足,则重新显示关卡选择界面,实现从关卡返回到关卡选择。

四、游戏图元设计和实现

本项目设计了四种鱼类,沙丁鱼(Sardine),蓝鱼(Bluefish),红鱼(Redfish),和鲨鱼(Shark),继承自父类鱼类(Fishs)。和三种其他类,枪(Gun),子弹(Bullet)和渔网(Net),继承自父类QpixmapItem。Fishs也继承自QpixmapItem,QpixmapItem继承自QGraphicdItem。

1.QGraphicsltem类

QGraphicsltem类是视图框架的一部分,是在一个QGraphicsScene中最基本的图形类,它为绘制你自己的item提供了一个轻量级的窗口,包括声明item的位置,碰撞检测,绘制重载和item之间的相互作用通过事件处理。

写自己的item图形,首先应该继承QGraphicsItem,然后重写他的两个纯虚公共函数,boundingRect()和paint(),第一个函数返回绘制item大概的区域,第二个函数用来绘制item内容。

boundingRect()函数有很多用处,场景在boundingRect()来建立它的item的index,视图view使用它来剪切可见的item,在重新绘制item时候,来决定相互重叠的部分,此外,item的碰撞检测机制也使用的boundingRect()来提供一个高效的定点,在collidesWithItem()更好的碰撞算法建立在调用函数shape(),shape()函数以QpainterPath类型返回item的精准的轮廓。

2. QpixmapItem类

自己的item图形,继承自QGraphicsltem,构造函数的参数有文件名称和场景,重写了两个纯虚公共函数,boundingRect()和paint(),用来绘图和判断碰撞。

#ifndef QPIXMAPITEM_H
#define QPIXMAPITEM_H
#include <QGraphicsItem>
#include <QPainter>
#include <QGraphicsScene>
class QpixmapItem:public QGraphicsItem
{
public:
    QpixmapItem(const QString & fileName,QGraphicsScene *scence);
    //重载纯虚函数
    virtual QRectF boundingRect() const;
    void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget);
protected:
    QPixmap pixmap;

};

#endif // QPIXMAPITEM_H
#include "qpixmapitem.h"

QpixmapItem::QpixmapItem(const QString & fileName,QGraphicsScene *scence)
{
    pixmap.load(fileName);
    scence->addItem(this);
}
QRectF QpixmapItem::boundingRect() const
{
    return QRectF(-pixmap.width()/2,-pixmap.height(),pixmap.width(),pixmap.height());
}
//
void QpixmapItem::paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget)
{
    //位置设置为-pixmap.width()/2,-pixmap.height(),以便在场景绘图时不在场景之外
    painter->drawPixmap(-pixmap.width()/2,-pixmap.height(),pixmap);
}

3.枪(Gun)类

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

#ifndef GUN_H
#define GUN_H
#include <QGraphicsItem>
#include <QPainter>
#include <QPixmap>
#include <qpixmapitem.h>
#include <QGraphicsScene>
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 <QLine>
#include <QLineF>
#include <QtGui>
#include "net.h"
#include <QGraphicsScene>
#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()<=0||mapToScene(0,0).x()>=1200||mapToScene(0,0).y()<=0)
    {
        //超出边界删除自己,以防占用内存
        delete this;
    }
    else
    {
        //没有超出则继续前进
        this->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 <qpixmapitem.h>
#include "fishs.h"
#include "level.h"
#include <QDebug>
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,分数增加,运行死亡函数,重新刷新位置。
        QList<QGraphicsItem *>list=this->collidingItems();
        QList<QGraphicsItem *>::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()<<a<<"!";
                fishs->fish_death(); //运行鱼死亡函数

            }
            i++;
        }
    }
    time+=1;
    if(time==3) //存在3个advace后才消失,让渔网多存在一会,不会太突兀
    {
        this->hide();
        delete this;
    }

}

6. 鱼(fishs)类

继承自QpixmapItem类,同时是其他所有鱼的父亲类。构造函数的参数有isLeft,life和price,分别是鱼类是否生成在左侧,鱼类的最大生命值和鱼类死亡后的价格。

advance()重载,用来判断鱼类是否超出了设定的边界值,如果超出就刷新它的位置,isLeft是1就刷新在左侧,是-1就刷新在右侧,如果没有超过就继续移动。为了让鱼的游动速度不一样,生成了随机数,让水平移动的速度不一样,上下移动的速度和方向不一样。

paint()没有什么功能,因为不同的鱼动画不同,要在子类里实现,所以之后重构时解决。

fish_death(),死亡函数,在鱼的生命值降到0时会执行,但不是直接delete掉,而是重新刷新在窗口外,然后将当前生命值变为最大生命值,让玩家看起来消灭了鱼,但其实没有,这样我可以不用再生成新的鱼。

#ifndef FISHS_H
#define FISHS_H

#include "qpixmapitem.h"
#include <QGraphicsScene>
class Fishs:public QpixmapItem
{
public:
    Fishs(const QString & fileName,QGraphicsScene *scence,int isLeft,int life,int price);
    void advance(int phase);
    void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget);
    void fish_death(); //鱼死亡函数
    int price;  //鱼的价格
    int sumprice; //一条鱼总共产生的价格
    int isLeft; //判断是否出生在左边
    int fulllife; //鱼的完整生命值,用来重置死亡后的当前生命值
    int life; //现在的生命值
    int upordown=rand()%2==1? 1:-1;

};

#endif // FISHS_H
#include "fishs.h"

Fishs::Fishs(const QString & fileName,QGraphicsScene *scence,int isLeft,int life,int price):QpixmapItem(fileName,scence)
{
    //初始化最大生命值和当前生命值
    this->fulllife=life;
    this->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()<=-400||mapToScene(0,0).x()>=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++的理解和熟练度。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捕赤鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值