1.实现攻击类,玩家类,敌人类,敌群类,可进行更大规模的拓展,也可把图形学的画直线算法接入其中
2.攻击检测是通过地图网格数组记录攻击符号,敌人坐标在地图里检测攻击符号敌人就改变flag
3.实现了简单的消息提示
#include <iostream>
#include <string.h>
#include <windows.h>
#include <time.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'; // 截至符号,防止后续粘贴多粘贴其他奇奇怪怪的旧数据
}
}
// 刷新缓冲区,清除之前的数据,防止混合新数据形成奇奇怪怪的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);
//隐藏两个缓冲区的光标
CONSOLE_CURSOR_INFO cci;
cci.bVisible=0;
cci.dwSize=1;
SetConsoleCursorInfo(hOutput, &cci);
SetConsoleCursorInfo(hOutBuf, &cci);
//双缓冲处理显示
DWORD bytes=0;
char data[4900];
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;
Sleep(50);
ReadConsoleOutputCharacterA(hOutput, data, 4900, coord, &bytes);
WriteConsoleOutputCharacterA(hOutBuf, data, 4900, coord, &bytes);
// cout<<"测试数据位置。";
}
return 0;
}