IE盒子

搜索
查看: 140|回复: 1

用C/C++编写小游戏:8.3 打砖块游戏主循环

[复制链接]

2

主题

6

帖子

13

积分

新手上路

Rank: 1

积分
13
发表于 2022-12-5 13:03:52 | 显示全部楼层 |阅读模式
8.3  打砖块游戏主循环

下面可以讨论打砖块游戏的主程序了。首先应该把游戏规则写得更清楚:
(1)在一局游戏开始时,弹球静止置于挡板上面中央,可随挡板左右移动,当玩家按下空格键时随机地向左斜上方或右斜上方发射。
(2)在一局游戏中,玩家按下左箭头键(ASCII 码值为 75)控制挡板向左边运动,按下右箭头键(ASCII码值为 77)控制挡板向右边运动。游戏中途按空格键暂停游戏,按 ESC 键(ASCII 码值为 27)可以选择是否中途退出游戏。
(3)游戏过程中打碎的砖块都要计数,按照一定的规则计算得分(简单的规则是每打碎一个砖块计算为得1分)。
(4)在一局游戏开始时应该给玩家以一定数量的“生命”,当游戏失败一次时就减掉一条“生命”,在“生命”数减到 0 时判断为本局失败,得分清 0 。在生命数减少至 0 之前打碎所有砖块为该局胜利,得分可以继续带到下一局。
(5)无论是胜利、失败或中途退出,询问玩家是否继续,如果继续,则重新对地图和其它参数进行初始化,开始下一局游戏。
按照上面的规则说明,似乎程序中应该有三层循环:外层循环以“玩家愿意继续”作为主条件来以控制游戏的运行,第二层循环以“生命数大于 0”为一局的循环条件,第三层循环是在玩游戏过程中以时间流逝作为循环条件。如果按照这样写的话,大部分代码都处于较深的层次,按照缩进排版格式,会向右缩进太多。
仔细思考之后,发现程序中其实可以简化成一层循环:以时间流逝作为循环条件,在每一次循环里先判断当前游戏是否胜利或失败并输出信息,然后询问用户是否愿意继续,如果继续则以 continue 跳出当前循环继续游戏,如果不愿意继续则以 break 跳出循环终止游戏;如果既非胜利也非失败,则是正常游戏过程中(包括中途失败一次并减少一条生命)。
于是写出主函数结构如下:
int main() {
初始化随机数种子
initMap(bricks);    //初始化地图
initBarBall();      //初始化挡板和弹球
while (true) {  //游戏主循环
drawMap();  //绘制地图
showMsg(lives, bricks, score);    //输出信息
if (本局游戏达到胜利或失败条件) {
给出胜利消息或失败消息(失败时得分清零)
if (用户愿意继续游戏)
initMap(bricks);    //初始化地图
initBarBall();      //初始化挡板和弹球
continue;
} else
break;
}
既非胜利也非失败,正常进行游戏
} //主循环结束
}
在获得胜利或失败时的具体代码是比较容易写的,在此略过不述。
下面主要讨论在非胜利也非失败时(在生命数大于 0 且砖块数大于 0 时)如何正常进行游戏。
在程序中首先应该定义一些变量。容易想到的是:应该定义整型变量记录每局的“生命”数和得分,要记录弹球运动击中的砖块数(水平方向、竖直方向和斜向),还要定义变量记录用户按键。当然,应当用一个变量记录每局中的砖块数:开局时记录砖块总数,游戏过程中记录砖块剩余数,当砖块数减少到 0 时为玩家胜利。
static int score = 0;  //得分(胜利时可以带到下一局)
int lives = 3, bricks = 0, hits = 0; //本局的生命数,砖块数,已击中砖块数
char key;  //用户按键
在循环内部要做两件事:判断并处理玩家的按键,自动处理弹球的运动并判断是否击中砖块。或者说,在循环内部要进行两种数据更新:一种是与玩家输入有关的更新,另一种是与玩家输入无关的自动更新。在循环结束之后要给出游戏结果。
需要注意的,在游戏中不仅需要按一定的时间间隔处理玩家的按键,还要按一定的时间间隔处理弹球的运动。如果这两个时间间隔相同,每处理一次弹球运动才处理玩家一次按钮,那么游戏就会变成凝滞了:玩家无法灵活控制挡板的运动!为了解决这个问题,在游戏中应该更频繁地处理玩家的按钮,让这两个时间间隔呈一定的倍数。根据编程和游戏经验,合理的取值是每隔 30~50 毫秒处理玩家的按键,每隔 120~160 毫秒处理弹球的运动(这两个时间间隔的倍数为 3~6),进行自动更新。为此在函数中定义一个变量,每达到某个倍数才处理一次弹球的运动:
int loops;    //玩家按键时间与弹球运动时间间隔的倍数
因此处理正常游戏的程序结构如下:
if (kbhit()) {  //有键盘输入
key = getch();    //接收用户按键
处理按键为空格、左方向键、右方向键和ESC键
}
Sleep(50);
loops = (++loops) % 3;
if (loops > 0)  //仅在loops等于0时处理弹球运动, 否则跳过
continue; //跳过下面的代码,执行下一循环
处理弹球运动是否击中砖块
如果击中砖块,则计算剩余砖块数和得分
更新砖块坐标
处理用户按键是一个常规的编程工作,难度不大:
if (kbhit()) {  //有键盘输入
//既非胜利也非失败,则游戏正在进行
if (kbhit()) {  //有键盘输入
key = getch();    //接收用户按键
if (key <= 0 || key > 127) //读取方向键时,第一次读得的不是实际键值
key = getch(); //读取方向键时,第二次才读得实际键值
if (key == ' ') {  //空格
if (vx == 0 && vy == 0) { //开局发球。初速度随机斜向左右上方
vx = (rand() % 2 ? 1 : -1);
vy = -1;
} else  //中途暂停
getch();  //按任意键继续
} else if ((key == 75 || key == 'a' || key == 'A')
&& barx > 1) { //left
map[bary][barx + barwd] = ' '; //挡板右端空白
barx--;
map[bary][barx] = '='; //挡板左端点
if (vx == 0 && vy == 0) { //开局时尚未发球,则球随之挡板移动
map[y][x] = ' ';
map[y][--x] = 'O';
}
} else if ((key == 77 || key == 'd' || key == 'D')
&& (barx + barwd < WD - 2)) { //right
map[bary][barx] = ' '; //挡板左端空白
barx++;
map[bary][barx + barwd] = '='; //挡板右端点
if (vx == 0 && vy == 0) { //开局时尚未发球,则球随之挡板移动
map[y][x] = ' ';    //弹球原位置
map[y][++x] = 'O';    //弹球新位置
}
} else if (key == 'h' || key == 'H') {
showHelp();
} else if (key == 27) {    //是否退出游戏(ESC键的ASCII码为27)
gotoxy(0, HT + 1 );
showcursor(1);
cout << "是否退出游戏?(y/n): ";
while ((key = tolower(getch())) != 'y' && key != 'n')
;    //空循环体
cin.sync();    //清空缓冲区
gotoxy(0, HT + 1 );
cout << "                      \r";  //输出空格(清空该行文字)
if (key == 'y')
break;
else
showcursor(0);
}
}
注意其中能处理按键 h 或 H,以执行 showHelp 函数以显示游戏名称标志和帮助信息。这个做法与上一章类似(参见“6.6  清空屏幕与游戏帮助信息”)。其中的 showHelp 函数在此略过不述。
处理弹球击中砖块也是常规的编程工作,只要耐心地编写就可以完成。
        hits = 0; //在此循环中击中砖块数
  //先判断左右或竖直碰撞,如果发生判断则不再判断斜碰
        if (map[y][x + vx] == 'B') {  //横向运动方向为砖块
            map[y][x + vx] = ' ';     //打碎砖块变为空白
            hits++;  //横向碰撞记数
            vx = -vx;      //横向反弹
            cout << '\a';  //响铃
        }
        if (map[y][x + vx] == '#') { //围墙
            vx = -vx;    //横向反弹
            cout << '\a';
        }

        if (y + vy > bary) {    //如果下方是底部边界,则生命数减少
            map[y][x] = 'o';
            lives--;    //生命数减少
            showMsg(lives, bricks, score);
if (lives > 0) { //生命值尚未减少到0
gotoxy(0, HT + 1);
cout << "失败,按任意键继续";
getch();
gotoxy(0, HT + 1);
cout << "                    ";
map[y][x] = ' ';
initBarBall();  //初始化挡板和弹球
}
            continue;
        }
        if (map[y + vy][x] == 'B') { //如果竖直运动方向是砖块
            map[y + vy][x] = ' ';    //打碎砖块变为空白
            hits++; //竖直碰撞记数
            vy = -vy;   //竖直反弹
            cout << '\a';
        }
        if (map[y + vy][x] == '#' || map[y + vy][x] == '=' ) { //围墙,挡板
            vy = -vy; //竖直反弹
            cout << '\a';
        }

        //未发生横向或竖直碰撞,则判断是否发生斜碰
        if (hits == 0 && map[y + vy][x + vx] == 'B') {
            map[y + vy][x + vx] = ' ';
            hits++;
            vy = -vy;
            vx = -vx;
            cout << '\a';
        }
        bricks -= hits;  //剩余砖块数
        score += hits;  //得分
        showMsg(lives, bricks, score);

        map[y][x] = ' '; //弹球原位置变为空
        if ((vy < 0 && y > 1) || (vy > 0 && y < HT - 1))
            y = y + vy;
        if ((vx < 0 && x > 1) || (vx > 0 && x < WD - 2))
            x = x + vx;
        map[y][x] = 'O';
把以上代码拼装起来,就构成了一个完整的打砖块游戏程序,可以运行了!
读者可以从本文作者的 Gitee 开源程序库中(https://gitee.com/devcpp/cgames)下载这个完整的源程序,文件名称为 cgame8(bricks)v1.cpp。

继续阅读:8.4  优化加速游戏程序
回复

使用道具 举报

4

主题

10

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 2025-3-26 20:36:36 | 显示全部楼层
报告!别开枪,我就是路过来看看的。。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表