C语言-一步步教你做一个带有图形界面的冒险小游戏
C语言 一步步教你做一个带有图形界面的冒险小游戏
目录
简要介绍
本文没有繁难的代码,所以很适合想做游戏但不知道如何做游戏的同学食用~
游戏基本框架&主循环
要想做一个游戏,首先要弄清楚游戏的基本框架:
其中,
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 <time.h>来构造主循环,具体如下:
#include <time.h>
#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 <conio.h>
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 <string.h>//由于用到了strlen函数
void init(void)
{
for (int i = 0; i<HEIGHT; i++) screen[i][WIDTH] = '\n';
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<actor->height; i++) {
for (int j = 0; j<actor->width; 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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <conio.h>
#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<HEIGHT; i++) screen[i][WIDTH] = '\n';
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<actor->height; i++) {
for (int j = 0; j<actor->width; 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 <windows.h>
system("cls");
————»>
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { 0,0 };
SetConsoleCursorPosition(hOut, pos);
最后对一遍代码~
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
#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<HEIGHT; i++) screen[i][WIDTH] = '\n';
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<actor->height; i++) {
for (int j = 0; j<actor->width; 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);
}