C语言FlappyBird飞扬的小鸟实现开发流程
莫浅子 人气: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 [行坐标;列坐标]。
//x 为行坐标 ,y 为列坐标
printf ( "%c[%d;%df" ,0x1B,y,x);
Windows 环境下光标定位
在 Windows
环境中,光标定位的方法有所不同,引入 windows.h 头文件,以下所使用的到的结构体或者函数均在存在于该头文件。
首先需要使用到 windows.h 中的 COORD
结构体,其定义为,
typedef struct _COORD {
SHORT X; // horizontal coordinate
SHORT Y; // vertical coordinate
} COORD;
再通过 GetStdHandle()
获得标准输出设备句柄 HANDLE
;
HANDLE hp = GetStdHandle(STD_OUTPUT_HANDLE);
最后通过 SetConsoleCursorPosition()
设置控制台光标位置。
//变量 pos 为 COORD 结构体对象
SetConsoleCursorPosition(hp, pos);
现在,我们可以在不同环境中,在不同位置进行打印输出。
代码
#include <stdio.h> #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; }
打印墙体
我们使用链表来存放墙体,链表是一种常用的数据结构,由若干个结点组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
typedef struct wall{
COORD pos;
struct wall *next;//指向下一链表
}WALL;
在这里我们要注意变量的生存周期,如果函数将变量内存地址存放在栈区的时候,当创建该变量的函数结束时,其内部创建在栈区的地址将被释放。
因此我们需要将结点申请在堆区,在 C 语言中,我们可以通过 malloc()
函数申请堆区,例如。
WALL *wall = (WALL *)malloc(sizeof(WALL));
当该变量不需要使用的时候,使用 free()
函数将其地址空间释放。
free(wall);
第 1 行和第 BOOTEM
行是上下边界,从第 2 行开始打印墙体,其中上下墙体间隔 BLANK
行。DISTANCE
为相邻两个墙体的间距,WIDTH
为墙体的宽度。
#define BOOTEM 26 //下边界的高度
#define DISTANCE 10 //两个墙体之间的距离
#define WIDTH 5 //墙体宽度
#define BLANK 10 //上下墙体之间的距离
墙体样式为,
prtNode(p, "*");
代码
#include "./head.h" //EVALUATING 宏定义 1 为 评测模式 0 为命令行模式 #define EVALUATING 1 typedef 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时,给其赋值2 int 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时,给其赋值2 rd = 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。
当小鸟与上下边界发生碰撞时,
//与上下边界发生碰撞
if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM)
当小鸟与墙体发生碰撞时,
//小鸟与墙体发生碰撞
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 <stdio.h> #include <stdlib.h> #include <curses.h> #include <time.h> #include <termios.h> #include <unistd.h> #include <fcntl.h> #include <stdbool.h> #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();//输入的字符存入ch printf("%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();//输入的字符存入ch printf("%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.Y<wall[i].Y; temp.Y++)//从第2行开始显示 { Gotoxy(temp.X, temp.Y); printf("■■■■■■"); } temp.X--;//向左移动一格显示图案 Gotoxy(temp.X, temp.Y); printf("■■■■■■■■"); //显示下半部分柱子墙 temp.Y += BLAN; Gotoxy(temp.X, temp.Y); printf("■■■■■■■■"); temp.X++; //向右缩进一格显示图案 temp.Y++; //在下一行显示下面的图案 for (; (temp.Y)<26; temp.Y++)//一直显示到第25行 { Gotoxy(temp.X, temp.Y); printf("■■■■■■"); } } Gotoxy(0, 26); printf("\n");//第1行显示分数 } //函数功能:显示小鸟 void PrtBird(BIRD *bird) { SetConsoleColor(40,33); //设置黑色背景,亮黄色前景 Gotoxy(bird->pos.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)++; //分数加1 return 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环境下即可完成游戏
加载全部内容