Qt项目——飞机大战

一、主界面

主界面的布局采用固定大小的窗口,使用图片背景;有一个由图片填充的按钮,点击可开始游戏。 主界面窗口的参数需在配置头文件 config.h 中提前设定:

#define GAME_WIDTH 480
#define GAME_HEIGHT 854
#define GAME_TITLE "飞机大战"

开始按钮需设定为图片填充:

bt_start = new QPushButton;
bt_start->setGeometry(GAME_WIDTH/2, GAME_HEIGHT/2, 161, 43);
bt_start->clearMask();
bt_start->setBackgroundRole(QPalette::Base);
QPixmap startpix;
startpix.load(":/images/start.png");
bt_start->setFixedSize(startpix.width(), startpix.height());
bt_start->setMask(startpix.createHeuristicMask());
bt_start->setIcon(startpix);
bt_start->setIconSize(QSize(startpix.width(), startpix.height()));

此时的按钮布局并不在窗口的中间,这里采用了透明的lable和网格布局,将按钮居中显示:

fill1 = new QLabel;
fill2 = new QLabel;
fill3 = new QLabel;
QGridLayout *gbox = new QGridLayout;
gbox->addWidget(fill1, 1, 0, 3, 1);
gbox->addWidget(bt_start, 1, 1, 1, 1);
gbox->addWidget(fill2, 1, 2, 3, 1);
gbox->addWidget(fill3, 0, 0, 1, 3);
setLayout(gbox);

最后再连接按钮的槽函数:

connect(bt_start, SIGNAL(clicked(bool)), this, SLOT(showgame()));

在showgame()槽函数中显示出游戏主界面

二、游戏界面

1、地图滚动的实现

地图滚动的实现原理:使用两张相同的地图资源图片,在窗口中从上到下循环播放,当第二张照片滚动出屏幕时,回到初始位置重新开始滚动,具体实现函数如下:

    pos1Y += SCROLL_SPEED;
    if(pos1Y >= 0)
        pos1Y = -GAME_HEIGHT;
    pos2Y += SCROLL_SPEED;
    if(pos2Y >= GAME_HEIGHT)
        pos2Y = 0;

其中,SCOLL_SPEED等参数在config.h中提前定义。

2、将本机添加到界面中

在实现了地图滚动之后,就要将自己的飞机添加到游戏界面中了,在myplane类中设定好飞机的初始坐标和边框,再使用drawPixmap绘制在窗口中即可,部分代码如下:

myplane::myplane(QWidget *parent) : QWidget(parent)
{
    my_plane.load(MY_PLANE);

    my_x = GAME_WIDTH * 0.5 - my_plane.width()*0.5;
    my_y = GAME_HEIGHT - my_plane.height();

    my_rect.setWidth(my_plane.width());
    my_rect.setHeight(my_plane.height());
    my_rect.moveTo(my_x, my_y);
}

void myplane::setPosition(int x, int y)   //设置飞机位置
{
    my_x = x;
    my_y = y;
    my_rect.moveTo(my_x, my_y);
}

3、游戏界面无边框

为了使玩家只能通过死亡,暂停后退出来退出当前游戏界面,将该界面设置为无边框可拖动的形式,代码如下:

this->setWindowFlags(Qt::FramelessWindowHint);
void gamewindow::mousePressEvent(QMouseEvent *event)
{
    this->windowPos = this->pos();                // 获得部件当前位置
    this->mousePos = event->globalPos();     // 获得鼠标位置
    this->dPos = mousePos - windowPos;       // 移动后部件所在的位置
}

void gamewindow::mouseMoveEvent(QMouseEvent *event)
{
    this->move(event->globalPos() - this->dPos);
}

至此,游戏界面已经初步成型了,接下来就需要控制飞机的移动和发射炮弹了

三、本机功能实现

1、本机移动

该游戏采用键盘方向键来控制飞机的移动,获取键盘喊下事件,将按下的方向键记录到本机移动方向变量中,再根据所记录的移动方向修改飞机响应的坐标即可,最后再将飞机更改后的位置绘制在游戏界面中,部分代码如下:

void gamewindow::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Up)
        me.fly_dir = FLY_UP;
    if(event->key() == Qt::Key_Down)
        me.fly_dir = FLY_DONW;
    if(event->key() == Qt::Key_Left)
        me.fly_dir = FLY_LEFT;
    if(event->key() == Qt::Key_Right)
        me.fly_dir = FLY_RIGHT;
}

void gamewindow::me_pos()
{
    if(me.fly_dir == FLY_UP){
        me.my_y = me.my_y - 30;
        if(me.my_y < 0)
            me.my_y = 0;
        me.setPosition(me.my_x, me.my_y);
        me.fly_dir = 0;
    }
    if(me.fly_dir == FLY_DONW){
        me.my_y = me.my_y + 30;
        if(me.my_y > GAME_HEIGHT - me.my_plane.height())
            me.my_y = GAME_HEIGHT- me.my_plane.height();
        me.setPosition(me.my_x, me.my_y);
        me.fly_dir = 0;
    }
    if(me.fly_dir == FLY_LEFT){
        me.my_x = me.my_x - 40;
        if(me.my_x < 0)
            me.my_x = 0;
        me.setPosition(me.my_x, me.my_y);
        me.fly_dir = 0;
    }
    if(me.fly_dir == FLY_RIGHT){
        me.my_x = me.my_x + 40;
        if(me.my_x > GAME_WIDTH - me.my_plane.width())
            me.my_x = GAME_WIDTH- me.my_plane.width();
        me.setPosition(me.my_x, me.my_y);
        me.fly_dir = 0;
    }
}

void gamewindow::me_position_change()
{
    p.start();      //启动位置更新定时器

    connect(&p, &QTimer::timeout, [=](){
       me_pos();

       update();
    });
}

2、本机炮弹发射

实现了本机的移动之后,就应该在本机的当前位置发射炮弹了。采用一个Vector容器存储一定量的炮弹,每发炮弹以一定的时间间隔发射,并记录容器中的炮弹是否被射出,在射出后为其置上一个标志,方便炮弹的”重复利用“;当炮弹飞出屏幕后(这里暂不判断射中敌机)将该炮弹置为未发射的状态即可。当炮弹被发射后,只需根据在config.h中设定的炮弹飞行速度来更改炮弹的Y轴坐标并重绘图片即可实现炮弹的飞行,部分代码如下:

bullet::bullet(QWidget *parent) : QWidget(parent)
{
    b_pix.load(MY_BULLET);

    b_x = GAME_WIDTH * 0.5 - b_pix.width() * 0.5;
    b_y = GAME_HEIGHT;

    b_is_shoot = false;   //子弹未发射

    b_speed = BULLET_SPEED;

    b_rect.setWidth(b_pix.width());
    b_rect.setHeight(b_pix.height());
    b_rect.moveTo(b_x, b_y);
}

void bullet::bullet_pos()
{
    if(b_is_shoot)     //子弹已发射
    {
        b_y -= b_speed;
        b_rect.moveTo(b_x, b_y);

        if(b_y <= -b_rect.height())
            b_is_shoot = false;
    }
    else
        return;
}

void gamewindow::shoot()     //发射子弹
{
    shoot_record++;

    if(shoot_record < BULLET_INTERVAL)
        return;
    shoot_record = 0;
    for(int i = 0; i < BULLET_NUM; i++){
        if(!my_bullet[i].b_is_shoot)
        {
            my_bullet[i].b_is_shoot = true;

            my_bullet[i].b_x = me.my_x + me.my_rect.width()*0.5 - 10;
            my_bullet[i].b_y = me.my_y - 25;
            break;
        }
    }
}

至此,本机的主要功能也已基本实现。

四、敌机功能实现

1、生成敌机

敌机的生成与本机的生成类似,区别仅在于敌机产生位置的X轴坐标为随机数,故需要设定合理的随机数在不同的位置来生成敌机,此外,敌机也和炮弹类似,需要使用一个Vector容器和是否被发射的标志来重复利用。敌机的移动也与炮弹类似,这里不再赘述,部分代码如下:

enemyplane::enemyplane()
{
    enemy_pix.load(ENEMY_PLANE);

    enemy_x = 0;
    enemy_y = 0;

    is_out = false;

    enemy_speed = ENEMY_SPEED;

    enemy_rect.setWidth(enemy_pix.width());
    enemy_rect.setHeight(enemy_pix.height());
    enemy_rect.moveTo(enemy_x, enemy_y);
}

void enemyplane::enemy_pos()
{
    if(!is_out)
        return;
    enemy_y += enemy_speed;
    enemy_rect.moveTo(enemy_x, enemy_y);

    if(enemy_y >= GAME_HEIGHT + enemy_rect.height())
    {
        is_out = false;
    }
}

void gamewindow::out_enemy_plane()
{
    out_record++;

    if(out_record < ENEMY_INTERVAL)
        return;

    out_record = 0;
    for(int i = 0; i < ENEMY_NUM; i++){
        if(!enemy[i].is_out){
            enemy[i].is_out = true;

            enemy[i].enemy_x = rand()%(GAME_WIDTH - enemy[i].enemy_rect.width());
            enemy[i].enemy_y = -enemy[i].enemy_rect.height();
            break;
        }
    }
}

2、碰撞检测

在实现了本机的移动、本机炮弹发射和敌机生成后,就可以实现炮弹和敌机的碰撞检测了,本机发射的炮弹和敌机都有各自的边框,当检测到边框重合时,即发生了碰撞,便可以产生敌机爆炸,音效播放,敌机消失等事件,碰撞检测的部分代码如下:

for(int i = 0; i < ENEMY_NUM; i++){
        if(!enemy[i].is_out){
            continue;
        }

        for(int j = 0; j < BULLET_NUM; j++){
            if(!my_bullet[j].b_is_shoot)
                continue;

            if(enemy[i].enemy_rect.intersects(my_bullet[j].b_rect)){
                enemy[i].is_out = false;
                my_bullet[j].b_is_shoot = false;
                //score->setText(QString("得分:%1").arg(++s));
                }
        }
}

产生碰撞后,便可以开始播放爆炸图片,爆炸效果由七张图片组成,通过快速播放七张图片,模拟爆炸效果,爆炸和音效播放的部分代码如下:

explotion::explotion(QWidget *parent) : QWidget(parent)
{
    for(int i = 1; i <= EXPLOTION_MAX; i++){
        QString str = QString(EXPLOTION_PATH).arg(i);
        exp_pix.push_back(QPixmap(str));
    }

    exp_x = 0;
    exp_y = 0;

    exp_boom = false;

    pix_index = 0;

    exp_record = 0;
}

void explotion::updatepix()
{
    if(!exp_boom)
        return;

    exp_record++;
    if(exp_record < EXPLOTION_INTERVAL)
        return;

    exp_record = 0;
    pix_index++;
    if(pix_index > EXPLOTION_MAX - 1){
        pix_index = 0;
        exp_boom = false;
    }
}

//以下代码在碰撞检测代码中
for(int k = 0; k < EXPLOTION_NUM; k++){
     if(!exp[k].exp_boom){
           QSoundEffect *boom=new QSoundEffect;
           boom->setSource(QUrl::fromLocalFile(SOUND_EXPLOTION));
           boom->setVolume(0.07f); //音量
           boom->play();
           exp[k].exp_boom = true;
           exp[k].exp_x = enemy[i].enemy_x;
           exp[k].exp_y = enemy[i].enemy_y;
           break;
     }
}

至此已经可以实现正常的发射炮弹,摧毁敌机的功能,接下来就需要实现敌机炮弹的发射了

3、敌机发射炮弹

敌机的炮弹发射与本机的炮弹发射原理一致,这里不再赘述,只展示部分代码:

enemybullet::enemybullet()
{
    e_b_pix.load(ENEMY_BULLET);

    e_b_x = GAME_WIDTH * 0.5 - e_b_pix.width() * 0.5;
    e_b_y = e_b_pix.height();

    e_b_is_shoot = false;     //敌机子弹未发射

    e_b_speed = ENEMY_BULLET_SPEED;

    e_b_rect.setWidth(e_b_pix.width());
    e_b_rect.setHeight(e_b_pix.height());
    e_b_rect.moveTo(e_b_x, e_b_y);
}

void enemybullet::enemy_bullet_pos()
{
    if(e_b_is_shoot){
        e_b_y += e_b_speed;
        e_b_rect.moveTo(e_b_x, e_b_y);

        if(e_b_y >= GAME_HEIGHT)
            e_b_is_shoot = false;
    }
    else
        return;
}

在实现敌机可发射炮弹后,同样需要检测本机与敌机子弹的碰撞,并增加本机血条与得分机制,增强可玩性。

五、可玩性设计

1、本机生命值

生命值初始为10点,当检测到本机与敌机子弹发生碰撞后会减少生命值,并触发爆炸特效,当生命值减为0时,会弹窗提示重新开始游戏,并返回主界面,部分代码如下:

void gamewindow::lifecount()
{
    for(int j = 0; j < ENEMY_BULLET_NUM; j++){
        if(!en_bullet[j].e_b_is_shoot)
            continue;
        if(me.my_rect.intersects(en_bullet[j].e_b_rect)){
            life->setText(QString("生命:%1").arg(--l));
            en_bullet[j].e_b_is_shoot = false;
            if(l == 0){
                mbox = new QMessageBox;
                mbox->setText("很遗憾,请再次尝试");
                mbox->setStandardButtons(QMessageBox::Ok);
                if(mbox->exec() == QMessageBox::Ok){
                    t.stop();
                    p.stop();
                    bells->stop();
                    gamewindow::close();
                }
            }
            for(int k = 0; k < EXPLOTION_NUM; k++){
                if(!exp[k].exp_boom){
                    QSoundEffect *boom=new QSoundEffect;
                    boom->setSource(QUrl::fromLocalFile(SOUND_EXPLOTION));
                    boom->setVolume(0.07f); //音量
                    boom->play();
                    exp[k].exp_boom = true;
                    exp[k].exp_x = en_bullet[j].e_b_x;
                    exp[k].exp_y = en_bullet[j].e_b_y;
                    break;
                }
            }
        }
    }
}

2、分数显示

当击落一架敌机后,会将总分数加1,并将分数显示在左上角,部分代码如下:

if(enemy[i].enemy_rect.intersects(my_bullet[j].b_rect)){
                enemy[i].is_out = false;
                my_bullet[j].b_is_shoot = false;
                score->setText(QString("得分:%1").arg(++s));}

3、暂停游戏

在游戏界面的右上角有暂停按钮,该按钮的设计同主界面的开始游戏按钮类似,点击暂停后可以选择继续游戏或停止游戏返回主菜单,部分代码如下:

void gamewindow::pause_window()
{
    t.stop();
    p.stop();
    bells->stop();
    retwindow = new QMessageBox;
    retwindow->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
    retwindow->button(QMessageBox::Yes)->setText("继续游戏");
    retwindow->button(QMessageBox::No)->setText("返回主菜单");

    if(retwindow->exec() == QMessageBox::Yes){
        t.start();
        p.start();
        bells->play();
        retwindow->close();
    }
    else{
        t.stop();
        p.stop();
        bells->stop();
        gamewindow::close();
    }
}

你可能感兴趣的:(qt,开发语言)