cmd 控制台 RPG :追加无中生有的第一个敌人类,攻击类,敌群类,实现敌人死亡复活。

 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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值