目录
0.制作准备与制作结果
当我们想要制作一款游戏的时候除了必要的c语言知识等,我们还需要对自己的游戏功能与大概的制作过程做一个大概的归纳(大纲),然后根据自己的归纳(大纲)一步一步来制作
以下是鄙人的贪吃蛇大纲
先来给大家看一下制作结束后的样子如下图所示
想不想和你的好舍友来一把呀,想玩的话就好好学噢
一.开始制作前界面的设置
1.控制台的参数设置
想要制作贪吃蛇首先我们要学会对控制台参数的设置 调试窗口到底要好大要先设置好设置代码如下
system("mode con cols=100 lines=30");//30行 100列
system("title 饕餮");
getchar();
但是还有这个光标所以我们要再想办法去除光标的显示
2.光标显示信息设置
可以看下面这个函数GetConsoleCursorInfo
这个函数的参数如下
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标
所以我们在调用这个函数的时候首先要获取句柄(也就是第一个函数的参数)和一个CONSOLE_CURSOR_INFO结构体变量的地址
那么第一个HADLDE类型怎么得到呢,这里要用到GetStdHandle函数来获得HANDLE类型的变量
函数参数如下
HANDLE GetStdHandle(DWORD nStdHandle);
其中的DWORD其实是一个unsigned long获得句柄的操作如下
//获取标准输出的句柄(⽤来标识不同设备的数值)
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
获得了第一个句柄信息过后我们还要全去获得CONSOLE_CURSOR_INFO结构的的指针
CONSOLE_CURSOR_INFO结构体的定义如下
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
//dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。
// bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE。
所以我们先获取信息操作如下
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
获取后再进行设置在上面加上最后一句SetConsoleCursorInfo函数它的参数和GetConsoleCursorInfo差不多
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
这样我们就可以隐藏光标信息啦
ok但是这实在也是太枯燥了
我们直接跳转地图的打压
我们知道地图的打印是又一又一个的方块打印的
3.光标的位置(打印位置的设置)
像这样的地图我们该怎么去打印呢,如果打印第一排还好,但是如果去一列一列的打印和最后一排的打印该怎么办呢,这里就需要用到光标的位置设置
所以我们要用到这个函数SetConsoleCursorPosition
函数定义如下
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
很明显我们可以看到它需要句柄与一个COORD类型变量
其实这个COORD就是一个结构体定义如下
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
可以看到它其中的有着X与Y的信息所以我们可以如下图知道
所以我们可以先给坐标赋值
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };//可以谁便给x和y赋值
SetConsoleCursorPosition(hOutput, pos);
想这样我们就修改了原来的光标位置,但是如果每次都要这样来写是不是有些麻烦所以我们可以封装成一个函数
ok当我们可以控制光标位置后就可以进行地图的打印了
void InitMap()
{
int cont = 0;
//上墙打印
for(int i=0;i<=COLUMN;i+=2){
wprintf(L"%lc", WALL);
}
//下墙打印
SetPos(0, WIDTH);
for (int i = 0; i <= COLUMN; i += 2) {
wprintf(L"%lc", WALL);
}
//左墙打印
for (int i = 1; i <= WIDTH-1; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右墙打印
for (int i = 1; i < WIDTH; i++)
{
SetPos(COLUMN, i);
wprintf(L"%lc", WALL);
}
}
COLUMN就是列的意思,WIDTH是宽,这里又有一个小技巧,为了提高代码的可塑性,提高可读性与提高效率运用宏的定义将这些数值定义为宏以变后续的修改,这样我们的地图就很好的打印出来了。认真的朋友肯定发现了这里面的wprintf是什么,后面的L"%lc"又是什么这些其实是宽字符的打印
4.宽字符与本地化
宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应wprintf() 的占位符为 %ls 。
为什么要运用宽字符的知识呢这因为在我们打印蛇的身体是时候占的是两个字符,为了美观所以选择这样,因为在计算机的控制台上一个像素是是这样的
如果不像这样进行宽字符的打印将会非常的难看,并且影响游戏体验,所以要进行宽字符的打印
到这里其实如果是自己去尝试过的同学就会感觉到有些字符打印不出来!或者打印出来的值感觉不太对
这是因为没有进行本地化操作运用如下操作进行初始化
我们首先运用setlocale函数进行本地化
<locale.h>//需要的头文件
setlocale(LC_ALL, "");
5.获取按键情况(GetAsyncKeyState)函数
现在再让我们想一想当我们想要进行贪吃蛇游戏我们是不是一定会用键盘上的WASD或者键盘右边的↑↓←→,那我们应该怎么做才能让电脑实时获得按键情况呢,这里我们就要学习到这个
GetAsyncKeyState函数了,这个函数原型为
SHORT GetAsyncKeyState(
int vKey
);
然后我们需要知道一下它的返回规则
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果返回的16位的short数据中, 最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。如果我们要判断⼀个键是否被按过, 可以检测GetAsyncKeyState返回值的最低值是否为1
那怎么检测最低位是否为1呢,其实这里有一个很简单的方法,(GetAsyncKeyState(VK)&1)如果你还记得位运算符的话这个表达式,这里如果GetAsyncKeyState 的最低位返回值是1,就是1,最低位不是1就是0,也就是说可以用这个表达式去判断(假设VK为想要传递的值)
技巧
为了避免每次都去敲一遍这个复杂的函数我们可以选择将其定义位宏
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK))&1)
这样我们的前期准备工作才算基本完成,而真正的重头戏还在外面呢
二.蛇的属性的创建
1.了解在C语言控制台上蛇移动的逻辑与蛇的性质创建
如下图 可以很直观的看见如果想要实现这样的蛇的移动我们到底应该怎么去做
当我们想要移动蛇的时候,应该怎么去处理,其实蛇身的移动完全是可以看作是一个单链表的头插与尾删,当移动的下一个位置不是食物的时候进行头插与尾删,当下一个位置是食物的时候只进行头插,不进行尾删,那这个时候我们就会去思考了,那如果给蛇的每一个结点定义为一个结构体的类型让里面有指向下一个蛇结点的next,还有一个x横坐标,y纵坐标,如果没有坐标怎么进行打印呢是吧,这就是蛇的每一个结点的信息。然后在整个游戏过程还需要蛇的哪些信息呢,你可以一下子全部写出来,也可以一边实现一边去慢慢添加,这里我选择后者,首先让定义一个结构体,这个结构体将会包含游戏里所有的信息包括食物的信息,蛇身的状态等详细的在后面将会讲到
大致的蛇的属性如下(有些不懂也没关系下面将会用到并详细讲解)
typedef struct SnakeNode//蛇的结点信息
{
struct SnakeNode* next;
short x;
short y;
}SnakeNode,* pSnakeNode;
//蛇的信息
typedef struct Snake
{
pSnakeNode _pFood;
pSnakeNode _pBigFood;
int count;
int _foodWeight;
int _speed;//单位毫秒
//第一条蛇的信息
pSnakeNode _pSnakeHead;
int _score;
enum Direction _direction;//方向利用枚举提高可读性
enum SnakeState _state;//状态
}Snake,*pSnake;
enum Direction//定义枚举来容易理解
{
UP=1,
DOWN,
LEFT,
RIGHT
};
enum SnakeState//状态
{
DIE=0,
ALIVE,
KILLBYSELF,
KILLBYWALL,
KILLBYOPPONENT,
PAUSE,
ALLDIE,
VICTORY
};
2.蛇身的创建与第一个食物的创建
其实到这我们贪吃蛇的基本逻辑就已经完成了,但是想要生成食物的是随机生成的啊,这里就不过多解释这几个函数的原理了,只要运用这个表达式就可以生成随机数
食物的创建必须要素就是随机数的生成
srand((unsigned int)time(NULL));
然后在我们想要生成随机数的时候我们就可以只去调用rand()函数了,它的返回值就是一个随机数.
创建的身体与食物的位置区间
你真的以为会这么的简单吗,nonono,你错了我的朋友,不管是食物的生成还是蛇身的设计都是要考虑区间的只能在墙内进行生成,食物的生成不能与蛇重合等等,我的代码参考如下
void CreatFoodAndPrint(pSnake psnake)
{
assert(psnake);
int x = 0;
int y = 0;
//生成随机数
again://相等则重新生成
do {
x = rand() % (COLUMN - 2)+2;//列数
y = rand()%(WIDTH-1)+1;//行数
} while ((x&1)==1);//x为奇数继续循环
//遍历蛇身不能让食物生成与蛇重合
pSnakeNode cur = psnake->_pSnakeHead->next;
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
pSnakeNode pfood=BuySnakeNode(x,y);//新生成一个结点
psnake->_pFood = pfood;
SetPos(pfood->x, pfood->y);
wprintf(L"%lc", FOOD);
}
可以看到我们这里运用到了goto语句,说实话这也是我第一次在实际操作中使用
这个里面其实是有很多很多细节的,比如说生成的食物横坐标不能为奇数,为什么呢,其实你只要仔细看看图就理解了,一个食物是宽字符占两个像素,想要整齐打印横坐标必须为偶数
然后我这里是进行了宏的定义了的,因为如果你这样做可以提高代码的可塑性,当你想要修改墙的宽,长范围的时候不用再去管其他与其相关的位置
接下来就还需要让蛇蠕动动起来
由于篇幅限制将在下篇进行讲解!!!
小bit!!!