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)
上一篇 2022年4月10日 下午8:20
下一篇 2022年4月10日 下午8:40


相关推荐

  • 讯飞星火这次不玩虚的,新版X1直指AI的“无人区”

    讯飞星火这次不玩虚的,新版X1直指AI的“无人区”

    2026年3月14日
    2
  • SpringBoot整合Druid「建议收藏」

    SpringBoot整合Druid「建议收藏」SpringBoot整合DruidDruid简介配置数据源配置Druid数据源监控Druid数据源具有监控的功能,并提供了一个web界面方便用户查看,类似安装路由器时,人家也提供了一个默认的web页面。Druid简介Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。Druid是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0、DBCP等DB池的优点,同时加入了日志监控。Druid可以很好的监控DB池连接和SQL的执行情

    2022年7月23日
    26
  • 深度学习集成编译工具:IDLE与pycharm、Anaconda的关系

    深度学习集成编译工具:IDLE与pycharm、Anaconda的关系一 IDLE 与 pycharm Anaconda 的关系 IDLE 是开发 python 程序的基本 IDE 集成开发环境 IDLE 是你装完 Python 解释器后就可以用了 是一个自带集成开发环境 可以运行和调试一些简单的小程序 一般开始学习 Python 的时候用的比较多 但不适合做项目开发 Pycharm 是一种专门的 Python 集成开发软件 和微软的 VisualStudio 类似 只是 VS 用于 C 和 C

    2026年3月27日
    3
  • Java开发手册之代码格式

    Java开发手册之代码格式Java开发手册之代码格式

    2022年4月22日
    45
  • CSGO国内开箱网站大全incsgo skinsdog狗网 coolkaixiang 88steam「建议收藏」

    CSGO国内开箱网站大全incsgo skinsdog狗网 coolkaixiang 88steam「建议收藏」CSGO国内开箱网站大全收录incsgo官网,skinsdog狗网官网,coolkaixiang官网,88steam官网,Box818官网,Piggycase官网,Yskins官网incsgo国内CSGO饰品皮肤开箱网站官方链接:www.incsgo.gg注册登录自动免费获得$1.00美金取回状态:直接取回**优惠码:**csgogo(充值使用csgogo可增加5%充值金额)skinsdog狗网CSGO饰品皮肤开箱网站可直接取回官方链接:skinsdog.c.

    2022年10月6日
    4
  • C/C++程序内存的各种变量存储区域和各个区域详解

    C/C++程序内存的各种变量存储区域和各个区域详解C 语言在内存中一共分为如下几个区域 分别是 1 内存栈区 存放局部变量名 2 内存堆区 存放 new 或者 malloc 出来的对象 3 常数区 存放局部变量或者全局变量的值 4 静态区 用于存放全局变量或者静态变量 5 代码区 二进制代码 知道如上一些内存分配机制 有助于我们理解指针的概念 C C 不提供垃圾回收机制 因此需要对堆中的数据进行及时销毁 防止内存泄漏 使用 free 和 de

    2026年3月18日
    2

发表回复

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

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