Qt5实现飞机大战1.0(上)

前言:最近学习Qt时,当做项目练手,把以前小学期要做的飞机大战又重新做了一遍。以前的时候是用MFC做的,现在改到了用Qt界面来做。不得不说,Qt确实是要好用多了。在此记录,方便自己,希望也能有助于大家。

飞机大战本身并不是很难,这里就介绍我实现过程中的重点了,主要分为以下几个部分:
1、类模型的建立
2、飞机如何移动
3、碰撞检测
4、爆炸动画的实现
5、飞机图片透明效果的实现
6、背景图的移动
7、游戏状态的转换

以下带着源码,分别介绍。

一、类模型的建立
学习完C++之后,飞机大战确实是一个比较好的练手项目,可以检验咱C++这个对象到底“谈”妥了没。 惭愧的说,到现在,我的面向对象思想建立的还是不够完善。在这个项目中,还没有体现到很深层次的面向对象的思路。以下是我简历的UML类图:
飞机大战UML类图其中:
(1)、VObj类:这是我一开始抽象出来的物体类,就像Qt中的Object一样,但后来发现它把Plane类和Bullet类要做的工作都完成了,导致Plane和Bullet没什么可干的。这里完成的工作主要是物体的移动和判断物体是否消失。
(2)、Plane类和Bullet类:几乎是空的。为了与后面的类衔接合理些,也就没去删掉这个中间层。
(3)、MyPlane类:这是继承Plane的我方战机类。其中主要飞机的移动与发射子弹。稍微要注意点是这个类中含有MyBullet类的指针,这是为了发射子弹之用。
(4)、EnemyFIsh类:继承Plane类的敌方杂鱼类。其工作主要由上层的父类完成了,这里也几乎是空的。
(5)、MyBullet类:这是继承Bullet的子类。除了完成一些必要的工作,也几乎是空的。
(6)、Explosion类:爆炸类。这个类是一个独立的类,和其他的类都没有太大关系(和Widge类有点关系)。它主要是完成爆炸的一些工作,如加载爆炸的一系列图片,切换爆炸的图片,设置爆炸的位置等等。
(7)、Widget类:这个类是一个综合的类。从类图中也可以看出来,它和MyPlane、Explosion、EnemyFish都有关系。这个类继承了Qt的Object,通过重绘事件、键盘事件、定时器事件,把以上各类综合起来以达到飞机大战的效果。整个工程80%的工作都这这个类中,后面要介绍的其他几个重点也大部分都是在这个类中实现的。(说句废话,也正是从这个类中,可以看出来我的面向对象的设计理解的还不够深,还不能够设计出耦合性更小,代码利用率更高的程序。)

二、飞机如何移动
飞机时如何移动的呢?这里用了比较低级的做法,即通过检测按键事件,判断飞机的移动方向,然后在定时器事件中移动飞机,最后在重绘事件中不断绘制飞机。这样就达到了飞机移动的效果。同理,子弹的移动和敌方杂鱼机的移动也是这个原理。(这里应该可以用Qt更高级的动画框架来实现,目前我还没学会,嘿嘿)
主要的代码如下:

void Widget::keyPressEvent(QKeyEvent *event)        
{

    if(state == RUNNING_STATE)          //第二个阶段时
     {
        int fly_dir = 0;
        //判断按下哪个按键,并打上标记
        if(event->key() == Qt::Key_Up)
            fly_dir = FLY_UP;
         if(event->key() == Qt::Key_Down)
            fly_dir = FLY_DOWN;
         if(event->key() == Qt::Key_Left)
           fly_dir = FLY_LEFT;
         if(event->key() == Qt::Key_Right)
            fly_dir = FLY_RIGHT;

         myPlane->SetFlyDir(fly_dir,true);  //给指定的方向打上标记

         if(event->key() == Qt::Key_Space)
         {
            myPlane->FireBull();        //发射子弹
         }
    }
    else
    {
        if(event->key() == Qt::Key_Return)
        {
            state = RUNNING_STATE;
            Init();
        }
    }

}

//定时器事件:主要用于移动飞机;判断战机和敌机、敌机和子弹是否发生碰撞;增加敌机数量;显示爆炸(这里只有定时器的一部分代码)
void Widget::timerEvent(QTimerEvent *event)
{
    if(event->timerId() == timer1)  //移动飞机和子弹
    {
        //移动我方战机
        for(int i = 1;i<=4;++i)
        {
            if(myPlane->GetFLyDir(i))       //上下左右:对应1 2 3 4
                myPlane->Move(i);
        }
        //移动子弹
        QLinkedList<MyBullet*>::iterator iter_bul;
       for(iter_bul = myPlane->my_bulles.begin();iter_bul!=myPlane->my_bulles.end();++iter_bul)
       {
           MyBullet *temp_bul = *iter_bul;
           temp_bul->Move(-1);
       }
       //移动敌方杂鱼机
       QLinkedList<EnemyFish*>::iterator iter_fish;
       for(iter_fish = fish_list.begin();iter_fish != fish_list.end();++iter_fish)
       {
           EnemyFish * temp_fish = *iter_fish;
           temp_fish->Move(0);
       }
        this->update();
    }
    }

//重绘事件:
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter;
    painter.begin(this);
    if(state == RUNNING_STATE)
    {
        ui->label_life->setText(QString::number(myPlane->GetLife()));
        ui->label_sco->setText(QString::number(myPlane->GetScore()));
        //绘制动态背景
        painter.drawPixmap(0,back_spe,back);
        painter.drawPixmap(0,back_spe-back.height(),back);
        //绘制我方飞机
        painter.drawPixmap(myPlane->pos,myPlane->img);
        //绘制敌机
        foreach(EnemyFish *temp_fish,fish_list)
            painter.drawPixmap(temp_fish->pos,temp_fish->img);
        //绘制我方子弹
        foreach (MyBullet *temp_bul, myPlane->my_bulles)
            painter.drawPixmap(temp_bul->pos,temp_bul->img);
        //绘制爆炸
        foreach(Explosion * temp_exp,exp_list)
        {
            if(temp_exp->GetCurPicNum()<=EXPLOSION_IMG_NUM)
                painter.drawPixmap(temp_exp->GetPos(),*(temp_exp->exp_img_list[temp_exp->GetCurPicNum()-1]));
        }
    }
    else
    {
         painter.drawPixmap(0,0,back);
         if(state == END_STATE)
         {
             ui->label_life->setText(QString::number(myPlane->GetLife()));
             ui->label_sco->setText(QString::number(myPlane->GetScore()));
         }
    }
     painter.end();
}

这里还需要注意一点的是:在按键事件中不能直接移动飞机,而应该做一个标志位,然后在定时器中再进行移动,否则不能同时相应两个按键,比如同时按下左和上,飞机向左上方飞。

三、碰撞检测
何为碰撞检测?即在游戏中判断飞机是否和敌机发生碰撞,飞机的子弹是否和敌机发生碰撞(即是否攻击到敌机)。在这里,基本的原理是判断两个对象(飞机和敌机、敌机和子弹)矩形(即图像所占的空间)是否有交叉,如果有交叉,即发生了碰撞。使用的是QRect::intersects()方法。主要代码如下:

 else if(event->timerId() == timer2)     //判断是否发生碰撞
    {
        QLinkedList<EnemyFish*>::iterator iter;
        QLinkedList<MyBullet*>::iterator iter_bul;
        bool IsAttacked = false;
        bool IsCon = false;
        for(iter = fish_list.begin();iter!=fish_list.end();++iter)      //遍历敌方所有杂鱼
        {
            EnemyFish *temp_fish = *iter;
            IsAttacked = false;     //初始未被攻击

            QRect temp(temp_fish->pos,QSize(temp_fish->img.width(),temp_fish->img.height()));
            for(iter_bul = myPlane->my_bulles.begin();iter_bul !=myPlane->my_bulles.end();++iter_bul)  //遍历我方所有子弹
            {
                MyBullet *temp_bul = *iter_bul;
                QRect temp_rec(temp_bul->pos,QSize(temp_bul->img.width(),temp_bul->img.height()));

                IsAttacked = temp.intersects(temp_rec);       //判断是否敌机是否受到我方子弹攻击

                if(IsAttacked == true )       //子弹攻击到敌机
                {
                    EneFishIsAttacked();
                    Explosion *temp_exp = new Explosion;        //产生爆炸
                    temp_exp->SetPos(temp_fish->pos);
                    exp_list.append(temp_exp);
                    myPlane->my_bulles.removeOne(*iter_bul);      //移除子弹
                    delete temp_bul;

                    fish_list.removeOne(*iter);        //小虾米碰撞到子弹,删除敌机
                    delete temp_fish;

                    break;
                }
                else
                {
                    if(temp_bul->IsDisappear() == true)
                    {
                        myPlane->my_bulles.removeOne(*iter_bul);      //子弹消失,移除子弹
                        delete temp_bul;
                    }
                }
            }
            if(IsAttacked == false)
            {
                //判断我方战机是否碰撞到敌方战机
                QRect myPlane_rect(myPlane->pos,QSize(myPlane->img.width(),myPlane->img.height()));
                IsCon = temp.intersects(myPlane_rect);
                if(IsCon == true)
                {
                    Explosion *temp_exp = new Explosion;        //产生爆炸
                    temp_exp->SetPos(temp_fish->pos);
                    exp_list.append(temp_exp);
                    fish_list.removeOne(*iter);        //小虾米碰撞到我方飞机,删除敌机
                    delete temp_fish;
                    EneFishIsAttacked();
                    MyPlaneIsAttacked();        //我方战机损失生命力
                    break;
                }
                else
                {
                    if(temp_fish->IsDisappear() == true)
                    {
                        fish_list.removeOne(*iter);        //敌机移除屏幕,删除敌机
                        delete temp_fish;
                    }
                }
            }
        }
        back_spe= (back_spe+1)%back.height();   //背景图片移动
    }

具体来说,就是遍历子弹列表和敌机列表,在两层循环里判断每个子弹是否和敌机列表中的敌机发生碰撞。判断战机和敌机是否发生碰撞也是这个原理。(总的来说,我是感觉这种方式有点low。Qt的高级图形视图框架里有碰撞检测机制,可以运用到这里,图形视图框架,我弄的还不是很熟,也就没采用了)。

今天,先写到这儿吧。有点多,剩下的下次在写吧。在这儿,先上张图:运行画面1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值