EasyX自学笔记3(割草游戏3)

Player类

class Player
{
public:
	Player()
	{
		loadimage(&ImgShadowP, _T("img/shadow_player.png"));
		anim_left=new Animation (_T("img/player_left_%d.png"), 6, 45);
		anim_right=new Animation (_T("img/player_right_%d.png"), 6, 45);
	}
	~Player()
	{
		delete anim_left;
		delete anim_right;
	}
	void Input(const ExMessage& msg)
	{
		
	}
	
	void Move()
	{
		
	}
	void Draw(int delta)
	{
		
	}


private:
	const int PlayerWidth = 80;
	const int PlayerHeight = 80;
	const int ShadowWidth = 32;

	IMAGE ImgShadowP;
	Animation* anim_left;
	Animation* anim_right;
	POINT player_pos = { 500,500 };
	const int PLAYER_SPEED = 10;
	bool is_move_up = 0;
	bool is_move_down = 0;
	bool is_move_left = 0;
	bool is_move_right = 0;
};

我们建立Player类,将散落在外的变量放入私有变量。

在析构函数中对游戏背景及阴影初始化路径,并在析构函数中删除。

Input函数

存放输入按键,将这一部分复制过来

void Input(const ExMessage& msg)
	{
		if (msg.message == WM_KEYDOWN)
		{
			switch (msg.vkcode)
			{
			case VK_UP:
				is_move_up = true;
				break;
			case VK_DOWN:
				is_move_down = true;
				break;
			case VK_LEFT:
				is_move_left = true;
				break;
			case VK_RIGHT:
				is_move_right = true;
				break;
			}
		}
		else if (msg.message == WM_KEYUP)
		{
			switch (msg.vkcode)
			{
			case VK_UP:
				is_move_up = false;
				break;
			case VK_DOWN:
				is_move_down = false;
				break;
			case VK_LEFT:
				is_move_left = false;
				break;
			case VK_RIGHT:
				is_move_right = false;
				break;
			}
		}
	}

Move函数

将移动方式复制过来

void Move()
	{
		int dir_x = is_move_right - is_move_left;
		int dir_y = is_move_down - is_move_up;
		double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
		if (len_dir != 0)
		{
			double normalized_x = dir_x / len_dir;
			double normalized_y = dir_y / len_dir;
			player_pos.x += (int)(PLAYER_SPEED*normalized_x);
			player_pos.y += (int)(PLAYER_SPEED*normalized_y);
		}
		if (player_pos.x < 0)	player_pos.x = 0;
		if (player_pos.y < 0)	player_pos.y = 0;
		if (player_pos.x + PlayerWidth > WindowWidth)player_pos.x = WindowWidth - PlayerWidth;
		if (player_pos.y + PlayerHeight > WindowHeight)player_pos.y = WindowHeight - PlayerHeight;
	}

其中的窗口范围边界依旧属于全局变量、派蒙和敌人都要引用所以不放入

#include <graphics.h>
#include<vector>

const int WindowWidth = 1280;
const int WindowHeight = 720;

Draw函数

将DrawPlayer内部函数复制过来,并直接将dir_x的表达式写进去。

void Draw(int delta)
	{
		int PosShadowX = player_pos.x + (PlayerWidth / 2 - ShadowWidth / 2);
		int PosShadowY = player_pos.y + PlayerHeight - 8;
		putimageA(PosShadowX, PosShadowY, &ImgShadowP);
		static bool facing_left = false;
		int dir_x = is_move_right - is_move_left;
		if (dir_x < 0)facing_left = true;
		else if (dir_x > 0)facing_left = false;
		if (facing_left)
		{
			anim_left->Play(player_pos.x, player_pos.y, delta);
		}
		else
		{
			anim_right->Play(player_pos.x, player_pos.y, delta);
		}
	}

Enemy类


class Enemy
{
public:
	Enemy()
	{
		loadimage(&ImgShadow, _T("img/shadow_enemy.png"));
		anim_left = new Animation(_T("img/enemy_left_%d.png"), 6, 45);
		anim_right = new Animation(_T("img/enemy_right_%d.png"), 6, 45);
	}
	void Hurt()
	{
		alive = false;
	}
	bool checkAlive()
	{
		return alive;
	}
	bool CheckBullet(const Bullet& bullet)
	{

	}
	bool CheckPlayer(const Player& player)
	{

    }
	void Move(const Player&player)
	{
		const POINT player_position = player.GetPosition();
		int dir_x = player_position.x - position.x;
		int dir_y = player_position.y - position.y;
		double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
		if (len_dir != 0)
		{
			double normalized_x = dir_x / len_dir;
			double normalized_y = dir_y / len_dir;
			position.x += (int)(SPEED*normalized_x);
			position.y += (int)(SPEED*normalized_y);
		}
		if (dir_x<0)
		{
			facing_left = true;
		}
		else if (dir_x > 0)
		{
			facing_left = false;
		}
	}
	void Draw(int delta)
	{
		int PosShadowX = position.x + (EnemyWidth / 2 - ShadowWidth / 2);
		int PosShadowY = position.y + EnemyHeight -35;
		putimageA(PosShadowX, PosShadowY, &ImgShadow);
		if (facing_left)
		{
			anim_left->Play(position.x, position.y, delta);
		}
		else
		{
			anim_right->Play(position.x, position.y, delta);
		}


	}
	~Enemy()
	{
		delete anim_left;
		delete anim_right;
	}

private:
	const int EnemyWidth = 80;
	const int EnemyHeight = 80;
	const int ShadowWidth = 48;

	class Bullet;
	IMAGE ImgShadow;
	Animation* anim_left;
	Animation* anim_right;
	POINT position = { 0,0 };
	const int SPEED = 2;
	bool facing_left = false;
	bool alive = true;
};

敌人与派蒙的运动方式类似,区别在于派蒙有默认点并且随着玩家的Input而改变坐标。

敌人则是初始位置随机、向着派蒙坐标移动、碰触派蒙游戏结束、被子弹碰到游戏结束。

构造函数析构函数

与Player类类似,只不过需要从边界外生成敌人,所以我们在创建一个随机边界生成敌人代码。

Enemy()
	{
		loadimage(&ImgShadow, _T("img/shadow_enemy.png"));
		anim_left = new Animation(_T("img/enemy_left_%d.png"), 6, 45);
		anim_right = new Animation(_T("img/enemy_right_%d.png"), 6, 45);
		enum class SpawnEdge
		{
			Up, Down, Left, Right
		};
		SpawnEdge edge = (SpawnEdge)(rand() % 4);
		switch (edge)
		{
		case SpawnEdge::Up:
			position.x = rand() % WindowWidth;
			position.y = -EnemyHeight;
			break;
		case SpawnEdge::Down:
			position.x = rand() % WindowWidth;
			position.y = WindowHeight;
			break;
		case SpawnEdge::Left:
			position.x =  -EnemyWidth;
			position.y = rand() % WindowHeight;
			break;
		case SpawnEdge::Right:
			position.x = WindowWidth;
			position.y = rand() % WindowHeight;
			break;
		default:
			break;
		}
	}

Move函数

派蒙的Move函数接收来自Input函数的数据,而Enemy需要接收Player的实时坐标,所以我们在Player类中创建获取坐标函数,其余与Player类似。

	const POINT& GetPosition()const
	{
		return player_pos;
	}

Draw函数

与Player一样,改一下Enemy阴影的相对位置即可。

Hurt函数

敌人的受伤机制,在这里可以写受子弹撞击一下掉一半血,也可一直致命或者任何掉血方式。

CheckBullet与CheckPlayer函数

敌人与子弹、敌人与玩家碰撞检测。bool变量,碰撞为true、未碰撞为false。

先写与玩家碰撞

知道玩家坐标与敌人坐标,检测敌人中心位置坐标是否落入玩家80x80像素内即可。

	bool CheckPlayer(const Player& player)
	{
		POINT check_position = { position.x + EnemyWidth / 2,position.y + EnemyHeight/2 };
		bool is_overlap_x = check_position.x >= player.GetPosition().x&&check_position.x <= player.GetPosition().x + EnemyWidth;
		bool is_overlap_y = check_position.y >= player.GetPosition().y&&check_position.y <= player.GetPosition().y + EnemyHeight;
		return is_overlap_x && is_overlap_y;
	}

不去做敌人的正方形与派蒙的正方型碰撞检测是为什么呢?

我们可以正方形与正方形碰撞检测,但是两个方形都大于模型本身,容易出现碰撞箱碰触到了,但肉眼观察属于未碰触,与直觉不符。所以我们采取点对面的检测,当然你还可以将派蒙与敌人的碰撞箱缩小到模型体内。随便你。

Bullet类


class Bullet
{
public:
	POINT position = { 0,0 };
public:
	Bullet() {};
	~Bullet() {};
	void Draw()const
	{
		setlinecolor(RGB(255, 155, 50));
		setlinecolor(RGB(255, 155, 50));
		fillcircle(position.x, position.y, RADIUS);
	}
private:
	const int RADIUS = 10;
};

构造函数析构函数不重要。

我们需要子弹的坐标position,芸运用函数绘制半径为10的白色子弹。

对于两个变量,显然我们不希望改变子弹的大小只希望变换子弹的位置。所以将两个变量一个为私有一个为公有。

构造完毕Buttlet类我们编写CheckBullet函数

bool CheckBullet(const Bullet& bullet)
	{
		bool is_overlap_x = bullet.position.x >= position.x&&bullet.position.x <= position.x + EnemyWidth;
		bool is_overlap_y = bullet.position.y >= position.y&&bullet.position.y <= position.y + EnemyHeight;
		return is_overlap_x&& is_overlap_y;
	}

记得将Buttlet类放置在Enemy类以上否则无法引用相关函数。(编译器从上向下扫描代码)

#include <graphics.h>
#include<vector>

const int WindowWidth = 1280;
const int WindowHeight = 720;

#pragma comment(lib,"MSIMG32.LIB")
inline void  putimageA(int x, int y, IMAGE* img)
{
	int w = img->getwidth();
	int h = img->getheight();
	AlphaBlend(GetImageHDC(NULL), x, y, w, h,
		GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}

class Animation
{
public:
	Animation(LPCTSTR path,int num,int interval)
	{
		interval_ms = interval;
		TCHAR path_file[256];
		for (size_t i = 0; i < num; i++)
		{
			_stprintf_s(path_file, path, i);

			IMAGE* frame = new IMAGE();
			loadimage(frame, path_file);
			FrameList.push_back(frame);
		}
	}
	~Animation()
	{
		for (size_t i = 0; i < FrameList.size(); i++)
		{
			delete FrameList[i];
		}
	}
	void Play(int x, int y, int delta)
	{
		timer += delta;
		if (timer >= interval_ms)
		{
			idx_frame = (idx_frame + 1) % FrameList.size();
			timer = 0;
		}
		putimageA(x, y, FrameList[idx_frame]);
	}
private:
	int timer = 0;
	int idx_frame = 0;
	int interval_ms = 0;
	std::vector<IMAGE*> FrameList;//动画帧序列
};

class Bullet
{
public:
	POINT position = { 0,0 };
public:
	Bullet() {};
	~Bullet() {};
	void Draw()const
	{
		setlinecolor(RGB(255, 155, 50));
		setlinecolor(RGB(255, 155, 50));
		fillcircle(position.x, position.y, RADIUS);
	}
private:
	const int RADIUS = 10;
};
class Player
{
public:
	Player()
	{
		loadimage(&ImgShadowP, _T("img/shadow_player.png"));
		anim_left=new Animation (_T("img/player_left_%d.png"), 6, 45);
		anim_right=new Animation (_T("img/player_right_%d.png"), 6, 45);
	}
	~Player()
	{
		delete anim_left;
		delete anim_right;
	}
	void Input(const ExMessage& msg)
	{
		if (msg.message == WM_KEYDOWN)
		{
			switch (msg.vkcode)
			{
			case VK_UP:
				is_move_up = true;
				break;
			case VK_DOWN:
				is_move_down = true;
				break;
			case VK_LEFT:
				is_move_left = true;
				break;
			case VK_RIGHT:
				is_move_right = true;
				break;
			}
		}
		else if (msg.message == WM_KEYUP)
		{
			switch (msg.vkcode)
			{
			case VK_UP:
				is_move_up = false;
				break;
			case VK_DOWN:
				is_move_down = false;
				break;
			case VK_LEFT:
				is_move_left = false;
				break;
			case VK_RIGHT:
				is_move_right = false;
				break;
			}
		}
	}
	
	void Move()
	{
		int dir_x = is_move_right - is_move_left;
		int dir_y = is_move_down - is_move_up;
		double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
		if (len_dir != 0)
		{
			double normalized_x = dir_x / len_dir;
			double normalized_y = dir_y / len_dir;
			player_pos.x += (int)(PLAYER_SPEED*normalized_x);
			player_pos.y += (int)(PLAYER_SPEED*normalized_y);
		}
		if (player_pos.x < 0)	player_pos.x = 0;
		if (player_pos.y < 0)	player_pos.y = 0;
		if (player_pos.x + PlayerWidth > WindowWidth)player_pos.x = WindowWidth - PlayerWidth;
		if (player_pos.y + PlayerHeight > WindowHeight)player_pos.y = WindowHeight - PlayerHeight;
	}
	void Draw(int delta)
	{
		int PosShadowX = player_pos.x + (PlayerWidth / 2 - ShadowWidth / 2);
		int PosShadowY = player_pos.y + PlayerHeight - 8;
		putimageA(PosShadowX, PosShadowY, &ImgShadowP);
		static bool facing_left = false;
		int dir_x = is_move_right - is_move_left;
		if (dir_x < 0)facing_left = true;
		else if (dir_x > 0)facing_left = false;
		if (facing_left)
		{
			anim_left->Play(player_pos.x, player_pos.y, delta);
		}
		else
		{
			anim_right->Play(player_pos.x, player_pos.y, delta);
		}
	}
	const POINT& GetPosition()const
	{
		return player_pos;
	}
public:
	const int EnemyWidth = 80;
	const int EnemyHeight = 80;
private:
	const int PlayerWidth = 80;
	const int PlayerHeight = 80;
	const int ShadowWidth = 32;

	IMAGE ImgShadowP;
	Animation* anim_left;
	Animation* anim_right;
	POINT player_pos = { 500,500 };
	const int PLAYER_SPEED = 10;
	bool is_move_up = 0;
	bool is_move_down = 0;
	bool is_move_left = 0;
	bool is_move_right = 0;
};

class Enemy
{
public:
	Enemy()
	{
		loadimage(&ImgShadow, _T("img/shadow_enemy.png"));
		anim_left = new Animation(_T("img/enemy_left_%d.png"), 6, 45);
		anim_right = new Animation(_T("img/enemy_right_%d.png"), 6, 45);
		enum class SpawnEdge
		{
			Up, Down, Left, Right
		};
		SpawnEdge edge = (SpawnEdge)(rand() % 4);
		switch (edge)
		{
		case SpawnEdge::Up:
			position.x = rand() % WindowWidth;
			position.y = -EnemyHeight;
			break;
		case SpawnEdge::Down:
			position.x = rand() % WindowWidth;
			position.y = WindowHeight;
			break;
		case SpawnEdge::Left:
			position.x =  -EnemyWidth;
			position.y = rand() % WindowHeight;
			break;
		case SpawnEdge::Right:
			position.x = WindowWidth;
			position.y = rand() % WindowHeight;
			break;
		default:
			break;
		}
	}
	void Hurt()
	{
		alive = false;
	}
	bool checkAlive()
	{
		return alive;
	}
	bool CheckBullet(const Bullet& bullet)
	{
		bool is_overlap_x = bullet.position.x >= position.x&&bullet.position.x <= position.x + EnemyWidth;
		bool is_overlap_y = bullet.position.y >= position.y&&bullet.position.y <= position.y + EnemyHeight;
		return is_overlap_x&& is_overlap_y;
	}
	bool CheckPlayer(const Player& player)
	{
		POINT check_position = { position.x + EnemyWidth / 2,position.y + EnemyHeight/2 };
		bool is_overlap_x = check_position.x >= player.GetPosition().x&&check_position.x <= player.GetPosition().x + EnemyWidth;
		bool is_overlap_y = check_position.y >= player.GetPosition().y&&check_position.y <= player.GetPosition().y + EnemyHeight;
		return is_overlap_x && is_overlap_y;
	}
	void Move(const Player&player)
	{
		const POINT player_position = player.GetPosition();
		int dir_x = player_position.x - position.x;
		int dir_y = player_position.y - position.y;
		double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
		if (len_dir != 0)
		{
			double normalized_x = dir_x / len_dir;
			double normalized_y = dir_y / len_dir;
			position.x += (int)(SPEED*normalized_x);
			position.y += (int)(SPEED*normalized_y);
		}
		if (dir_x<0)
		{
			facing_left = true;
		}
		else if (dir_x > 0)
		{
			facing_left = false;
		}
	}
	void Draw(int delta)
	{
		int PosShadowX = position.x + (EnemyWidth / 2 - ShadowWidth / 2);
		int PosShadowY = position.y + EnemyHeight -35;
		putimageA(PosShadowX, PosShadowY, &ImgShadow);
		if (facing_left)
		{
			anim_left->Play(position.x, position.y, delta);
		}
		else
		{
			anim_right->Play(position.x, position.y, delta);
		}


	}
	~Enemy()
	{
		delete anim_left;
		delete anim_right;
	}

private:
	const int EnemyWidth = 80;
	const int EnemyHeight = 80;
	const int ShadowWidth = 48;

	class Bullet;
	IMAGE ImgShadow;
	Animation* anim_left;
	Animation* anim_right;
	POINT position = { 0,0 };
	const int SPEED = 2;
	bool facing_left = false;
	bool alive = true;
};

至此三个对象建立完毕,我们来处理main函数。

main

int main()
{
	initgraph(1280, 720);
	bool GameOver = false;
	Player player;
	ExMessage msg;
	IMAGE ImgBackground;
	

	loadimage(&ImgBackground, _T("img/background.png"));

	BeginBatchDraw();
	while (!GameOver)
	{
		DWORD StartTime = GetTickCount();
		while (peekmessage(&msg))
		{
			player.Input(msg);
		}

		player.Move();
		
		cleardevice();

		putimage(0, 0, &ImgBackground);
		player.Draw(1000/144);


		FlushBatchDraw();

		DWORD EndTime = GetTickCount();
		DWORD DeleteTime = EndTime - StartTime;
		if (DeleteTime <1000/144)
		{
			Sleep(1000 / 144 - DeleteTime);
		}
	}
	EndBatchDraw();
}

在main函数中一些初始化被封装到类以内,事件循环中的一大段函数替换为Input函数、Move函数与Draw函数分别进行替换。

TryGenerateEnemy

我们来编写敌人出现函数放置到main外。

std::vector<Enemy*>enemy_list;
void TryGenerateEnemy(std::vector<Enemy*>& enemy_list)
{
	const int INTERVAL = 100;
	static int counter = 0;
	if ((++counter) % INTERVAL == 0)
	{
		enemy_list.push_back(new Enemy());
	}
}

屏幕依次出现敌人123456。。。且每个敌人都是指针,数据长度需要时实时变化,那我们就想到动态数组。

创建一个指针数组类型的动态数组,利用计数器每100向容器内推入一个数据。

UpdataBullets

void UpdataBullets(std::vector<Bullet>&bullet_list, const Player& player)
{
	const double RadialSpeed = 0.0045;
	const double TangentSpeed = 0.0045;
	double radian_interval = 2 * 3.14159 / bullet_list.size();
	POINT PlayerPosition = player.GetPosition();
	double radius = 100 + 25 * sin(GetTickCount()*RadialSpeed);
	for (size_t i = 0; i < bullet_list.size(); i++)
	{
		double radian = GetTickCount()*TangentSpeed + radian_interval * i;
		bullet_list[i].position.x = PlayerPosition.x + player.EnemyWidth / 2 + (int)(radius*sin(radian));
		bullet_list[i].position.y = PlayerPosition.y + player.EnemyHeight / 2 + (int)(radius*cos(radian));
	}
}

子弹围绕着派蒙圆周运动,并附带一定的波动速度,让子弹效果更灵动。

此时main函数

int main()
{
	initgraph(1280, 720);
	bool GameOver = false;
	Player player;
	ExMessage msg;
	IMAGE ImgBackground;
	std::vector<Bullet>bullet_list(3);

	loadimage(&ImgBackground, _T("img/background.png"));

	BeginBatchDraw();
	while (!GameOver)
	{
		DWORD StartTime = GetTickCount();
		while (peekmessage(&msg))
		{
			player.Input(msg);
		}

		player.Move();
		UpdataBullets(bullet_list, player);
		TryGenerateEnemy(enemy_list);
		for (Enemy*enemy : enemy_list)
			enemy->Move(player);

		for (Enemy* enemy:enemy_list)
		{
			if (enemy->CheckPlayer(player))
			{
				MessageBox(GetHWnd(), _T("666"), _T("游戏结束"), MB_OK);
				GameOver = true;
				break;
			}
		}
		for (Enemy* enemy : enemy_list)
		{
			for (const Bullet&bullet : bullet_list)
			{
				if(enemy->CheckBullet(bullet))
				{
					enemy->Hurt();
				}
			}
		}
		for (size_t i = 0; i < enemy_list.size(); i++)
		{
			Enemy* enemy = enemy_list[i];
			if (!enemy->checkAlive())
			{
				std::swap(enemy_list[i], enemy_list.back());
				enemy_list.pop_back();
				delete enemy;
			}
		}
		cleardevice();

		putimage(0, 0, &ImgBackground);
		player.Draw(1000/144);
		for (Enemy*enemy : enemy_list)
			enemy->Draw(1000/144);
		for (const Bullet& bullet : bullet_list)
			bullet.Draw();

		FlushBatchDraw();

		DWORD EndTime = GetTickCount();
		DWORD DeleteTime = EndTime - StartTime;
		if (DeleteTime <1000/144)
		{
			Sleep(1000 / 144 - DeleteTime);
		}
	}
	EndBatchDraw();
}

std::vector<Bullet>bullet_list(3);创建三个子弹

第一个for敌人向派蒙移动

第二个for检测是否碰撞玩家

第三个for检测碰撞子弹执行受伤函数

第四个for检测到碰触子弹标志位对敌人进行删除

由于敌人出现随机,被玩家击杀随机,可能第五个出现被第一个击杀,所以我们使用swap函数。

每次Hurt的敌人其与栈尾交换并推出。

完结撒花

至此所有的代码已经实现,有更好的想法可以在评论区交流,初次学习还是有很多bug。

一杯茶,一包瓜子,一个bug改一天。。。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少杰是小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值