0.基本效果界面展示:
一、思路与细节1.仍然采用单例设计模式,高度抽象出几个类如point、graph、element(敌机、主机(role包含数据成员包含子弹)均包含其作为一个数据成员,进行共通的操作)。
2.子弹十分密集,按一下空格会发出很多子弹:写一个timer定时器
(注:当声明和实现放在同一个文件去实现的时候,可以以.hpp结尾)
MyTimer::Timer(100,0) #100ms产生一颗
3.注:虽然作了前向声明,但是在.cpp文件中,找不到其相应的成员函数,
那一定是没有在.cpp中包含其相关的头文件。
4.我承认:debug最好的定位方式,注释掉疑似代码,看相关功能的变化。
二、具体实现:公共头文件:common.h
#pragma once #include#include #include #include #include #include
计时器:timer.h
#pragma once
#include"common.h"
class MyTimer
{
public:
static bool Timer(int duration, int id);
protected:
};
time.cpp
#include "timer.h"
bool MyTimer::Timer(int duration, int id)
{
static int startTime[10];
int endTime = clock();
if (endTime - startTime[id] >= duration)
{
startTime[id] = endTime;
return true;
}
return false;
}
资源存放类: res.h在上个推箱子的项目中已经讲过单例设计的基本操作了。在此大致提几个点,都是相通的,大部分的数据成员和成员函数都是static,直接通过类名调用。
①构造函数是私有!
②给一个公有接口,返回资源类的指针,进行对其内部的相关函数以及成员的操作
③数据成员用的map进行映射相关的资源!方便后面对资源的索引与调用。
#pragma once
#include"common.h"
class Res
{
public:
static int WIDTH(string name);
static int HEIGHT(string name);
static Res* GetInstance();
static void DrawIMG(int x, int y,string name);
static void DrawRole(int x, int y, string name, int preIndex);
//static DWORD WINAPI PlayMusic(LPVOID lparame);
public:
static map img;
static map music;
public:
~Res();
private:
Res();
};
res.cpp
(静态数据成员的初始化,
此外,此处还涉及一个细节,如何去掉一个物体图片周围的阴影部分:大致的实现原理是:准备原图一份,需要切除部分一张图片,->按位合&,实现背景部分的透明化操作)
参考博客:c愤怒小鸟
#include "res.h" mapRes::img; map Res::music; int Res::WIDTH(string name) { return GetInstance()->img[name]->getwidth(); //img是一个map容器,通过name去映射出IMAGE*类型的数组, } //IMAGE自带获取图片的宽度和高度的相关功能 int Res::HEIGHT(string name) { return GetInstance()->img[name]->getheight(); } Res* Res::GetInstance() { static Res* res = new Res; return res; } //只有一张图片的贴图:即背景图贴图 void Res::DrawIMG(int x, int y, string name) { putimage(x, y, GetInstance()->img[name]); } void Res::DrawRole(int x, int y, string name, int preIndex) { //透明贴图(注意:一个固定的贴图方式->去掉背景!!!!) putimage(x, y, GetInstance()->img[name] + preIndex, NOTSRCERASE); putimage(x, y, GetInstance()->img[name] + preIndex+1, SRCINVERT); } Res::~Res() { delete img["背景"]; delete[]img["角色"]; delete[]img["敌机"]; delete[]img["子弹"]; } Res::Res() { string background = "..//res//background.jpg"; string roleImg[4] = { "..//res//planeExplode_1.jpg","..//res//planeExplode_2.jpg", "..//res//planeNormal_1.jpg","..//res//planeNormal_2.jpg" }; //->注:只有下标0和2为有效的人物角色,下标1和3是0和2的遮罩,用于去阴影的。 string ballImg[2] = {"..//res//bullet1.jpg","..//res//bullet2.jpg"}; string enemy[4] = { "..//res//enemy_1.jpg","..//res//enemy_2.jpg", "..//res//enemyPlane1.jpg", "..//res//enemyPlane2.jpg", }; img["背景"] = new IMAGE; img["角色"] = new IMAGE[4]; img["子弹"] = new IMAGE[2]; img["敌机"] = new IMAGE[4]; loadimage(img["背景"], background.c_str()); for (int i = 0; i < 4; i++) { loadimage(img["角色"] + i, roleImg[i].data()); loadimage(img["敌机"] + i, enemy[i].data()); } for (int i = 0; i < 2; i++) { loadimage(img["子弹"] + i, ballImg[i].c_str()); } }
专门用于地图操作的类: graph.h负责①创建句柄,并②将地图绘制好!
#pragma once
class Graph
{
public:
Graph();
~Graph();
void DrawMap();
protected:
};
graph.cpp
#include "graph.h"
#include"res.h"
Graph::Graph()
{
initgraph(Res::GetInstance()->img["背景"]->getwidth(),
Res::GetInstance()->img["背景"]->getheight());
}
Graph::~Graph()
{
closegraph();
}
void Graph::DrawMap()
{
Res::DrawIMG(0, 0, "背景");
}
抽象出敌机、玩家(主机)、子弹的共性类: element.h共同的特征方法:①x、y坐标②是否存活(bool状态)③血量④相关图片的宽度和高度
⑤自身的绘制(直接调用Res类中的绘制函数即可。) ⑥自身的移动(直接调用point类的相关方法即可(见下))
共同的数据成员:①坐标②名称③是否存活标记④血量
#include"common.h"
#include"res.h"
#include"point.h"
class Element
{
public:
virtual ~Element();
Element();
Element(int x, int y, string name, bool live, int hp = 0);
int& GetX();
int& GetY();
bool& GetLive();
int& GetHp();
int GetWidth();
int GetHeight();
void DrawElement(int pre);
void MoveElement(int speed,Point::Dir dir);
protected:
Point point;
string name;
bool live;
int hp;
};
element.cpp
#include "element.h"
#include"res.h"
Element::~Element()
{
}
Element::Element()
{
}
Element::Element(int x, int y, string name, bool live, int hp):point(x,y),name(name)
{
this->live = live;
this->hp = hp;
}
int& Element::GetX()
{
return point.getX();
}
int& Element::GetY()
{
return point.getY();
}
bool& Element::GetLive()
{
return live;
}
int& Element::GetHp()
{
return hp;
}
int Element::GetWidth()
{
return Res::GetInstance()->WIDTH(name);
}
int Element::GetHeight()
{
return Res::GetInstance()->HEIGHT(name);
}
void Element::DrawElement(int pre)
{
Res::GetInstance()->DrawRole(point.getX(), point.getY(), name, pre);
}
void Element::MoveElement(int speed, Point::Dir dir)
{
point.move(speed, dir);
}
内部物体的移动原理抽象出来的类: point.h关键:枚举类型->上下左右
成员函数:①x、y坐标的接口②构造函数、拷贝构造函数③关键还是move()方法:如何实现物体的移动,要做好与Element类的对接。
#pragma once
#include"common.h"
class Point
{
public:
enum Dir{left,right,down,up};
Point(int x = 0, int y = 0);
Point(const Point& point);
int& getX();
int& getY();
void move(int speed, Dir dir);
protected:
int x;
int y;
};
point.cpp
#include "point.h"
Point::Point(int x, int y)
{
this->x = x;
this->y = y;
}
Point::Point(const Point& point)
{
this->x = point.x;
this->y = point.y;
}
int& Point::getX()
{
return x;
}
int& Point::getY()
{
return y;
}
void Point::move(int speed, Dir dir)
{
switch (dir)
{
case Point::left:
this->x -= speed;
break;
case Point::right:
this->x += speed;
break;
case Point::up:
this->y -= speed;
break;
case Point::down:
this->y += speed;
break;
}
}
主机的具体实现类: role.h数据成员:主角Element*类型的指针plane、和一个存放Element*类型的list
容器(当做bullet来用!)
方法:①构造函数(飞机的居中,hp和live状态的设置)
②画飞机和子弹(遍历调用)只需要调用element中写好的方法即可。
③数据成员的接口
④子弹的移动(只会向前移动,遍历每一个子弹-=速度,改变y坐标即可)
⑤plane的移动(异步函数的调用),GetAsyncKeyState()函数调用(具体说明看实现部分的注释!)
注:plane的移动中,核心的几点:(i)按上下左右键实现移动调用element类中写好的方法暂且不提,(ii)还有按下空格键,产生子弹(塞到链表中,new出来(注意产生的位置)),为了不让子弹过于密集,采用timer计时器!(iii)子弹的移动后,移动子弹+绘制子弹!!control中就直接调用此函数即可。
#pragma once
#include"common.h"
class Element;
class Role
{
public:
Role();
~Role();
void DrawPlane(int preIndex);
void MovePlane(int speed);
void DrawBullet();
void MoveBullet(int speed);
Element*& GetPlane();
list& GetBullet();
protected:
Element* plane;
listbullet;
};
role.cpp
#include "role.h"
#include"res.h"
#include"element.h"
#include"timer.h"
Role::Role()
{
plane = new Element(Res::GetInstance()->WIDTH("背景")/2-Res::GetInstance()->WIDTH("角色")/2, //x
Res::GetInstance()->HEIGHT("背景")-Res::GetInstance()->HEIGHT("角色"), //y
"角色", //角色名
true, //存活状态
100); //hp
}
Role::~Role()
{
}
void Role::DrawPlane(int preIndex)
{
plane->DrawElement(preIndex);
}
void Role::MovePlane(int speed)
{
if (GetAsyncKeyState(VK_UP) && plane->GetY() >= 0)
{
plane->MoveElement(speed, Point::up);
}
if (GetAsyncKeyState(VK_DOWN) && plane->GetY() <= Res::GetInstance()->HEIGHT("背景") - Res::GetInstance()->HEIGHT("角色"))
{
plane->MoveElement(speed, Point::down);
}
if (GetAsyncKeyState(VK_RIGHT) &&
plane->GetX() <= Res::GetInstance()->WIDTH("背景") - Res::GetInstance()->WIDTH("角色"))
{
plane->MoveElement(speed, Point::right);
}
if (GetAsyncKeyState(VK_LEFT) && plane->GetX() >= 0)
{
plane->MoveElement(speed, Point::left);
}
if (GetAsyncKeyState(VK_SPACE)&&MyTimer::Timer(100,0))
{
//HANDLE threadID = CreateThread(NULL, 0, Res::PlayMusic, (int*)1, 0, 0);
bullet.push_back(new Element(plane->GetX()+45,plane->GetY()-10,"子弹",1));
//CloseHandle(threadID);
}
MoveBullet(1);
DrawBullet();
}
void Role::DrawBullet()
{
for (auto v : bullet)
{
if (v->GetLive())
{
v->DrawElement(0);
}
}
}
void Role::MoveBullet(int speed)
{
for (auto v : bullet)
{
v->GetY() -= speed;
}
}
Element*& Role::GetPlane()
{
return plane;
}
list& Role::GetBullet()
{
return bullet;
}
敌机的具体实现类: enemy.h数据成员:由于有多个敌机,本质上和子弹一样,创建一个list容器存放Element*(当做enemy来用)
成员函数:①构造+析构
②for逐一地遍历绘制
③敌机的移动(调用element中封装好的move(方向固定向下))
④敌机的创建(不在构造函数中写),设计到随机生成+从边界一点一点飞出来的效果,单独写一个create函数,随机生成rand()%宽度,起始创建位置在-1*敌机的高度处。
#pragma once
#include"common.h"
class Element;
class Enemy
{
public:
Enemy();
~Enemy();
void DrawEnemy(int preIndex);
void MoveEnemy(int speed);
Element* CreateEnemy();
list& GetEnemy();
protected:
list enemyPlane;
};
enemy.cpp
#include "enemy.h"
#include"element.h"
#include"res.h"
Enemy::Enemy()
{
}
Enemy::~Enemy()
{
}
void Enemy::DrawEnemy(int preIndex)
{
for (auto v : enemyPlane)
{
if (v->GetLive())
{
v->DrawElement(preIndex);
}
}
}
void Enemy::MoveEnemy(int speed)
{
for (auto v : enemyPlane)
{
v->MoveElement(speed, Point::down);
}
}
Element* Enemy::CreateEnemy()
{
return new Element(rand() % Res::GetInstance()->WIDTH("背景"),
-1 * Res::GetInstance()->HEIGHT("敌机"), "敌机", 1, 3);
}
list& Enemy::GetEnemy()
{
return enemyPlane;
}
函数流程的具体操作封装的类: control.h数据成员:①敌机 的指针
②主机(包含子弹) 的指针
③地图 的指针
成员函数:①构造函数+析构函数
②绘制:
(i)地图,就直接调用graph类的绘制地图函数即可
(ii)主机,调用绘制主机的同时,调用move函数(包含了检测键盘信息的功能,传参速度)
(iii)敌机:A:每过1s即1000ms产生敌机,并放置到容器中
B:每过10ms,敌机进行移动
C:绘制敌机
③打飞机的核心逻辑:
(i)碰撞处理:碰到子弹,把子弹的live置为0(这个地方只改标记,删除放在第二步,思路清晰)
(ii)通过标记去删除相应的数据(否则内存会越来越大)
(注意:迭代器删除的细节!)
#pragma once
#include"role.h"
#include"graph.h"
class Role;
class pMap;
class Enemy;
class Control
{
public:
Control(Graph* pMap = nullptr, Role* pRole = nullptr,Enemy*pEnemy=nullptr);
~Control();
void DrawMap();
void DrawRole();
void DrawEnemy();
void PlayEnemy();
protected:
Role* pRole;
Graph* pMap;
Enemy* pEnemy;
};
control.cpp
#include "control.h"
#include"timer.h"
#include"role.h"
#include"graph.h"
#include"enemy.h"
#include"element.h"
#include"res.h"
Control::Control(Graph* pMap, Role* pRole,Enemy* pEnemy)
{
this->pMap = pMap;
this->pRole = pRole;
this->pEnemy = pEnemy;
}
Control::~Control()
{
}
void Control::DrawMap()
{
pMap->DrawMap();
}
void Control::DrawRole()
{
pRole->DrawPlane(0);
pRole->MovePlane(1);
}
void Control::DrawEnemy()
{
if (MyTimer::Timer(1000, 1))
{
pEnemy->GetEnemy().push_back(pEnemy->CreateEnemy());
}
if (MyTimer::Timer(10, 2))
{
pEnemy->MoveEnemy(1);
}
pEnemy->DrawEnemy(0);
}
void Control::PlayEnemy()
{
for (auto bullet : pRole->GetBullet())
{
if (bullet->GetLive()==0)
{
continue;
}
if (bullet->GetY() < 0)
{
bullet->GetLive() = false;
}
for (auto enemy : pEnemy->GetEnemy())
{
if (enemy->GetLive() &&
bullet->GetX() > enemy->GetX() &&
bullet->GetX() < enemy->GetX() + Res::WIDTH("敌机") &&
bullet->GetY() > enemy->GetY() &&
bullet->GetY() < enemy->GetY() + Res::HEIGHT("敌机"))
{
enemy->GetHp() --;
if (enemy->GetHp() <= 0)
{
enemy->GetLive() = false;
}
bullet->GetLive() = false;
}
if (enemy->GetY() >= Res::HEIGHT("背景"))
{
enemy->GetLive() = false;
}
}
}
for (auto iterE = pEnemy->GetEnemy().begin(); iterE != pEnemy->GetEnemy().end();)
{ //细节:迭代的删除,一定要分开,不把++放在for中(否则会删不干净)
if ((*iterE)->GetLive()==false)
{
iterE = pEnemy->GetEnemy().erase(iterE);
}
else
{
iterE++;
}
}
for (auto iterB = pRole->GetBullet().begin(); iterB != pRole->GetBullet().end();)
{ //细节:迭代的删除,一定要分开,不把++放在for中(否则会删不干净)
if ((*iterB)->GetLive() == false)
{
iterB = pRole->GetBullet().erase(iterB);
}
else
{
iterB++;
}
}
}
主函数:MainControl.cpp生成随机数种子->pControl初始化->双缓冲绘制图片(防闪屏)->
while(1)
{cleardevice()->绘制核心物件->打飞机核心逻辑检测};
#include"control.h"
#include"graph.h"
#include"role.h"
#include"enemy.h"
int main()
{
srand((size_t)time(NULL));
Graph* pMap = new Graph;
Role* pRole = new Role;
Enemy* pEnemy = new Enemy;
Control*pControl=new Control(pMap,pRole,pEnemy);
BeginBatchDraw();
while (1)
{
cleardevice();
pControl->DrawMap();
pControl->DrawRole();
pControl->DrawEnemy();
pControl->PlayEnemy();
FlushBatchDraw();
}
while (1);
return 0;
}
三、资源:
C++飞机大战项目代码和素材https://download.csdn.net/download/zjjaibc/85802748



