文章目录
- 1. 游戏背景
- 2. 游戏效果演示
- 3. 项目目标
- 4. 前置知识
- 5. Win32 API
- 5. 1 控制台程序(Console)
- 5. 2 控制台屏幕上的坐标 `COORD`
- 5. 3 GetStdHandle
- 5. 4 GetConsoleCursorlnfo
- 5. 4. 1 CONSOLE_CURSOR_INFO
- 5. 4. 2 SetConsoleCursorlnfo
- 5. 5 SetconsoleCursorPosition
- 5. 6 GetAsyncKeyState
1. 游戏背景
贪吃蛇是久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列。
2. 游戏效果演示
贪吃蛇演示视频
3. 项目目标
使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。
实现基本的功能:
- 贪吃蛇地图绘制
- (上、下、左、右方向键控制蛇的动作)
- 蛇吃食物
- 蛇撞墙死亡
- 蛇撞自身死亡
- 计算得分
- 再来一把
- 加速、减速
- 暂停游戏
4. 前置知识
C语言函数、结构体与枚举、动态内存管理、预处理指令、单链表、Win32 API(本文介绍)等(除了这些,还应该熟知C语言的基本语法,比如操作符什么的,就不一一列举了)。
5. Win32 API
Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启窗口、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便称之为 Application Programming Interface
,简称API函数。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口。
win32API提供了非常多的接口以供使用,本博客只介绍在贪吃蛇游戏中会用到的接口。
win32API不同于C语言基础语法,大多都是封装好的函数,初次接触这样的开发可能会有些头晕,但这是向制作更复杂的项目的必经之路,也是学习更高级的语言的必然过程,需要尝试去接受。
5. 1 控制台程序(Console)
平常我们运行起来的黑框程序其实就是控制台程序。
我们可以使用cmd
命令来设置控制台窗口的长宽:设置控制台窗口的大小为30行,100列
mode con cols=100 lines=30
你可以在微软开发文档中获取更多mode
命令。
也可以通过命令设置控制台窗口的名字:
title 贪吃蛇
你可以在微软开发文档中详细了解title
命令。
这些能在控制台窗口执行的命令,也可以调用C语言函数system
来执行。例如:
#include <stdio.h>
int main()
{
//设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列
system("mode con cols=100 lines=30");
//设置cmd窗口名称
system("title 贪吃蛇");
getchar(); //让程序不要直接结束,才能看到窗口名称的改变
return 0;
}
这样就能在游戏开始前固定窗口大小与名称了。
补充:
本文代码均在cmd
窗口中有效,如果你的编译器默认打开的不是cmd
而是终端,可能会出现一些奇怪的问题,你可以按照下图方式修改:
5. 2 控制台屏幕上的坐标 COORD
COORD
是Windows API
中定义的一个结构体,表示一个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)
的原点位于缓冲区的顶部左侧单元格。
COORD
类型的声明:
typedef struct _COORD {
short X;
short Y;
} COORD, *PCOORD;
给坐标赋值:
COORD pos = { 10,15 };
关于它的使用,我们在后文再提。
5. 3 GetStdHandle
GetStdHandle 是一个Windows API函数。它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(HANDLE
,用来标识不同设备的数值,必须要有句柄我们才能对标准设备进行操作),使用这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);
使用示例:
//本代码用于获取标准输出流的句柄并存储在 houtput 中
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
5. 4 GetConsoleCursorlnfo
GetConsoleCursorlnfo用于检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
); //不要在意它的返回值类型,我们重点关注它的参数
PCONSOLE_CURSOR_INFO
是指向 CONSOLE_CURSOR_INFO
结构的指针,该结构接收有关主机游标(光标)的信息。
5. 4. 1 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
。
CursorInfo.bVisible = false;//隐藏控制台光标
当然,直接写这个代码是无法生效的!需要配合GetConsoleCursorlnfo
和下面的SetConsoleCursorlnfo
使用。
5. 4. 2 SetConsoleCursorlnfo
设置指定控制台屏幕缓冲区的光标的大小和可见性。
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO* lpConsoleCursorInfo
); //在意这个返回值
以上接口配合使用:
//获取句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏光标操作
//创建接受光标信息的变量
CONSOLE_CURSOR_INFO CursorInfo;
//获取控制台光标信息
GetConsoleCursorInfo(hOutput, &CursorInfo);
//隐藏控制台光标
CursorInfo.bVisible = false;
//设置控制台光标状态
SetConsoleCursorInfo(hOutput, &CursorInfo);
这是设置控制台光标不可见的固定格式,不可修改!(自定变量名除外)
5. 5 SetconsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD
类型的pos
变量中,调用SetConsoleCursorPosition
函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
使用示例:
COORD pos = { 10, 5 };
//获取标准输出的句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为 pos
SetConsoleCursorPosition(hOutput, pos);
在贪吃蛇的项目中,我们会非常频繁地设置光标的位置,为了使用方便,我们可以封装一个函数来方便我们使用:
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
//获取标准输出的句柄(用来标识不同设备的数值)
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为 pos
SetConsoleCursorPosition(hOutput, pos);
}
这样,我们只需要传入x,y就可以很方便地设置光标的位置了。
5. 6 GetAsyncKeyState
GetAsyncKeystate用于2获取按键情况,它的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起,如果最低位被置为1则说明,该按键被按过,否则为0。如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1。
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
你可以使用这个宏来方便地判断这个键是否被按下,当然你也可以用函数,但是这样简单的逻辑并没有太大的必要去使用函数。
参考:虚拟键码(注意只有表格第一列有虚拟键值的键才能被这个接口检测)
实例:检测数字键
#include <stdio.h>
#include <windows.h>
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
int main()
{
while (1)
{
if (KEY_PRESS(0x30)) //0
printf("0\n");
else if (KEY_PRESS(0x31)) //1
printf("1\n");
else if (KEY_PRESS(0x32)) //2
printf("2\n");
else if (KEY_PRESS(0x33)) //3
printf("3\n");
else if (KEY_PRESS(0x34)) //4
printf("4\n");
else if (KEY_PRESS(0x35)) //5
printf("5\n");
else if (KEY_PRESS(0x36)) //6
printf("6\n");
else if (KEY_PRESS(0x37)) //7
printf("7\n");
else if (KEY_PRESS(0x38)) //8
printf("8\n");
else if (KEY_PRESS(0x39)) //9
printf("9\n");
}
return 0;
}
这个代码的功能就是在按下键盘上方的数字(小数字键盘无效)时,在屏幕上打印一个对应的数字,注意全程没有使用scanf
或getchar
。
贪吃蛇所需要用到的win32库基本只涉及到这些,下篇博客将开始正式设计贪吃蛇游戏。
谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章