用C语言开发入门游戏FlappyBird
创始人
2024-01-19 14:18:08
0

前言

《flappy bird》是一款由来自越南的独立游戏开发者Dong Nguyen所开发的作品,游戏于2013年5月24日上线,并在2014年2月突然暴红。2014年2月,《Flappy Bird》被开发者本人从苹果及谷歌应用商店撤下。2014年8月份正式回归APP Store,正式加入Flappy迷们期待已久的多人对战模式。游戏中玩家必须控制一只小鸟,跨越由各种不同长度水管所组成的障碍。

通过游戏开发可以做到

1)在游戏窗口中显示从右向左运动的障碍物,显示三根柱子墙;

2)用户使用空格键控制小鸟向上移动,以不碰到障碍物为准,即需要从柱子墙的缝隙中穿

行,确保随机产生的障碍物之间的缝隙大小可以足够小鸟通过;

3)在没有用户按键操作情况下,小鸟受重力影响会自行下落;

4)进行小鸟与障碍物的碰撞检测,如果没有碰到,则给游戏者加 1 分。

5)如果小鸟碰到障碍物或者超出游戏画面的上下边界,则游戏结束。

使用空格键控制小鸟向上移动,在没有用户按键操作情况下,小鸟受重力影响会自行下落。如果小鸟碰到障碍物或者超出游戏画面的上下边界,则游戏结束。

打印上下边界

Linux 环境下光标定位

学会在 Linux 环境中光标定位,在屏幕上在不同的位置,打印出不同的内容。

光标报告的格式是: 0x1B [行坐标;列坐标]。

  1. //x 为行坐标 ,y 为列坐标
  2. printf ( "%c[%d;%df" ,0x1B,y,x);

Windows 环境下光标定位

Windows 环境中,光标定位的方法有所不同,引入 windows.h 头文件,以下所使用的到的结构体或者函数均在存在于该头文件。

首先需要使用到 windows.h 中的 COORD 结构体,其定义为,

  1. typedef struct _COORD {
  2. SHORT X; // horizontal coordinate
  3. SHORT Y; // vertical coordinate
  4. } COORD;

再通过 GetStdHandle() 获得标准输出设备句柄 HANDLE

  1. HANDLE hp = GetStdHandle(STD_OUTPUT_HANDLE);

最后通过 SetConsoleCursorPosition() 设置控制台光标位置。

  1. //变量 pos 为 COORD 结构体对象
  2. SetConsoleCursorPosition(hp, pos);

现在,我们可以在不同环境中,在不同位置进行打印输出。

代码

#include 
#define BOOTEM 26 //下边界的高度
#define DISTANCE 10 //两个墙体之间的距离
#define WIDTH 5 //墙体宽度
#define BLANK 10 //上下墙体之间的距离
/**********Begin**********/
//光标定位
void gotoxy(int x, int y){printf("%c[%d;%df", 0x1B, y, x);
}//函数功能:显示上下边界和分数
void begin(){system("clear");//打印上边界gotoxy(0, 0);printf("\n==========================\n");//打印下边界gotoxy(0, BOOTEM);printf("\n==========================");}
/**********End**********/int main()
{begin();return 0;}

 


 

打印小鸟

代码

#include "./head.h"
typedef struct COORD {
short X; // horizontal coordinate
short Y; // vertical coordinate
} COORD;typedef struct bird
{COORD pos;int score;
} BIRD;//函数功能:显示小鸟
void prtBird(BIRD *bird){/**********Begin**********/void prtBird(BIRD *bird){gotoxy(bird->pos.X, bird->pos.Y);printf("O^^0");fflush(stdout);}/**********End**********///linux环境printf频繁偶尔会在缓存中不会立即打印,fflush函数可以清空缓存并立即打印fflush(stdout);
}int main()
{BIRD bird = {{10, 13}, 0};//小鸟的初始位置begin();prtBird(&bird);return 0;}

 


 

小鸟移动

代码

#include "./head.h"//EVALUATING 宏定义  1 为 评测模式  0 为命令行模式
#define EVALUATING 1//函数功能:检测键盘输入
//有输入值时,该函数返回 1 ,没有输入时,该函数返回 0
int kbhit()
{struct termios oldt, newt; int ch; int oldf; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); oldf = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); fcntl(STDIN_FILENO, F_SETFL, oldf); if(ch != EOF) { ungetc(ch, stdin); return 1; } return 0; 
}
//函数功能:移动小鸟
void moveBird(BIRD *bird){/**********Begin**********//**********Begin**********/char ch;  //下面两行代码  作用是覆盖上次打印出来的小鸟位置,如果不加的话 会显示残影  gotoxy(bird->pos.X, bird->pos.Y);  printf("     ");if (kbhit()) {  ch = getchar();  if (ch == ' ') {  bird->pos.Y--;//向上移动  }  }  else {  bird->pos.Y++;//向下移动  }/**********End**********/}int main()
{begin();BIRD bird = {{10, 13}, 0};//小鸟的初始位置//EVALUATING 宏定义 1 为评测模式  0 为命令行模式if(EVALUATING){int cnt=3;// 请忽略 辅助评测 无实际意义 while(cnt--){prtBird(&bird);usleep(400000);moveBird(&bird);}}else{while(1){prtBird(&bird);usleep(400000);moveBird(&bird);}}return 0;
}

 


 

打印墙体

我们使用链表来存放墙体,链表是一种常用的数据结构,由若干个结点组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

  1. typedef struct wall{
  2. COORD pos;
  3. struct wall *next;//指向下一链表
  4. }WALL;

在这里我们要注意变量的生存周期,如果函数将变量内存地址存放在栈区的时候,当创建该变量的函数结束时,其内部创建在栈区的地址将被释放。

因此我们需要将结点申请在堆区,在 C 语言中,我们可以通过 malloc() 函数申请堆区,例如。

  1. WALL *wall = (WALL *)malloc(sizeof(WALL));

当该变量不需要使用的时候,使用 free() 函数将其地址空间释放。

  1. free(wall);

第 1 行和第 BOOTEM 行是上下边界,从第 2 行开始打印墙体,其中上下墙体间隔 BLANK 行。DISTANCE 为相邻两个墙体的间距,WIDTH 为墙体的宽度。

  1. #define BOOTEM 26 //下边界的高度
  2. #define DISTANCE 10 //两个墙体之间的距离
  3. #define WIDTH 5 //墙体宽度
  4. #define BLANK 10 //上下墙体之间的距离

墙体样式为,

  1. prtNode(p, "*");

代码

#include "./head.h"//EVALUATING 宏定义  1 为 评测模式  0 为命令行模式
#define EVALUATING 1typedef struct wall{COORD pos;struct wall *next;
}WALL;/**********Begin**********///创建节点
WALL *createWall(int x, int y){//首先生成一个节点WALL *wall = (WALL *)malloc(sizeof(WALL));if(wall == NULL) return NULL;wall->pos.X = x;wall->pos.Y = y;wall->next = NULL;return wall;
}
//遍历链表
WALL *lastNode(WALL *wall){WALL *p = wall;if(wall == NULL) return NULL;while(p->next){p = p->next;}return p;
}
//创建链表
WALL *createLink(){//生成一个随机数,作为上半部分墙体的高度srand(0);//生成随机值,当rd等于0或等于1时,给其赋值2int rd = rand() % (BOOTEM / 2);if(rd == 1||rd==0) rd = 2;//初始化一个节点WALL *wall = createWall(20, rd);WALL *temp, *p;for(int i = 1; i <= 2; i ++){//生成随机值,当rd等于0或等于1时,给其赋值2rd = rand() % (BOOTEM / 2);if(rd == 1||rd==0) rd = 2;p = lastNode(wall);//找到了链表的尾节点//创建节点temp = createWall(p->pos.X + DISTANCE + WIDTH * 2, rd);p->next = temp;//尾插法}return wall;
}
//销毁链表
void deleteLink(WALL *wall){WALL *p, *q;p = q = wall;if(wall != NULL){while(p->next != NULL){q = p->next;p->next = q->next;free(q);}}free(wall);//free(p);
}
//打印单个墙体
void prtNode(WALL *node, char ch[]){if(node == NULL) return ;int i, j;//上半部分墙体,第一行是上边界,从第2行开始打印for(i = 2; i <= node->pos.Y; i ++){gotoxy(node->pos.X, i);for(j = 1; j <= WIDTH; j ++){printf("%s", ch);}}//下半部分墙体 第BOOTEM行是下边界,此处 BOOTEM -1 避免墙体覆盖边界for(i = node->pos.Y + BLANK; i < BOOTEM - 1; i ++){gotoxy(node->pos.X, i);for(j = 1; j <= WIDTH; j ++){printf("%s", ch);}}
}
//打印整个墙体
void prtWall(WALL *wall){if(wall == NULL) return ;WALL *p = wall;while(p){prtNode(p, "*");p = p->next;}
}int main()
{begin();BIRD bird = {{10, 13}, 0};//小鸟的初始位置WALL *wall= createLink();//链表生成墙体prtWall(wall);//EVALUATING 宏定义 1 为评测模式  0 为命令行模式if(EVALUATING){int cnt=3;// 请忽略 辅助评测 无实际意义 while(cnt--){prtBird(&bird);usleep(400000);moveBird(&bird);}}else{while(1){prtBird(&bird);usleep(400000);moveBird(&bird);}}deleteLink(wall);return 0;}

 


 

检测碰撞

justHead() 函数没有检测到碰撞时,返回 0,当检测到碰撞时,返回 1。

当小鸟与上下边界发生碰撞时,

  1. //与上下边界发生碰撞
  2. if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM)

当小鸟与墙体发生碰撞时,

  1. //小鸟与墙体发生碰撞
  2. bird->pos.X >= wall->pos.X && bird->pos.X <= wall->pos.X+ WIDTH

代码

#include "./head.h"//EVALUATING 宏定义  1 为 评测模式  0 为命令行模式
#define EVALUATING 1//监测小鸟碰撞
int justHead(WALL *wall, BIRD *bird){if(wall == NULL) return -1;//与上下边界发生碰撞if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM) return 1;//小鸟与墙体发生碰撞if(bird->pos.X  >= wall->pos.X && bird->pos.X <= wall->pos.X+ WIDTH){if(bird->pos.Y <= wall->pos.Y || bird->pos.Y >= wall->pos.Y + BLANK){return 1;}}return 0;
} int main()
{begin();BIRD bird = {{10, 13}, 0};//小鸟的初始位置WALL *wall= createLink();//链表生成墙体prtWall(wall);//EVALUATING 宏定义 1 为评测模式  0 为命令行模式if(EVALUATING){int cnt=3;// 请忽略 辅助评测 无实际意义 while(cnt--&&justHead(wall,&bird)==0){prtBird(&bird);usleep(400000);moveBird(&bird);wall = moveWall(wall,&bird);}}else{while(justHead(wall,&bird)==0){prtBird(&bird);usleep(400000);moveBird(&bird);wall = moveWall(wall,&bird);}}deleteLink(wall);return 0;}

 

 Flappy bird 实践练习

代码

#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 
#define DIS 22
#define BLAN 9   //上下两部分柱子墙之间的缝隙
typedef struct COORD {
short X; // horizontal coordinate
short Y; // vertical coordinate
} COORD;
typedef struct bird
{COORD pos;int score;
} BIRD;
//bool SetConsoleColor(unsigned int wAttributes); //设置颜色
void Gotoxy(int x, int y);//定位光标
bool SetConsoleColor(int back,int front); //设置颜色
void CheckWall(COORD wall[]);//显示柱子墙体
void PrtBird(BIRD *bird);//显示小鸟
int CheckWin(COORD *wall, BIRD *bird);//检测小鸟是否碰到墙体或者超出上下边界
void Begin(BIRD *bird);//显示上下边界和分数
int SelectMode();  //选择模式
int kbhit()
{struct termios oldt, newt; int ch; int oldf; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); oldf = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); fcntl(STDIN_FILENO, F_SETFL, oldf); printf("%c\n",ch);if(ch != EOF) { ungetc(ch, stdin); return 1; } return 0; 
}
//主函数
int main(int argc, char* argv[])
{BIRD bird = {{20, 13}, 0};//小鸟的初始位置COORD wall[3] = {{40, 10},{60, 6},{80, 8}}; //柱子的初始位置和高度int i;char ch;int gameMode = 1;gameMode = SelectMode();if(gameMode==1) //用于评测{int count = 0;while (count < 60)      //游戏循环执行{        Begin(&bird); //清屏并显示上下边界和分数PrtBird(&bird);//显示小鸟CheckWall(wall);//显示柱子墙ch = getchar();//输入的字符存入chprintf("%c",ch);if (ch == 'u')//输入的是u{bird.pos.Y -= 1; //小鸟向上移动一格}if (ch == 'd')//输入的是d{bird.pos.Y += 1; //小鸟向下移动一格}for (i=0; i<3; ++i){wall[i].X--; //柱子墙向左移动一格}if(CheckWin(wall, &bird)==0){printf("Bird Lose!");return 0;}count++;}printf("Bird Win!");return 0;}while (CheckWin(wall, &bird)){Begin(&bird); //清屏并显示上下边界和分数PrtBird(&bird);//显示小鸟CheckWall(wall);//显示柱子墙usleep(400000);if (kbhit()) //检测到有键盘输入{ch = getchar();//输入的字符存入chprintf("%c",ch);if (ch == ' ')//输入的是空格{bird.pos.Y -= 1; //小鸟向上移动一格}}        else //未检测到键盘输入{bird.pos.Y += 1;//小鸟向下移动一格}for (i=0; i<3; ++i){wall[i].X--; //柱子墙向做移动一格}}return 0;
}
int SelectMode()
{printf(" 请选择模式:\n 1.评测模式\n 2.自由体验游戏\n");int mode;while(scanf("%d", &mode)){if (mode != 1 && mode != 2){printf(" 输入数据有误,请重新输入!\n\n 请选择模式:\n 1.评测模式\n 2.自由体验游戏\n");continue;}else return mode;}return 1;
}
//函数功能:定位光标
void Gotoxy(int x, int y)//void Gotoxy(COORD pos)
{
printf ( "%c[%d;%df" ,0x1B,y,x);
}
//函数功能:设置颜色
bool SetConsoleColor(int back,int front)
{printf("\033[%dm",(front));return TRUE; 
}
//函数功能:显示柱子墙体
void CheckWall(COORD wall[])
{int i;srand(time(0));COORD temp = {wall[2].X + DIS, rand() % 13 + 5};//随机产生一个新的柱子if (wall[0].X < 10)  //超出预设的左边界{    //最左侧的柱子墙消失,第二个柱子变成第一个,第三个柱子变成第二个,新产生的柱子变成第三个/********** Begin **********/wall[0] = wall[1];//最左侧的柱子墙消失,第二个柱子变成第一个wall[1] = wall[2];//第三个柱子变成第二个wall[2] = temp;   //新产生的柱子变成第三个/********** End **********/}SetConsoleColor(40,31); //设置黑色背景,亮红色前景for (i=0; i<3; ++i)//每次显示三个柱子墙{//显示上半部分柱子墙temp.X = wall[i].X + 1;//向右缩进一格显示图案for (temp.Y=2; temp.Ypos.X, bird->pos.Y);printf("o->");
}
//函数功能:检测小鸟是否碰到墙体或者超出上下边界,是则返回0,否则分数加1并返回1
int CheckWin(COORD *wall, BIRD *bird)
{  /********** Begin **********/if (bird->pos.X >= wall->X) //小鸟的横坐标进入柱子坐标范围{if (bird->pos.Y <= wall->Y || bird->pos.Y >= wall->Y + BLAN) {return 0; //小鸟的纵坐标碰到上下柱子,则返回0}}if (bird->pos.Y < 1 || bird->pos.Y > 26) {return 0; //小鸟的位置超出上下边界,则返回0}(bird->score)++; //分数加1return 1;/********** End **********/
}
//函数功能:显示上下边界和分数
void Begin(BIRD *bird)
{system("clear");    Gotoxy(0, 26); //第26行显示下边界
printf("=========================================================="
"================================Bottom");
Gotoxy(0, 1); //第1行显示上边界
printf("=========================================================="
"================================Top");SetConsoleColor(40,33);//设置黑色背景,亮黄色前景printf("\n%4d", bird->score);//第1行显示分数
}

最后在liunx环境下即可完成游戏

相关内容

热门资讯

我发现了蚂蚁的秘密作文 我发现了蚂蚁的秘密作文(精选73篇)  在日常学习、工作和生活中,大家都接触过作文吧,借助作文可以提...
迪斯尼儿歌歌词:蛤蜊哟,淡菜 迪斯尼儿歌歌词:蛤蜊哟,淡菜  蛤蜊哟,淡菜  COCKLES AND MUSSELS  蛤蜊哟,淡...
优秀作文欣赏 优秀作文欣赏(精选10篇)  在日常学习、工作或生活中,许多人都写过作文吧,作文是由文字组成,经过人...
篮球王子作文800字 篮球王子作文800字  天空依然万里无云,从一家医院的手术室传出了一阵哭声,一个胖嘟嘟的小男孩顺利地...
三国人物的歇后语 三国人物有关的歇后语(精选80条)  歇后语是中国劳动人民自古以来在生活实践中创造的一种特殊语言形式...
献给母亲的诗 献给母亲的一首诗  在学习、工作乃至生活中,大家一定都接触过一些使用较为普遍的诗歌吧,诗歌是一种抒情...
黑夜里天空是愤怒的拳头作文 黑夜里天空是愤怒的拳头作文  在学习、工作或生活中,大家都不可避免地要接触到作文吧,作文是一种言语活...
他的父亲作文 他的父亲作文(5篇)  无论是身处学校还是步入社会,大家都有写作文的经历,对作文很是熟悉吧,作文是通...
向阳花木易逢春的作文 向阳花木易逢春的作文(通用26篇)  在学习、工作乃至生活中,大家总少不了接触作文吧,作文一定要做到...
以五一为话题的作文 以五一为话题的作文(精选26篇)  在学习、工作或生活中,大家都经常看到作文的身影吧,作文是经过人的...
责任与担当作文 责任与担当作文800字(精选25篇)  在学习、工作或生活中,大家都接触过作文吧,借助作文人们可以反...
离职申请书的离职原因 离职申请书的离职原因5篇  在经济飞速发展、人们往来越来越密切的今天,申请书使用的情况越来越多,申请...
月光下的村庄铁蛋作文 月光下的村庄铁蛋作文(精选25篇)  在日常的学习、工作、生活中,大家都尝试过写作文吧,作文一定要做...
优秀作文音乐人生 优秀作文音乐人生  音乐,是一种快乐;音乐,是一种享受;音乐,是我们童年的好朋友;音乐,也是我的人生...
动漫《镇魂街》插曲歌词 动漫《镇魂街》插曲歌词  闪耀 - 南征北战  (动漫《镇魂街》插曲)  作词:南征北战  作曲:南...
美好的旅行作文 美好的旅行作文  在日复一日的学习、工作或生活中,大家都不可避免地会接触到作文吧,作文是从内部言语向...
「励志故事」历数那些80后C... 「励志故事」历数那些80后CEO的校园生活  【马克·扎克伯格的校园生活】  年龄:26岁  净资产...
中考以奖品为话题作文600字 中考以奖品为话题作文600字(通用17篇)  在日常生活或是工作学习中,大家都跟作文打过交道吧,写作...
留一点微笑给自己作文 留一点微笑给自己作文600字(通用28篇)  在日常生活或是工作学习中,大家都跟作文打过交道吧,写作...
军训必唱歌曲《弹起我心爱的土... 军训必唱歌曲《弹起我心爱的土琵琶》歌词   《弹起我心爱的土琵琶》  西边的太阳快要落山了  微山湖...