当前位置:网站首页>C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]
C语言游戏 双缓存解决闪屏问题 详细总结[通俗易懂]
2022-07-25 21:44:00 【全栈程序员站长】
大家好,又见面了,我是你们的朋友全栈君。
最近,应学校课程要求,要完成一个C语言课程设计。可以是写一个小游戏,或是写管理系统等。
所以,准备做一个改版贪吃蛇:消灭小虫虫(瞎起的名字 :D)。
之前学过Java,所以学C语言也就比较顺利。而在刚学完C语言刚着手准备做C语言的小游戏时,却发现了一个问题——闪屏。
(我在网上查找了很多关于双缓存,有关的解答很少,更少能够让一个完全不了解的小白一个明白的解释。下面我想和大家分享我使用双缓存完成了小游戏后的总结体会。希望能够一目了然。)
编辑器 —— Dev-C++ 5.11
先说一下,C语言来做游戏的原理: 就是在控制台打印图案,然后使用 system(“cls”); 来擦除界面,然后再打印图案的循环过程。
闪屏现象
我们正常打印输出内容的时候,是按顺序输出的。从第一个一直打印的最后一个。
当我们输出的内容十分庞大的时候,第一个和最后一个会存在输出时间差。
也就是前面先输出了,而后面你还没看到。所以会有闪屏的现象。
如何解决闪屏?
治标须治本——双缓存技术
何为双缓存?
我希望大家去看看这个网站:猛击这里
这个网站是我理解双缓存的主要网站,何为双缓存,这位作者写得还是比较易懂的。
不过怎么用?怎么能够用在我的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);
}
}运行结果:
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 );那结果是这样的:
还有这里我定义了COORD coord = {5,0};也就是初始输出点是<5,0>,又因为coord.Y = 1;所以最后coord = {5,1}
在上面输出结果中,我们还能看到有光标在闪动,如果是做游戏的话,这个光标是很碍眼的。所以就可以用我上面提到过的SetConsoleCursorInfo来隐藏光标。
以上我们用的还是一维数组,只输出一行内容。当然我们可以使用二维数组,直接循环输出以二维数组横坐标和纵坐标大小的面。如下图:
主要代码:
……
#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);
}动态更新数值:
主要代码:
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.cn/127926.html原文链接:https://javaforall.cn
边栏推荐
- 少儿编程 电子学会图形化编程等级考试Scratch一级真题解析(判断题)2022年6月
- Autojs learning - Automatic screenshot of the king
- 【leetcode天梯】链表 · 876 查找链表中间结点
- Creation and destruction of function stack frames
- Unity Metaverse(二)、Mixamo & Animator 混合树与动画融合
- 工作面试总遇秒杀? 看了京东 T8 大咖私藏的秒杀系统笔记, 已献出膝盖
- PHP zero time task, PHP laravel time task schedule [dry goods]
- In Oracle 19C version, logminer package continuous_ The outdated function of mine leads to CDC failure
- ES6 -- Deconstruction assignment
- 【面试:并发篇23:多线程:join】join再理解
猜你喜欢

Idea resolves the prompt of profile properties disappear

Optimization analysis of storage structure and IO performance of openharmony littlefs file system

Trusted and controllable way of Tencent cloud database
QT | learn about QT creator by creating a simple project

IJCAI2022开会了! 微软等《领域泛化Domain Generalization》教程

Why do independent sellers like to do e-mail marketing? The original conversion rate can be improved so much!

五、品达通用权限系统__pd-tools-xxs(防跨站脚本攻击)

函数栈帧的创建和销毁
![PHP zero time task, PHP laravel time task schedule [dry goods]](/img/09/c9a4c83fe23c852aa76a6f5a6cea52.png)
PHP zero time task, PHP laravel time task schedule [dry goods]

Byte side: can TCP and UDP use the same port?
随机推荐
【饭谈】Web3.0到来后,测试人员该何去何从?(十条预言和建议)
[MAIXPY]kpu: load error:2005, ERR_ READ_ File: read file failed problem solving
CNN structural design skills: taking into account speed accuracy and engineering implementation
【面试:并发篇25:多线程:volatile】可见性
[Flink] flick rocksdbliststate reports an error you cannot add null to a liststate
分享|智慧消防应急管理平台解决方案(附PDF)
Why do independent sellers like to do e-mail marketing? The original conversion rate can be improved so much!
On Web Performance Optimization (1)
Huawei occupies half of the folding mobile phone market, proving its irreplaceable position in the high-end market
MPI learning notes (II): two implementation methods of matrix multiplication
[leetcode ladder] linked list · 021 merge two ordered linked lists
919. Complete binary tree inserter: simple BFS application problem
Vivo official website app full model UI adaptation scheme
少儿编程 电子学会图形化编程等级考试Scratch一级真题解析(判断题)2022年6月
FAW red flag "King fried" is listed, which is safe and comfortable
【Redis底层解析】字符串类型
Excuse me, how to deal with repeated consumption of MySQL data
Reading the pointpillar code of openpcdet -- Part 3: Calculation of loss function
大厂面试官:千万级数据量的表,如何进行快速查询?
I/O案例实操