C++火柴人跑酷

火柴人跑酷游戏开发流程文档

第一阶段:基础框架搭建(v1.0)

1.1 基础头文件和常量定义

cpp 复制代码
#include <graphics.h>
#include <stdio.h>
#include <time.h>

#define WIDTH 640
#define HEIGHT 400
#define FLOOR_Y 360

int main() {
    initgraph(WIDTH, HEIGHT);
    
    while (1) {
        setbkcolor(WHITE);
        cleardevice();
        
        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);
        
        FlushBatchDraw();
        Sleep(10);
    }
    
    closegraph();
    return 0;
}

效果:显示白色背景和蓝色地面的基础窗口


第二阶段:角色系统开发(v2.0)

2.1 角色状态和属性定义

cpp 复制代码
enum State { Run, Roll, Jump };
struct FireMan {
    int x, y;
    State state;
    int w, h;
};

#define ROLE_IMGW 70
#define ROLE_IMGH 100
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH

struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };

2.2 简单角色绘制

cpp 复制代码
void DrawRole() {
    // 用简单矩形代替角色
    setfillcolor(RED);
    fillrectangle(fireMan.x, fireMan.y, fireMan.x + fireMan.w, fireMan.y + fireMan.h);
}

效果:屏幕上出现一个红色矩形代表火柴人


第三阶段:资源管理系统(v3.0)

3.1 图像资源加载

cpp 复制代码
IMAGE runIMG[8];

void LoadResouces() {
    char fileURL[50] = "";
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH); 
    }
}

3.2 更新角色绘制

cpp 复制代码
void DrawRole() {
    putimage(fireMan.x, fireMan.y, runIMG + 0); // 先使用第一帧
}

效果:火柴人显示为真实的奔跑动画第一帧

【注意】:如果无法正常使用loadimage函数,把项目改为:使用多字节字符集

  • bug修复:以上代码,为何画面有不时的闪烁和抖动?
  • 答:画面闪烁和抖动的主要原因是没有使用双缓冲技术。虽然你使用了FlushBatchDraw(),但缺少了BeginBatchDraw()来开启批量绘制。
  • 主要修改点:添加双缓冲初始化 BeginBatchDraw() --- EndBatchDraw()
cpp 复制代码
// 主函数:游戏入口,负责初始化和主循环
int main() {
    initgraph(WIDTH, HEIGHT);  // 初始化图形窗口,设置宽高
    LoadResouces(); //加载角色动画图片
    BeginBatchDraw();          // 开启批量绘制,消除闪烁的关键!
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);
        DrawRole(); // 绘制角色
        FlushBatchDraw();
        Sleep(10);
    }
	
    EndBatchDraw();        // 结束批量绘制
    closegraph();  // 关闭图形窗口(本代码主循环为死循环,此句实际不会执行)
    return 0;
}

第四阶段:动画系统(v4.0)

4.1 动画帧控制

cpp 复制代码
int frameNum = 0;
#define MAX_FRAME 8

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
			break;
        }
		case Jump:
		{
			frameNum = 0;
		}
        default:
            break;
    }
}

void DrawRole() {
    putimage(fireMan.x, fireMan.y, runIMG + frameNum);
}

效果:火柴人开始奔跑动画

4.2 定时器控制

  • 火柴人奔跑的速度太快了,我们新增一个定时器,来控制火柴人奔跑的速度。
cpp 复制代码
// 定时器函数封装
bool Timer(clock_t& lastTime, int interval) {
    clock_t currentTime = clock();
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime;
        return true;
    }
    return false;
}
clock_t animLastTime = 0;

//...
while (1) {
    //...
    if (Timer(animLastTime, 30)) { // 30毫秒间隔
        MoveRole();
    }
}

效果:火柴人奔跑速度明显减缓,30毫秒之后才会切换下一帧动画。

  • 完整代码:
cpp 复制代码
#include <graphics.h>
#include <stdio.h>
#include <time.h>

#define WIDTH 640
#define HEIGHT 400
#define FLOOR_Y 360

enum State { Run, Roll, Jump };
struct FireMan {
    int x, y;
    State state;
    int w, h;
};

#define ROLE_IMGW 70
#define ROLE_IMGH 100
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH

struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };


IMAGE runIMG[8];

void LoadResouces() {
    char fileURL[50] = "";
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH);
    }
}

int frameNum = 0;
#define MAX_FRAME 8

// 定时器函数封装
bool Timer(clock_t& lastTime, int interval) {
    clock_t currentTime = clock();
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime;
        return true;
    }
    return false;
}
clock_t animLastTime = 0;

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
			break;
        }
		case Jump:
		{
			frameNum = 0;
		}
		
        default:
            break;
    }
}


void DrawRole() {
    putimage(fireMan.x, fireMan.y, runIMG + frameNum); 
}


int main() {
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);
        
		DrawRole();
        if (Timer(animLastTime, 30)) { // 30毫秒间隔
            MoveRole();
        }

        FlushBatchDraw();
        Sleep(10);
    }
    EndBatchDraw();
    closegraph();
    return 0;
}

第五阶段:输入控制系统(v5.0)

5.1 跳跃功能

cpp 复制代码
int speed = -10;
#define JUMP_MIN_Y 60

void KeyDown() {
    if (GetAsyncKeyState(VK_UP)) {
        fireMan.state = Jump;
    }
}

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
			break;
        }
		case Jump:
        {
            frameNum = 0;
            fireMan.y += speed;
            if (fireMan.y < JUMP_MIN_Y) {
                speed = -speed;
            }
            if (fireMan.y >= BEGIN_Y) {
                fireMan.y = BEGIN_Y;
                speed = -speed;
                fireMan.state = Run;
            }
        }
        default:
            break;
    }
}

效果:按上键火柴人可以跳跃

  • 【补充说明】:MoveRole函数


  • 完整代码:
cpp 复制代码
#include <graphics.h>
#include <stdio.h>
#include <time.h>

#define WIDTH 640
#define HEIGHT 400
#define FLOOR_Y 360

enum State { Run, Roll, Jump };
struct FireMan {
    int x, y;
    State state;
    int w, h;
};

#define ROLE_IMGW 70
#define ROLE_IMGH 100
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH

struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };


IMAGE runIMG[8];

void LoadResouces() {
    char fileURL[50] = "";
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH);
    }
}

int frameNum = 0;
#define MAX_FRAME 8


// 定时器函数封装
bool Timer(clock_t& lastTime, int interval) {
    clock_t currentTime = clock();
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime;
        return true;
    }
    return false;
}
clock_t animLastTime = 0;

int speed = -10;
#define JUMP_MIN_Y 60

void KeyDown() {
    if (GetAsyncKeyState(VK_UP)) {
        fireMan.state = Jump;
    }
}

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
			break;
        }
		case Jump:
        {
            frameNum = 0;
            fireMan.y += speed;
            if (fireMan.y < JUMP_MIN_Y) {
                speed = -speed;
            }
            if (fireMan.y >= BEGIN_Y) {
                fireMan.y = BEGIN_Y;
                speed = -speed;
                fireMan.state = Run;
            }
        }
        default:
            break;
    }
}


void DrawRole() {
    putimage(fireMan.x, fireMan.y, runIMG + frameNum); 
}


int main() {
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);
        
		DrawRole();
        if (Timer(animLastTime, 30)) { // 30毫秒间隔
            MoveRole();
        }
		KeyDown();

        FlushBatchDraw();
        Sleep(10);
    }
    EndBatchDraw();
    closegraph();
    return 0;
}

第六阶段:下蹲功能(v6.0)

6.1 翻滚动画资源

cpp 复制代码
IMAGE rollIMG[8];
#define ROLE_DOWN_IMGW 50
#define ROLE_DOWN_IMGH 50

// 在LoadResouces中添加
for (int i = 1; i <= 8; i++) {
    sprintf_s(fileURL, 50, "./Res/Roll/gun8_%d.jpg", i);
    loadimage(rollIMG + i - 1, fileURL, ROLE_DOWN_IMGW, ROLE_DOWN_IMGH);
}

6.2 下蹲逻辑

cpp 复制代码
void KeyDown() {
    if (GetAsyncKeyState(VK_DOWN)) {
        fireMan.state = Roll;
    }
}

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            fireMan.y = BEGIN_Y + (ROLE_IMGH - ROLE_DOWN_IMGH);//注意设置正确的y坐标
			break;
        }
		case Jump:
        {
            frameNum = 0;
            fireMan.y += speed;
            if (fireMan.y < JUMP_MIN_Y) {
                speed = -speed;
            }
            if (fireMan.y >= BEGIN_Y) {
                fireMan.y = BEGIN_Y;
                speed = -speed;
                fireMan.state = Run;
            }
        }
        default:
            break;
    }
}

void DrawRole() {
    switch (fireMan.state) {
	    case Run: putimage(fireMan.x, fireMan.y, runIMG + frameNum); break;
	    case Roll: putimage(fireMan.x, fireMan.y, rollIMG + frameNum); break;
	    case Jump: ; break;
    }
}

效果:按空格键火柴人可以下蹲翻滚

  • 【完善代码】补充:加载正确的跳跃图片
cpp 复制代码
void LoadResouces() {
	//...
	loadimage(&jumpIMG, "./Res/Jump/jump.jpg", ROLE_IMGW, ROLE_IMGH);
}


void DrawRole() {
    switch (fireMan.state) {
        case Run: putimage(fireMan.x, fireMan.y, runIMG + frameNum); break;
        case Roll: putimage(fireMan.x, fireMan.y, rollIMG + frameNum); break;
        case Jump: putimage(fireMan.x, fireMan.y, &jumpIMG); break;
    }
}
  • 完整代码:
cpp 复制代码
#include <graphics.h>
#include <stdio.h>
#include <time.h>

#define WIDTH 640
#define HEIGHT 400
#define FLOOR_Y 360

enum State { Run, Roll, Jump };
struct FireMan {
    int x, y;
    State state;
    int w, h;
};

#define ROLE_IMGW 70
#define ROLE_IMGH 100
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH

struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };


IMAGE runIMG[8];
IMAGE rollIMG[8];
IMAGE jumpIMG;
#define ROLE_DOWN_IMGW 50
#define ROLE_DOWN_IMGH 50

void LoadResouces() {
    char fileURL[50] = "";
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH);
    }
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Roll/gun8_%d.jpg", i);
        loadimage(rollIMG + i - 1, fileURL, ROLE_DOWN_IMGW, ROLE_DOWN_IMGH);
    }
	loadimage(&jumpIMG, "./Res/Jump/jump.jpg", ROLE_IMGW, ROLE_IMGH);
}

int frameNum = 0;
#define MAX_FRAME 8


// 定时器函数封装
bool Timer(clock_t& lastTime, int interval) {
    clock_t currentTime = clock();
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime;
        return true;
    }
    return false;
}
clock_t animLastTime = 0;

int speed = -10;
#define JUMP_MIN_Y 60

void KeyDown() {
    if (GetAsyncKeyState(VK_UP)) {
        fireMan.state = Jump;
    }
    if (GetAsyncKeyState(VK_DOWN)) {
        fireMan.state = Roll;
    }
}

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            fireMan.y = BEGIN_Y + (ROLE_IMGH - ROLE_DOWN_IMGH);
			break;
        }
		case Jump:
        {
            frameNum = 0;
            fireMan.y += speed;
            if (fireMan.y < JUMP_MIN_Y) {
                speed = -speed;
            }
            if (fireMan.y >= BEGIN_Y) {
                fireMan.y = BEGIN_Y;
                speed = -speed;
                fireMan.state = Run;
            }
        }
        default:
            break;
    }
}


void DrawRole() {
    switch (fireMan.state) {
        case Run: putimage(fireMan.x, fireMan.y, runIMG + frameNum); break;
        case Roll: putimage(fireMan.x, fireMan.y, rollIMG + frameNum); break;
        case Jump: putimage(fireMan.x, fireMan.y, &jumpIMG); break;
    }
}


int main() {
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);
        
		DrawRole();
        if (Timer(animLastTime, 30)) { // 30毫秒间隔
            MoveRole();
        }
		KeyDown();

        FlushBatchDraw();
        Sleep(10);
    }
    EndBatchDraw();
    closegraph();
    return 0;
}

第七阶段:障碍物系统(v7.0)

7.1 障碍物定义

cpp 复制代码
#include <vector>

enum ObstacleType { CACTUS, BIRD };
struct Obstacle {
    int x, y;
    ObstacleType type;
    int width, height;
};

std::vector<Obstacle> obstacles;
int gameSpeed = 5;

7.2 障碍物绘制

cpp 复制代码
void DrawObstacles() {
    for (const auto& obs : obstacles) {
        setfillcolor(GREEN);
        fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
    }
}

7.3 障碍物生成和移动

cpp 复制代码
void GenerateObstacle() {
    static int timer = 0;
    timer++;
    if (timer > 100) {
        Obstacle obs;
        obs.type = CACTUS;
        obs.width = 30; obs.height = 60;
        obs.x = WIDTH; obs.y = FLOOR_Y - obs.height;
        obstacles.push_back(obs);
        timer = 0;
    }
}

void MoveObstacles() {
    for (auto it = obstacles.begin(); it != obstacles.end();) {
        it->x -= gameSpeed;
        if (it->x + it->width < 0) {
            it = obstacles.erase(it);
        } else {
            ++it;
        }
    }
}

效果:绿色障碍物从右侧出现并向左移动

  • 解读代码

一、基础定义:ObstacleType 枚举与 Obstacle 结构体
在分析函数前,需先明确障碍物的 "类型标识" 和 "数据载体",这是所有障碍物操作的基础。

cpp 复制代码
// 枚举:定义障碍物的类型(后续可扩展更多类型,如石头、陷阱等)
enum ObstacleType { CACTUS, BIRD }; 
// 结构体:存储单个障碍物的核心数据(位置、类型、尺寸)
struct Obstacle {
    int x, y;               // 障碍物左上角坐标(x:水平位置,y:垂直位置)
    ObstacleType type;      // 障碍物类型(关联上面的枚举,当前仅支持CACTUS仙人掌)
    int width, height;      // 障碍物的宽度和高度(用于绘制和碰撞检测)
};

// 动态数组:存储所有当前屏幕上的障碍物(vector自动管理内存,方便添加/删除)
std::vector<Obstacle> obstacles;
int gameSpeed = 5;         // 障碍物移动速度(水平向左的速度,数值越大移得越快)

二、GenerateObstacle 函数:生成障碍物(创建新障碍物并添加到数组)
功能:按固定频率在屏幕右侧生成仙人掌障碍物,确保游戏中持续出现新挑战。

cpp 复制代码
void GenerateObstacle() {
    // 静态变量timer:用于控制生成频率(静态变量只初始化一次,函数调用时保留上次值)
    static int timer = 0;
    timer++;  // 每次调用函数,计时器+1(配合主循环帧率,实现"间隔生成")

    // 控制生成频率:当计时器超过100时,生成一个障碍物(主循环每10ms执行一次,100次即1秒生成1个)
    if (timer > 100) {
        Obstacle obs;  // 创建一个临时障碍物对象(存储当前要生成的障碍物数据)
        
        obs.type = CACTUS;  // 设置障碍物类型为"仙人掌"(当前仅支持该类型,可扩展BIRD)
        obs.width = 30;     // 仙人掌宽度(30像素,后续可改为宏定义,方便统一修改)
        obs.height = 60;    // 仙人掌高度(60像素,与地面高度配合,确保贴合地面)
        
        // 障碍物生成位置:屏幕最右侧(x=WIDTH,即640像素处),垂直方向贴合地面
        obs.x = WIDTH;  // x坐标设为屏幕宽度,确保生成时在屏幕外右侧,不会突然出现在中间
        // y坐标 = 地面y坐标 - 障碍物高度(FLOOR_Y=360,360-60=300,刚好让仙人掌底部贴地面)
        obs.y = FLOOR_Y - obs.height;

        obstacles.push_back(obs);  // 将生成的障碍物添加到动态数组中(后续绘制、移动都操作这个数组)
        timer = 0;  // 计时器重置,重新开始计数,确保下次生成仍间隔100次主循环
    }
}

三、DrawObstacles 函数:绘制障碍物(将数组中的障碍物显示到屏幕)
功能:遍历所有已生成的障碍物,在屏幕上绘制对应的图形(当前用绿色矩形代表仙人掌)。

cpp 复制代码
void DrawObstacles() {
    // 范围for循环:遍历动态数组obstacles中的每一个障碍物(auto自动推导类型,const防止修改原数据)
    for (const auto& obs : obstacles) {
        setfillcolor(GREEN);  // 设置填充颜色为绿色(代表仙人掌的颜色,可根据type修改,如BIRD设为棕色)
        
        // 绘制填充矩形:参数为"左上角x、左上角y、右下角x、右下角y"
        // 右下角x = 左上角x + 宽度(obs.x + obs.width)
        // 右下角y = 左上角y + 高度(obs.y + obs.height)
        // 效果:在(obs.x, obs.y)位置绘制一个30x60的绿色矩形,即仙人掌的可视化形状
        fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
    }
}

四、MoveObstacles 函数:移动与清理障碍物(让障碍物向左移动,移除屏幕外的障碍物)
功能:实现障碍物的水平移动(模拟角色向前跑的视觉效果),并清理屏幕左侧已消失的障碍物(避免内存泄漏)。

cpp 复制代码
void MoveObstacles() {
    // 迭代器遍历:vector删除元素时需用迭代器(不能用范围for,否则删除后迭代器失效)
    for (auto it = obstacles.begin(); it != obstacles.end();) {
        // 障碍物水平向左移动:x坐标 -= 游戏速度(gameSpeed=5,每次主循环向左移5像素)
        it->x -= gameSpeed;

        // 判断障碍物是否完全移出屏幕左侧:障碍物右边界(x+width)< 0时,说明已看不到
        if (it->x + it->width < 0) {
            // 从数组中删除该障碍物(erase返回下一个有效迭代器,避免迭代器失效)
            it = obstacles.erase(it);
        } else {
            // 未移出屏幕,迭代器向后移动,继续处理下一个障碍物
            ++it;
        }
    }
}

核心逻辑:因为角色位置固定(视觉上向前跑),所以让障碍物向左移动,模拟相对运动;删除屏幕外的障碍物是为了释放内存,避免数组无限增大。

  • 问题:MoveObstacles函数中,为何it放在else里面且需要自增?

7.4 障碍物出现频率改为随机

  • 第一步:添加随机数初始化(在 main 函数开头)
cpp 复制代码
int main() {
    // 新增:初始化随机数种子(用系统时间做种子,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL)); 
    
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        // 原有代码不变...
    }
    // 原有代码不变...
}
  • 第二步:修改 GenerateObstacle 函数
cpp 复制代码
void GenerateObstacle() {
    static int timer = 0;
    static int randomThreshold = 0; // 新增:存储随机触发阈值
    
    timer++;
    
    // 新增:如果是第一次运行,或已生成一个障碍物,重新随机生成阈值
    if (randomThreshold == 0) {
        // rand() % 150 + 50:生成 50~200 之间的随机数(可调整范围)
        // 范围说明:50=最小间隔(生成快),200=最大间隔(生成慢),150=200-50
        randomThreshold = rand() % 150 + 50; 
    }
    
    // 修改:用随机阈值替代固定的100
    if (timer > randomThreshold) {
        Obstacle obs;
        obs.type = CACTUS;
        obs.width = 30; 
        obs.height = 60;
        obs.x = WIDTH; 
        obs.y = FLOOR_Y - obs.height;
        obstacles.push_back(obs);
        
        timer = 0; // 重置计时器
        randomThreshold = 0; // 重置阈值,下次会重新生成随机值
    }
}
  • 完整代码
cpp 复制代码
#include <graphics.h>
#include <stdio.h>
#include <time.h>
#include <vector>
#define WIDTH 640
#define HEIGHT 400
#define FLOOR_Y 360

enum State { Run, Roll, Jump };
struct FireMan {
    int x, y;
    State state;
    int w, h;
};

#define ROLE_IMGW 70
#define ROLE_IMGH 100
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH

struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };


IMAGE runIMG[8];
IMAGE rollIMG[8];
IMAGE jumpIMG;
#define ROLE_DOWN_IMGW 50
#define ROLE_DOWN_IMGH 50
int frameNum = 0;
#define MAX_FRAME 8

clock_t animLastTime = 0;
int speed = -10;
#define JUMP_MIN_Y 60
std::vector<Obstacle> obstacles;
int gameSpeed = 5;

void LoadResouces() {
    char fileURL[50] = "";
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH);
    }
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Roll/gun8_%d.jpg", i);
        loadimage(rollIMG + i - 1, fileURL, ROLE_DOWN_IMGW, ROLE_DOWN_IMGH);
    }
	loadimage(&jumpIMG, "./Res/Jump/jump.jpg", ROLE_IMGW, ROLE_IMGH);
}


// 定时器函数封装
bool Timer(clock_t& lastTime, int interval) {
    clock_t currentTime = clock();
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime;
        return true;
    }
    return false;
}


void KeyDown() {
    if (GetAsyncKeyState(VK_UP)) {
        fireMan.state = Jump;
    }
    if (GetAsyncKeyState(VK_DOWN)) {
        fireMan.state = Roll;
    }
}

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            fireMan.y = BEGIN_Y + (ROLE_IMGH - ROLE_DOWN_IMGH);
			break;
        }
		case Jump:
        {
            frameNum = 0;
            fireMan.y += speed;
            if (fireMan.y < JUMP_MIN_Y) {
                speed = -speed;
            }
            if (fireMan.y >= BEGIN_Y) {
                fireMan.y = BEGIN_Y;
                speed = -speed;
                fireMan.state = Run;
            }
        }
        default:
            break;
    }
}


void DrawRole() {
    switch (fireMan.state) {
        case Run: putimage(fireMan.x, fireMan.y, runIMG + frameNum); break;
        case Roll: putimage(fireMan.x, fireMan.y, rollIMG + frameNum); break;
        case Jump: putimage(fireMan.x, fireMan.y, &jumpIMG); break;
    }
}



enum ObstacleType { CACTUS, BIRD };
struct Obstacle {
    int x, y;
    ObstacleType type;
    int width, height;
};



void GenerateObstacle() {
    static int timer = 0;
    static int randomThreshold = 0; // 新增:存储随机触发阈值
    timer++;
    if (randomThreshold == 0) {
        // rand() % 150 + 50:生成 50~200 之间的随机数
        randomThreshold = rand() % 150 + 50;
    }
    if (timer > randomThreshold) {
        Obstacle obs;
        obs.type = CACTUS;
        obs.width = 30; 
        obs.height = 60;
        obs.x = WIDTH; 
        obs.y = FLOOR_Y - obs.height;
        obstacles.push_back(obs);
        timer = 0;
        randomThreshold = 0;
    }
}

void DrawObstacles() {
    for (const auto& obs : obstacles) {
        setfillcolor(GREEN);
        fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
    }
}

void MoveObstacles() {
    for (auto it = obstacles.begin(); it != obstacles.end();) {
        it->x -= gameSpeed;
        if (it->x + it->width < 0) {
            it = obstacles.erase(it);
        }
        else {
            ++it;
        }
    }
}

int main() {
    // 新增:初始化随机数种子(用系统时间做种子,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL));
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);
        
		DrawRole();
        if (Timer(animLastTime, 30)) { // 30毫秒间隔
            MoveRole();
        }
		KeyDown();

        GenerateObstacle();// 生成障碍物
        DrawObstacles();   // 绘制障碍物
        MoveObstacles();   // 移动障碍物
        

        FlushBatchDraw();
        Sleep(10);
    }
    EndBatchDraw();
    closegraph();
    return 0;
}

第八阶段:碰撞检测(v8.0)

8.1 碰撞检测函数

cpp 复制代码
bool gameOver = false;

bool CheckCollision() {
    for (const auto& obs : obstacles) {
        if (fireMan.x + fireMan.w > obs.x && 
            fireMan.x < obs.x + obs.width &&
            fireMan.y + fireMan.h > obs.y && 
            fireMan.y < obs.y + obs.height) {
            return true;
        }
    }
    return false;
}

效果:碰到障碍物时游戏停止

  • CheckCollision碰撞函数解读:
  • main函数代码:
cpp 复制代码
int main() {
    // 新增:初始化随机数种子(用系统时间做种子,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL));
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);

		
        GenerateObstacle();// 生成障碍物
        DrawObstacles();   // 绘制障碍物
		DrawRole();// 绘制角色
		KeyDown();// 处理按键
        
		if (!gameOver) { //游戏未结束

            if (Timer(animLastTime, 30)) { // 30毫秒间隔
				MoveRole();// 移动角色
            }
			MoveObstacles(); // 移动障碍物

            // 检测碰撞
            if (CheckCollision())
            {
                gameOver = true;
            }
        }

        FlushBatchDraw();
        Sleep(10);
    }
    EndBatchDraw();
    closegraph();
    return 0;
}

第九阶段:计分系统(v9.0)

9.1 分数显示

cpp 复制代码
int score = 0;

void MoveObstacles() {
    for (auto it = obstacles.begin(); it != obstacles.end();) {
        it->x -= gameSpeed;
        if (it->x + it->width < 0) {
            it = obstacles.erase(it);
            score += 10;  // 障碍物移出屏幕加分
        } else {
            ++it;
        }
    }
}

while (1) {
    // ... 其他绘制代码
    
    // 绘制分数
    settextcolor(BLACK);
    char scoreText[50];
    sprintf_s(scoreText, 50, "Score: %d", score);
    outtextxy(500, 20, scoreText);
}

效果 :右上角显示分数,成功躲避障碍物得分

第十阶段:按R重新开始(v10.0)

10.1 按R重新开始,并显示对应分数

cpp 复制代码
void KeyDown() {
    //...
    if (GetAsyncKeyState('R') && gameOver) {
        // 重新开始游戏
        gameOver = false;
        obstacles.clear();
        score = 0;
        frameNum = 0;
        fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
    }
}

int main() {
	//...

    // 游戏主循环(无限循环,直到关闭窗口)
    while (1) {
			//...

        // 游戏未结束时,执行移动和碰撞检测
        if (!gameOver) {
            // 每30毫秒更新一次角色动画/位置(控制动画帧率)
            if (Timer(animLastTime, 30)) {
                MoveRole();// 更新角色位置和动画
            }
            MoveObstacles(); // 移动障碍物并清理

            // 检测碰撞,若碰撞则标记游戏结束
            if (CheckCollision())
            {
                gameOver = true;
            }
        }
        else {
            //游戏结束,作相应的文字提示
            settextcolor(RED);
            settextstyle(60, 0, _T("黑体"));
            outtextxy(WIDTH / 2 - 120, HEIGHT / 2 - 50, _T("游戏结束!"));

            settextstyle(30, 0, _T("黑体"));
            outtextxy(WIDTH / 2 - 100, HEIGHT / 2 + 20, _T("按R键重新开始"));

            // 修正:先格式化得分字符串,再输出
            char scoreStr[50];
            sprintf_s(scoreStr, 50, "最终得分:%d", score);
            outtextxy(WIDTH / 2 - 80, HEIGHT / 2 + 60, _T(scoreStr));
        }

        // 动态提升游戏速度(每得100分,速度+1,增加难度)
        gameSpeed = 5 + score / 100;

        // 刷新批量绘制(将所有绘制操作显示到屏幕)
        FlushBatchDraw();
        // 休眠10毫秒(控制主循环帧率,减少CPU占用)
        Sleep(10);
    }

    // 结束批量绘制(实际不会执行,因主循环是无限循环)
    EndBatchDraw();
    // 关闭游戏窗口
    closegraph();
    return 0;
}

效果:游戏结束时,按R重新开始,显示对应分数

  • 完整代码:
cpp 复制代码
#include <graphics.h>
#include <stdio.h>
#include <time.h>
#include <vector>
#define WIDTH 640
#define HEIGHT 400
#define FLOOR_Y 360
#define ROLE_IMGW 70
#define ROLE_IMGH 100
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH
#define ROLE_DOWN_IMGW 50
#define ROLE_DOWN_IMGH 50
#define MAX_FRAME 8
#define JUMP_MIN_Y 60
IMAGE runIMG[8];
IMAGE rollIMG[8];
IMAGE jumpIMG;

int gameSpeed = 5;
bool gameOver = false;
clock_t animLastTime = 0;
int speed = -10;
int frameNum = 0;
int score = 0;
enum State { Run, Roll, Jump };
enum ObstacleType { CACTUS, BIRD };
struct FireMan {
    int x, y;
    State state;
    int w, h;
};
struct Obstacle {
    int x, y;
    ObstacleType type;
    int width, height;
};
struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
std::vector<Obstacle> obstacles;

void LoadResouces() {
    char fileURL[50] = "";
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH);
    }
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Roll/gun8_%d.jpg", i);
        loadimage(rollIMG + i - 1, fileURL, ROLE_DOWN_IMGW, ROLE_DOWN_IMGH);
    }
	loadimage(&jumpIMG, "./Res/Jump/jump.jpg", ROLE_IMGW, ROLE_IMGH);
}

bool Timer(clock_t& lastTime, int interval) {
    clock_t currentTime = clock();
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime;
        return true;
    }
    return false;
}

void KeyDown() {
    if (GetAsyncKeyState(VK_UP)) {
        fireMan.state = Jump;
    }
    if (GetAsyncKeyState(VK_DOWN)) {
        fireMan.state = Roll;
    }
    if (GetAsyncKeyState('R') && gameOver) {
        // 重新开始游戏
        gameOver = false;
        obstacles.clear();
        score = 0;
        frameNum = 0;
        fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
    }
}

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            fireMan.y = BEGIN_Y + (ROLE_IMGH - ROLE_DOWN_IMGH);
			break;
        }
		case Jump:
        {
            frameNum = 0;
            fireMan.y += speed;
            if (fireMan.y < JUMP_MIN_Y) {
                speed = -speed;
            }
            if (fireMan.y >= BEGIN_Y) {
                fireMan.y = BEGIN_Y;
                speed = -speed;
                fireMan.state = Run;
            }
        }
        default:
            break;
    }
}

void DrawRole() {
    switch (fireMan.state) {
        case Run: putimage(fireMan.x, fireMan.y, runIMG + frameNum); break;
        case Roll: putimage(fireMan.x, fireMan.y, rollIMG + frameNum); break;
        case Jump: putimage(fireMan.x, fireMan.y, &jumpIMG); break;
    }
}

void GenerateObstacle() {
    static int timer = 0;
    static int randomThreshold = 0; // 新增:存储随机触发阈值
    timer++;
    if (randomThreshold == 0) {
        // rand() % 150 + 50:生成 50~200 之间的随机数
        randomThreshold = rand() % 150 + 50;
    }
    if (timer > randomThreshold) {
        Obstacle obs;
        obs.type = CACTUS;
        obs.width = 30; 
        obs.height = 60;
        obs.x = WIDTH; 
        obs.y = FLOOR_Y - obs.height;
        obstacles.push_back(obs);
        timer = 0;
        randomThreshold = 0;
    }
}

void DrawObstacles() {
    for (const auto& obs : obstacles) {
        setfillcolor(GREEN);
        fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
    }
}

void MoveObstacles() {
    for (auto it = obstacles.begin(); it != obstacles.end();) {
        it->x -= gameSpeed;
        if (it->x + it->width < 0) {
            it = obstacles.erase(it);
            score += 10;  // 障碍物移出屏幕加分
        }
        else {
            ++it;
        }
    }
}

bool CheckCollision() {
    for (const auto& obs : obstacles) {
        if (fireMan.x + fireMan.w > obs.x &&
            fireMan.x < obs.x + obs.width &&
            fireMan.y + fireMan.h > obs.y &&
            fireMan.y < obs.y + obs.height) {
            return true;
        }
    }
    return false;
}

int main() {
    // 新增:初始化随机数种子(用系统时间做种子,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL));
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);

        // 绘制分数
        settextcolor(BLACK);
        char scoreText[50];
        sprintf_s(scoreText, 50, "Score: %d", score);
        outtextxy(500, 20, scoreText);
		
        GenerateObstacle();// 生成障碍物
        DrawObstacles();   // 绘制障碍物
		DrawRole();// 绘制角色
        KeyDown();// 处理按键
        
		if (!gameOver) { //游戏未结束

            if (Timer(animLastTime, 30)) { // 30毫秒间隔
				MoveRole();// 移动角色
            }
			MoveObstacles(); // 移动障碍物

            // 检测碰撞
            if (CheckCollision())
            {
                gameOver = true;
            }
        }
        else {
		    //游戏结束,作相应的文字提示
		    settextcolor(RED);
		    settextstyle(60, 0, _T("黑体"));
		    outtextxy(WIDTH / 2 - 120, HEIGHT / 2 - 50, _T("游戏结束!"));
		
		    settextstyle(30, 0, _T("黑体"));
		    outtextxy(WIDTH / 2 - 100, HEIGHT / 2 + 20, _T("按R键重新开始"));
		
		    // 修正:先格式化得分字符串,再输出
		    char scoreStr[50];
		    sprintf_s(scoreStr, 50, "最终得分:%d", score);
		    outtextxy(WIDTH / 2 - 80, HEIGHT / 2 + 60, _T(scoreStr));
		}

        FlushBatchDraw();
        Sleep(10);
    }
    EndBatchDraw();
    closegraph();
    return 0;
}

第十一阶段:完善功能(v11.0)

11.1 多种障碍物【添加小鸟】

cpp 复制代码
// 障碍物生成函数:定时在窗口右侧生成新的障碍物
void GenerateObstacle() {
    static int timer = 0;
    static int randomThreshold = 0; // 新增:存储随机触发阈值
    timer++;
    if (randomThreshold == 0) {
        // rand() % 150 + 50:生成 50~200 之间的随机数
        randomThreshold = rand() % 150 + 50;
    }
    if (timer > randomThreshold) {
        Obstacle obs;
        if (rand() % 2 == 0) {
            obs.type = CACTUS;
            obs.width = 30;
            obs.height = 60;
            obs.x = WIDTH;
            obs.y = FLOOR_Y - obs.height;
        }
        else {
            obs.type = BIRD;
            obs.width = 40;  
            obs.height = 30;
            obs.x = WIDTH;
            obs.y = 250;
        }
        obstacles.push_back(obs);
        timer = 0;
        randomThreshold = 0;
    }
}

// 障碍物绘制函数:遍历障碍物容器,绘制所有障碍物
void DrawObstacles() {
    for (const auto& obs : obstacles) {
        if (obs.type == CACTUS) {
            // 绘制仙人掌(绿色矩形)
            setfillcolor(GREEN);
            fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
        }
        else if (obs.type == BIRD) {
            // 绘制小鸟(棕色三角形)
            setfillcolor(BROWN);

            // 定义三角形的三个顶点
            POINT triangle[3];
            triangle[0].x = obs.x;                          // 顶点
            triangle[0].y = obs.y;
            triangle[1].x = obs.x - obs.width / 2;          // 左下角
            triangle[1].y = obs.y + obs.height;
            triangle[2].x = obs.x + obs.width / 2;          // 右下角
            triangle[2].y = obs.y + obs.height;

            fillpolygon(triangle, 3);
        }
    }
}

bool CheckCollision() {
    for (const auto& obs : obstacles) {
        if (obs.type == CACTUS) {
            if (fireMan.x + fireMan.w > obs.x &&
                fireMan.x < obs.x + obs.width &&
                fireMan.y + fireMan.h > obs.y &&
                fireMan.y < obs.y + obs.height) {
                return true;
            }
        }else if(obs.type == BIRD){
            if (fireMan.x + fireMan.w > obs.x - obs.width / 2 &&
                fireMan.x < obs.x + obs.width / 2 &&
                fireMan.y + fireMan.h > obs.y &&
                fireMan.y < obs.y + obs.height) {
                return true;
            }
		}
    }
    return false;
}

效果:小鸟出现在游戏窗口,碰到小鸟,游戏停止

11.2 难度递增

cpp 复制代码
// 随着分数增加,游戏速度逐渐加快
gameSpeed = 5 + score / 100;

效果:游戏有仙人掌和小鸟两种障碍物,难度随分数增加

  • 完整代码:
cpp 复制代码
#include <graphics.h>
#include <stdio.h>
#include <time.h>
#include <vector>
#define WIDTH 640
#define HEIGHT 400
#define FLOOR_Y 360
#define ROLE_IMGW 70
#define ROLE_IMGH 100
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH
#define ROLE_DOWN_IMGW 50
#define ROLE_DOWN_IMGH 50
#define MAX_FRAME 8
#define JUMP_MIN_Y 60
IMAGE runIMG[8];
IMAGE rollIMG[8];
IMAGE jumpIMG;

int gameSpeed = 5;
bool gameOver = false;
clock_t animLastTime = 0;
int speed = -10;
int frameNum = 0;
int score = 0;
enum State { Run, Roll, Jump };
enum ObstacleType { CACTUS, BIRD };
struct FireMan {
    int x, y;
    State state;
    int w, h;
};
struct Obstacle {
    int x, y;
    ObstacleType type;
    int width, height;
};
struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
std::vector<Obstacle> obstacles;

void LoadResouces() {
    char fileURL[50] = "";
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH);
    }
    for (int i = 1; i <= 8; i++) {
        sprintf_s(fileURL, 50, "./Res/Roll/gun8_%d.jpg", i);
        loadimage(rollIMG + i - 1, fileURL, ROLE_DOWN_IMGW, ROLE_DOWN_IMGH);
    }
	loadimage(&jumpIMG, "./Res/Jump/jump.jpg", ROLE_IMGW, ROLE_IMGH);
}

bool Timer(clock_t& lastTime, int interval) {
    clock_t currentTime = clock();
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime;
        return true;
    }
    return false;
}

void KeyDown() {
    if (GetAsyncKeyState(VK_UP)) {
        fireMan.state = Jump;
    }
    if (GetAsyncKeyState(VK_DOWN)) {
        fireMan.state = Roll;
    }
    if (GetAsyncKeyState('R') && gameOver) {
        // 重新开始游戏
        gameOver = false;
        obstacles.clear();
        score = 0;
        frameNum = 0;
        fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
    }
}

void MoveRole() {
    switch (fireMan.state)
    {
        case Run:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            break;
		}
		case Roll:
        {
            frameNum++;
            if (frameNum == MAX_FRAME) {
                frameNum = 0;
            }
            fireMan.y = BEGIN_Y + (ROLE_IMGH - ROLE_DOWN_IMGH);
			break;
        }
		case Jump:
        {
            frameNum = 0;
            fireMan.y += speed;
            if (fireMan.y < JUMP_MIN_Y) {
                speed = -speed;
            }
            if (fireMan.y >= BEGIN_Y) {
                fireMan.y = BEGIN_Y;
                speed = -speed;
                fireMan.state = Run;
            }
        }
        default:
            break;
    }
}

void DrawRole() {
    switch (fireMan.state) {
        case Run: putimage(fireMan.x, fireMan.y, runIMG + frameNum); break;
        case Roll: putimage(fireMan.x, fireMan.y, rollIMG + frameNum); break;
        case Jump: putimage(fireMan.x, fireMan.y, &jumpIMG); break;
    }
}

void GenerateObstacle() {
    static int timer = 0;
    static int randomThreshold = 0; // 新增:存储随机触发阈值
    timer++;
    if (randomThreshold == 0) {
        // rand() % 150 + 50:生成 50~200 之间的随机数
        randomThreshold = rand() % 150 + 50;
    }
    if (timer > randomThreshold) {
        Obstacle obs;
        if (rand() % 2 == 0) {
            obs.type = CACTUS;
            obs.width = 30;
            obs.height = 60;
            obs.x = WIDTH;
            obs.y = FLOOR_Y - obs.height;
        }
        else {
            obs.type = BIRD;
            obs.width = 40;  
            obs.height = 30;
            obs.x = WIDTH;
            obs.y = 250;
        }
        obstacles.push_back(obs);
        timer = 0;
        randomThreshold = 0;
    }
}

void DrawObstacles() {
    for (const auto& obs : obstacles) {
        if (obs.type == CACTUS) {
            // 绘制仙人掌(绿色矩形)
            setfillcolor(GREEN);
            fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
        }
        else if (obs.type == BIRD) {
            // 绘制小鸟(棕色三角形)
            setfillcolor(BROWN);

            // 定义三角形的三个顶点
            POINT triangle[3];
            triangle[0].x = obs.x;                          // 顶点
            triangle[0].y = obs.y;
            triangle[1].x = obs.x - obs.width / 2;          // 左下角
            triangle[1].y = obs.y + obs.height;
            triangle[2].x = obs.x + obs.width / 2;          // 右下角
            triangle[2].y = obs.y + obs.height;

            fillpolygon(triangle, 3);
        }
    }
}

void MoveObstacles() {
    for (auto it = obstacles.begin(); it != obstacles.end();) {
        it->x -= gameSpeed;
        if (it->x + it->width < 0) {
            it = obstacles.erase(it);
            score += 10;  // 障碍物移出屏幕加分
        }
        else {
            ++it;
        }
    }
}

bool CheckCollision() {
    for (const auto& obs : obstacles) {
        if (obs.type == CACTUS) {
            if (fireMan.x + fireMan.w > obs.x &&
                fireMan.x < obs.x + obs.width &&
                fireMan.y + fireMan.h > obs.y &&
                fireMan.y < obs.y + obs.height) {
                return true;
            }
        }else if(obs.type == BIRD){
            if (fireMan.x + fireMan.w > obs.x - obs.width / 2 &&
                fireMan.x < obs.x + obs.width / 2 &&
                fireMan.y + fireMan.h > obs.y &&
                fireMan.y < obs.y + obs.height) {
                return true;
            }
		}
    }
    return false;
}

int main() {
    // 新增:初始化随机数种子(用系统时间做种子,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL));
    initgraph(WIDTH, HEIGHT);
    LoadResouces();
    BeginBatchDraw();
    while (1) {
        setbkcolor(WHITE);
        cleardevice();

        // 绘制简单地面
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);

        // 绘制分数
        settextcolor(BLACK);
        char scoreText[50];
        sprintf_s(scoreText, 50, "Score: %d", score);
        outtextxy(500, 20, scoreText);
		
        GenerateObstacle();// 生成障碍物
        DrawObstacles();   // 绘制障碍物
		DrawRole();// 绘制角色
        KeyDown();// 处理按键
        
		if (!gameOver) { //游戏未结束

            if (Timer(animLastTime, 30)) { // 30毫秒间隔
				MoveRole();// 移动角色
            }
			MoveObstacles(); // 移动障碍物

            // 检测碰撞
            if (CheckCollision())
            {
                gameOver = true;
            }
        }
        else {
		    //游戏结束,作相应的文字提示
		    settextcolor(RED);
		    settextstyle(60, 0, _T("黑体"));
		    outtextxy(WIDTH / 2 - 120, HEIGHT / 2 - 50, _T("游戏结束!"));
		
		    settextstyle(30, 0, _T("黑体"));
		    outtextxy(WIDTH / 2 - 100, HEIGHT / 2 + 20, _T("按R键重新开始"));
		
		    // 修正:先格式化得分字符串,再输出
		    char scoreStr[50];
		    sprintf_s(scoreStr, 50, "最终得分:%d", score);
		    outtextxy(WIDTH / 2 - 80, HEIGHT / 2 + 60, _T(scoreStr));
		}
        gameSpeed = 5 + score / 100;
        FlushBatchDraw();
        Sleep(10);
    }
    EndBatchDraw();
    closegraph();
    return 0;
}

第十二阶段:完整代码【加注释】

cpp 复制代码
// 引入图形库头文件(EasyX图形库,用于绘制图形、处理窗口)
#include <graphics.h>
// 标准输入输出头文件(用于字符串格式化)
#include <stdio.h>
// 时间相关头文件(用于随机数种子、定时器)
#include <time.h>
// 动态数组头文件(存储障碍物)
#include <vector>

// ===================== 全局常量定义 =====================
// 游戏窗口宽度(像素)
#define WIDTH 640
// 游戏窗口高度(像素)
#define HEIGHT 400
// 地面的Y坐标(角色站立的基准线)
#define FLOOR_Y 360

// 角色宽度(奔跑/跳跃状态)
#define ROLE_IMGW 70
// 角色高度(奔跑/跳跃状态)
#define ROLE_IMGH 100
// 角色初始X坐标
#define BEGIN_X 50
// 角色初始Y坐标(地面Y - 角色高度,确保角色站在地面)
#define BEGIN_Y FLOOR_Y-ROLE_IMGH

// 角色宽度(翻滚状态)
#define ROLE_DOWN_IMGW 50
// 角色高度(翻滚状态)
#define ROLE_DOWN_IMGH 50

// 动画最大帧数(奔跑/翻滚动画的帧总数)
#define MAX_FRAME 8
// 跳跃最小Y坐标(角色跳跃的最高位置)
#define JUMP_MIN_Y 60

// ===================== 全局变量定义 =====================
// 声明动画帧图片数组(奔跑动画8帧)
IMAGE runIMG[8];
// 声明动画帧图片数组(翻滚动画8帧)
IMAGE rollIMG[8];
// 声明跳跃状态图片
IMAGE jumpIMG;

// 游戏速度(障碍物向左移动的速度)
int gameSpeed = 5;
// 游戏结束标记(true=结束,false=运行)
bool gameOver = false;
// 动画定时器上次触发时间(控制角色动画帧率)
clock_t animLastTime = 0;
// 跳跃速度(负数向上,正数向下)
int speed = -10;
// 动画帧计数器(切换奔跑/翻滚动画帧)
int frameNum = 0;
// 游戏得分(躲避一个障碍物加10分)
int score = 0;

// ===================== 枚举与结构体定义 =====================
// 角色状态枚举(奔跑、翻滚、跳跃)
enum State { Run, Roll, Jump };
// 障碍物类型枚举(仙人掌、小鸟)
enum ObstacleType { CACTUS, BIRD };

// 角色(消防员)结构体
struct FireMan {
    int x, y;           // 角色左上角坐标
    State state;        // 角色当前状态(奔跑/翻滚/跳跃)
    int w, h;           // 角色宽度、高度
};

// 障碍物结构体
struct Obstacle {
    int x, y;               // 障碍物左上角坐标
    ObstacleType type;      // 障碍物类型(仙人掌/小鸟)
    int width, height;      // 障碍物宽度、高度
};

// 初始化角色对象(初始位置、默认状态、尺寸)
struct FireMan fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
// 动态数组存储所有障碍物(自动管理内存,支持增删)
std::vector<Obstacle> obstacles;

// ===================== 资源加载函数 =====================
// 加载角色动画图片资源(奔跑、翻滚、跳跃)
void LoadResouces() {
    // 存储图片路径的字符数组
    char fileURL[50] = "";

    // 加载奔跑动画8帧图片
    for (int i = 1; i <= 8; i++) {
        // 格式化图片路径(./Res/Run/move8_1.jpg 至 move8_8.jpg)
        sprintf_s(fileURL, 50, "./Res/Run/move8_%d.jpg", i);
        // 加载图片到数组,指定宽高
        loadimage(&runIMG[i - 1], fileURL, ROLE_IMGW, ROLE_IMGH);
    }

    // 加载翻滚动画8帧图片
    for (int i = 1; i <= 8; i++) {
        // 格式化图片路径(./Res/Roll/gun8_1.jpg 至 gun8_8.jpg)
        sprintf_s(fileURL, 50, "./Res/Roll/gun8_%d.jpg", i);
        // 加载图片到数组,指定宽高
        loadimage(rollIMG + i - 1, fileURL, ROLE_DOWN_IMGW, ROLE_DOWN_IMGH);
    }

    // 加载跳跃状态图片
    loadimage(&jumpIMG, "./Res/Jump/jump.jpg", ROLE_IMGW, ROLE_IMGH);
}

// ===================== 工具函数 =====================
// 定时器封装函数(控制固定时间间隔触发)
// 参数:lastTime-上次触发时间(引用传递),interval-触发间隔(毫秒)
// 返回值:true=到达间隔时间,false=未到达
bool Timer(clock_t& lastTime, int interval) {
    // 获取当前系统时钟(毫秒级)
    clock_t currentTime = clock();
    // 判断是否达到触发间隔
    if (currentTime - lastTime >= interval) {
        lastTime = currentTime; // 更新上次触发时间
        return true;
    }
    return false;
}

// ===================== 按键处理函数 =====================
// 检测键盘按键并更新角色状态/游戏状态
void KeyDown() {
    // 检测向上方向键(跳跃)
    if (GetAsyncKeyState(VK_UP)) {
        fireMan.state = Jump; // 切换角色状态为跳跃
    }
    // 检测向下方向键(翻滚)
    if (GetAsyncKeyState(VK_DOWN)) {
        fireMan.state = Roll; // 切换角色状态为翻滚
    }
    // 检测R键且游戏已结束(重新开始游戏)
    if (GetAsyncKeyState('R') && gameOver) {
        gameOver = false; // 重置游戏结束标记
        obstacles.clear(); // 清空所有障碍物
        score = 0; // 重置得分
        frameNum = 0; // 重置动画帧计数器
        // 重置角色初始状态
        fireMan = { BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
    }
}

// ===================== 角色移动/动画函数 =====================
// 更新角色位置和动画帧(根据当前状态)
void MoveRole() {
    switch (fireMan.state)
    {
        // 奔跑状态
    case Run:
    {
        frameNum++; // 动画帧计数器+1
        // 帧计数器到达最大值时重置(循环播放动画)
        if (frameNum == MAX_FRAME) {
            frameNum = 0;
        }
        break;
    }
    // 翻滚状态
    case Roll:
    {
        frameNum++; // 动画帧计数器+1
        // 帧计数器到达最大值时重置(循环播放动画)
        if (frameNum == MAX_FRAME) {
            frameNum = 0;
        }
        // 调整角色Y坐标(翻滚时角色高度降低,位置上移)
        fireMan.y = BEGIN_Y + (ROLE_IMGH - ROLE_DOWN_IMGH);
        break;
    }
    // 跳跃状态
    case Jump:
    {
        frameNum = 0; // 跳跃时暂停动画,固定显示跳跃帧
        fireMan.y += speed; // 更新角色Y坐标(向上/向下移动)

        // 到达跳跃最高点,切换为下落(速度取反)
        if (fireMan.y < JUMP_MIN_Y) {
            speed = -speed;
        }
        // 落回地面,重置状态和速度
        if (fireMan.y >= BEGIN_Y) {
            fireMan.y = BEGIN_Y; // 固定在地面Y坐标
            speed = -speed; // 重置跳跃速度(下次跳跃向上)
            fireMan.state = Run; // 切换回奔跑状态
        }
        break;
    }
    // 默认状态(无操作)
    default:
        break;
    }
}

// ===================== 绘制函数 =====================
// 绘制角色(根据当前状态显示对应动画/图片)
void DrawRole() {
    switch (fireMan.state) {
    case Run:
        // 绘制奔跑动画当前帧
        putimage(fireMan.x, fireMan.y, runIMG + frameNum);
        break;
    case Roll:
        // 绘制翻滚动画当前帧
        putimage(fireMan.x, fireMan.y, rollIMG + frameNum);
        break;
    case Jump:
        // 绘制跳跃状态图片
        putimage(fireMan.x, fireMan.y, &jumpIMG);
        break;
    }
}

// 随机生成障碍物(仙人掌/小鸟,随机间隔)
void GenerateObstacle() {
    // 静态计时器(控制生成间隔,函数调用时保留值)
    static int timer = 0;
    // 静态随机阈值(每次生成后重新随机,控制生成间隔)
    static int randomThreshold = 0;

    timer++; // 计时器+1(主循环每10ms执行一次,累计次数)

    // 初始化随机阈值(首次运行或上次生成后重置)
    if (randomThreshold == 0) {
        // 生成50~200之间的随机数(控制障碍物生成间隔)
        randomThreshold = rand() % 150 + 50;
    }

    // 达到随机阈值,生成新障碍物
    if (timer > randomThreshold) {
        Obstacle obs; // 定义临时障碍物对象
        // 50%概率生成仙人掌,50%概率生成小鸟
        if (rand() % 2 == 0) {
            obs.type = CACTUS; // 障碍物类型:仙人掌
            obs.width = 30;    // 仙人掌宽度
            obs.height = 60;   // 仙人掌高度
            obs.x = WIDTH;     // 生成位置:屏幕最右侧
            obs.y = FLOOR_Y - obs.height; // Y坐标:贴地面
        }
        else {
            obs.type = BIRD;   // 障碍物类型:小鸟
            obs.width = 40;    // 小鸟宽度
            obs.height = 30;   // 小鸟高度
            obs.x = WIDTH;     // 生成位置:屏幕最右侧
            obs.y = 250;       // Y坐标:空中(高于角色)
        }
        obstacles.push_back(obs); // 将障碍物添加到动态数组
        timer = 0; // 重置计时器
        randomThreshold = 0; // 重置随机阈值(下次重新生成)
    }
}

// 绘制所有障碍物(根据类型绘制不同图形)
void DrawObstacles() {
    // 遍历所有障碍物(const auto& 避免拷贝,提高效率)
    for (const auto& obs : obstacles) {
        if (obs.type == CACTUS) {
            // 绘制仙人掌:绿色填充矩形
            setfillcolor(GREEN); // 设置填充色为绿色
            // 绘制矩形:参数(左上角X, 左上角Y, 右下角X, 右下角Y)
            fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
        }
        else if (obs.type == BIRD) {
            // 绘制小鸟:棕色填充三角形
            setfillcolor(BROWN); // 设置填充色为棕色

            // 定义三角形的三个顶点坐标
            POINT triangle[3];
            triangle[0].x = obs.x;                          // 三角形顶点(上)
            triangle[0].y = obs.y;
            triangle[1].x = obs.x - obs.width / 2;          // 三角形左下角
            triangle[1].y = obs.y + obs.height;
            triangle[2].x = obs.x + obs.width / 2;          // 三角形右下角
            triangle[2].y = obs.y + obs.height;

            // 绘制填充三角形(参数:顶点数组,顶点数)
            fillpolygon(triangle, 3);
        }
    }
}

// ===================== 障碍物移动与清理 =====================
// 移动障碍物(向左),并清理移出屏幕的障碍物
void MoveObstacles() {
    // 迭代器遍历障碍物数组(支持安全删除元素)
    for (auto it = obstacles.begin(); it != obstacles.end();) {
        it->x -= gameSpeed; // 障碍物向左移动(X坐标 -= 游戏速度)

        // 判断障碍物是否完全移出屏幕左侧
        if (it->x + it->width < 0) {
            it = obstacles.erase(it); // 删除该障碍物(返回下一个迭代器)
            score += 10;  // 躲避成功,加10分
        }
        else {
            ++it; // 未移出,迭代器指向下一个障碍物
        }
    }
}

// ===================== 碰撞检测函数 =====================
// 检测角色与障碍物的碰撞(AABB轴对齐包围盒算法)
// 返回值:true=碰撞,false=无碰撞
bool CheckCollision() {
    // 遍历所有障碍物
    for (const auto& obs : obstacles) {
        // 仙人掌碰撞检测(矩形碰撞)
        if (obs.type == CACTUS) {
            if (fireMan.x + fireMan.w > obs.x &&          // 角色右边界 > 仙人掌左边界
                fireMan.x < obs.x + obs.width &&          // 角色左边界 < 仙人掌右边界
                fireMan.y + fireMan.h > obs.y &&          // 角色下边界 > 仙人掌上边界
                fireMan.y < obs.y + obs.height) {         // 角色上边界 < 仙人掌下边界
                return true; // 满足所有条件,碰撞发生
            }
        }
        // 小鸟碰撞检测(三角形碰撞,简化为矩形)
        else if (obs.type == BIRD) {
            if (fireMan.x + fireMan.w > obs.x - obs.width / 2 &&  // 角色右边界 > 小鸟左边界
                fireMan.x < obs.x + obs.width / 2 &&              // 角色左边界 < 小鸟右边界
                fireMan.y + fireMan.h > obs.y &&                  // 角色下边界 > 小鸟上边界
                fireMan.y < obs.y + obs.height) {                 // 角色上边界 < 小鸟下边界
                return true; // 满足所有条件,碰撞发生
            }
        }
    }
    return false; // 遍历完所有障碍物,无碰撞
}

// ===================== 主函数(程序入口) =====================
int main() {
    // 初始化随机数种子(用系统时间,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL));
    // 创建游戏窗口(宽640,高400)
    initgraph(WIDTH, HEIGHT);
    // 加载角色图片资源
    LoadResouces();
    // 开启批量绘制(减少闪烁,提高绘制效率)
    BeginBatchDraw();

    // 游戏主循环(无限循环,直到关闭窗口)
    while (1) {
        // 设置背景色为白色
        setbkcolor(WHITE);
        // 清空绘图设备(刷新屏幕)
        cleardevice();

        // 绘制地面:浅蓝色矩形(从X=0到窗口宽,Y=地面Y到窗口高)
        setfillcolor(LIGHTBLUE);
        solidrectangle(0, FLOOR_Y, WIDTH, HEIGHT);

        // 绘制得分(右上角显示)
        settextcolor(BLACK); // 设置文字颜色为黑色
        char scoreText[50];  // 存储得分字符串
        sprintf_s(scoreText, 50, "Score: %d", score); // 格式化得分文本
        outtextxy(500, 20, scoreText); // 在(500,20)位置绘制得分

        // 生成障碍物
        GenerateObstacle();
        // 绘制所有障碍物
        DrawObstacles();
        // 绘制角色
        DrawRole();
        // 处理按键输入
        KeyDown();

        // 游戏未结束时,执行移动和碰撞检测
        if (!gameOver) {
            // 每30毫秒更新一次角色动画/位置(控制动画帧率)
            if (Timer(animLastTime, 30)) {
                MoveRole();// 更新角色位置和动画
            }
            MoveObstacles(); // 移动障碍物并清理

            // 检测碰撞,若碰撞则标记游戏结束
            if (CheckCollision())
            {
                gameOver = true;
            }
        }
        else {
            //游戏结束,作相应的文字提示
            settextcolor(RED);
            settextstyle(60, 0, _T("黑体"));
            outtextxy(WIDTH / 2 - 120, HEIGHT / 2 - 50, _T("游戏结束!"));

            settextstyle(30, 0, _T("黑体"));
            outtextxy(WIDTH / 2 - 100, HEIGHT / 2 + 20, _T("按R键重新开始"));

            // 修正:先格式化得分字符串,再输出
            char scoreStr[50];
            sprintf_s(scoreStr, 50, "最终得分:%d", score);
            outtextxy(WIDTH / 2 - 80, HEIGHT / 2 + 60, _T(scoreStr));
        }

        // 动态提升游戏速度(每得100分,速度+1,增加难度)
        gameSpeed = 5 + score / 100;

        // 刷新批量绘制(将所有绘制操作显示到屏幕)
        FlushBatchDraw();
        // 休眠10毫秒(控制主循环帧率,减少CPU占用)
        Sleep(10);
    }

    // 结束批量绘制(实际不会执行,因主循环是无限循环)
    EndBatchDraw();
    // 关闭游戏窗口
    closegraph();
    return 0;
}

开发流程总结

版本 主要功能 效果展示
v1.0 基础窗口和地面 白色背景+蓝色地面
v2.0 简单角色 红色矩形代表火柴人
v3.0 加载资源 真实火柴人图片
v4.0 奔跑动画 火柴人奔跑动画
v5.0 跳跃功能 按上键跳跃
v6.0 下蹲功能 按空格键下蹲
v7.0 障碍物系统 绿色障碍物移动
v8.0 碰撞检测 碰到障碍物游戏停止
v9.0 计分系统 显示分数
v10.0 游戏重开 重新开始
v11.0 完善功能 多种障碍物+难度递增

游戏效果演示

火柴人跑酷

相关推荐
ID_180079054731 小时前
基于 Python 的 Cdiscount 商品详情 API 调用与 JSON 核心字段解析(含多规格 SKU 提取)
开发语言·python·json
悟能不能悟1 小时前
Caused by: java.sql.SQLException: ORA-28000: the account is locked怎么处理
java·开发语言
亦是远方2 小时前
南京邮电大学使用计算机求解问题实验一(C语言简单编程练习)
c语言·开发语言·实验报告·南京邮电大学
我是哈哈hh2 小时前
【Python数据分析】Numpy总结
开发语言·python·数据挖掘·数据分析·numpy·python数据分析
Michelle80232 小时前
24大数据 14-2 函数练习
开发语言·python
qq_381454992 小时前
Python学习技巧
开发语言·python·学习
勇气要爆发2 小时前
物种起源—JavaScript原型链详解
开发语言·javascript·原型模式
Gomiko3 小时前
C/C++基础(四):运算符
c语言·c++
freedom_1024_3 小时前
【c++】使用友元函数重载运算符
开发语言·c++