在上一篇文章中,我们实现了地图,航点,防御塔坑的绘制,并实现了鼠标点击出现防御塔,现在敌人可以登场了!
在这一阶段,我们要完成如下功能:
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;
//类内
public:
void 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中添加使敌人出现的函数:
public:
bool loadWaves();//加载敌人的函数
private:
int 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.cpp的getRemoved()的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:完善防御塔类,成功实现了一个初级塔防游戏。
下一篇文章,我们将对这个初级的塔防游戏进行优化,实现防御塔的多样性,地图的多样性等。
好啦,我们下一篇文章见。
来源:oschina
链接:https://my.oschina.net/u/4382386/blog/4326468