火柴人跑酷游戏开发流程文档
第一阶段:基础框架搭建(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 | 完善功能 | 多种障碍物+难度递增 |
游戏效果演示
火柴人跑酷


