Qt之塔防游戏 c++(二)

风格不统一 提交于 2020-08-15 07:36:52

在上一篇文章中,我们实现了地图,航点,防御塔坑的绘制,并实现了鼠标点击出现防御塔,现在敌人可以登场了!

在这一阶段,我们要完成如下功能:
1:敌人的出现和移动。
2:子弹类的构造,为防御塔的攻击做准备。
3:防御塔类的完善,能够对敌人进行攻击。


敌人的出现和移动

我们先添加一个头文件utility,来判断两个点是否相撞
utility.h中的实现:

#ifndef UTILITY_H
#define UTILITY_H

#endif // UTILITY_H
#include <QPoint>
#include <cmath>

inline bool collisionWithCircle(QPoint p1,int r1,QPoint p2,int r2)
{//我们只创建了h文件,没有创建cpp文件,所以要定义内联函数,直接在h文件内部实现函数
    int xx=p1.x()-p2.x();
    int yy=p1.y()-p2.y();
    int dis=sqrt(xx*xx+yy*yy);
    if(dis<r1+r2)
    {
        return true;
    }
    return false;
}

添加Enemy类。
enemy.h中的实现:

#ifndef ENEMY_H
#define ENEMY_H

#include <QObject>
#include <QPoint>
#include <QString>
#include <QPainter>
#include <QSize>

#include "waypoint.h"
#include "mainwindow.h"
#include "tower.h"

class MainWindow;
class Tower;
class QPainter;
class wayPoint;

class Enemy:public QObject
{
    Q_OBJECT
public:
    Enemy(wayPoint * startPoint, MainWindow * game,QString path=":/images/monster1.png");
    ~Enemy();
    void draw(QPainter * painter)const;
    void move();//敌人的移动
    
    QPoint getPos();//得到敌人的当前位置
    
    void getAttacked(Tower * tower);//被tower攻击
    void getDamaged(int damage);//敌人被攻击受到伤害
    void getRemoved();//当血量非正时,敌人死亡,需要移除
    void getLostSight(Tower * tower);//敌人脱离tower的攻击范围
    
private slots:
    void doActive();//私有信号槽,敌人是否可以移动
    
private:
    int m_maxHp;//最大血量
    int m_currentHp;//当前血量
    int m_walkingSpeed;//移动速度
    bool m_active;//是否可以移动
    
    wayPoint * m_destinationWayPoint;//目标航点的指针
    MainWindow * m_game;//mainwindow的指针
    QPoint m_pos;//当前位置
    QString m_path;//图片路径
    QList<Tower *> m_attackerTowerList;//正在攻击该敌人的防御塔list
    
    static const QSize m_fixedSize;
};
#endif // ENEMY_H

mainwindow.h中做如下添加:

//类外
#include "enemy.h"
class Enemy;

//类内
publicvoid getHpDamaged();//敌人进入基地内部,基地要扣血
    void awardGlod();//敌人死亡,奖励金钱
    void removeEnemy(Enemy * enemy);//敌人死亡,在mainwindow中要移除
    QList<Enemy *> getEnemyList();//得到Enemy*的list
    
private:
    QList<Enemy *> m_enemyList;//用来储存敌人的list
    int m_playerHp;//基地的血量
    int m_playerGlod;//初始金钱

mainwindow.cpp中添加:
构造函数完善:

    , m_playerHp(5)
    ,m_playerGlod(1000)

其他函数的实现:

void MainWindow::getHpDamaged()
{
    m_playerHp-=1;//敌人进入基地,扣一滴血
}

void MainWindow::awardGlod()
{
    m_playerGlod+=200;//杀死一个敌人,奖励200,制作者可以自由更改数值
}

void MainWindow::removeEnemy(Enemy *enemy)
{
    Q_ASSERT(enemy);
    m_enemyList.removeOne(enemy);//死亡的敌人从enemylist中移除
    delete enemy;
}

QList<Enemy *> MainWindow::getEnemyList()
{
    return m_enemyList;
}

下面在enemy.cpp中进行Enemy类的实现:

#include "enemy.h"
#include "mainwindow.h"
#include "tower.h"
#include "utility.h"
#include "waypoint.h"

#include <QPainter>
#include <QPoint>
#include <QSize>
#include <QString>
#include <QVector2D>

const QSize Enemy::m_fixedSize(22,23);//

Enemy::Enemy(wayPoint * startWayPoint,MainWindow *game,QString path):
    QObject(0),
    m_game(game),
    m_pos(startWayPoint->getPos()),
    m_path(path)
{
    m_maxHp=40;
    m_currentHp=m_maxHp;
    m_walkingSpeed=1;
    m_active=false;
    m_destinationWayPoint=startWayPoint->getNextWayPoint();//这里就充分利用了航点的好处,我们可以直接从当前航点得到下一个航点
}

Enemy::~Enemy()
{
    m_attackerTowerList.clear();//清楚在攻击该敌人的防御塔
    m_destinationWayPoint=NULL;//全部设为空指针
    m_game=NULL;
}

void Enemy::draw(QPainter *painter) const
{
    if(!m_active)//如果敌人不能移动,就不对它进行绘画
    {
        return ;
    }

    painter->save();
    //下面准备绘画敌人的血条
    static const int healthBarWidth=m_fixedSize.width();//设置血条的长度
    QPoint healthBarPoint=m_pos+QPoint(-m_fixedSize.width()/2,-m_fixedSize.height());//设置血条的左上点
    painter->setPen(Qt::NoPen);//画笔的颜色
    painter->setBrush(Qt::red);//刷子的颜色,用来填充内部
    QRect healthBarBackRect(healthBarPoint,QSize(healthBarWidth,2));
    painter->drawRect(healthBarBackRect);//画出血条
    
    //由于敌人被攻击后会扣血,因此还要画一个显示敌人当前血量的柱形
    painter->setBrush(Qt::green);
    QRect healthBarRect(healthBarPoint,QSize((double)m_currentHp/m_maxHp*healthBarWidth,2));
    painter->drawRect(healthBarRect);//画出当前血量血条
    
    //把敌人画出来
    QPoint tmp(m_pos.x()-m_fixedSize.width()/2,m_pos.y()-m_fixedSize.height()/2);//得到图片的左上点
    painter->drawPixmap(tmp.x(),tmp.y(),m_path);
    painter->restore();
}

void Enemy::move()
{
    if(!m_active)//如果不能移动,就直接return
    {
        return ;
    }
    if(collisionWithCircle(m_pos,1,m_destinationWayPoint->getPos(),1))//如果到达了目标航点
    {
        if(m_destinationWayPoint->getNextWayPoint())//如果还存在下一个航点
        {//重新设置敌人的位置,和目标航点
            m_pos=m_destinationWayPoint->getPos();
            m_destinationWayPoint=m_destinationWayPoint->getNextWayPoint();
        }
        else//如果没有下一个航点了,代表敌人已经到达了基地
        {
            m_game->getHpDamaged();
            m_game->removeEnemy(this);
            return ;
        }
    }
    else//如果还在去目标航点的路上
    {//采用QVectoer2D中的两点移动方法
        QPoint targetPoint=m_destinationWayPoint->getPos();
        double movementSpeed=m_walkingSpeed;
        QVector2D normailzed(targetPoint-m_pos);
        normailzed.normalize();
        m_pos=m_pos+normailzed.toPoint()*movementSpeed;
    }
}

void Enemy::doActive()
{
    m_active=true;
}

QPoint Enemy::getPos()
{
    return m_pos;
}

void Enemy::getAttacked(Tower *tower)
{
    m_attackerTowerList.push_back(tower);
}

void Enemy::getDamaged(int damage)
{
    m_currentHp-=damage;
    if(m_currentHp<=0)
    {
        m_game->awardGlod();
        getRemoved();
    }
}

void Enemy::getRemoved()
{
    if(m_attackerTowerList.empty())
    {
        return ;
    }
    else
    {
        m_game->removeEnemy(this);
    }
}

void Enemy::getLostSight(Tower *tower)
{
    m_attackerTowerList.removeOne(tower);
}

Enemy类已经比较完善,我们现在在mainwindow.h中添加使敌人出现的函数:

publicbool loadWaves();//加载敌人的函数
    
privateint m_waves;//波数
    bool m_gameWin;
    bool m_gameLose;
    
private slots:
    //私有信号槽,将Enemy,Tower和后续的Bullet连接
    void updateMap();
    void gameStart();

mainwindow.cpp中添加:

mainwindow构造函数:

,m_waves(0)
,m_gameWin(false)
,m_gameLose(false)

QTimer * timer=new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(updateMap()));
timer->start(30);
QTimer::singleShot(300,this,SLOT(gameStart()));

其他函数的实现:

#include <QTimer>
#include "enemy"

bool MainWindow::loadWaves()
{
    if(m_waves>=6)
    {
        return false;
    }
    int enemyStartInterval[]={100,500,600,1000,3000,6000};//敌人出现的时间
    for(int i=0;i<6;++i)
    {
        wayPoint * startWayPoint;
        startWayPoint=m_wayPointList.first();
        Enemy * enemy=new Enemy(startWayPoint,this);//创建一个新得enemy
        m_enemyList.push_back(enemy);
        QTimer::singleShot(enemyStartInterval[i],enemy,SLOT(doActive()));
    }
    return true;
}

void MainWindow::gameStart()
{
    loadWaves();
}

void MainWindow::updateMap()
{
    foreach(Enemy * enemy,m_enemyList)
        enemy->move();
    update();
}

removeEnemy()方法中添加:

if(m_enemyList.empty())
    {
        ++m_waves;
        if(!loadWaves())
        {
            m_gameWin=true;
        }
    }

paintEvent()函数中添加:

//添加在函数的前端
if(m_gameLose || m_gameWin)//画出游戏结束的画面
    {
        QString text=m_gameLose ? "YOU LOST":"YOU WIN";
        QPainter painter(this);
        painter.setPen(Qt::red);
        painter.drawText(rect(),Qt::AlignCenter,text);
        return ;
    }
//添加在函数的后端
    foreach(const Enemy * enemy,m_enemyList)
        enemy->draw(&painter);

运行上面的代码,我们可以得到下面的结果:
在这里插入图片描述

可以看到敌人出现了,并且按照我们绘画的航点进行移动。
接下来我们对敌人进行攻击,在攻击之前,我们需要攻击的媒介:子弹

子弹类的构造,为防御塔的攻击做准备

先创建Bullet
Bullet.h的实现:

#ifndef BULLET_H
#define BULLET_H

#include <QObject>
#include <QPoint>
#include <QPainter>
#include <QString>
#include <QSize>

#include "enemy.h"
#include "tower.h"
#include "mainwindow.h"

class Enemy;
class MainWindow;
class Tower;

class Bullet :public QObject
{
    Q_OBJECT
    Q_PROPERTY(QPoint m_currentPos READ getCurrentPos WRITE setCurrentPos)//子弹动态移动
public:
    Bullet();
    Bullet(QPoint startPos,QPoint targetPos,int damage,Enemy * targetEnemy,MainWindow * game,QString path=":/images/bullet.png");
    QPoint getCurrentPos();//得到子弹的当前位置
    void setCurrentPos(QPoint pos);//设置子弹的当前位置
    
    void move();//子弹的移动
    void draw(QPainter * painter)const;//绘画子弹
    
private slots:
    void hitTarget();//私有信号槽,击中敌人的时候触发
    
private:
    QPoint m_startPos;
    QPoint m_targetPos;
    QPoint m_currentPos;
    int m_damage;
    QString m_path;
    
    Enemy * m_targetEnemy;
    MainWindow * m_game;
    
    static const QSize m_fixedSize;
};

#endif // BULLET_H

同时在mainwindow.h文件中做如下添加:

//类外
#include "bullet.h"

class Bullet;

//类内
public:
    void removeBullet(Bullet * bullet);
    void addBullet(Bullet * bullet);
private:
    QList<Bullet *> m_bulletList;//用来储存子弹的list

Bullet.cpp中对Bullet类进行实现

#include "bullet.h"
#include "tower.h"
#include "mainwindow.h"
#include "utility.h"
#include "enemy.h"

#include <QPoint>
#include <QPainter>
#include <QString>
#include <QPropertyAnimation>

const QSize Bullet::m_fixedSize(8,8);

Bullet::Bullet()
{
}

Bullet::Bullet(QPoint startPos,QPoint targetPos,int damage,Enemy * targetEnemy,MainWindow * game,QString path):
    m_startPos(startPos),
    m_targetPos(targetPos),
    m_damage(damage),
    m_path(path),
    m_targetEnemy(targetEnemy),
    m_game(game)
{
}

QPoint Bullet::getCurrentPos()
{
    return m_currentPos;
}

void Bullet::setCurrentPos(QPoint pos)
{
    m_currentPos=pos;
}

void Bullet::move()
{
    static int duration=100;//子弹飞行的时间,经过100ms击中敌人
    QPropertyAnimation * animation=new QPropertyAnimation(this,"m_currentPos");
    animation->setDuration(duration);//设置持续时间
    animation->setStartValue(m_startPos);//设置起始位置
    animation->setEndValue(m_targetPos);//设置目标位置
    connect(animation,SIGNAL(finished()),this,SLOT(hitTarget()));//连接信号槽,击中敌人后,子弹动态运动结束
    animation->start();
}

void Bullet::hitTarget()
{
    if(m_game->getEnemyList().indexOf(m_targetEnemy)!=-1)//如果mainwindow的敌人列表中,有子弹击中的这个敌人,该敌人受到相应的伤害
    {
        m_targetEnemy->getDamaged(m_damage);
    }
    m_game->removeBullet(this);//击中敌人后子弹就要消失
}

void Bullet::draw(QPainter *painter) const
{
    painter->drawPixmap(m_currentPos,m_path);
}

现在Bullet类构造好了,我们接着完善Tower类,使其能够攻击敌人,并在mainwindow中显示

防御塔类的完善,能够对敌人进行攻击

tower.h进行相应攻击功能的添加:

//类外
#include <QTimer>
#include "enemy.h"
class Enemy;
class QTimer;

//类内
public:
    void attackEnemy();//攻击敌人
    void targetKilled();//当目标被击杀后
    void chooseEnemyFromAttack(Enemy * enemy);//从可以攻击的敌人中,选出攻击的敌人

    void removeBullet();//移除防御塔产生的子弹
    void lostSightOfEnemy();//丢失攻击目标的视野
    void checkEnemyInRange();//检查敌人是否在攻击范围内
    Enemy * getAttackedEnemy();//得到正在攻击的敌人
    
private slots:
    void shootWeapon();//私有信号槽,实现和子弹类的连接

private:
    bool m_attacking;//是否在攻击
    int m_damage;//防御塔的攻击力
    int m_fireRate;//射速
    Enemy * m_chooseEnemy;//正在攻击的敌人
    QTimer * m_fireRateTime;

tower.cpp中进行添加,并实现相应功能:

//构造函数
    m_attacking(false),
    m_damage(10),//攻击力10
    m_fireRate(1000),//1000ms
    m_chooseEnemy(NULL),
    
    m_fireRateTime=new QTimer(this);
    connect(m_fireRateTime,SIGNAL(timeout()),this,SLOT(shootWeapon()));


//其他函数
Tower::~Tower()
{
    delete m_fireRateTime;
    m_fireRateTime=NULL;
    m_chooseEnemy=NULL;
    m_game=NULL;
    delete m_chooseEnemy;
}
void Tower::chooseEnemyFromAttack(Enemy *enemy)
{
    m_chooseEnemy=enemy;
    attackEnemy();
    m_chooseEnemy->getAttacked(this);//该敌人受到该防御塔的攻击
}

void Tower::attackEnemy()
{
    m_fireRateTime->start(m_fireRate);//开始攻击
}

void Tower::shootWeapon()
{
    Bullet * bullet=new Bullet(m_pos,m_chooseEnemy->getPos(),m_damage,m_chooseEnemy,m_game);//构造一个子弹,准备攻击敌人
    bullet->move();
    m_game->addBullet(bullet);//将该子弹添加到mainwindow中
}

void Tower::targetKilled()
{
    if(m_chooseEnemy)
    {
        m_chooseEnemy=NULL;
    }
    m_fireRateTime->stop();//敌人死亡,停止开火
}

void Tower::lostSightOfEnemy()
{
    m_chooseEnemy->getLostSight(this);
    if(m_chooseEnemy)
    {
        m_chooseEnemy=NULL;
    }
    m_fireRateTime->stop();
}

void Tower::checkEnemyInRange()
{
    if(m_chooseEnemy)//如果有了攻击的敌人
        {
            QVector2D normalized(m_chooseEnemy->getPos()-m_pos);
            normalized.normalize();
            if(!collisionWithCircle(m_pos,m_attackRange,m_chooseEnemy->getPos(),1))//当敌人不在范围内的时候
            {
                lostSightOfEnemy();
            }
        }
        else//如果没有攻击的敌人,就遍历enemylist,找到在攻击范围内的敌人
        {
            QList<Enemy * > enemyList=m_game->getEnemyList();
            foreach(Enemy * enemy,enemyList)
                if(collisionWithCircle(m_pos,m_attackRange,enemy->getPos(),1))
                {
                    chooseEnemyFromAttack(enemy);
                    break;
                }
        }
}

Enemy * Tower::getAttackedEnemy()
{
    return m_chooseEnemy;
}

enemy.cppgetRemoved()的else结构中添加:

foreach(Tower * tower,m_attackTowerList)
            tower->targetKilled();

现在防御塔也比较完善了,我们接着在mainwindow.h中添加一些功能

public:
    void drawHp(QPainter * painter)const;//画出当前基地血量的信息
    void drawGlod(QPainter * painter)const;//画出当前玩家金钱的信息
    void drawWaves(QPainter * painter)const;//画出当前的波数信息
    bool canBuyTower();
    
    void doGameOver();//执行游戏结束

mainwindow.cpp中实现相应的功能:

void MainWindow::doGameOver()
{
    if(!m_gameLose)
    {
        m_gameLose=true;
    }
}

bool MainWindow::canBuyTower()
{
    if(m_playerGlod>=300)
    {
        return true;
    }
    return false;
}

void MainWindow::drawWaves(QPainter *painter) const
{
    painter->save();
    painter->setPen(Qt::red);
    painter->drawText(QRect(500,5,100,25),QString("WAVES: %1").arg(m_waves+1));
    painter->restore();
}

void MainWindow::drawHp(QPainter *painter) const
{
    painter->save();
    painter->setPen(Qt::red);
    painter->drawText(QRect(30,5,100,25),QString("HP: %1").arg(m_playerHp));
    painter->restore();
}

void MainWindow::drawGlod(QPainter *painter) const
{
    painter->save();
    painter->setPen(Qt::red);
    painter->drawText(QRect(300,5,100,25),QString("GLOD: %1").arg(m_playerGlod));
}

void MainWindow::removeBullet(Bullet *bullet)
{
    Q_ASSERT(bullet);
    m_bulletList.removeOne(bullet);
    
}

void MainWindow::addBullet(Bullet *bullet)
{
    Q_ASSERT(bullet);
    m_bulletList.push_back(bullet);    
}

updateMap()中添加:

    foreach(Tower * tower,m_towerList)
        tower->checkEnemyInRange();

paintEvent()中添加:

    foreach(const Bullet * bullet,m_bulletList)
        bullet->draw(&painter);
        
    drawHp(&painter);
    drawGlod(&painter);
    drawWaves(&painter)

mousePressEvent()进行调整:

void MainWindow::mousePressEvent(QMouseEvent * event)
{
    QPoint pressPos=event->pos();//得到鼠标点击的位置
    auto it=m_towerPositionList.begin();
    while(it!=m_towerPositionList.end())//遍历所有的防御塔坑
    {
        if(Qt::LeftButton==event->button())//如果是鼠标左键点击
        {
            if(it->ContainPos(pressPos) && !it->hasTower() && canBuyTower())//如果鼠标点击的位置在防御塔坑的范围内
            {
                Tower * tower=new Tower(it->getCenterPos(),this);//创建一个新的防御塔
                m_playerGlod-=300;
                m_towerList.push_back(tower);//把这个防御塔放到储存防御塔的list中
                it->setHasTower(true);
                update();//更新地图
                break;//进行了一次操作,可以直接退出循环了
            }
        }
        ++it;
    }
}

运行上述程序,可以得到下面的界面:
在这里插入图片描述
可以看到,我们已经实现了一个初步的塔防游戏了,已经具备基本的功能了。

我们来对本篇文章做一个总结吧!
1:我们完成了Enemy类的构造,实现了敌人在地图上的移动,主要使用了QVector2D中的移动方法
2:完成了Bullet类的构造,为防御塔的攻击做准备,其移动也是使用了QVector2D中的移动方法。不太明白的读者,可以去百度一些QVcetor2D中的方法。
3:完善防御塔类,成功实现了一个初级塔防游戏。


下一篇文章,我们将对这个初级的塔防游戏进行优化,实现防御塔的多样性,地图的多样性等。
好啦,我们下一篇文章见。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!