重整控制台RPG——双缓冲无屏闪以及第一个无中生有的地图类,玩家类_哔哩哔哩_bilibili
是鄙人解说鄙人的代码
#include <iostream>
#include <string.h>
#include <windows.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;// 加入的游戏背景数据,通常不修改,放到 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'; // 截至符号,防止后续粘贴多粘贴其他奇奇怪怪的旧数据}}// 刷新缓冲区,清除之前的数据,防止混合新数据形成奇奇怪怪的bugvoid 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;}}
// 检测buffvoid 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; // 玩家状态复位}}
};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 次循环,序号是1Akingv2* akv2 = new Akingv2(10,1,12,2);Player* player = new Player(0,0,20,50);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);//隐藏两个缓冲区的光标CONSOLE_CURSOR_INFO cci;cci.bVisible=0;cci.dwSize=1;SetConsoleCursorInfo(hOutput, &cci);SetConsoleCursorInfo(hOutBuf, &cci);//双缓冲处理显示DWORD bytes=0;char data[4900];while(1){if (KEY_DOWN_FOREGROUND(hwnd, VK_ESCAPE)){printf("游戏退出\n");break;}player->checkmove(hwnd); // 移动按键检测player->checkatk(hwnd); // 攻击按键检测player->checkspeed(); // 速度检测player->move(); // 玩家移动player->checkboundary(); // 边界检测bkmap->fresh(gamemap); // 清空操作网格区数据player->putinmap(bkmap); // 玩家位置写入操作网格区switch (player->is_atk) // 攻击选择方式{case 1:ak->atking(bkmap,player); // 攻击写入操作网格区break;case 2:akv2->atkingv2(bkmap,player);break;}cout<<"冲冲冲"<<endl;showmap->adddata(bkmap); // 操作网格区写入显示缓冲区
// system("cls"); // 双缓冲时,去除清屏命令cout<<"玩家位置:"<<player->playerx<<","<<player->playery<<endl;showmap->show();cout<<endl;cout<<endl;cout<<endl;cout<<endl; // 用于顶到顶部,把一些奇奇怪怪的数据顶出屏幕Sleep(50);ReadConsoleOutputCharacterA(hOutput, data, 4900, coord, &bytes);SetConsoleTextAttribute(hOutBuf, FOREGROUND_INTENSITY | FOREGROUND_RED);WriteConsoleOutputCharacterA(hOutBuf, data, 4900, coord, &bytes);cout<<"测试数据位置。";}return 0;}