笔者也算是学了小半年的c++,之前都停留在控制台做做题,最近觉得这样实在是差点成就感,于是就想试试写一写简单的小游戏,在用打砖块,flappy bird等小游戏练练手后,在查阅书籍,查找一些网络资料后写了一个完成度还算高的空战小游戏的控制台应用,希望能对其他觉得迷茫的c++新手提供一些方向
游戏的完成效果:
先看看游戏的主要框架:
#include<iostream>
#include<cstdlib>
#include<conio.h>
#include<windows.h>
using namespace std;
//宏定义
#define High 15 //游戏界面高度
#define Width 25 //游戏界面宽度
#define EnemyNum 6 //敌机数量
//全局变量
int pos_x,pos_y; //飞机坐标
int canvas[High][Width]={0}; //背景画布,用二维数组表示
int enemy_x[EnemyNum],enemy_y[EnemyNum]; //敌机的坐标,因为有多架敌机,用一维数组表示
int BulletWidth; //子弹宽度,实现散弹功能
int EnemySpeed; //敌机速度,实现动态难度
int score; //得分
复制代码
复制代码
int main(){
HideCursor();//隐藏光标
startup();//游戏参数初始化
while(1){//循环保持游戏进程
show();//输出游戏画面
UpdateWithoutInput();//与用户输入无关的数据更新
UpdateWithInput();//与用户输入有关的数据更新
}
return 0;
}
大部分简单的游戏实现都可以依靠这类框架实现,还可以增加一个判断游戏是否进行函数作为循环条件,增加重新开始游戏的功能函数等等,这些就留给读者实现吧
让我们按顺序来看看这些函数的原理和实现方法吧
首先是隐藏光标函数,作为控制台应用,有个光标在屏幕乱闪肯定极其破坏游戏体验,那么就需要隐藏它。来看看这个函数长什么样:
void HideCursor(){
CONSOLE_CURSOR_INFO cursor_info={1,0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info);
}
这个函数的原理呢,我也不知道,是在网上抄的,windows.h头文件的详细解析在网上笔者没有找到,如果读者能对其有所了解自然是再好不过了
然后是游戏参数的初始化,这个函数也毫无理解难度:
void startup(){
pos_x=High-1; //x是纵坐标
pos_y=Width/2; //y是横坐标
canvas[pos_x][pos_y]=1; //在画布中飞机相应的位置设定值为1,表示这个位置是飞机
for(int i=0;i<EnemyNum;i++){ //给每架敌机设定随机的出现位置
enemy_y[i]=rand()%Width;
enemy_x[i]=rand()%2;
canvas[enemy_x[i]][enemy_y[i]]=3; //在画布中敌机相应的位置设定值为3,表示这个位置是敌机
}
score=0;
BulletWidth=0;
EnemySpeed=13; //速度其实是刷新的时间间隔,因此速度越小速度越快
}
接下来是输出游戏画面的函数了,算是重头戏:
void show(){
gotoxy(0,0); //让输出内容的光标回到左上角
for(int i=0;i<High;i++){ //这两行是对画布元素值
for(int j=0;j<Width;j++){ //逐一扫描
if(canvas[i][j]==0) //如果值为0
printf(" "); //输出空格,表示空白
else if(canvas[i][j]==1) //如果值为1
printf("*"); //输出*,表示飞机
else if(canvas[i][j]==2) //如果值为2
printf("|"); //输出|,表示子弹
else if(canvas[i][j]==3) //如果值为3
printf("@"); //输出@,表示敌机
}
printf("\n"); //扫描完一行,换行开始下一行
}
printf("Score:%3d\n",score); //输出分数
Sleep(20); //停留2毫秒,作为刷新频率
}
大概讲解一下输出画面的思路。首先将二维数组作为画布背景,其中的元素值代表画面上的游戏对象,如果值是0,就说明此处是空白;如果是1,代表是飞机;如果是2,代表是飞机发射的子弹;如果是3,代表是敌机。然后通过对此画布二维数组遍历,通过其值来判断输出的内容。那么怎么实现动画效果呢,当再一次调用此函数时,第一行的gotoxy(0,0)会将输出光标移动到窗口左上角,此时再次输出画面,就会实现其中元素的移动效果啦,gotoxy不是标准库函数,同样需要window.h来自己编写,具体原理不用管,直接copy使用就是了:
void gotoxy(int x,int y){
HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X=x;
pos.Y=y;
SetConsoleCursorPosition(handle,pos);
}
当然,只靠画面输出函数是无法实现游戏的动画效果的,还需要画布中数据的更新,于是就需要UpdateWithoutInput();(与用户输入无关的数据更新) UpdateWithInput();(与用户输入有关的数据更新)。先看看UpdateWithoutInput()的实现:
void UpdateWithoutInput(){
for(int i=0;i<High;i++){ //这两个循环
for(int j=0;j<Width;j++){//是逐一扫描画布元素
if(canvas[i][j]==2){ //如果元素值是2(即子弹),会发生如下的数据改变
for(int k=0;k<EnemyNum;k++){ //对每架敌机的相关数据进行遍历
if(i==enemy_x[k]&&j==enemy_y[k]){ //如果子弹横纵坐标和敌机的相等
score++; //加分
if(score<7&&EnemySpeed>2) EnemySpeed=13;//实现不同分数段难度不同
else if(score>=7&&score<14) EnemySpeed=11;
else if(score>=14&&score<21) EnemySpeed=9;
else if(score>=21&&score<40) EnemySpeed=7;
else if(score>=40&&score<60) EnemySpeed=5;
else if(score>=60) EnemySpeed=3;
if(score<15) BulletWidth=0; //如果分数小于15,子弹宽度为0,那么只有单发子弹
else if(score<30&&score>=15) BulletWidth=1;//分数大于15小于30,宽度为1,那么有3发子弹
else if(score<45&&score>=30) BulletWidth=2;//分数大于30小于45,宽度为2,那么有5发子弹
canvas[enemy_x[k]][enemy_y[k]]=0; //既然子弹和敌机的坐标重合,那么敌机被消灭,相应的位置也就变为了空白
enemy_x[k]=rand()%2; //随机出新的敌机的纵坐标
enemy_y[k]=rand()%Width; //随机出新的敌机的横坐标
canvas[enemy_x[k]][enemy_y[k]]=3; //生成新的敌机
canvas[i][j]=0; //子弹消失(实际上子弹并未消失,笔者没搞出原因)
}
}
canvas[i][j]=0;//令子弹的原位置为空白
if(i>0)
canvas[i-1][j]=2;//生成子弹的新位置数据,配合输出画面函数实现动画效果
}
}
}
static int speed=0; //这俩句可能比较难懂,设置成静态变量是为了防止超出作用域后speed直接消失导致无法更新数据
if(speed<EnemySpeed) speed++; //设置speed的目的是只有当speed加到等于EnemySpeed时才更新在画布中移动的敌机的位置数据,从而实现敌机移动较慢的动画效果
for(int k=0;k<EnemyNum;k++){ //对每架敌机都进行处理
if(pos_x==enemy_x[k]&&pos_y==enemy_y[k]){ //敌机与飞机位置重合,游戏失败
printf("You Lose!\n");
Sleep(3000);
system("pause");
exit(0);
}
if(enemy_x[k]>High){ //敌机飞出了范围
canvas[enemy_x[k]][enemy_y[k]]=0; //将原位置置为空白
enemy_y[k]=rand()%Width; //生成新的敌机坐标
enemy_x[k]=rand()%2;
canvas[enemy_x[k]][enemy_y[k]]=3; //生成新的敌机
score--; //扣分
}
if(speed==EnemySpeed){ //speed等于EnemySpeed,可以更新敌机的位置数据了
for(int i=0;i<EnemyNum;i++){
canvas[enemy_x[i]][enemy_y[i]]=0;
enemy_x[i]++; //纵坐标加1,向前移动
speed=0;
canvas[enemy_x[i]][enemy_y[i]]=3;
}
}
}
}
这个函数可以说是主体了,输出画面函数配合数据的更新可以实现简单的动画效果,很多小游戏都可以用这种方法实现,如果能理解这种体系那么配合注释应该可以看懂,还是建议自己敲一遍加深理解
然后是与用户有关的操作了:
void UpdateWithInput(){
char input;//输入字符
if(_kbhit()){ //如果按动了键盘
input=_getch();//读取键盘输入的字符
if(input=='a'){ //如果输入的字符是a,那么向左移动,以下几个分支类同
canvas[pos_x][pos_y]=0;
pos_y--;
canvas[pos_x][pos_y]=1;
}
else if(input=='w'){
canvas[pos_x][pos_y]=0;
pos_x--;
canvas[pos_x][pos_y]=1;
}
else if(input=='s'){
canvas[pos_x][pos_y]=0;
pos_x++;
canvas[pos_x][pos_y]=1;
}
else if(input=='d'){
canvas[pos_x][pos_y]=0;
pos_y++;
canvas[pos_x][pos_y]=1;
}
else if(input==' '){ //如果读入的是空格键
int l=pos_y-BulletWidth; //子弹流的左端
int r=pos_y+BulletWidth; //子弹流的右端for(int k=l;k<=r;k++) //输出一排子弹
canvas[pos_x-1][k]=2;
}
}
}
这段也很简单,不多解释了
以下是完整代码:
#include<iostream>
#include<cstdlib>
#include<conio.h>
#include<windows.h>
using namespace std;
#define High 15
#define Width 25
#define EnemyNum 6
int pos_x,pos_y;
int canvas[High][Width]={0};
int enemy_x[EnemyNum],enemy_y[EnemyNum];
int BulletWidth;
int EnemySpeed;
int score;
void gotoxy(int x,int y){
HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X=x;
pos.Y=y;
SetConsoleCursorPosition(handle,pos);
}
void HideCursor(){
CONSOLE_CURSOR_INFO cursor_info={1,0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info);
}
void startup(){
pos_x=High-1;
pos_y=Width/2;
canvas[pos_x][pos_y]=1;
for(int i=0;i<EnemyNum;i++){
enemy_y[i]=rand()%Width;
enemy_x[i]=rand()%2;
canvas[enemy_x[i]][enemy_y[i]]=3;
}
score=0;
BulletWidth=0;
EnemySpeed=13;
}
void show(){
gotoxy(0,0);
for(int i=0;i<High;i++){
for(int j=0;j<Width;j++){
if(canvas[i][j]==0)
printf(" ");
else if(canvas[i][j]==1)
printf("*");
else if(canvas[i][j]==2)
printf("|");
else if(canvas[i][j]==3)
printf("@");
}
printf("\n");
}
printf("Score:%3d\n",score);
Sleep(20);
}
void UpdateWithoutInput(){
for(int i=0;i<High;i++){
for(int j=0;j<Width;j++){
if(canvas[i][j]==2){
for(int k=0;k<EnemyNum;k++){
if(i==enemy_x[k]&&j==enemy_y[k]){
score++;
if(score<7&&EnemySpeed>2) EnemySpeed=13;
else if(score>=7&&score<14) EnemySpeed=11;
else if(score>=14&&score<21) EnemySpeed=9;
else if(score>=21&&score<40) EnemySpeed=7;
else if(score>=40&&score<60) EnemySpeed=5;
else if(score>=60) EnemySpeed=3;
if(score<15) BulletWidth=0;
else if(score<30&&score>=15) BulletWidth=1;
else if(score<45&&score>=30) BulletWidth=2;
canvas[enemy_x[k]][enemy_y[k]]=0;
enemy_x[k]=rand()%2;
enemy_y[k]=rand()%Width;
canvas[enemy_x[k]][enemy_y[k]]=3;
canvas[i][j]=0;
}
}
canvas[i][j]=0;
if(i>0)
canvas[i-1][j]=2;
}
}
}
static int speed=0;
if(speed<EnemySpeed) speed++;
for(int k=0;k<EnemyNum;k++){
if(pos_x==enemy_x[k]&&pos_y==enemy_y[k]){
printf("You Lose!\n");
Sleep(3000);
system("pause");
exit(0);
}
if(enemy_x[k]>High){
canvas[enemy_x[k]][enemy_y[k]]=0;
enemy_y[k]=rand()%Width;
enemy_x[k]=rand()%2;
canvas[enemy_x[k]][enemy_y[k]]=3;
score--;
}
if(speed==EnemySpeed){
for(int i=0;i<EnemyNum;i++){
canvas[enemy_x[i]][enemy_y[i]]=0;
enemy_x[i]++;
speed=0;
canvas[enemy_x[i]][enemy_y[i]]=3;
}
}
}
}
void UpdateWithInput(){
char input;
if(_kbhit()){
input=_getch();
if(input=='a'){
canvas[pos_x][pos_y]=0;
pos_y--;
canvas[pos_x][pos_y]=1;
}
else if(input=='w'){
canvas[pos_x][pos_y]=0;
pos_x--;
canvas[pos_x][pos_y]=1;
}
else if(input=='s'){
canvas[pos_x][pos_y]=0;
pos_x++;
canvas[pos_x][pos_y]=1;
}
else if(input=='d'){
canvas[pos_x][pos_y]=0;
pos_y++;
canvas[pos_x][pos_y]=1;
}
else if(input==' '){
int l=pos_y-BulletWidth;
int r=pos_y+BulletWidth;
for(int k=l;k<=r;k++)
canvas[pos_x-1][k]=2;
}
}
}
int main(){
HideCursor();
startup();
while(1){
show();
UpdateWithoutInput();
UpdateWithInput();
}
return 0;
}
以后应该还会更新一些别的小游戏,可能还会用图形库更新这个游戏
PS:推荐阅读《C语言课程设计与游戏开发实践教程》,如果不知道C语言能干嘛就看看这本书吧!