用ReadConsoleOutput 解决双缓冲ReadConsoleOutputCharacterA 函数只读取字符,转义字符被过滤导致的颜色丢失问题

 起因很简单,想换RPG颜色,但是游戏里没有

于是就查,结果不知道查什么,后来灵光乍现,把 ReadConsoleOutputCharacterA 截取部分

搜ReadConsoleOutput,发现真有,参考

请教关于函数ReadConsoleOutput()问题-CSDN社区 

超过来代码,和颜色缓冲排列组合一些一下,看看会对printf打印颜色有什么影响

真行。但是结构体还没来得及解释。后续再看看。 

#include <iostream>
#include <string.h>
#include <windows.h>
#include <time.h>
#include <stdio.h>
#define KEY_DOWN(vKey) ((GetAsyncKeyState(vKey) & 0x8000) ? 1:0)							// 判断是否按下按键,按下之后,最高位变成 1,所以需要位运算去除其他位,只检测最高位
#define KEY_DOWN_FOREGROUND(hWnd, vk) (KEY_DOWN(vk) && GetForegroundWindow() == hWnd) 		// 前景窗口判断
#pragma warning(disable : 4996)																// 便于移植到非微软的编译器里 
using namespace std;

//颜色宏定义
#define NONE         "\033[m"
#define RED          "\033[0;32;31m"
#define LIGHT_RED    "\033[1;31m"
#define GREEN        "\033[0;32;32m"
#define LIGHT_GREEN  "\033[1;32m"
#define BLUE         "\033[0;32;34m"
#define LIGHT_BLUE   "\033[1;34m"
#define DARY_GRAY    "\033[1;30m"
#define CYAN         "\033[0;36m"
#define LIGHT_CYAN   "\033[1;36m"
#define PURPLE       "\033[0;35m"
#define LIGHT_PURPLE "\033[1;35m"
#define BROWN        "\033[0;33m"
#define YELLOW       "\033[1;33m"
#define LIGHT_GRAY   "\033[0;37m"
#define WHITE        "\033[1;37m"

// 加入的游戏背景数据,通常不修改,放到 Bkmap 进行修改
class Gamemap {
public:
	Gamemap(int height, int wide) {
		this->wide = wide;
		this->height = height;
		this->gamemap = new int*[this->height];
		for (int i = 0; i < height; i++) {
			this->gamemap[i] = new int[this->wide];
			for (int j = 0; j < this->wide; j++) {
				this->gamemap[i][j] = 0;
			}
		}
	}
	~Gamemap();
public:
	int** gamemap;															// 游戏地图数组
	int wide;																// 游戏长宽
	int height;
};
// 游戏背景操作网格数组,可以通过坐标直接改
class Bkmap {
public:
	Bkmap(int height, int wide) {
		this->wide = wide;
		this->height = height;
		this->bkmap = new char*[this->height];
		for (int i = 0; i < height; i++) {
			this->bkmap[i] = new char[this->wide];
			for (int j = 0; j < wide; j++) {
				this->bkmap[i][j] = '\0';
			}
		}
	}
public:
	char** bkmap;														// 游戏地图操作网格数组
	int wide;															// 操作网格长宽
	int height;
public:
	// 测试用,程序闪退,检测当时哪里数组溢出,哪里数据缺失
	void adddata() {
		for (int i = 0; i < this->height; i++) {							// 地图数据写入
			for (int j = 0; j < this->wide - 1; j++) {
				this->bkmap[i][j] = '*';
			}
			this->bkmap[i][this->wide - 1] = '\0';							// 截至符号,防止后续粘贴多粘贴其他奇奇怪怪的旧数据
		}
	}
	// 刷新缓冲区,清除之前的数据,防止混合新数据形成奇奇怪怪的bug
	void fresh(Gamemap* gamemap) {
		for (int i = 0; i < gamemap->height; i++) {							// 地图复印到操作网格区
			this->bkmap[i][0] = '#';											// 最左侧是边界
			for (int j = 1; j < gamemap->wide; j++) {
				if (gamemap->gamemap[i][j] == 0)
					this->bkmap[i][j] = ' ';									// 这里决定地图打印在屏幕的样子
			}
			this->bkmap[i][gamemap->wide] = '#';								// 设置截断点,防止残留数据
			this->bkmap[i][gamemap->wide + 1] = '\0';
		}
		for (int j = 0; j < gamemap->wide; j++)
			this->bkmap[gamemap->height][j] = '#';							// 底部也有边界线
		this->bkmap[gamemap->height][gamemap->wide + 1] = '\0';
	}
	
};
// 字符串缓冲类
class Showmap {
public:
	Showmap(int height, int wide) {
		this->showmap = new char[wide * height + 1000];
		strcpy(showmap, "");
//			showmap={};												// 不能这样写,否则报错,指针重新变空
	}
public:
	char* showmap;												// 显示区缓冲
public:
	// 加入缓冲数据,用于一键打出,多个 cout printf 反而会打印慢,有一行行残影
	void adddata(Bkmap* bkmap) {
		if (showmap == NULL) {
			cout << "NULL";
			cout << "函数因空指针结束" << endl;
			return;
		}
		strcpy(showmap, "");										// 清空旧数据 ,实际上是把标志位放到开头,保证从头粘贴数据
		for (int i = 0; i < bkmap->height; i++) {					// 选区加入到打印缓冲区
			strcat(showmap, bkmap->bkmap[i]);
			strcat(this->showmap, "\n");
		}
	}
	// 一键显示到屏幕
	void show() {
		cout << this->showmap;
	}
};



// 玩家类,移动,攻击,地图修改
class Player {
public:
	Player(int x, int y, int limity, int limitx) {
		this->playerx = x;
		this->playery = y;
		is_atk = 0;
		is_buff = 0;
		flag_x = 0;
		flag_y = 0;
		this->limitx = limitx;
		this->limity = limity;
	}
	~Player();
public:
	char player = '1';
	int playerx;
	int playery;
	int flag_x;
	int flag_y;
	int limitx;
	int limity;
	int is_atk;
	int is_buff;
public:
	//玩家移动检测
	void checkmove(HWND hwnd) {
		flag_x = 0;
		flag_y = 0;
		if (KEY_DOWN_FOREGROUND(hwnd, 0x41)) {		// A
			flag_x -= 1;
		}
		if (KEY_DOWN_FOREGROUND(hwnd, 0x57)) {		// W
			flag_y -= 1;
		}
		if (KEY_DOWN_FOREGROUND(hwnd, 0x44)) {		// D
			flag_x += 1;
		}
		if (KEY_DOWN_FOREGROUND(hwnd, 0x53)) {		// S
			flag_y += 1;
		}
	}
	// 打包速度检测
	void checkspeed() {
		if (flag_x > 1)														// 速度限制
			flag_x = 1;
		else if (flag_x < -1)
			flag_x = -1;
		if (flag_y > 1)
			flag_y = 1;
		else if (flag_y < -1)
			flag_y = -1;
	}
	// 改变玩家位置
	void move() {
		if (flag_x)															// 位移改变
			playerx += flag_x;
		if (flag_y)
			playery += flag_y;
	}
//		边界检测
	void checkboundary() {
		if (playerx >= limitx)												// 角色位置限制
			playerx = limitx - 1;
		else if (playerx < 0)												// 左侧边界,可以修改成 1 这样就不会进入栅栏,和'#’重叠了
			playerx = 0;
		if (playery >= limity)
			playery = limity - 1;
		else if (playery < 0)
			playery = 0;
	}
	// 玩家通过操作网格可以通过坐标直接查找所在位置并写入地图中
	void putinmap(Bkmap* bkmap) {
		bkmap->bkmap[playery][playerx] = player;
	}
//		检测攻击
	void checkatk(HWND hwnd) {
		if (is_atk == 0 ) {
			if ( KEY_DOWN_FOREGROUND(hwnd, 0x4A))			// j 键攻击
				is_atk = 1;
			else if (KEY_DOWN_FOREGROUND(hwnd, 0x31))			// 1 键攻击
				is_atk = 2;
		}
	}
//		检测buff
	void checkbuff(HWND hwnd) {
		if (is_buff == 0 && KEY_DOWN_FOREGROUND(hwnd, 0x4B)) {	// k 键增加范围 buff
			is_buff = 1;
		}
	}
	// 绘制地图
	void drawmap(HWND hwnd, Gamemap* gamemap) {
		if (KEY_DOWN_FOREGROUND(hwnd, 0x30))								// 0 键绘制地图
			gamemap->gamemap[playery][playerx] = 0;
		if (KEY_DOWN_FOREGROUND(hwnd, 0x36))								// 6 键绘制地图
			gamemap->gamemap[playery][playerx] = 6;
		if (KEY_DOWN_FOREGROUND(hwnd, 0x37))								// 7 键绘制地图
			gamemap->gamemap[playery][playerx] = 7;
		if (KEY_DOWN_FOREGROUND(hwnd, 0x38))								// 8 键绘制地图
			gamemap->gamemap[playery][playerx] = 8;
		if (KEY_DOWN_FOREGROUND(hwnd, 0x39))								// 9 键绘制地图
			gamemap->gamemap[playery][playerx] = 9;
	}
};
// 攻击类,这里直线攻击做测试
class Aking {
public:
	Aking(int height, int wide, int atk_time, int number) {
		this->height = height;
		this->wide = wide;
		this->number = number;
		this->atk_time = atk_time;
		atk_count = 0;
	}
public:
	int height;																// 攻击范围长宽
	int wide;
	int number;																// 攻击序号,因为有多个攻击技能,所以编号,可以查找攻击
	int atk_time;															// 攻击时长
	int atk_count;															// 当前攻击持续时间
public:
	// 攻击中
	void atking(Bkmap* bkmap, Player* player) {
		if (atk_count != atk_time) {									// 攻击
			atk_count++;
			for (int j = 0; j < 10; j++) {
				bkmap->bkmap[player->playery][player->playerx + 1 + j] = 'P';
				
			}
		} else {
			atk_count = 0;
			player->is_atk = 0; 												// 玩家状态复位
		}
	}
};
// 攻击类v2继承之前的攻击类,这里做范围攻击做测试
class Akingv2: public Aking {
public:
	Akingv2(int height, int wide, int atk_time, int number): Aking( height, wide, atk_time, number) {
		this->height = height;
		this->wide = wide;
		this->number = number;
		this->atk_time = atk_time;
		atk_count = 0;
	}
	
public:
	void atkingv2(Bkmap* bkmap, Player* player) {
		if (atk_count != atk_time) {									// 攻击
			atk_count++;
			for (int i = 0; i < 10; i++)
				for (int j = 0; j < 10; j++) {
					bkmap->bkmap[player->playery - 5 + i][player->playerx + 1 + j] = 'P';
				}
		} else {
			atk_count = 0;
			player->is_atk = 0; 												// 玩家状态复位
		}
	}
};
// 敌人移动
class Enemy {
public:
	Enemy(char enemy, int number) {
		
		x = rand() % 50;													// 设置随机位置
		y = rand() % 20;
		count = 0;														// 参考攻击时长,记录时间,但是用于限速,延缓敌人行动
		sence = 2;														// 每两次循环进行反应
		speed = 1;
		this->enemy = enemy;
		this->number = number;											// 编号,同类敌人用于区分,在敌群时才有用
		this->islive = 1;													// 默认刚创造出来的敌人是活的
	}
public:
	char enemy; 														// 敌人样子
	int number;															// 敌人编号,因为敌群有好几个敌人
	int islive;															// 敌人死活,死敌人可以复活重复利用
	int x;																// 位置
	int y;
	int speed;															// 速度
	int count;															// 计时,用于选择某个时间移动
	int sence;															// 灵敏度,记录选择的时间
public:
	// 移动,向玩家方向
	void move(Player* player) {
		count++;
		if (count == sence) {											// 到反应时间了才移动一次 这里设置的是每两次循环移动一次
			if (x < player->playerx)
				x++;
			else if (x > player->playerx)
				x--;
			if (y < player->playery)
				y++;
			else if (y > player->playery)
				y--;
			count = 0;
		}
	}
	// 死亡后不移动
	void movev2(Player* player) {
		if (islive) {
			count++;
			if (count == sence) {											// 到反应时间了才移动一次 这里设置的是每两次循环移动一次
				if (x < player->playerx)
					x++;
				else if (x > player->playerx)
					x--;
				if (y < player->playery)
					y++;
				else if (y > player->playery)
					y--;
				count = 0;
			}
		}
	}
	// 撞击玩家
	void atk(Player* player) {
		if (player->playerx == x && player->playery == y)
			cout << "被追击了" << endl;
		else cout << endl;						// 占位,因为之前测试有时敌群弹出就会导致最上面信息上下颠簸
	}
	// 写入地图
	// 敌人通过操作网格可以通过坐标直接查找所在位置并写入地图中
	void putinmap(Bkmap* bkmap) {
		bkmap->bkmap[y][x] = enemy;
	}
	// 死亡就不写入地图
	void putinmapv2(Bkmap* bkmap) {
		if (islive == 1)
			bkmap->bkmap[y][x] = enemy;
	}
	// 敌人死亡,写敌群类时增加的
	void checkdead(Bkmap* bkmap) {
		if (bkmap->bkmap[y][x] == 'P')
			islive = 0;
	}
	// 敌人复活 ,写敌群时增加的
	void relive() {
		if (islive == 0) {
			islive = 1;
			x = rand() % 50;
			y = rand() % 20;
		}
	}
};
// 敌群
class Enemygroup {
public:
	Enemygroup(char enemy, int number, int limitnum) {			// 设定敌人样子与敌人编号敌人个数
		maxnum = 30;
		enemys = new Enemy*[maxnum];
		for (int i = 0; i < number; i++) {
			enemys[i] = new Enemy(enemy, i);
		}
		this->limitnum = limitnum;									// 记录维持个数
		this->livenum = limitnum;
	}
public:
	Enemy** enemys;
	int maxnum;										// 设定最大容量
	int limitnum;									// 设定敌群个数维持值,例如小于5个敌人就增加敌人
	int livenum;									// 当前存活敌人个数
public:
	// 增加敌人
	void addenemy() {
		if (livenum < limitnum) {					// 当前存活敌人小于设定界限,开始补充敌人
			for (int i = 0; i < limitnum; i++) {
				if (enemys[i] != NULL) {
					if (enemys[i]->islive == 0) {
						enemys[i]->relive();
						livenum++;
					}
				}
				if (livenum == limitnum)
					break;
			}
		}
	}
	// 敌人移动
	void move(Player* player) {
		for (int i = 0; i < limitnum; i++) {
			if (enemys[i] != NULL && enemys[i]->islive == 1) {
				enemys[i]->move(player);
			}
		}
		cout << "存活敌人数目:" << livenum << endl;
	}
	// 敌群死亡
	void dead(Bkmap* bkmap) {
		livenum = 0;															// 重新数有多少敌人
		for (int i = 0; i < limitnum; i++) {
			if (enemys[i] != NULL) {
				enemys[i]->checkdead(bkmap);
				if (enemys[i]->islive) {
					livenum++;
				}
			}
		}
	}
	// 打印敌人
	void putinmap(Bkmap* bkmap) {
		for (int i = 0; i < limitnum; i++) {
			if (enemys[i] != NULL && enemys[i]->islive) {
				bkmap->bkmap[enemys[i]->y][enemys[i]->x] = enemys[i]->enemy;
			}
		}
	}
};
int main() {
	
	Gamemap* gamemap = new Gamemap(20, 50);									// 生成一个游戏地图
	Bkmap* bkmap = new Bkmap(20 + 3, 50);										// 生成一个操作网格区
	Showmap* showmap = new Showmap(20, 80);									// 生成一个显示区
	Aking* ak = new Aking(10, 1, 12, 1);										// 直线攻击,长 10 宽 1 持续时间 12 次循环,序号是1
	Akingv2* akv2 = new Akingv2(10, 1, 12, 2);									// 攻击方式v2
	Player* player = new Player(0, 0, 20, 50);									// 生成一个玩家
	
	Enemy* mk = new Enemy('%', 2);												// 生成一个敌人,敌人是 ”%"
	Enemy* mkv2 = new Enemy('0', 3);											// 生成另一个敌人,敌人是 “0”
	Enemygroup* mkvs = new Enemygroup('9', 4, 10);
	
	bkmap->fresh(gamemap);													// 清空旧数据
//	bkmap->adddata();														// 测试数据 改bug 测试用
	showmap->adddata(bkmap);
//	showmap->show();
	
	HWND hwnd = GetForegroundWindow();										// 获取前端窗口句柄,由于程序刚运行时是在前端,所以这就是本程序的窗口句柄
	
	
	
	
//获取默认标准显示缓冲区句柄
	HANDLE hOutput;
	COORD coord = {0, 0};
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	
	//创建新的缓冲区
	HANDLE hOutBuf = CreateConsoleScreenBuffer(
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		CONSOLE_TEXTMODE_BUFFER,
		NULL
		);
	
	//设置新的缓冲区为活动显示缓冲
	SetConsoleActiveScreenBuffer(hOutBuf);
	
	
	
	
//	设置启用颜色
//	DWORD dwMode = 0;
//
//
//	dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
//	if (!SetConsoleMode(hOutBuf, dwMode)) {
//		return GetLastError();
//	}
	
	SMALL_RECT lpReadRegion;
	lpReadRegion.Top = 0;
	lpReadRegion.Bottom = 5000;
	lpReadRegion.Left = 0;
	lpReadRegion.Right = 5000;
	
	
	
	const int X = 0;
	const int Y = 0;
	const int MROW = 100;
	const int MCOL = 300;
	HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
	SMALL_RECT rect;
	CHAR_INFO buffer[MROW * MCOL]; //lpBuffer
	COORD coordBufSize; //dwBufferSize
	COORD coordBufCoord; // dwBufferCoord
	
	coordBufSize.Y = MROW;
	coordBufSize.X = MCOL;
	coordBufCoord.X = 1; //???????
	coordBufCoord.Y = 1; //???????
	
	rect.Top = Y;
	rect.Left = X;
	rect.Bottom = MROW + Y;
	rect.Right = MCOL + X;
	
	
	
	
	//隐藏两个缓冲区的光标
	CONSOLE_CURSOR_INFO cci;
	cci.bVisible = 0;
	cci.dwSize = 1;
	SetConsoleCursorInfo(hOutput, &cci);
	SetConsoleCursorInfo(hOutBuf, &cci);
	
	//双缓冲处理显示
	DWORD bytes = 0;
	char data[4900];
	_CHAR_INFO datas[6000];
	int cnt = 0;
	srand(time(NULL));														// 设置随机数种子
	while (1) {
		cout << "2" ;															// 测试最顶端,顶端之上的是奇奇怪怪的数据
		cout << endl;															// 把一些奇奇怪怪的数据顶上去,让地图下移两行显示
		cout << endl;
		bkmap->fresh(gamemap);												// 清空操作网格区数据
		if (KEY_DOWN_FOREGROUND(hwnd, VK_ESCAPE)) {
			printf("游戏退出\n");
			break;
		}
		player->checkmove(hwnd);											// 移动按键检测
		player->checkatk(hwnd);												// 攻击按键检测
		player->checkspeed();												// 速度检测
		player->move();														// 玩家移动
		player->checkboundary();											// 边界检测
		player->putinmap(bkmap);											// 玩家位置写入操作网格区
		
		switch (player->is_atk) {										// 攻击选择方式
		case 1:
			ak->atking(bkmap, player);									// 攻击写入操作网格区
			break;
		case 2:
			akv2->atkingv2(bkmap, player);
			break;
		}
		
		// 一定要在攻击之后检测敌人死亡 ,地图在攻击之后才有标记
		
		mkv2->movev2(player);
		mkv2->atk(player);
		mkv2->checkdead(bkmap);												// 通过检测网格敌人坐标是否有攻击记号来确定是否被攻击
		mkv2->putinmapv2(bkmap);
		
		mkvs->move(player);
		mkvs->dead(bkmap);
		mkvs->putinmap(bkmap);
		
		// 不死的敌人要最后打印,因为之前先打印后,清除了攻击标记,敌群死亡个数少,表现为复活少了,但是总数还是正常的。
		mk->move(player);													// 敌人移动
		mk->atk(player);													// 敌人攻击
		mk->putinmap(bkmap);												// 敌人写入地图
		
		
		cnt++;
		if (cnt == 16) {
			if (mkv2->islive == 0)
				mkv2->relive();
		}
		if (cnt == 40) {
			mkvs->addenemy();
		}
		if (cnt == 60)
			cnt = 0;
		
		
		cout << "冲冲冲" << endl;
		showmap->adddata(bkmap);											// 操作网格区写入显示缓冲区
//		system("cls");														// 双缓冲时,去除清屏命令
		cout << "玩家位置:" << player->playerx << "," << player->playery << endl;
		showmap->show();
		
		// 每增加一个cout输出,这里就注释掉一行
		cout << endl;															// 用于顶到顶部,把一些奇奇怪怪的数据顶出屏幕
		cout << endl;															// 这几条回车和最开头的回车可以调节地图在黑窗口的显示位置
//		cout<<endl;															// 这里被注释,地图就下移,上边被注释,地图就上移
//		cout<<endl;
		
		
		printf("\033[31;4mRed Underline Text\033[0m\n");
		
		
		printf(RED "红色****************************\n" NONE);
		printf(LIGHT_RED"亮红色**************************\n"NONE);
		printf(GREEN "绿色****************************\n" NONE);
		printf(LIGHT_GREEN "亮绿色****************************\n" NONE);
		
		printf(BLUE"蓝色******************************\n"NONE);
		printf(LIGHT_BLUE"亮蓝色****************************\n"NONE);
		
		wprintf(L"\x1b[31mThis text has a red foreground using SGR.31.\r\n");
		
		printf(""NONE);   //		注释掉,就会使得整个游戏地图也是红色的,因为上一行的wprintf 的 \x1b 一直作用,没有NONE 宏终止就会一直作用
		Sleep(50);
		
		ReadConsoleOutput(hout, buffer, coordBufSize, coordBufCoord, &rect);
//		ReadConsoleOutput(hOutput, datas, coordBufCoord, coordBufSize, &rect);
//		ReadConsoleOutputCharacterA(hOutput, data, 4900, coord, &bytes);
//		WriteConsoleOutputCharacterA(hOutBuf, data, 4900, coord, &bytes);
//		WriteConsoleOutput(hOutBuf, datas, coordBufCoord, coordBufSize, &rect);
		WriteConsoleOutput(hOutBuf, buffer, coordBufSize, coordBufCoord, &rect);
//		cout<<"测试打印消息所在窗口的位置。";
	}
	return 0;
	
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值