跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 火柴人跑酷游戏开发流程详解

详细讲解了使用 C++ 和 EasyX 图形库开发火柴人跑酷游戏的完整流程。从基础窗口搭建开始,逐步实现角色动画、资源加载、输入控制、障碍物生成与移动、碰撞检测及计分系统。教程分为十二个阶段,涵盖双缓冲防闪烁、定时器控制帧率、随机障碍物生成、难度递增等核心功能,最终提供带注释的完整可运行代码,适合初学者学习游戏开发基础逻辑。

刀狂发布于 2026/3/28更新于 2026/7/2142 浏览

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

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

1.1 基础头文件和常量定义
#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 角色状态和属性定义
enum State{ Run, Roll, Jump };
struct FireMan{
    int x, y;
    State state;
    int w, h;
};
#define ROLE_IMGW 70
# ROLE_IMGH 100


  fireMan ={ BEGIN_X, BEGIN_Y, Run, ROLE_IMGW, ROLE_IMGH };
define
#define BEGIN_X 50
#define BEGIN_Y FLOOR_Y-ROLE_IMGH
struct
FireMan
2.2 简单角色绘制
void DrawRole(){
    // 用简单矩形代替角色
    setfillcolor(RED);
    fillrectangle(fireMan.x, fireMan.y, fireMan.x + fireMan.w, fireMan.y + fireMan.h);
}

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


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

3.1 图像资源加载
IMAGE runIMG[8];
void LoadResources(){
    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 更新角色绘制
void DrawRole(){
    putimage(fireMan.x, fireMan.y, runIMG +0);// 先使用第一帧
}

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

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

  • bug 修复:以上代码,为何画面有不时的闪烁和抖动?

答:画面闪烁和抖动的主要原因是没有使用双缓冲技术。虽然你使用了 FlushBatchDraw(),但缺少了 BeginBatchDraw() 来开启批量绘制。主要修改点:添加双缓冲初始化 BeginBatchDraw() — EndBatchDraw()

// 主函数:游戏入口,负责初始化和主循环
int main(){
    initgraph(WIDTH, HEIGHT);// 初始化图形窗口,设置宽高
    LoadResources();//加载角色动画图片
    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 动画帧控制
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 定时器控制
  • 火柴人奔跑的速度太快了,我们新增一个定时器,来控制火柴人奔跑的速度。
// 定时器函数封装
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 毫秒之后才会切换下一帧动画。

  • 完整代码:
#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 LoadResources(){
    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);
    LoadResources();
    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 跳跃功能
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;
    }
}

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

  • 完整代码:
#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 LoadResources(){
    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);
    LoadResources();
    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 翻滚动画资源
IMAGE rollIMG[8];
#define ROLE_DOWN_IMGW 50
#define ROLE_DOWN_IMGH 50
// 在 LoadResources 中添加
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 下蹲逻辑
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;
    }
}

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

  • 【完善代码】补充:加载正确的跳跃图片
void LoadResources(){
    //...
    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;
    }
}
  • 完整代码:
#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 LoadResources(){
    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);
    LoadResources();
    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 障碍物定义
#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 障碍物绘制
void DrawObstacles(){
    for(const auto& obs : obstacles){
        setfillcolor(GREEN);
        fillrectangle(obs.x, obs.y, obs.x + obs.width, obs.y + obs.height);
    }
}
7.3 障碍物生成和移动
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 结构体 在分析函数前,需先明确障碍物的'类型标识'和'数据载体',这是所有障碍物操作的基础。

// 枚举:定义障碍物的类型(后续可扩展更多类型,如石头、陷阱等)
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 函数:生成障碍物(创建新障碍物并添加到数组) 功能:按固定频率在屏幕右侧生成仙人掌障碍物,确保游戏中持续出现新挑战。

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 坐标设为屏幕宽度,确保生成时在屏幕外右侧,不会突然出现在中间
        obs.y = FLOOR_Y - obs.height;// y 坐标 = 地面 y 坐标 - 障碍物高度(FLOOR_Y=360,360-60=300,刚好让仙人掌底部贴地面)
        obstacles.push_back(obs);// 将生成的障碍物添加到动态数组中(后续绘制、移动都操作这个数组)
        timer =0;// 计时器重置,重新开始计数,确保下次生成仍间隔 100 次主循环
    }
}

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

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

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 函数开头)
int main(){
    // 新增:初始化随机数种子(用系统时间做种子,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL));
    initgraph(WIDTH, HEIGHT);
    LoadResources();
    BeginBatchDraw();
    while(1){
        // 原有代码不变...
    }
    // 原有代码不变...
}
  • 第二步:修改 GenerateObstacle 函数
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;// 重置阈值,下次会重新生成随机值
    }
}
  • 完整代码
#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 LoadResources(){
    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);
    LoadResources();
    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 碰撞检测函数
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 函数代码:

int main(){
    // 新增:初始化随机数种子(用系统时间做种子,确保每次运行随机结果不同)
    srand((unsigned int)time(NULL));
    initgraph(WIDTH, HEIGHT);
    LoadResources();
    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 分数显示
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 重新开始,并显示对应分数
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 重新开始,显示对应分数

  • 完整代码:
#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 LoadResources(){
    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);
    LoadResources();
    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;
}

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

11.1 多种障碍物【添加小鸟】
// 障碍物生成函数:定时在窗口右侧生成新的障碍物
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);
        }elseif(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;
            }
        }elseif(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 难度递增
// 随着分数增加,游戏速度逐渐加快 
gameSpeed =5+ score /100;

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

  • 完整代码:
#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 LoadResources(){
    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);
        }elseif(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;
            }
        }elseif(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);
    LoadResources();
    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;
}

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

// 引入图形库头文件(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 LoadResources(){
    // 存储图片路径的字符数组
    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);
        }elseif(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;// 满足所有条件,碰撞发生
            }
        }
        // 小鸟碰撞检测(三角形碰撞,简化为矩形)
        elseif(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);
    // 加载角色图片资源
    LoadResources();
    // 开启批量绘制(减少闪烁,提高绘制效率)
    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完善功能多种障碍物 + 难度递增

游戏效果演示

火柴人跑酷

目录

  1. 火柴人跑酷游戏开发流程文档
  2. 第一阶段:基础框架搭建(v1.0)
  3. 1.1 基础头文件和常量定义
  4. 第二阶段:角色系统开发(v2.0)
  5. 2.1 角色状态和属性定义
  6. 2.2 简单角色绘制
  7. 第三阶段:资源管理系统(v3.0)
  8. 3.1 图像资源加载
  9. 3.2 更新角色绘制
  10. 第四阶段:动画系统(v4.0)
  11. 4.1 动画帧控制
  12. 4.2 定时器控制
  13. 第五阶段:输入控制系统(v5.0)
  14. 5.1 跳跃功能
  15. 第六阶段:下蹲功能(v6.0)
  16. 6.1 翻滚动画资源
  17. 6.2 下蹲逻辑
  18. 第七阶段:障碍物系统(v7.0)
  19. 7.1 障碍物定义
  20. 7.2 障碍物绘制
  21. 7.3 障碍物生成和移动
  22. 7.4 障碍物出现频率改为随机
  23. 第八阶段:碰撞检测(v8.0)
  24. 8.1 碰撞检测函数
  25. 第九阶段:计分系统(v9.0)
  26. 9.1 分数显示
  27. 第十阶段:按 R 重新开始(v10.0)
  28. 10.1 按 R 重新开始,并显示对应分数
  29. 第十一阶段:完善功能(v11.0)
  30. 11.1 多种障碍物【添加小鸟】
  31. 11.2 难度递增
  32. 第十二阶段:完整代码【加注释】
  33. 开发流程总结
  34. 游戏效果演示
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 前端核心概念、技术栈与实战路径
  • OpenClaw Skills 原理与实战:构建机器人专属技能模块
  • C++ 多容器非空检查的逻辑陷阱与最佳实践
  • 相干伊辛机在医疗与医疗 AI 领域的应用前景
  • OpenClaw 与 Telegram 机器人集成指南
  • OpenClaw 集成 Telegram 机器人开发指南
  • C/C++ 中 const 关键字的用法与差异详解
  • Java IO API 获取文件元素的方法
  • Jetpack Compose 完全开发手册:从入门到精通
  • VRM4U插件完整指南:在Unreal Engine 5中高效处理VRM模型
  • Java 数据结构:链表原理与 LinkedList 核心应用
  • 从前端到 DevOps:各类开发者 AI 工作流工具
  • OpenClaw 飞书机器人权限配置与安全指南
  • Linux 库的制作与原理
  • Web 创建与设计指南
  • Python 真的有必要学吗?基于工作场景的实用性分析
  • 多智能体协作驱动的多模态医疗大模型系统:RAG-KAG 双路径知识增强与架构设计
  • 无人机低空智能巡飞巡检平台:全域感知与智能决策
  • 医疗 AI 驱动下医院数据仓库的智能化升级:异构采集与精准评估
  • Claude Code的完美平替:OpenCode + GitHub Copilot

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online