目录

C语言-一步步教你做一个带有图形界面的冒险小游戏

C语言 一步步教你做一个带有图形界面的冒险小游戏

目录

简要介绍

本文没有繁难的代码,所以很适合想做游戏但不知道如何做游戏的同学食用~

游戏基本框架&主循环

要想做一个游戏,首先要弄清楚游戏的基本框架:

https://i-blog.csdnimg.cn/blog_migrate/832125dd6615bd0ee71a6708f8523df8.png

其中,

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()还没有写好,所以你还看不见~( ̄▽ ̄)~*)

https://i-blog.csdnimg.cn/blog_migrate/9aeb4298b2abfeeab89633fbc226252d.png

然后,为了能尽快看到效果,我们先把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};

效果:

https://i-blog.csdnimg.cn/blog_migrate/ebcbd4528f1dec8796a5b978f0e38207.png

对了,还要注意一件事情,就是我们定义的结构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写的很简单,所以游戏也很简单,如果可以的话,建议你也写一个自己的故事,然后自己设计一下角色图片,比如之前我做的第一个图形小游戏: (这个小游戏剧情也有一百多行呢~)

https://i-blog.csdnimg.cn/blog_migrate/a718cb307640d9a0f1bada3b36575850.png https://i-blog.csdnimg.cn/blog_migrate/d4e093f2edfc2964859cc8643b24ac40.png

https://i-blog.csdnimg.cn/blog_migrate/4a2c11aef86c9053ebb02c01eb6cfc55.png https://i-blog.csdnimg.cn/blog_migrate/68827b1da320e7b81f32ca18846c2528.png

消除闪屏

之所以会闪屏,原因就在于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);
}