起因很简单,想换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;
}