C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

最近,应学校课程要求,要完成一个C语言课程设计。可以是写一个小游戏,或是写管理系统等。所以,准备做一个改版贪吃蛇:消灭小虫虫(瞎起的名字:D)。之前学过Java,所以学C语言也就比较顺利。而在刚学完C语言刚着手准备做C语言的小游戏时,却发现了一个问题——闪屏。(我在网上查找了很多关于双缓存,有关的解答很少,更少能够让一个完全不了解的小白一个明白的解释。下面我想和大家分享我使用…

大家好,又见面了,我是你们的朋友全栈君。

最近,应学校课程要求,要完成一个C语言课程设计。可以是写一个小游戏,或是写管理系统等。

所以,准备做一个改版贪吃蛇:消灭小虫虫(瞎起的名字  :D)。

之前学过Java,所以学C语言也就比较顺利。而在刚学完C语言刚着手准备做C语言的小游戏时,却发现了一个问题——闪屏

(我在网上查找了很多关于双缓存,有关的解答很少,更少能够让一个完全不了解的小白一个明白的解释。下面我想和大家分享我使用双缓存完成了小游戏后的总结体会。希望能够一目了然。)

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

编辑器 —— Dev-C++ 5.11


先说一下,C语言来做游戏的原理:

就是在控制台打印图案,然后使用 system(“cls”); 来擦除界面,然后再打印图案的循环过程。

闪屏现象

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

我们正常打印输出内容的时候,是按顺序输出的。从第一个一直打印的最后一个。

当我们输出的内容十分庞大的时候,第一个和最后一个会存在输出时间差。

也就是前面先输出了,而后面你还没看到。所以会有闪屏的现象。

如何解决闪屏?

治标须治本——双缓存技术

何为双缓存?

我希望大家去看看这个网站:猛击这里

这个网站是我理解双缓存的主要网站,何为双缓存,这位作者写得还是比较易懂的。

不过怎么用?怎么能够用在我的C语言小游戏上?还是会让人一头雾水。

(下面只针对双缓存的实现分享我的总结,不对这个游戏的原理做详解。如果有同学想了解贪吃蛇的实现原理可以去看这位笔者:猛击这里  我的消灭小虫虫以及双缓存的学习也有借鉴他。)


Win32 API

#include<windows.h>  头文件引用

双缓存技术主要使用到了Win32 API

用到的函数有:CreateConsoleScreenBuffer、WriteConsoleOutputCharacter、ReadConsoleOutputCharacter、SetConsoleActiveScreenBuffer、SetConsoleCursorInfo

官方API文档:猛击这里

CreateConsoleScreenBuffer

简单来说就是 初始化新缓存,并配置新缓存参数。

HANDLE WINAPI CreateConsoleScreenBuffer(
  _In_             DWORD               dwDesiredAccess,
  _In_             DWORD               dwShareMode,
  _In_opt_   const SECURITY_ATTRIBUTES *lpSecurityAttributes,
  _In_             DWORD               dwFlags,
  _Reserved_       LPVOID              lpScreenBufferData
);
dwDesiredAccess:控制台缓冲安全与访问权限,可取值:
    GENERIC_READ (0x80000000L),读权限
    GENERIC_WRITE (0x40000000L),写权限
dwShareMode:共享模式,可取值:
    FILE_SHARE_READ:读共享
    FILE_SHARE_WRITE:写共享
lpSecurityAttributes:安全属性,NULL
dwFlags:缓冲区类型,仅可选:
    CONSOLE_TEXTMODE_BUFFER,控制台文本模式缓冲
lpScreenBufferData:保留,NULL

范例:

//具体使用范例
hOutBuf = CreateConsoleScreenBuffer(
	GENERIC_WRITE,  //对控制台屏幕缓冲区的访问
	FILE_SHARE_WRITE, //定义缓冲区可共享写权限
	NULL,//安全属性默认为NULL 
	CONSOLE_TEXTMODE_BUFFER,//缓冲区类型,固定参数 
	NULL
);

//第一个缓存区赋值为hOutBuf,一般是创建两个缓存区(我这命名第二缓存区为:hOutput)

hOutput  = CreateConsoleScreenBuffer(
	GENERIC_WRITE,  //对控制台屏幕缓冲区的访问
	FILE_SHARE_WRITE, //定义缓冲区可共享写权限
	NULL,//安全属性默认为NULL 
	CONSOLE_TEXTMODE_BUFFER,//缓冲区类型,固定参数 
	NULL
);

WriteConsoleOutputCharacter

指定一个缓存区,将需要输出的内容(这规定的类型是字符数组)输出到控制台。

BOOL WINAPI WriteConsoleOutputCharacter(
  _In_  HANDLE  hConsoleOutput,   //控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_WRITE访问权限。
  _In_  LPCTSTR lpCharacter,      //写入的字符数组指针
  _In_  DWORD   nLength,          //写入的长度
  _In_  COORD   dwWriteCoord,     //写入起始坐标,  一个COORD结构(后面讲)
  _Out_ LPDWORD lpNumberOfCharsWritten  //指向变量的指针,该变量接收实际写入的字符数。
);

范例:

char score_char1[] = "012345678901234567890123456789";
coord.Y = 1;//第一行位置输出
WriteConsoleOutputCharacter( hOutBuf, score_char1, strlen(score_char1), coord, &bytes );
//之前全局变量定义了: COORD coord = {0,0};  DWORD bytes = 0;

COORD:

typedef struct _COORD {
SHORT X; // 横坐标
SHORT Y; // 纵坐标
} COORD;
//使用范例
COORD coord = {0,0};

ReadConsoleOutputCharacter

指定缓存区,读取控制台内容输出到字符数组。

用法和WriteConsoleOutputCharacterA相同,不做范例。

SetConsoleActiveScreenBuffer

双缓存,顾名思义就是有两个缓存。那么这个函数就是用来切换两个缓存的。

//设置控制台活动显示缓冲
BOOL WINAPI SetConsoleActiveScreenBuffer(
  _In_ HANDLE hConsoleOutput //hConsoleOutput:控制台输出设备句柄
);

范例:

SetConsoleActiveScreenBuffer(hOutBuf);//设置hOutBuf为活动显示的缓冲区

//*...这里是设置不同缓存区的内容等操作的代码...*//

SetConsoleActiveScreenBuffer(hOutput);//设置hOutput为活动显示的缓冲区,即实现了切换缓冲区

SetConsoleCursorInfo

这是一个设置光标的函数:大小,可见度。

BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,  //控制台输出设备句柄
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo //光标信息(大小、可见性)
);

范例:

//隐藏两个缓冲区的光标
CONSOLE_CURSOR_INFO cci;
cci.bVisible = 0; // 可见度
cci.dwSize =1;// 大小
SetConsoleCursorInfo(hOutput, &cci);
SetConsoleCursorInfo(hOutBuf, &cci);

/*注: 这里的CONSOLE_CURSOR_INFO结构体如下:
typedef struct _CONSOLE_CURSOR_INFO {
  DWORD dwSize;// 光标百分比厚度(1~100) 
  BOOL  bVisible;// 可见性 FALSE,0,不可见;TRUE,1,可见
  } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO; */

总体代码:

#include<stdio.h>
#include<windows.h>

HANDLE hOutput,hOutBuf;  //控制台屏幕缓冲区句柄
HANDLE houtpoint;
COORD coord = {5,0};//初始输出位置 
DWORD bytes = 0;
int hop_flag = 0;  //通过指针轮流指向两个缓冲区,实现双缓冲 

void printPic();

int main(){
	hOutBuf = CreateConsoleScreenBuffer(
	    GENERIC_WRITE,  
	    FILE_SHARE_WRITE, 
	    NULL, 
	    CONSOLE_TEXTMODE_BUFFER,
	    NULL
    );
    hOutput = CreateConsoleScreenBuffer(
        GENERIC_WRITE,  
        FILE_SHARE_WRITE, 
        NULL,
        CONSOLE_TEXTMODE_BUFFER,
        NULL
    );
    
    while(1){
		printPic();
		Sleep(600);
	}
} 

void printPic(){
	hop_flag = !hop_flag; 
	if(!hop_flag){
		char score_char1[] = "这是一个缓存区显示内容!11111111";
		coord.Y = 1;
	        WriteConsoleOutputCharacter( hOutBuf, score_char1, strlen(score_char1), coord, &bytes );
		SetConsoleActiveScreenBuffer(hOutBuf);
	}else{
		char score_char2[] = "这是另一个缓存区显示内容!22222222";
		coord.Y = 1;
    	        WriteConsoleOutputCharacter( hOutput, score_char2, strlen(score_char2), coord, &bytes );
		SetConsoleActiveScreenBuffer(hOutput);
	}
	
	
}

运行结果:

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

WriteConsoleOutputCharacter( hOutBuf, score_char1, strlen(score_char1), coord, &bytes );

在这里,输出的是字符数组score_char1,用strlen()获得字符数组长度。当然这个要看你想要输出的长度。如果我改成:strlen(score_char1)-10

WriteConsoleOutputCharacter( hOutBuf, score_char1, strlen(score_char1)-10, coord, &bytes );

那结果是这样的:

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

还有这里我定义了COORD coord = {5,0};也就是初始输出点是<5,0>,又因为coord.Y = 1;所以最后coord = {5,1}

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

在上面输出结果中,我们还能看到有光标在闪动,如果是做游戏的话,这个光标是很碍眼的。所以就可以用我上面提到过的SetConsoleCursorInfo来隐藏光标。


以上我们用的还是一维数组,只输出一行内容。当然我们可以使用二维数组,直接循环输出以二维数组横坐标和纵坐标大小的面。如下图:

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

主要代码:

……
#define _Y 15  //15行 
#define _X 20  // 20列 

char data[_Y][_X];//这是全局变量定义的字符数组
……


int main(){
    ……//这里的代码不变,和上面一样
}

void printPic(){
	int i,j;
	hop_flag = !hop_flag;
	if(!hop_flag){    //这里是每次交替,直接把hOutput或hOutBuf赋给houtpoint 
		houtpoint = hOutput;
	}else{
		houtpoint = hOutBuf;
	}
    
	for(i = 0;i < _Y;i++){    //打印你需要的二维数组图案
		for(j = 0;j < _X;j++){
			if(i == 0|| i == _Y-1 || j == 0 || j == _X-1){
				data[i][j] = '*';
			}else{
				data[i][j] = ' ';
			}
		}
	}
	coord.Y = 1;
	for(i = 0;i < _Y;i++){    //循环打印每一行
		coord.Y++;    //每次都打印到下一行
    	WriteConsoleOutputCharacter( houtpoint, data[i], _X, coord, &bytes );
	}    //data[i]:每行的地址。 _X: 每行的长度

	SetConsoleActiveScreenBuffer(houtpoint);
}

动态更新数值:

C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]

主要代码:

int key = 0;//计数器
……

int main(){
    ……//这里的代码不变,和上面一样
}

void printPic(){
	hop_flag = !hop_flag; 
	if(!hop_flag){
		houtpoint = hOutBuf;
	}else{
		houtpoint = hOutput;
	}
	
	key++;
	char score_char1[] = "Score:";
	char score_char2[10];
	itoa(key,score_char2,10);//将整型key转换成字符串,存入score_char2,10为十进制转换
	strcat(score_char1,score_char2);//合并两个字符数组
	
	coord.Y = 1;
	for(int i=0;i<20;i++){//这里循环只是为让大家能看出真的不闪屏
		coord.Y++;
    	WriteConsoleOutputCharacter( houtpoint, score_char1, strlen(score_char1), coord, &bytes );
	}	
	SetConsoleActiveScreenBuffer(houtpoint);
	
}

看了这么多我相信你们也可以使用C语言写出一个小游戏咯~

在这也感谢其他博主的经验,希望大家一起加油~

如有错误之处,虚心接受~

???

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/127926.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 本地计算机上的task scheduler服务启动后停止_task

    本地计算机上的task scheduler服务启动后停止_task1.如果对应服务依赖都正常情况下,请参考下面解决方案进入注册表(cmd–>regedit,依次找到HKEY_LOCAL_MACHINE\HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\RPC\INTERNET,删除INTERNET,重启服务器注:删除前请导出备份…

    2022年10月11日
    0
  • 转运公司比较_横向对比是什么意思

    转运公司比较_横向对比是什么意思http://post.smzdm.com/p/49324/小编注:日本JChere株式会社是一家大型的网络媒体公司,在国内也设有分支机构,业务众多。JChere转运是其下属业务之一,文中作者会进行一些简单介绍。开篇序:由于撸主最近收了几个吐血包裹,心情激荡之下决定来分享一下日淘转运的经验,主要是五家转运各自的特色和优势、横向对比、增值服务如何选择之类的问题。日淘的转运公司

    2022年10月4日
    0
  • 通俗理解LDA主题模型

    通俗理解LDA主题模型0前言印象中,最开始听说“LDA”这个名词,是缘于rickjin在2013年3月写的一个LDA科普系列,叫LDA数学八卦,我当时一直想看来着,记得还打印过一次,但不知是因为这篇文档的前序铺垫太长(现在才意识到这些“铺垫”都是深刻理解LDA的基础,但如果没有人帮助初学者提纲挈领、把握主次、理清思路,则很容易陷入…

    2022年4月6日
    54
  • Python 从菜鸟到大咖的必经之路「建议收藏」

    目录一、模块和包1.1模块的基础知识1.2模块的导入1.3使用第三方模块1.4包二、文件和目录操作2.1open()函数——打开文件并返回文件对象2.2文件操作的常用方法2.3应用三、面向对象3.1面向对象基础语法3.2初始化方法__init__3.3属性查找与绑定方法3.4案例3.4.1跑步案例3.4.2家具案例3.5私有属性3.6继承3.6.1面向对象的三大特性3.6.2单继承3.6.2.1继承的概念3.6.2.2继承的语法3.6.2.3方法的重写3.6.2.4

    2022年4月11日
    43
  • PHPSTORM去除警告波浪线的方法

    PHPSTORM去除警告波浪线的方法

    2022年2月19日
    53
  • python信号处理库_python开源协议

    python信号处理库_python开源协议本发明涉及的是一种在用电采集终端上实现标准MBUS协议接口用来采集水表、热量表、气表数据的方法,具体涉及一种基于MBUS标准协议接口模块采集水、热、气表的方法,属于用电信息采集领域。背景技术:我国正处于自动化楼宇建设事业蓬勃发展的时期,远程抄表系统作为其中重要的一环,正朝着自动化及智能化方向发展;MBUS(仪表总线)总线作为一种通讯方式,以其高性价比,在水热测量仪表中得到了广泛的应用,大大推动了远…

    2022年10月15日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号