控制台赛车躲避游戏
控制台赛车躲避游戏 ------ 设计与实现
适用对象 :已掌握 C++ 基础、循环、分支、数组、自定义函数的学生
最终效果:可移动小车、自动下落敌人、计分、碰撞、菜单界面
一、课程目标
- 学会使用自定义函数进行模块化程序设计
- 掌握控制台游戏的核心原理:定位 + 擦除 + 重绘 + 延时
- 理解按键控制、动画、随机数、碰撞检测的简单实现
- 完成一个可独立运行的图形化小游戏
二、游戏功能说明
- 玩家通过 A/D 键 控制小车左右移动
- 上方不断有敌方车辆向下行驶
- 躲避车辆,不发生碰撞
- 每成功躲过一辆车 得分+1
- 发生碰撞 → 游戏结束,返回主菜单
- 包含:主菜单、游戏说明、游戏界面、得分显示
三、整体程序结构(模块化设计)
程序由 9 类函数 + 全局数据构成,一个函数只完成一件事:
gotoxy光标定位(核心)setcursor隐藏光标drawBorder绘制赛道边框drawCar绘制玩家小车drawEnemy绘制敌方车辆eraseCar擦除玩家(动画用)eraseEnemy擦除敌人(动画用)collision碰撞检测play游戏主循环main菜单与程序入口
四、知识点回顾(学生已掌握)
- 变量、数组、循环、判断
- 自定义函数定义与调用
while/for循环- 简单输入输出
cin / cout
五、游戏新增必备知识点
1. 光标定位 gotoxy(x, y)
作用:把光标跳到指定坐标输出文字,是游戏画面的基础。
需要:
HANDLE控制台句柄COORD坐标结构体SetConsoleCursorPosition
2. 隐藏光标 setcursor
作用:让游戏画面不闪烁、更整洁。
3. 按键检测
kbhit()判断是否有按键按下getch()获取按键(不回显)
4. 延时 Sleep(ms)
作用:控制游戏速度,实现流畅动画。
5. 随机数 rand() / srand()
作用:让敌人每次在不同车道出现。
6. 动画原理
擦除旧位置 → 更新坐标 → 绘制新位置 → 延时
循环执行 → 看起来在动。
六、分步实现过程
第 1 步:定义头文件与全局数据
用于存放:屏幕大小、车辆坐标、分数、图案。
cpp
// 1. 输入输出流头文件:用于控制台显示文字和图形
#include<iostream>
// 2. 控制台输入头文件:用于检测按键 kbhit() 和获取按键 getch()
#include<conio.h>
// 3. Windows系统API:用于控制控制台窗口(光标定位、隐藏光标等)
#include <windows.h>
// 4. 时间头文件:用于设置随机数种子,让敌人每次出现位置不同
#include <time.h>
// 宏定义:定义游戏屏幕的总宽度(固定数值,方便修改)
#define SCREEN_WIDTH 90
// 宏定义:定义游戏屏幕的总高度
#define SCREEN_HEIGHT 26
// 宏定义:定义游戏车道的宽度(小车行驶的区域)
#define WIN_WIDTH 70
// 使用标准命名空间:简化代码,不用写 std::cout
using namespace std;
// 控制台句柄:相当于控制台的"遥控器",用来控制光标和输出
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
// 光标坐标结构体:用来存储光标的 X、Y 坐标
COORD CursorPosition;
// 敌人车辆的 Y 坐标数组(存储3个敌人的垂直位置)
int enemyY[3];
// 敌人车辆的 X 坐标数组(存储3个敌人的水平位置)
int enemyX[3];
// 敌人状态标记:1=显示敌人,0=隐藏敌人
int enemyFlag[3];
// 玩家小车的图案:用 4行×4列 的字符数组拼成小车形状
char car[4][4] = {
' ','#','#',' ',
'#','#','#','#',
' ','#','#',' ',
'#','#','#','#'
};
// 玩家小车的水平位置(初始在车道正中间)
int carPos = WIN_WIDTH/2;
// 游戏得分变量,初始为0,躲避成功后加分
int score = 0;
第 2 步:实现工具函数
(1)光标定位
cpp
// 函数功能:将光标移动到控制台的 (x, y) 坐标位置
// 参数 x:水平坐标(列)
// 参数 y:垂直坐标(行)
void gotoxy(int x, int y){
// 设置光标要移动到的 X 坐标(水平位置)
CursorPosition.X = x;
// 设置光标要移动到的 Y 坐标(垂直位置)
CursorPosition.Y = y;
// 调用Windows系统API,执行光标移动
SetConsoleCursorPosition(console, CursorPosition);
}
(2)隐藏光标
cpp
// 函数功能:设置控制台光标的显示状态和大小
// 参数 visible:true=显示光标,false=隐藏光标
// 参数 size:光标的大小(取值 1-100),传入 0 则使用默认值
void setcursor(bool visible, DWORD size) {
// 如果传入的尺寸为0,设置为默认大小 20(固定写法,不用改)
if(size == 0)
size = 20;
// 定义一个 光标信息结构体 变量,用来存放光标的属性
CONSOLE_CURSOR_INFO lpCursor;
// 设置光标是否可见(true显示 / false隐藏)
lpCursor.bVisible = visible;
// 设置光标的大小(1~100,数字越大光标越粗)
lpCursor.dwSize = size;
// 把设置好的光标信息应用到控制台(生效)
SetConsoleCursorInfo(console,&lpCursor);
}
第 3 步:绘制函数
(1)绘制赛道边框
cpp
// 函数功能:绘制游戏的边界线(车道左右边框 + 最右侧边框)
void drawBorder(){
// 外层循环:遍历屏幕的每一行(垂直方向 i 代表行号)
for(int i=0; i<SCREEN_HEIGHT; i++){
// 内层循环:绘制左右两侧的双线边框(j 控制边框粗细)
for(int j=0; j<17; j++){
// 在第 i 行、左侧 0+j 列 绘制左边界符号
gotoxy(0+j,i);
cout<<"±";
// 在第 i 行、右侧 WIN_WIDTH-j 列 绘制右边界符号
gotoxy(WIN_WIDTH-j,i);
cout<<"±";
}
}
// 额外绘制最右侧的单条边框(整个屏幕的右边缘)
for(int i=0; i<SCREEN_HEIGHT; i++){
gotoxy(SCREEN_WIDTH,i);
cout<<"±";
}
}
(2)绘制玩家小车
cpp
// 函数功能:在控制台中绘制玩家控制的小车
void drawCar(){
// 外层循环:遍历小车的 4 行(i 代表行号)
for(int i=0; i<4; i++){
// 内层循环:遍历小车的 4 列(j 代表列号)
for(int j=0; j<4; j++){
// 定位到小车对应字符的控制台坐标
// j+carPos:水平位置 = 小车初始X坐标 + 字符在图案中的列
// i+22:垂直位置 = 固定在屏幕下方 22 行的位置
gotoxy(j+carPos, i+22);
// 输出二维数组中存储的对应字符(# 或 空格),拼成小车图案
cout << car[i][j];
}
}
}
(3)绘制敌人小车
cpp
// 函数功能:绘制敌方车辆
// 参数 ind:要绘制的敌人编号(0代表第一个敌人,1代表第二个敌人)
void drawEnemy(int ind){
// 判断:只有当前敌人处于激活状态(enemyFlag[ind]为true)时,才进行绘制
if(enemyFlag[ind]==true){
// 定位到敌人的 (X,Y) 坐标,绘制敌人第一行
gotoxy(enemyX[ind], enemyY[ind]);
cout<<"****";
// 定位到敌人的下一行,绘制敌人第二行
gotoxy(enemyX[ind], enemyY[ind]+1);
cout<<" ** ";
// 定位到敌人的下一行,绘制敌人第三行
gotoxy(enemyX[ind], enemyY[ind]+2);
cout<<"****";
// 定位到敌人的下一行,绘制敌人第四行
gotoxy(enemyX[ind], enemyY[ind]+3);
cout<<" ** ";
}
}
第 4 步:擦除函数(实现动画)
cpp
// 函数功能:擦除玩家小车(用空格覆盖原来的位置)
// 作用:实现动画效果 ------ 先擦除,再画新位置
void eraseCar(){
// 遍历小车的 4 行(i 是行)
for(int i=0; i<4; i++){
// 遍历小车的 4 列(j 是列)
for(int j=0; j<4; j++){
// 定位到小车原来的位置
gotoxy(j+carPos, i+22);
// 输出空格 → 把原来的 # 覆盖掉(擦除)
cout<<" ";
}
}
}
// 函数功能:擦除指定编号的敌人车辆
// 参数 ind:敌人的编号(0=第一个敌人,1=第二个敌人)
void eraseEnemy(int ind){
// 只有敌人处于显示状态时,才需要擦除
if(enemyFlag[ind]==true){
// 定位到敌人第一行 → 输出4个空格擦除
gotoxy(enemyX[ind], enemyY[ind]);
cout<<" ";
// 定位到敌人第二行 → 擦除
gotoxy(enemyX[ind], enemyY[ind]+1);
cout<<" ";
// 定位到敌人第三行 → 擦除
gotoxy(enemyX[ind], enemyY[ind]+2);
cout<<" ";
// 定位到敌人第四行 → 擦除
gotoxy(enemyX[ind], enemyY[ind]+3);
cout<<" ";
}
}
擦除函数 = 动画的关键
控制台不能直接移动图形,必须:
→ 先擦除旧位置
→ 再更新坐标
→ 再画新位置
擦除方法
用空格覆盖原来的 # 或 *
位置完全对应
擦除的坐标 必须和绘制时一模一样,否则画面会乱。
第 5 步:游戏逻辑函数
(1)随机生成敌人位置
cpp
// 函数功能:生成敌人的随机 X 坐标(让敌人在车道里随机位置出现)
// 参数 ind:敌人编号(0表示第一个敌人,1表示第二个敌人)
void genEnemy(int ind){
// 17:保证敌人出现在**车道内部**(不会卡在左边界)
// rand()%33:生成 0~32 的随机数,控制敌人在车道内左右随机分布
// 最终 enemyX[ind] 范围:17 ~ 49,正好在游戏车道内
enemyX[ind] = 17 + rand()%33;
}
rand()%33→ 生成0~32的随机数- 加上
17→ 让敌人只在车道中间区域出现,不会跑到边框外 - 每次调用,敌人都会换一个横向位置,实现随机车道效果
(2)重置敌人位置
cpp
// 函数功能:重置敌人车辆(当敌人开到屏幕底部后,重新回到顶部)
// 参数 ind:要重置的敌人编号(0=第一个敌人,1=第二个敌人)
void resetEnemy(int ind){
// 1. 先擦除当前敌人在屏幕底部的旧图像
eraseEnemy(ind);
// 2. 把敌人的Y坐标设置为1(回到屏幕最上方)
enemyY[ind] = 1;
// 3. 重新生成一个随机的X坐标(让敌人换个车道出现)
genEnemy(ind);
}
这个函数就是让敌人无限循环出现的关键:
- 敌人跑到屏幕下面
- 调用
resetEnemy - 擦除旧位置 → 放回顶部 → 换个随机车道
- 游戏就可以一直玩下去
(3)碰撞检测
cpp
// 函数功能:碰撞检测
// 作用:判断玩家小车和敌人小车是否撞上了
// 返回值:1 = 碰撞,0 = 未碰撞
int collision(){
// 第一层判断:敌人的Y坐标 + 自身高度(4行) 到达 玩家小车的Y坐标(23行)
// 意思是:敌人已经下落到玩家所在的高度了
if(enemyY[0]+4 >= 23){
// 第二层判断:敌人和玩家的 X 坐标区间发生重叠
// enemyX[0]+4 是敌人的右侧位置,carPos 是玩家的左侧位置
// 两者差值在 0~9 之间,说明两车宽度重叠,判定为相撞
if(enemyX[0]+4 - carPos >=0 && enemyX[0]+4 - carPos <9)
return 1; // 两个条件都满足,发生碰撞,返回 1
}
return 0; // 不满足条件,没有碰撞,返回 0
}
- 先看高度:敌人是不是掉到玩家的高度了
- 再看左右:敌人和玩家的左右位置是不是重叠了
- 两个条件都满足 → 撞车 → 返回
1 - 否则 → 没撞 → 返回
0
(4)更新分数
cpp
// 函数功能:更新并显示游戏得分
void updateScore(){
// 定位光标到屏幕右侧固定位置
// WIN_WIDTH + 7:车道右侧区域(不遮挡游戏画面)
// 5:垂直第5行
gotoxy(WIN_WIDTH + 7, 5);
// 输出当前得分(中文显示)
cout<<"得分: "<<score<<endl;
}
第 6 步:界面函数
(1)游戏结束
cpp
void gameover(){
system("cls");
cout<<endl;
cout<<"\t\t--------------------------"<<endl;
cout<<"\t\t------- 游戏结束 -------"<<endl;
cout<<"\t\t--------------------------"<<endl<<endl;
cout<<"\t\t按任意键返回主菜单.";
getch();
}
(2)游戏说明
cpp
void instructions(){
system("cls");
cout<<"游戏说明";
cout<<"\n----------------";
cout<<"\n 通过左右移动躲避车辆。 ";
cout<<"\n\n 按 A 键向左移动";
cout<<"\n 按 D 键向右移动";
cout<<"\n 按 ESC 键退出游戏";
cout<<"\n\n按任意键返回主菜单";
getch();
}
第 7 步:游戏主循环 play()
游戏核心运行逻辑:
- 按键控制
- 绘制与擦除
- 敌人移动
- 碰撞与计分
cpp
// 函数功能:游戏主逻辑函数(游戏核心运行代码)
void play(){
// 1. 初始化玩家小车位置:居中显示
carPos = -1 + WIN_WIDTH/2;
// 2. 初始化游戏得分:0分
score = 0;
// 3. 第一个敌人默认激活(显示)
enemyFlag[0] = 1;
// 4. 第二个敌人初始未激活(隐藏)
enemyFlag[1] = 0;
// 5. 两个敌人都从屏幕最顶部开始
enemyY[0] = enemyY[1] = 1;
// 清空控制台屏幕
system("cls");
// 绘制游戏赛道边框
drawBorder();
// 显示初始得分(0分)
updateScore();
// 随机生成第一个敌人的X坐标
genEnemy(0);
// 随机生成第二个敌人的X坐标
genEnemy(1);
// 在屏幕右侧显示游戏标题
gotoxy(WIN_WIDTH + 7, 2);cout<<"赛车游戏";
// 显示操作说明:A键左移
gotoxy(WIN_WIDTH + 2, 14);cout<<" A 键 - 左移";
// 显示操作说明:D键右移
gotoxy(WIN_WIDTH + 2, 15);cout<<" D 键 - 右移";
// 提示玩家按任意键开始
gotoxy(18, 5);cout<<"按任意键开始游戏";
// 等待玩家按一个键
getch();
// 清空提示文字(用空格覆盖)
gotoxy(18, 5);cout<<" ";
// 游戏主死循环(每一帧执行一次)
while(1){
// 检测是否有按键按下
if(kbhit()){
// 获取按下的字符(不显示在屏幕上)
char ch = getch();
// 如果按 A 或 a 键,向左移动
if(ch=='a' || ch=='A'){
// 限制左边界,不能超出赛道
if(carPos >18) carPos -=4;
}
// 如果按 D 或 d 键,向右移动
if(ch=='d' || ch=='D'){
// 限制右边界,不能超出赛道
if(carPos <50) carPos +=4;
}
// 如果按 ESC 键,退出游戏
if(ch==27) break;
}
// 绘制玩家小车
drawCar();
// 绘制第一个敌人
drawEnemy(0);
// 绘制第二个敌人
drawEnemy(1);
// 碰撞检测:如果返回1,表示撞车
if(collision()==1){
// 调用游戏结束函数
gameover();
// 退出play函数,返回菜单
return;
}
// 延时50毫秒,控制游戏速度
Sleep(50);
// 擦除玩家小车(准备下一帧重绘)
eraseCar();
// 擦除第一个敌人
eraseEnemy(0);
// 擦除第二个敌人
eraseEnemy(1);
// 当第一个敌人下落到第10行时,激活第二个敌人
if(enemyY[0]==10 && enemyFlag[1]==0)
enemyFlag[1]=1;
// 如果第一个敌人激活,Y坐标+1(向下移动)
if(enemyFlag[0]==1) enemyY[0] +=1;
// 如果第二个敌人激活,Y坐标+1(向下移动)
if(enemyFlag[1]==1) enemyY[1] +=1;
// 如果第一个敌人超出屏幕底部
if(enemyY[0] > SCREEN_HEIGHT-4){
resetEnemy(0); // 重置敌人到顶部
score++; // 得分+1
updateScore(); // 更新分数显示
}
// 如果第二个敌人超出屏幕底部
if(enemyY[1] > SCREEN_HEIGHT-4){
resetEnemy(1); // 重置敌人到顶部
score++; // 得分+1
updateScore(); // 更新分数显示
}
}
}
第 8 步:主函数 main()
菜单入口:开始游戏、查看说明、退出。
cpp
// 主函数:程序入口(整个游戏从这里开始运行)
int main()
{
// 隐藏控制台光标,让游戏画面更整洁
// 参数 0 对应 false(隐藏),0 代表使用默认光标大小
setcursor(0, 0);
// 设置随机数种子
// 用系统当前时间作为种子,保证每次运行游戏,敌人出现的位置都不同
srand((unsigned)time(NULL));
// 主菜单循环:死循环,保证游戏结束后能回到菜单
do
{
// 清空屏幕,为绘制菜单做准备
system("cls");
// 以下代码:在控制台指定位置绘制菜单界面
gotoxy(10, 5); cout<<" -------------------------- ";
gotoxy(10, 6); cout<<" | 赛车游戏 | ";
gotoxy(10, 7); cout<<" --------------------------";
gotoxy(10, 9); cout<<"1. 开始游戏"; // 选项1
gotoxy(10, 10); cout<<"2. 游戏说明"; // 选项2
gotoxy(10, 11); cout<<"3. 退出"; // 选项3
gotoxy(10, 13); cout<<"请选择选项: "; // 提示输入
// 获取用户输入的选项(getche:输入字符后立即生效,不回车)
char op = getche();
// 根据用户选择执行不同功能
if(op == '1')
play(); // 选择1:调用 play() 函数,开始游戏
if(op == '2')
instructions(); // 选择2:调用 instructions() 函数,查看游戏说明
if(op == '3')
exit(0); // 选择3:调用 exit(0),退出程序
// 无限循环,回到菜单界面
} while(1);
// 程序正常结束(这里理论上永远不会执行到)
return 0;
}
setcursor(0, 0)
游戏一开始就隐藏光标,画面更美观。srand((unsigned)time(NULL))
随机数初始化,必须写在最前面,否则敌人每次都会出现在同一个位置。do-while(1)循环
保证菜单永远存在,玩完一局、游戏结束后,都会回到主菜单。- 菜单逻辑
根据用户按的键(1、2、3),调用对应的函数:- 按
1→ 玩游戏 - 按
2→ 看说明 - 按
3→ 退出
- 按
七、游戏运行原理(重点讲解)
-
动画怎么动?
擦除旧图像 → 坐标变化 → 重画 → 延时
-
敌人怎么向下走?
每帧
enemyY++ -
碰撞怎么判断?
敌人到达小车高度 + X 区间重叠
-
为什么要用全局变量?
多个函数需要共用坐标、分数、开关
八、测试与调试
- A/D 能否移动
- 是否跑出边界
- 敌人是否随机出现
- 碰撞是否正确触发
- 得分是否正常增加
- 游戏结束是否返回菜单
九、课程总结
本项目综合使用:
- 函数模块化设计
- 二维数组绘制图形
- 光标定位实现画面布局
- 按键控制角色移动
- 简单碰撞与计分系统
只用 C++ 基础语法,即可完成有趣的小游戏。
完整代码
cpp
// 1. 输入输出流头文件:用于控制台显示文字和图形
#include<iostream>
// 2. 控制台输入头文件:用于检测按键 kbhit() 和获取按键 getch()
#include<conio.h>
// 3. Windows系统API:用于控制控制台窗口(光标定位、隐藏光标等)
#include <windows.h>
// 4. 时间头文件:用于设置随机数种子,让敌人每次出现位置不同
#include <time.h>
// 宏定义:定义游戏屏幕的总宽度(固定数值,方便修改)
#define SCREEN_WIDTH 90
// 宏定义:定义游戏屏幕的总高度
#define SCREEN_HEIGHT 26
// 宏定义:定义游戏车道的宽度(小车行驶的区域)
#define WIN_WIDTH 70
// 使用标准命名空间:简化代码,不用写 std::cout
using namespace std;
// 控制台句柄:相当于控制台的"遥控器",用来控制光标和输出
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
// 光标坐标结构体:用来存储光标的 X、Y 坐标
COORD CursorPosition;
// 敌人车辆的 Y 坐标数组(存储3个敌人的垂直位置)
int enemyY[3];
// 敌人车辆的 X 坐标数组(存储3个敌人的水平位置)
int enemyX[3];
// 敌人状态标记:1=显示敌人,0=隐藏敌人
int enemyFlag[3];
// 玩家小车的图案:用 4行×4列 的字符数组拼成小车形状
char car[4][4] = {
' ','#','#',' ',
'#','#','#','#',
' ','#','#',' ',
'#','#','#','#'
};
// 玩家小车的水平位置(初始在车道正中间)
int carPos = WIN_WIDTH/2;
// 游戏得分变量,初始为0,躲避成功后加分
int score = 0;
// 函数功能:将光标移动到控制台的 (x, y) 坐标位置
// 参数 x:水平坐标(列)
// 参数 y:垂直坐标(行)
void gotoxy(int x, int y){
// 设置光标要移动到的 X 坐标(水平位置)
CursorPosition.X = x;
// 设置光标要移动到的 Y 坐标(垂直位置)
CursorPosition.Y = y;
// 调用Windows系统API,执行光标移动
SetConsoleCursorPosition(console, CursorPosition);
}
// 函数功能:设置控制台光标的显示状态和大小
// 参数 visible:true=显示光标,false=隐藏光标
// 参数 size:光标的大小(取值 1-100),传入 0 则使用默认值
void setcursor(bool visible, DWORD size) {
// 如果传入的尺寸为0,设置为默认大小 20(固定写法,不用改)
if(size == 0)
size = 20;
// 定义一个 光标信息结构体 变量,用来存放光标的属性
CONSOLE_CURSOR_INFO lpCursor;
// 设置光标是否可见(true显示 / false隐藏)
lpCursor.bVisible = visible;
// 设置光标的大小(1~100,数字越大光标越粗)
lpCursor.dwSize = size;
// 把设置好的光标信息应用到控制台(生效)
SetConsoleCursorInfo(console,&lpCursor);
}
// 函数功能:绘制游戏的边界线(车道左右边框 + 最右侧边框)
void drawBorder(){
// 外层循环:遍历屏幕的每一行(垂直方向 i 代表行号)
for(int i=0; i<SCREEN_HEIGHT; i++){
// 内层循环:绘制左右两侧的双线边框(j 控制边框粗细)
for(int j=0; j<17; j++){
// 在第 i 行、左侧 0+j 列 绘制左边界符号
gotoxy(0+j,i);
cout<<"±";
// 在第 i 行、右侧 WIN_WIDTH-j 列 绘制右边界符号
gotoxy(WIN_WIDTH-j,i);
cout<<"±";
}
}
// 额外绘制最右侧的单条边框(整个屏幕的右边缘)
for(int i=0; i<SCREEN_HEIGHT; i++){
gotoxy(SCREEN_WIDTH,i);
cout<<"±";
}
}
// 函数功能:在控制台中绘制玩家控制的小车
void drawCar(){
// 外层循环:遍历小车的 4 行(i 代表行号)
for(int i=0; i<4; i++){
// 内层循环:遍历小车的 4 列(j 代表列号)
for(int j=0; j<4; j++){
// 定位到小车对应字符的控制台坐标
// j+carPos:水平位置 = 小车初始X坐标 + 字符在图案中的列
// i+22:垂直位置 = 固定在屏幕下方 22 行的位置
gotoxy(j+carPos, i+22);
// 输出二维数组中存储的对应字符(# 或 空格),拼成小车图案
cout << car[i][j];
}
}
}
// 函数功能:绘制敌方车辆
// 参数 ind:要绘制的敌人编号(0代表第一个敌人,1代表第二个敌人)
void drawEnemy(int ind){
// 判断:只有当前敌人处于激活状态(enemyFlag[ind]为true)时,才进行绘制
if(enemyFlag[ind]==true){
// 定位到敌人的 (X,Y) 坐标,绘制敌人第一行
gotoxy(enemyX[ind], enemyY[ind]);
cout<<"****";
// 定位到敌人的下一行,绘制敌人第二行
gotoxy(enemyX[ind], enemyY[ind]+1);
cout<<" ** ";
// 定位到敌人的下一行,绘制敌人第三行
gotoxy(enemyX[ind], enemyY[ind]+2);
cout<<"****";
// 定位到敌人的下一行,绘制敌人第四行
gotoxy(enemyX[ind], enemyY[ind]+3);
cout<<" ** ";
}
}
// 函数功能:擦除玩家小车(用空格覆盖原来的位置)
// 作用:实现动画效果 ------ 先擦除,再画新位置
void eraseCar(){
// 遍历小车的 4 行(i 是行)
for(int i=0; i<4; i++){
// 遍历小车的 4 列(j 是列)
for(int j=0; j<4; j++){
// 定位到小车原来的位置
gotoxy(j+carPos, i+22);
// 输出空格 → 把原来的 # 覆盖掉(擦除)
cout<<" ";
}
}
}
// 函数功能:擦除指定编号的敌人车辆
// 参数 ind:敌人的编号(0=第一个敌人,1=第二个敌人)
void eraseEnemy(int ind){
// 只有敌人处于显示状态时,才需要擦除
if(enemyFlag[ind]==true){
// 定位到敌人第一行 → 输出4个空格擦除
gotoxy(enemyX[ind], enemyY[ind]);
cout<<" ";
// 定位到敌人第二行 → 擦除
gotoxy(enemyX[ind], enemyY[ind]+1);
cout<<" ";
// 定位到敌人第三行 → 擦除
gotoxy(enemyX[ind], enemyY[ind]+2);
cout<<" ";
// 定位到敌人第四行 → 擦除
gotoxy(enemyX[ind], enemyY[ind]+3);
cout<<" ";
}
}
// 函数功能:生成敌人的随机 X 坐标(让敌人在车道里随机位置出现)
// 参数 ind:敌人编号(0表示第一个敌人,1表示第二个敌人)
void genEnemy(int ind){
// 17:保证敌人出现在**车道内部**(不会卡在左边界)
// rand()%33:生成 0~32 的随机数,控制敌人在车道内左右随机分布
// 最终 enemyX[ind] 范围:17 ~ 49,正好在游戏车道内
enemyX[ind] = 17 + rand()%33;
}
// 函数功能:重置敌人车辆(当敌人开到屏幕底部后,重新回到顶部)
// 参数 ind:要重置的敌人编号(0=第一个敌人,1=第二个敌人)
void resetEnemy(int ind){
// 1. 先擦除当前敌人在屏幕底部的旧图像
eraseEnemy(ind);
// 2. 把敌人的Y坐标设置为1(回到屏幕最上方)
enemyY[ind] = 1;
// 3. 重新生成一个随机的X坐标(让敌人换个车道出现)
genEnemy(ind);
}
// 函数功能:碰撞检测
// 作用:判断玩家小车和敌人小车是否撞上了
// 返回值:1 = 碰撞,0 = 未碰撞
int collision(){
// 第一层判断:敌人的Y坐标 + 自身高度(4行) 到达 玩家小车的Y坐标(23行)
// 意思是:敌人已经下落到玩家所在的高度了
if(enemyY[0]+4 >= 23){
// 第二层判断:敌人和玩家的 X 坐标区间发生重叠
// enemyX[0]+4 是敌人的右侧位置,carPos 是玩家的左侧位置
// 两者差值在 0~9 之间,说明两车宽度重叠,判定为相撞
if(enemyX[0]+4 - carPos >=0 && enemyX[0]+4 - carPos <9)
return 1; // 两个条件都满足,发生碰撞,返回 1
}
return 0; // 不满足条件,没有碰撞,返回 0
}
// 函数功能:更新并显示游戏得分
void updateScore(){
// 定位光标到屏幕右侧固定位置
// WIN_WIDTH + 7:车道右侧区域(不遮挡游戏画面)
// 5:垂直第5行
gotoxy(WIN_WIDTH + 7, 5);
// 输出当前得分(中文显示)
cout<<"得分: "<<score<<endl;
}
void gameover(){
system("cls");
cout<<endl;
cout<<"\t\t--------------------------"<<endl;
cout<<"\t\t------- 游戏结束 -------"<<endl;
cout<<"\t\t--------------------------"<<endl<<endl;
cout<<"\t\t按任意键返回主菜单.";
getch();
}
void instructions(){
system("cls");
cout<<"游戏说明";
cout<<"\n----------------";
cout<<"\n 通过左右移动躲避车辆。 ";
cout<<"\n\n 按 A 键向左移动";
cout<<"\n 按 D 键向右移动";
cout<<"\n 按 ESC 键退出游戏";
cout<<"\n\n按任意键返回主菜单";
getch();
}
// 函数功能:游戏主逻辑函数(游戏核心运行代码)
void play(){
// 1. 初始化玩家小车位置:居中显示
carPos = -1 + WIN_WIDTH/2;
// 2. 初始化游戏得分:0分
score = 0;
// 3. 第一个敌人默认激活(显示)
enemyFlag[0] = 1;
// 4. 第二个敌人初始未激活(隐藏)
enemyFlag[1] = 0;
// 5. 两个敌人都从屏幕最顶部开始
enemyY[0] = enemyY[1] = 1;
// 清空控制台屏幕
system("cls");
// 绘制游戏赛道边框
drawBorder();
// 显示初始得分(0分)
updateScore();
// 随机生成第一个敌人的X坐标
genEnemy(0);
// 随机生成第二个敌人的X坐标
genEnemy(1);
// 在屏幕右侧显示游戏标题
gotoxy(WIN_WIDTH + 7, 2);cout<<"赛车游戏";
// 显示操作说明:A键左移
gotoxy(WIN_WIDTH + 2, 14);cout<<" A 键 - 左移";
// 显示操作说明:D键右移
gotoxy(WIN_WIDTH + 2, 15);cout<<" D 键 - 右移";
// 提示玩家按任意键开始
gotoxy(18, 5);cout<<"按任意键开始游戏";
// 等待玩家按一个键
getch();
// 清空提示文字(用空格覆盖)
gotoxy(18, 5);cout<<" ";
// 游戏主死循环(每一帧执行一次)
while(1){
// 检测是否有按键按下
if(kbhit()){
// 获取按下的字符(不显示在屏幕上)
char ch = getch();
// 如果按 A 或 a 键,向左移动
if(ch=='a' || ch=='A'){
// 限制左边界,不能超出赛道
if(carPos >18) carPos -=4;
}
// 如果按 D 或 d 键,向右移动
if(ch=='d' || ch=='D'){
// 限制右边界,不能超出赛道
if(carPos <50) carPos +=4;
}
// 如果按 ESC 键,退出游戏
if(ch==27) break;
}
// 绘制玩家小车
drawCar();
// 绘制第一个敌人
drawEnemy(0);
// 绘制第二个敌人
drawEnemy(1);
// 碰撞检测:如果返回1,表示撞车
if(collision()==1){
// 调用游戏结束函数
gameover();
// 退出play函数,返回菜单
return;
}
// 延时50毫秒,控制游戏速度
Sleep(50);
// 擦除玩家小车(准备下一帧重绘)
eraseCar();
// 擦除第一个敌人
eraseEnemy(0);
// 擦除第二个敌人
eraseEnemy(1);
// 当第一个敌人下落到第10行时,激活第二个敌人
if(enemyY[0]==10 && enemyFlag[1]==0)
enemyFlag[1]=1;
// 如果第一个敌人激活,Y坐标+1(向下移动)
if(enemyFlag[0]==1) enemyY[0] +=1;
// 如果第二个敌人激活,Y坐标+1(向下移动)
if(enemyFlag[1]==1) enemyY[1] +=1;
// 如果第一个敌人超出屏幕底部
if(enemyY[0] > SCREEN_HEIGHT-4){
resetEnemy(0); // 重置敌人到顶部
score++; // 得分+1
updateScore(); // 更新分数显示
}
// 如果第二个敌人超出屏幕底部
if(enemyY[1] > SCREEN_HEIGHT-4){
resetEnemy(1); // 重置敌人到顶部
score++; // 得分+1
updateScore(); // 更新分数显示
}
}
}
// 主函数:程序入口(整个游戏从这里开始运行)
int main()
{
// 隐藏控制台光标,让游戏画面更整洁
// 参数 0 对应 false(隐藏),0 代表使用默认光标大小
setcursor(0, 0);
// 设置随机数种子
// 用系统当前时间作为种子,保证每次运行游戏,敌人出现的位置都不同
srand((unsigned)time(NULL));
// 主菜单循环:死循环,保证游戏结束后能回到菜单
do
{
// 清空屏幕,为绘制菜单做准备
system("cls");
// 以下代码:在控制台指定位置绘制菜单界面
gotoxy(10, 5); cout<<" -------------------------- ";
gotoxy(10, 6); cout<<" | 赛车游戏 | ";
gotoxy(10, 7); cout<<" --------------------------";
gotoxy(10, 9); cout<<"1. 开始游戏"; // 选项1
gotoxy(10, 10); cout<<"2. 游戏说明"; // 选项2
gotoxy(10, 11); cout<<"3. 退出"; // 选项3
gotoxy(10, 13); cout<<"请选择选项: "; // 提示输入
// 获取用户输入的选项(getche:输入字符后立即生效,不回车)
char op = getche();
// 根据用户选择执行不同功能
if(op == '1')
play(); // 选择1:调用 play() 函数,开始游戏
if(op == '2')
instructions(); // 选择2:调用 instructions() 函数,查看游戏说明
if(op == '3')
exit(0); // 选择3:调用 exit(0),退出程序
// 无限循环,回到菜单界面
} while(1);
// 程序正常结束(这里理论上永远不会执行到)
return 0;
}



