0.基本效果界面展示:

1.仍然采用单例设计模式,高度抽象出几个类如point、graph、element(敌机、主机(role包含数据成员包含子弹)均包含其作为一个数据成员,进行共通的操作)。
2.子弹十分密集,按一下空格会发出很多子弹:写一个timer定时器
(注:当声明和实现放在同一个文件去实现的时候,可以以.hpp结尾)
MyTimer::Timer(100,0) #100ms产生一颗
3.注:虽然作了前向声明,但是在.cpp文件中,找不到其相应的成员函数,
那一定是没有在.cpp中包含其相关的头文件。
4.我承认:debug最好的定位方式,注释掉疑似代码,看相关功能的变化。
公共头文件:common.h
- #pragma once
- #include<graphics.h>
- #include<iostream>
-
- #include<thread> /*c++多线程*/
- #include<ctime>
-
- #include<string>
- #include<map>
- #include<list>
- #include<vector>
-
- #include<mmsystem.h>
- #pragma commment(lib,"winmm.lib")
- using namespace std;
计时器: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];/*自动初始化为0*/
- 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);
- /*注;角色有不同的,用preIndex来索引*/
- //static DWORD WINAPI PlayMusic(LPVOID lparame); /*播放音乐的线程处理函数*/
-
- public:
- static map<string,IMAGE*> img; /*图片*/
- static map<string, string> music; /*音乐*/
-
- public:
- ~Res();
-
- private:
- Res();
- };
res.cpp
(静态数据成员的初始化,
此外,此处还涉及一个细节,如何去掉一个物体图片周围的阴影部分:大致的实现原理是:准备原图一份,需要切除部分一张图片,/*NOTSRCERASE 先对遮罩进行处理 , 使得遮罩白色变黑 ,黑色区域变透明*//*SRCINVERT 在对小鸟原图处理*/->按位合&,实现背景部分的透明化操作)
参考博客:c愤怒小鸟
- #include "res.h"
-
- /*step1:类外对静态数据成员进行初始化*/
- map<string, IMAGE*>Res::img; /*图片*/
- map<string, string>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);/*NOTSRCERASE 先对遮罩进行处理 , 使得遮罩白色变黑 ,黑色区域变透明*/
- putimage(x, y, GetInstance()->img[name] + preIndex+1, SRCINVERT);/*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()); /*c_str()和data()等效,都是转化为c语言风格的string*/
- }
- }
专门用于地图操作的类: 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, "背景"); /*第3个参数是用来map索引相关的资源的*/
- }
抽象出敌机、玩家(主机)、子弹的共性类: 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;
- };
- #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<Element*>& GetBullet();
- protected:
- Element* plane; /*角色*/
- list<Element*>bullet; /*子弹*/
-
- };
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)
- {
- /*GetAsyncKeyState是一个用来判断函数调用时指定虚拟键的状态,确定用户当前是否按下了键盘上的一个键的函数。如果按下,则返回值最高位为1。*/
- /*另一个函数GetKeyState与GetAsyncKeyState函数不同。GetAsyncKeyState在按下某键的同时调用,判断正在按下某键。
- GetKeyState则在按过某键之后再调用,它返回最近的键盘消息从线程的队列中移出时的键盘状态,判断刚按过了某键。
- GetAsyncKeyState 取异步键状态。*/
- 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_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)) /*100ms产生一颗*/
- {
- /*每产生一个子弹,就播放音乐*/
- //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<Element*>& 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<Element*>& GetEnemy();
- protected:
- list<Element*> 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); /*3点血量,需要打三次才能消失*/
- }
-
- list<Element*>& 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()
- {
- /*1.碰撞处理:碰到子弹,把子弹的live置为0(这个地方只改标记,删除放在第二步,思路清晰)*/
- 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;
- }
- }
- }
- /*2.通过标记去删除相应的数据(否则内存会越来越大)*/
- 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