淘气鸟项目设计
基于C++面向对象的程序设计方法
公众号【编程学习基地】后台发送关键字【淘气鸟】获取项目
面向对象三步法
创建对象
淘气鸟项目里定义了四个类
- Background 背景类
实现了背景图片加载、利用两张背景图片模拟场景移动 - Bird 小鸟类
实现了小鸟图片加载、利用六张背景图片模拟小鸟飞翔 - Block 障碍物类
实现了障碍物图片加载、初始化了十个障碍物循环阻碍飞翔 - Manage 管理类
将Background、Bird、Block这三个类整合起来,利用用户操作实现小鸟跳跃躲避障碍
定义的四个类中,用Background、Bird、Block组合成管理类实现管理
设置对象
Background、Bird、Block这三个类在这里都可以独自实现某个功能
Background back; //创建对象
DWORD begin, end, passTime;
begin = GetTickCount();
back.initBackground();
back.SetBkIsMove(true); //设置对象
while (1)
{
end = GetTickCount();
passTime = end - begin;
begin = end;
back.drawBackground();
back.updateBackground(passTime);
}
Bird bird; //创建对象
DWORD begin, end, passTime;
begin = GetTickCount();
bird.initBird();
bird.SetBirdIsStart(true); //设置对象
while (1)
{
end = GetTickCount();
passTime = end - begin;
begin = end;
bird.drawBird();
bird.updateBird(passTime);
}
Block block; //创建对象
DWORD begin, end, passTime;
begin = GetTickCount();
block.initBlock();
block.SetBlockIsMove(true);//设置对象
while (1)
{
end = GetTickCount();
passTime = end - begin;
begin = end;
block.drawBlock();
block.updateBlock(passTime);
}
使用对象
int main()
{
initgraph(400, 300);
Manage manage; //创建对象
DWORD begin, end, passTime;
begin = GetTickCount();
manage.initGame();
manage.startUI(); //使用对象
while (1)
{
end = GetTickCount();
passTime = end - begin;
begin = end;
manage.drawGame(); //使用对象
manage.updateGame(passTime);//使用对象
}
closegraph();
return 0;
}
Background 背景类
//BackGround.h
#pragma once
class Background
{
IMAGE m_bk; //背景图片
DWORD m_PassTime; //经过的时间
DWORD m_Dsp; //帧时间
int m_iMoveSpeed; //移动速度
bool m_isMove; //是否移动
int m_x, m_y; //绘制的位置
public:
Background();
~Background();
void initBackground(); //初始化
void updateBackground(DWORD PassTime); //数据更新
void drawBackground(); //绘制图形
public:
//设置是否开始移动
void SetBkIsMove(bool isMove) { m_isMove = isMove; }
};
面向对象编程的优点在于你需要什么就以去实现它,你想开放什么端口就开放什么端口,我这里只开放了是否移动端口,其实还可以加很多端口,像移动速度m_isMove,可以让管理类去设置背景图片的移动速度。
//BackGround.cpp
#include"stafx.h"
#include "Background.h"
Background::Background(){}
Background::~Background(){}
void Background::initBackground()
{
//初始化
m_Dsp = 1000 / 12; //单位是ms
m_iMoveSpeed = 2;
m_isMove = false;
m_PassTime = 0;
m_x = 0;
m_y = 0;
//加载图片
loadimage(&m_bk, L"imgs/bk/background.bmp");
}
void Background::updateBackground(DWORD PassTime)
{
if (m_isMove)
{
m_PassTime += PassTime;
if (m_PassTime >= m_Dsp)
{
for (int i = 0; i < 2; i++)
{
m_x -= m_iMoveSpeed;
if (m_x <= -WIDTH)
{
m_x = 0;
}
}
m_PassTime -= m_Dsp;
}
}
}
void Background::drawBackground()
{
//绘制两张图片模拟场景移动
putimage(m_x, m_y, &m_bk);
putimage(m_x + WIDTH, m_y, &m_bk);
}
.cpp就是来实现.h需要实现的功能,这里我就直说下updateBackground中的形参DWORD PassTime,这个是经过的时间,当经过的时间大于你设置的帧时间,那就会进入下一帧,这样背景图片就会移动,造成小鸟飞翔的视觉差错
Bird 小鸟类
#pragma once
class Bird
{
IMAGE m_bird[4][2];
int m_index; //小鸟的状态 平 飞 平 降
int m_x, m_y;
float m_Dps; //帧时间
float m_PassTime; //经过的时间
bool m_isMove; //是否移动
bool m_isStart; //是否开始进入场景
bool m_isUp; //是否跳跃
float m_JumpTimer; //跳跃的时间
RECT m_BirdRect; //小鸟的矩形区域
public:
Bird();
~Bird();
void initBird(); //初始化
void drawBird(); //绘制图形
void updateBird(DWORD PassTime);//数据更新
public:
//获取是否开始状态
bool GetBirdIsStart() { return m_isStart; }
//设置是否开始状态
void SetBirdIsStart(bool isStart) { m_isStart = isStart; }
void SetBirdIsUp(bool isUp) { m_isUp = isUp; }
//获取小鸟所在矩形
RECT GetBirdRect() { return m_BirdRect; }
};
这里可以开放的端口就比较多了,是否跳跃、跳跃时间、是否移动、是否进入场景、改变帧时间可以改变小鸟震动翅膀的频率,还有就是开放一个端口让管理类能够知道小鸟的位置,m_BirdRect用来和障碍物的m_BlockRect进行匹配判断是否输
#include"stafx.h"
#include "Bird.h"
Bird::Bird(){}
Bird::~Bird(){}
void Bird::initBird()
{
//初始化游戏数据
SetRect(&m_BirdRect, 0, 0, 0, 0);
m_Dps = 1000 / 12;
m_PassTime = 0;
m_isMove = false;
m_isStart = false;
m_isUp = false;
m_JumpTimer = 0;
m_index = 0;
m_x = m_y = 0;
//加载图片
loadimage(&m_bird[0][0], L"imgs/bird/bird1-1.gif");
loadimage(&m_bird[0][1], L"imgs/bird/bird1-2.gif");
loadimage(&m_bird[1][0], L"imgs/bird/bird2-1.gif");
loadimage(&m_bird[1][1], L"imgs/bird/bird2-2.gif");
loadimage(&m_bird[2][0], L"imgs/bird/bird3-1.gif");
loadimage(&m_bird[2][1], L"imgs/bird/bird3-2.gif");
loadimage(&m_bird[3][0], L"imgs/bird/bird4-1.gif");
loadimage(&m_bird[3][1], L"imgs/bird/bird4-2.gif");
}
void Bird::drawBird()
{
//setfillcolor(RED);
//fillrectangle(m_BirdRect.left, m_BirdRect.top, m_BirdRect.right, m_BirdRect.bottom);
putimage(m_x, m_y, &m_bird[m_index][0], NOTSRCERASE);
putimage(m_x, m_y, &m_bird[m_index][1], SRCINVERT);
}
void Bird::updateBird(DWORD PassTime)
{
m_PassTime += PassTime;
if (m_PassTime >= m_Dps)
{
//每隔m_Dps时间 改变小鸟状态index 改变小鸟纵坐标m_y
m_index = (++m_index) % 3;
m_PassTime -= m_Dps;
//进入场景
if (m_isStart)
{
m_x += 2;
m_y += 3;
if (m_x >= 100)
{
m_isStart = false;
m_isMove = true;
}
}
//开始游戏
if (m_isMove)
{
if (m_isUp)//上升
{
m_y -= 15;
m_JumpTimer += PassTime;
if (m_JumpTimer >= 10) //跳跃时间为10ms
{
m_JumpTimer = 0;
m_isUp = false;
}
}
else//下降
{
m_y += 3;
}
}
}
//设置小鸟所在矩形
SetRect(&m_BirdRect, m_x + 2, m_y + 2, m_x + 26, m_y + 23);
}
实现这个小鸟类,需要说明的是每次数据更新之后都要对m_BirdRect进行设置
SetRect(&m_BirdRect, m_x + 2, m_y + 2, m_x + 26, m_y + 23);
Block 障碍物类
#pragma once
class Block
{
IMAGE m_block;
float m_PassTime; //经过的时间
float m_Dps; //帧时间
int m_iMoveSpeed; //移动速度
bool m_isMove; //是否开始移动
RECT m_BlockRect[10];
public:
Block();
~Block();
void initBlock();
void updateBlock(DWORD PassTime);
void drawBlock();
public:
RECT* GetBlockRect() { return m_BlockRect; }
//设置是否开始移动
void SetBlockIsMove(bool isMove) { m_isMove = isMove; }
//可另行开放接口
//......
};
这个障碍物类其实和背景类差不多,就是多了几个矩形区域m_BlockRect[10],这个就是障碍物的区域
#include"stafx.h"
#include "Block.h"
Block::Block(){}
Block::~Block(){}
void Block::initBlock()
{
//初始化游戏数据
m_Dps = 1000 / 12;
m_PassTime = 0;
m_iMoveSpeed = 2;
m_isMove = false;
ZeroMemory(m_BlockRect, sizeof(m_BlockRect));
//加载图片
loadimage(&m_block, L"imgs/stone/block.bmp");
POINT pt[10] = { { 200, -20 },{ 200, 200 },{ 350, -60 },{ 350, 160 },{ 500, -40 },{ 500, 180 },{ 650, 0 },{ 650, 220 },{ 800, -60 },{ 800, 160 }};
for (int i = 0; i < 10; i++)
{
SetRect(&m_BlockRect[i], pt[i].x, pt[i].y, pt[i].x + 32/*这是墙图片的宽度*/, pt[i].y + 153);
}
}
void Block::updateBlock(DWORD PassTime)
{
if (m_isMove)
{
m_PassTime += PassTime;
if (m_PassTime >= m_Dps)
{
for (int i = 0; i < 10; i++)
{
m_BlockRect[i].left -= 2;
m_BlockRect[i].right -= 2;
}
m_PassTime -= m_Dps;
}
}
for (int i = 0; i < 10; i++)
{
if (m_BlockRect[i].left < -50)
{
//m_BlockRect[i].left += 750;
m_BlockRect[i].left = m_BlockRect[i].left + rand() % 75 + 675;
m_BlockRect[i].right = m_BlockRect[i].left + 32;
}
}
}
void Block::drawBlock()
{
for (int i = 0; i < 10; i++)
{
putimage(m_BlockRect[i].left, m_BlockRect[i].top, &m_block);
//setfillcolor(RED);
//fillrectangle(m_BlockRect[i].left, m_BlockRect[i].top, m_BlockRect[i].right, m_BlockRect[i].bottom);
}
}
实现障碍物类的时候,这里我就手动的设置了10个障碍物,然后循环造成障碍物无限的假象,没有学过相关算法,所以游戏也没啥难度,就是用来学习面向对象的一个过程
Manage 管理类
#pragma once
class Manage
{
Bird m_bird; //小鸟类
Background m_bk; //背景类
Block m_block; //障碍物
bool m_isStart; //是否开始游戏
DWORD m_passTime; //经过的时间 用来计算分数
int m_score; //分数
public:
Manage();
~Manage();
void initGame(); //初始化
void updateGame(DWORD PassTime); //数据更新
void drawGame(); //绘制游戏
void startUI(); //开始界面
};
Background、Bird、Block三个类都有了,管理类直接用就行了
#include"stafx.h"
#include"Manage.h"
Manage::Manage(){
m_isStart = false;
m_score = 0;
m_passTime = 0;
}
Manage::~Manage(){}
//初始化
void Manage::initGame()
{
//初始化其他
setbkmode(TRANSPARENT); //设置背景透明
//初始化对象
m_bk.initBackground();
m_bird.initBird();
m_block.initBlock();
}
//数据更新
void Manage::updateGame(DWORD PassTime)
{
//暂停或开始游戏
if (m_isStart)
{
m_passTime += PassTime;
//更新元素
m_bk.updateBackground(PassTime);
m_bird.updateBird(PassTime);
m_block.updateBlock(PassTime);
//开始游戏之后障碍物和背景移动
if (!m_bird.GetBirdIsStart())
{
m_block.SetBlockIsMove(true);
m_bk.SetBkIsMove(true);
//计算分数
if (m_passTime >= 2000)
{
m_passTime = 0;
m_score++;
}
}
/***************判断输赢****************/
RECT BirdRect = m_bird.GetBirdRect(); //获取障碍物所在矩形区域
RECT* BlockRect = m_block.GetBlockRect(); //获取小鸟所在矩形区域
for (int i = 0; i < 10; i++)
{
//小鸟碰到障碍物
if (!(BirdRect.right<BlockRect[i].left || BirdRect.left>BlockRect[i].right
|| BirdRect.top > BlockRect[i].bottom || BirdRect.bottom < BlockRect[i].top))
{
//游戏结束 可自行添加结束界面
this->initGame();
this->startUI();
m_isStart = false;
m_bird.SetBirdIsStart(false);
m_score = 0;
}
}
//小鸟飞出界面,游戏结束
if (BirdRect.bottom >= 300 || BirdRect.top <= -44)
{
//游戏结束 可自行添加结束界面 这里直接回到开始UI界面
this->initGame();
this->startUI();
m_isStart = false;
m_bird.SetBirdIsStart(false);
m_score = 0;
}
}
//玩家操作
if (kbhit())
{
switch (getch())
{
case ' ': //空格
m_bird.SetBirdIsUp(true);
break;
case 'p':
case 'P':
if (m_isStart)
m_isStart = false;
else
m_isStart = true;
break;
case 13: //回车
m_isStart = true;
m_bird.SetBirdIsStart(true);
break;
default:
break;
}
}
}
//绘制游戏
void Manage::drawGame()
{
if (m_isStart)
{
BeginBatchDraw();
m_bk.drawBackground();
m_bird.drawBird();
m_block.drawBlock();
settextcolor(WHITE);
settextstyle(24, 0, L"宋体");
wchar_t str[20];
wsprintf(str, L"分数:%d", m_score);
outtextxy(0, 0, str);
EndBatchDraw();
}
}
//开始界面
void Manage::startUI()
{
m_bk.drawBackground();
settextcolor(RED);
settextstyle(28, 0, L"宋体");
outtextxy(120, 50, L"愤怒的小鸟");
settextcolor(BLACK);
settextstyle(18, 0, L"宋体");
outtextxy(135, 90, L"操作说明:");
outtextxy(120, 120, L"1、按空格键跳跃");
outtextxy(120, 145, L"2、按P键暂停");
outtextxy(120, 170, L"3、按回车开始游戏");
outtextxy(200, 200, L"VX公众号:编程学习基地");
}
别看Manage类挺长的,其实大部分功能Background、Bird、Block三个类都实现了,就添加了判断输赢、统计分数、对玩家操作进行响应
判断输赢
我这里就只说明下判断输赢,我加了个填充矩形,然后就有了这三张图片
玩家正常玩游戏时:
在小鸟和障碍物的绘制那里加上填充矩形
void Bird::drawBird()
{
setfillcolor(RED);
fillrectangle(m_BirdRect.left, m_BirdRect.top, m_BirdRect.right, m_BirdRect.bottom);
putimage(m_x, m_y, &m_bird[m_index][0], NOTSRCERASE);
putimage(m_x, m_y, &m_bird[m_index][1], SRCINVERT);
}
void Block::drawBlock()
{
for (int i = 0; i < 10; i++)
{
setfillcolor(RED);
fillrectangle(m_BlockRect[i].left, m_BlockRect[i].top, m_BlockRect[i].right, m_BlockRect[i].bottom);
putimage(m_BlockRect[i].left, m_BlockRect[i].top, &m_block);
}
}
我们来看下图像
好像没什么变化,仔细点看,你会看到小鸟后方好像有个红色填充的矩形
我们将上面的顺序改下,改成下面的
void Bird::drawBird()
{
putimage(m_x, m_y, &m_bird[m_index][0], NOTSRCERASE);
putimage(m_x, m_y, &m_bird[m_index][1], SRCINVERT);
setfillcolor(RED);
fillrectangle(m_BirdRect.left, m_BirdRect.top, m_BirdRect.right, m_BirdRect.bottom);
}
void Block::drawBlock()
{
for (int i = 0; i < 10; i++)
{
putimage(m_BlockRect[i].left, m_BlockRect[i].top, &m_block);
setfillcolor(RED);
fillrectangle(m_BlockRect[i].left, m_BlockRect[i].top, m_BlockRect[i].right, m_BlockRect[i].bottom);
}
}
有没有看到,其实在逻辑里面没有什么图像不图像的,都是一个个矩形区域代表着某些东西
这样我们就好办了,判断输赢嘛,只要小鸟所在的矩形区域和障碍物所在的矩形区域没有交集就行了,我的代码大家可能看的有点懵逼
BirdRect.right<BlockRect[i].left ||
BirdRect.left>BlockRect[i].right ||
BirdRect.top > BlockRect[i].bottom ||
BirdRect.bottom < BlockRect[i].top
这个代码逻辑性还是很强的,一个矩形的最右边和另一个矩形的最左边隔着一段距离,说这两个图像不相交,我们把四个方向都列出来,这个就是不相交的区域,然后取反,就是相交。可能有点绕,这个还是需要自行领悟。