C语言 一步步教你做一个带有图形界面的冒险小游戏
6771
目录
简要介绍
游戏基本框架&主循环
如何在控制台实现图形界面
实现游戏不断的更新
消除闪屏
最后对一遍代码~
简要介绍
本文没有繁难的代码,所以很适合想做游戏但不知道如何做游戏的同学食用~
游戏基本框架&主循环
要想做一个游戏,首先要弄清楚游戏的基本框架:
其中,
Model代表游戏模型,存储着游戏的所有信息;
Update每时每刻都在根据Model的自身状态来对Model进行更新;
Handle是根据外界输入来调整Model的信息;
Show是对Model的信息进行翻译,并呈现在玩家眼前;
根据以上的基本理念,我们可以这样写:
int main()
{
//先处理用户的输入,并更改Model的数据
handle();
//在根据Model自身的数据来对Model进行更新
update();
//最后将Model的内容提取并展示出来
show();
handle();
update();
show();
handle();
update();
show();
...
...
}
当然,肯定不能就一直这样重复写,为此,我们需要构造一个main loop来不断读取用户输入,更新Model信息,并将信息翻译展示到玩家面前。
为了实现每秒循环一定次数的效果,我们需要#include
#include
#define FPS 4
int main()
{
clock_t t1=0,t2=0;
while (1) {
t2 = clock();
if (t2-t1 > CLOCKS_PER_SEC*1.0/FPS) {
t1 = t2;
handle();
update();
show();
}
}
}
其中,除了三个基本函数handle,update,show外都是main loop的内容,这样便可以实现按一定的帧率(FPS)来进行游戏循环。
但是,一款游戏至少要做到随时响应玩家的输入,而现在的代码每隔1.0/FPS的时间才会响应一次玩家输入,因此我们需要多加这样几行代码:
#include
char key;
int main()
{
clock_t t1=0,t2=0;
while (1) {
t2 = clock();
if (_kbhit()) key = _getch();
if (t2-t1 > CLOCKS_PER_SEC*1.0/FPS) {
t1 = t2;
handle();
update();
show();
}
}
}
通过声明一个全局变量char key,同时又用if (_kbhit()) key = _getch()来每时每刻响应用户输入,如有输入便将用户的按键值传给key,这样handle所处理的内容便只是key的值。
OK,现在基本的东西已经搞好了,下面就可以随意发挥了!
如何在控制台实现图形界面
显然,我们并不打算去制作一款3D游戏(而且俺也不会啊(≧﹏ ≦)),而制作一款2D平面游戏的话,我们只需要一张画布即可。
首先,声明一块长宽固定的二维数组,将其当作画布(screen),然后构造一种结构类型作为演员(Actor),这样,游戏的Model就只需要存储每一位演员的样子和位置,再在需要show的时候把这些演员给渲染到screen上,最后把screen展示给玩家看即可。
#define WIDTH 100
#define HEIGHT 40
typedef struct _actor{
int width; //演员的宽度
int height; //演员的高度
char** image; //指向演员的图形
int x; //最左侧的位置
int y; //最顶部的位置
} Actor;
char screen[HEIGHT][WIDTH+1];
Actor boy; //创建一个名为boy的演员
int main()
{
...
...
}
现在,我们有一块画布宽为WIDTH+1,高为HEIGHT,之所以宽要加1,是想让每一行的最后一个字符存储的是'\n',这样在show的时候,我们可以先赋值screen[HEIGHT-1][WIDTH] = '\0',再直接把整块画布以'%s'的格式printf出来(是不是很棒的一个小技巧~)。
但是,相信大家已经注意到了,screen,key,boy这些全局变量在声明之后都还没有进行初始化,所以我们有必要统一地在一个函数里面进行全局变量的初始化。
#include
void init(void)
{
for (int i = 0; i key = '\0'; static char* image_boy[] = //加static是为了保证离开init函数后,boy的图像依旧会被保护 {" ~@~ ", " /BOY\\ ", "~ ### ~", " _/ \\_ "}; boy = (Actor){.x=2,.y=4,.width=strlen(image_boy[0]),.height=4,.image = image_boy}; } int main() { init(); //main loop ... { 最后运行的效果如下:(当然,现在show()还没有写好,所以你还看不见~( ̄▽ ̄)~*) 然后,为了能尽快看到效果,我们先把show()给写出来。(ヾ(•ω•`)o) show()应该包含两个部分:render()以及draw()。render即渲染,把演员给誊到画布上,draw即显示,把画布给玩家显示出来。 void show(void); void render(void); void clear(void); void load(Actor* actor); void draw(void); void show(void) { render();//渲染 system("cls");//清空控制台内容 draw();//显示 } void render(void) { clear();//先把画布弄干净 load(&boy);//把boy誊上画布 } void clear(void) { for (int i = 0; i< HEIGHT; i++) { for (int j = 0; j < WIDTH; j ++) { screen[i][j] = ' ';//用' '填充画布 } } } void load(Actor* actor) //传入actor的指针,而非actor本身,传输速度更快 { for (int i = 0; i for (int j = 0; j if (actor->image[i][j] != ' ') { //if条件的添加,使得boy的图片像是png图像一样,有透明像素 screen[actor->y+i][actor->x+j] = actor->image[i][j]; } } } } void draw(void) { screen[HEIGHT-1][WIDTH] = '\0'; printf("%s\n",screen); } 好了!现在你也能看到小boy了,是不是非常奈斯~ 最后我们对一下代码,看看是不是一样的: #include #include #include #include #include #define FPS 4 #define WIDTH 100 #define HEIGHT 40 typedef struct _actor{ int width; int height; char** image; int x; int y; } Actor; char screen[HEIGHT][WIDTH+1]; char key; Actor boy; void init(void); void handle(void); void update(void); void show(void); void render(void); void clear(void); void load(Actor* actor); void draw(void); int main() { init(); clock_t t1=0,t2=0; while (1) { t2 = clock(); if (_kbhit()) key = _getch(); if (t2-t1 > CLOCKS_PER_SEC*1.0/FPS) { t1 = t2; handle(); update(); show(); } } } void init(void) { for (int i = 0; i key = '\0'; static char* image_boy[] = {" ~@~ ", " /BOY\\ ", "~ ### ~", " _/ \\_ "}; boy = (Actor){.x=2,.y=4,.width=strlen(image_boy[0]),.height=4,.image = image_boy}; } void handle(void) { } void update(void) { } void show(void) { render(); system("cls"); draw(); } void render(void) { clear(); load(&boy); } void clear(void) { for (int i = 0; i< HEIGHT; i++) { for (int j = 0; j < WIDTH; j ++) { screen[i][j] = ' '; } } } void load(Actor* actor) { for (int i = 0; i for (int j = 0; j if (actor->image[i][j] != ' ') { screen[actor->y+i][actor->x+j] = actor->image[i][j]; } } } } void draw(void) { screen[HEIGHT-1][WIDTH] = '\0'; printf("%s\n",screen); } 实现游戏不断的更新 现在,我们只剩下handle和update没有写了,之所以最后写,是因为这两个函数是十分灵活的,完全取决于你的游戏内容。 那我就写一个我想的故事吧(。・ω・。) 先介绍一下所有参演角色: static char* image_boy[] = {" ~@~ ", " /BOY\\ ", "~ ### ~", " _/ \\_ "}; boy = (Actor){.x=4,.y=22,.width=strlen(image_boy[0]),.height=4,.image = image_boy}; static char* image_bed[] = {"| |", "| |", "+#############################+", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "+#############################+", "| |", "| |"}; bed = (Actor){.x=50,.y=10,.width=strlen(image_bed[0]),.height=14,.image=image_bed}; static char* image_guard[] = {"! @ T", "*~~GUARD~~+", " \\###/ |", " /###\\ |", " L L |"}; guard = (Actor){.x=65,.y=16,.width=strlen(image_guard[0]),.height=5,.image = image_guard}; static char* image_ground[] = {"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"}; ground_1 = (Actor){.x=0,.y=30,.width=strlen(image_ground[0]),.height=1,.image=image_ground}; ground_2 = (Actor){.x=12,.y=23,.width=strlen(image_ground[0]),.height=1,.image=image_ground}; 效果: 对了,还要注意一件事情,就是我们定义的结构Actor还是比较简陋的,因为角色可能有很多状态,比如说,我可以搞一个叫做Monster的结构,在Actor的基础上加入血量,等级,防御力,状态等等内容,然后可以在初始化的时候声明出Monster许多的形态,比如受伤,愉悦等等。 然后就要写handle和update了,,, 好累,,写一整天了教程了,能不能赏一个赞再看(′д` )…卑微新人还没有被赞过呢...感谢~~ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ void handle(void) { switch (key) { case 'w': if (boy.y > 20) { boy.y --; } break; case 'a': if (boy.x > 0) { boy.x --; } break; case 's': if (boy.y < 26) { boy.y ++; } break; case 'd': if (boy.x < 90) { boy.x ++; } break; } key = '\0'; } void update(void) { if (boy.x >= guard.x-boy.width && boy.x <= guard.x+guard.width) { //boy进入guard的攻击范围 guard.y += 2;//冲撞 if (boy.y <= guard.y+guard.height) { //boy被撞到了 static char* image_boy[] = {" ~www", " (>_<) ", " ~###~ ", " / \\ "}; boy.image = image_boy;//更换表情 boy.x -= 10;//被撞飞 } } } 当然啦,这个update写的很简单,所以游戏也很简单,如果可以的话,建议你也写一个自己的故事,然后自己设计一下角色图片,比如之前我做的第一个图形小游戏: (这个小游戏剧情也有一百多行呢~) 消除闪屏 之所以会闪屏,原因就在于show()里面的system("cls")会导致整个界面会清空,所以我们可以不采用清空的做法,而是采用覆盖式显示,每次draw之前将光标移到控制台的最开始的位置,然后什么都不管,直接覆盖掉原有的图像。 所以很简单,只用把原来的system("cls")改成移动光标到开始处即可,别忘了#include system("cls"); ————>>> HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { 0,0 }; SetConsoleCursorPosition(hOut, pos); 最后对一遍代码~ #include #include #include #include #include #include #define FPS 4 #define WIDTH 100 #define HEIGHT 40 typedef struct _actor{ int width; int height; char** image; int x; int y; } Actor; char screen[HEIGHT][WIDTH+1]; char key; Actor boy; Actor bed; Actor guard; Actor ground_1; Actor ground_2; void init(void); void handle(void); void update(void); void show(void); void render(void); void clear(void); void load(Actor* actor); void draw(void); int main() { init(); clock_t t1=0,t2=0; while (1) { t2 = clock(); if (_kbhit()) key = _getch(); if (t2-t1 > CLOCKS_PER_SEC*1.0/FPS) { t1 = t2; handle(); update(); show(); } } } void init(void) { for (int i = 0; i key = '\0'; static char* image_boy[] = {" ~@~ ", " /BOY\\ ", "~ ### ~", " _/ \\_ "}; boy = (Actor){.x=4,.y=22,.width=strlen(image_boy[0]),.height=4,.image = image_boy}; static char* image_bed[] = {"| |", "| |", "+#############################+", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "| |__| |", "+#############################+", "| |", "| |"}; bed = (Actor){.x=50,.y=10,.width=strlen(image_bed[0]),.height=14,.image=image_bed}; static char* image_guard[] = {"! @ T", "*~~GUARD~~+", " \\###/ |", " /###\\ |", " L L |"}; guard = (Actor){.x=65,.y=16,.width=strlen(image_guard[0]),.height=5,.image = image_guard}; static char* image_ground[] = {"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"}; ground_1 = (Actor){.x=0,.y=30,.width=strlen(image_ground[0]),.height=1,.image=image_ground}; ground_2 = (Actor){.x=12,.y=23,.width=strlen(image_ground[0]),.height=1,.image=image_ground}; } void handle(void) { switch (key) { case 'w': if (boy.y > 20) { boy.y --; } break; case 'a': if (boy.x > 0) { boy.x --; } break; case 's': if (boy.y < 26) { boy.y ++; } break; case 'd': if (boy.x < 90) { boy.x ++; } break; } key = '\0'; } void update(void) { if (boy.x >= guard.x-boy.width && boy.x <= guard.x+guard.width) { guard.y += 2; if (boy.y <= guard.y+guard.height) { static char* image_boy[] = {" ~www", " (>_<) ", " ~###~ ", " / \\ "}; boy.image = image_boy; boy.x -= 10; } } } void show(void) { render(); HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { 0,0 }; SetConsoleCursorPosition(hOut, pos); draw(); } void render(void) { clear(); load(&ground_1); load(&ground_2); load(&bed); load(&guard); load(&boy); } void clear(void) { for (int i = 0; i< HEIGHT; i++) { for (int j = 0; j < WIDTH; j ++) { screen[i][j] = ' '; } } } void load(Actor* actor) { for (int i = 0; i for (int j = 0; j if (actor->image[i][j] != ' ') { screen[actor->y+i][actor->x+j] = actor->image[i][j]; } } } } void draw(void) { screen[HEIGHT-1][WIDTH] = '\0'; printf("%s\n",screen); }