flappyBird项目设计(有源码)

1.png

淘气鸟项目设计

基于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三个类都实现了,就添加了判断输赢、统计分数、对玩家操作进行响应

判断输赢

我这里就只说明下判断输赢,我加了个填充矩形,然后就有了这三张图片

玩家正常玩游戏时:

image.png

在小鸟和障碍物的绘制那里加上填充矩形

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);
    }
}

我们来看下图像

image.png

好像没什么变化,仔细点看,你会看到小鸟后方好像有个红色填充的矩形

我们将上面的顺序改下,改成下面的

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);
    }
}

image.png

有没有看到,其实在逻辑里面没有什么图像不图像的,都是一个个矩形区域代表着某些东西

这样我们就好办了,判断输赢嘛,只要小鸟所在的矩形区域和障碍物所在的矩形区域没有交集就行了,我的代码大家可能看的有点懵逼

BirdRect.right<BlockRect[i].left ||
    BirdRect.left>BlockRect[i].right || 
    BirdRect.top > BlockRect[i].bottom ||
    BirdRect.bottom < BlockRect[i].top

这个代码逻辑性还是很强的,一个矩形的最右边和另一个矩形的最左边隔着一段距离,说这两个图像不相交,我们把四个方向都列出来,这个就是不相交的区域,然后取反,就是相交。可能有点绕,这个还是需要自行领悟。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。