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

const int WINDOW_WIDTH = 1280;
const int WINDOW_HEIGHT = 720;

const int BUTTON_WIDTH = 192;
const int BUTTON_HEIGHT = 75;

const double PI = acos(-1.0);               // 精确圆周率
bool is_running = true; // 游戏运行标志
bool is_game_start = false; // 游戏开始标志

//编译器链接 Windows 自带的图像处理库 MSIMG32.LIB,这个库里包含了 AlphaBlend 函数,用于绘制带有透明通道(alpha)的图像
#pragma comment(lib, "MSIMG32.LIB") 
#pragma comment(lib,"Winmm.lib")  // 链接多媒体库

inline void putimage_alpha(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 }:混合模式,表示源图像覆盖目标图像
	AlphaBlend(GetImageHDC(NULL), x, y, w, h,
		GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}

// Atlas类表示图集
class Atlas
{
public:
	Atlas(LPCTSTR path, int num)
	{
		TCHAR path_file[256]; // 存储图片路径的数组 通用字符类型宏
		for (size_t i = 0; i < num; i++)
		{
			// 生成路径字符串保存到第一个参数中 例如 path_file = img/player_right_i.png
			_stprintf_s(path_file, path, i);

			IMAGE* frame = new IMAGE();
			loadimage(frame, path_file);
			frame_list.push_back(frame); // 将图像加入帧列表
		}
	}

	~Atlas()
	{
		for (size_t i = 0; i < frame_list.size(); i++)
			delete frame_list[i];
	}

public:
	std::vector<IMAGE*> frame_list;  // 动画帧列表
};

// 享元模式:让玩家和敌人共享以下对应的图集
Atlas* atlas_player_left; //Atlas(图集)——每一个玩家朝向左边的动画都使用这个图集
Atlas* atlas_player_right;
Atlas* atlas_enemy_left; //每一个敌人朝向左边的动画都使用这个图集
Atlas* atlas_enemy_right;

// 动画类,用于管理动画帧和播放动画
class Animation
{
public:
	Animation(Atlas* atlas, int interval) //LPCTSTR "指向常量字符串的指针,字符类型为 TCHAR"	ANSI 编译:const char*	Unicode 编译:const wchar_t*
	{
		anim_atlas = atlas; // 设置动画图集
		this->interval_ms = interval; // 设置动画帧间隔时间
	}

	~Animation() = default;

	void Play(int x, int y, int delta)
	{
		timer += delta; // delta = 1000 / 144 每帧之间的时间间隔( 6.94ms ), 累加时间
		if (timer >= interval_ms) // 如果计时器达到帧间隔时间
		{
			idx_frame = (idx_frame + 1) % anim_atlas->frame_list.size(); // 切换到下一动画帧
			timer = 0; // 重置计时器

			//putimage_alpha(x, y, frame_list[idx_frame]);  // 绘制当前帧 
			// 写这里会导致画面一闪一闪的 一帧画了内容(满足 timer >= interval_ms)
			// 下一帧 cleardevice() 清空了画面,但 Play() 没画新图(因为 timer < interval_ms)
			// 所以看到的就是“闪一下,消失,再闪一下”。
		}
		putimage_alpha(x, y, anim_atlas->frame_list[idx_frame]); // 绘制当前帧  无论是否更新帧,都要绘制当前帧
	}

private:
	int timer = 0;  // 动画计时器
	int idx_frame = 0;  // 当前动画帧索引
	int interval_ms = 0;  // 动画帧间隔ms: 两帧动画间的时间 这里是45ms
	Atlas* anim_atlas;
};

class Button
{
public:
	Button(RECT rect, LPCTSTR path_img_idle, LPCTSTR path_img_hovered, LPCTSTR path_img_pushed)
	{
		region = rect;

		loadimage(&imd_idle, path_img_idle); // 加载静态图像
		loadimage(&imd_hovered, path_img_hovered); // 加载悬停图像
		loadimage(&imd_pushed, path_img_pushed); // 加载按下图像
	}

	~Button() = default;

	void ProcessEvent(const ExMessage& msg)
	{
		switch (msg.message)
		{
		case WM_MOUSEMOVE: // 鼠标移动
			if (status == Status::Idle && CheckCursorHit(msg.x, msg.y)) // 检测鼠标是否在按钮区域内
			{
				status = Button::Status::Hovered; // 设置状态为悬停
			}
			else if (status == Button::Status::Hovered && !CheckCursorHit(msg.x, msg.y)) // 检测鼠标是否离开按钮区域
			{
				status = Button::Status::Idle; // 设置状态为静止
			}
			break;
		case WM_LBUTTONDOWN: // 鼠标左键按下
			if (status == Status::Hovered && CheckCursorHit(msg.x, msg.y)) // 检测鼠标是否在按钮区域内
			{
				status = Button::Status::Pushed; // 设置状态为按下
			}
			break;
		case WM_LBUTTONUP: // 鼠标左键抬起
			if (status == Status::Pushed && CheckCursorHit(msg.x, msg.y)) // 检测鼠标是否在按钮区域内
			{
				OnClick(); // 调用点击事件
			}
			break;
		default:
			break;
		}
	}

	void Draw()
	{
		switch (status)
		{
		case Button::Status::Idle:
			putimage(region.left, region.top, &imd_idle); // 绘制静态图像
			break;
		case Button::Status::Hovered:
			putimage(region.left, region.top, &imd_hovered); // 绘制悬停图像 
			break;
		case Button::Status::Pushed:
			putimage(region.left, region.top, &imd_pushed); // 绘制按下图像
			break;
		}
	}

protected:
	virtual void OnClick() = 0; // 鼠标点击按钮事件——因不同按钮效果,所以将其设为纯虚函数,运用多态

private:
	enum class Status
	{
		Idle = 0,
		Hovered,
		Pushed
	};

private:
	RECT region;
	IMAGE imd_idle;
	IMAGE imd_hovered;
	IMAGE imd_pushed;
	Status status = Status::Idle;

private:
	// 检测鼠标点击
	bool CheckCursorHit(int x, int y)
	{
		return x >= region.left && x <= region.right && y >= region.top && y <= region.bottom; // 检测鼠标是否在按钮区域内
	}
};

class StartButton :public Button // 游戏开始按钮
{
public:
	StartButton(RECT reg, LPCTSTR img_path_idle, LPCTSTR img_path_hovered, LPCTSTR img_path_pushed)
		:Button(reg, img_path_idle, img_path_hovered, img_path_pushed) {
	}
	~StartButton() = default;

protected:
	void OnClick()
	{
		is_game_start = true;// 开始游戏

		// 进入游玩界面之后在播放背景音乐:
		mciSendString(_T("play bgm repeat from 0"), NULL, 0, NULL); // 播放bgm音乐,repeat表示循环播放,from 0表示从头开始播放
	}
};

class QuitButton :public Button // 游戏退出按钮
{
public:
	QuitButton(RECT reg, LPCTSTR img_path_idle, LPCTSTR img_path_hovered, LPCTSTR img_path_pushed)
		:Button(reg, img_path_idle, img_path_hovered, img_path_pushed) {
	}
	~QuitButton() = default;

protected:
	void OnClick()
	{
		is_running = false; // 结束运行游戏
	}
};

// 玩家类,管理玩家的移动、动画绘制和事件处理
class Player
{
public:
	const int PLAYER_WIDTH = 80; // 玩家宽度
	const int PLAYER_HEIGHT = 80; // 玩家高度
public:
	Player()
	{
		loadimage(&img_shadow, _T("img/shadow_player.png"));
		anim_left = new Animation(atlas_player_left, 45);
		anim_right = new Animation(atlas_player_right, 45);
	}

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

	// 处理事件(按键事件)
	void ProcessEvent(const ExMessage& msg)
	{
		switch (msg.message)  // 根据消息类型处理
		{
		case 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;
			}
			break;
		case 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;
			}
			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)(normalized_x * PLAYER_SPEED); // 更新玩家水平位置
			player_pos.y += (int)(normalized_y * PLAYER_SPEED); // 更新玩家垂直位置
		}

		if (player_pos.x < 0) player_pos.x = 0; // 限制玩家水平位置不小于0
		if (player_pos.x > WINDOW_WIDTH - PLAYER_WIDTH) player_pos.x = WINDOW_WIDTH - PLAYER_WIDTH; // 限制玩家水平位置不大于窗口宽度减去玩家宽度
		if (player_pos.y < 0) player_pos.y = 0; // 限制玩家垂直位置不小于0
		if (player_pos.y > WINDOW_HEIGHT - PLAYER_HEIGHT) player_pos.y = WINDOW_HEIGHT - PLAYER_HEIGHT; // 限制玩家垂直位置不大于窗口高度减去玩家高度
	}

	// 绘制玩家
	void Draw(int delta)
	{
		int pos_shadow_x = player_pos.x + (PLAYER_WIDTH / 2 - SHADOW_WIDTH / 2); // 计算阴影水平位置
		int pos_shadow_y = player_pos.y + PLAYER_HEIGHT - 8; // 计算阴影垂直位置
		putimage_alpha(pos_shadow_x, pos_shadow_y, &img_shadow); // 绘制阴影

		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;  // 返回玩家当前位置
	}

private:
	const int PLAYER_SPEED = 3;    // 玩家移动速度
	POINT player_pos = { 500, 500 }; // 玩家初始位置
	IMAGE img_shadow; // 玩家阴影
	const int SHADOW_WIDTH = 32; // 阴影宽度
	Animation* anim_left;
	Animation* anim_right;
	// 玩家移动方向标志
	bool is_move_up = false;
	bool is_move_down = false;
	bool is_move_left = false;
	bool is_move_right = false;
};


// 子弹类,管理子弹的绘制
class Bullet
{
public:
	POINT position = { 0, 0 }; // 子弹位置
public:
	Bullet() = default; // 默认构造函数
	~Bullet() = default; // 默认析构函数

	// 绘制子弹
	void Draw() const
	{
		setlinecolor(RGB(255, 155, 50)); // 设置线条颜色
		setfillcolor(RGB(200, 75, 10)); // 设置填充颜色
		fillcircle(position.x, position.y, RADIUS); // 绘制子弹
	}
private:
	const int RADIUS = 10; // 子弹半径
};

// 敌人类,管理敌人的移动、绘制和碰撞检测
class Enemy
{
public:
	// 构造函数,初始化敌人
	Enemy()
	{
		loadimage(&img_shadow, _T("img/shadow_enemy.png"));
		anim_left = new Animation(atlas_enemy_left, 45);
		anim_right = new Animation(atlas_enemy_right, 45);


		// 随机选择敌人出现的边缘
		enum class SpawnEdge
		{
			Up = 0, // 上边缘
			Down,
			Left,
			Right
		};


		SpawnEdge edge = (SpawnEdge)(rand() % 4); // 随机生成敌人所处边界
		switch (edge)
		{
		case SpawnEdge::Up:  // 上边缘
			position.x = rand() % WINDOW_WIDTH;  // 随机水平位置
			position.y = -FRAME_HEIGHT;  // 初始垂直位置在窗口上方
			break;
		case SpawnEdge::Down:  // 下边缘
			position.x = rand() % WINDOW_WIDTH;  // 随机水平位置
			position.y = WINDOW_HEIGHT;  // 初始垂直位置在窗口下方
			break;
		case SpawnEdge::Left:  // 左边缘
			position.x = -FRAME_WIDTH;  // 初始水平位置在窗口左侧
			position.y = rand() % WINDOW_HEIGHT;  // 随机垂直位置
			break;
		case SpawnEdge::Right:  // 右边缘
			position.x = WINDOW_WIDTH;  // 初始水平位置在窗口右侧
			position.y = rand() % WINDOW_HEIGHT;  // 随机垂直位置
			break;
		default:
			break;
		}
	}



	// 检测子弹碰撞
	bool CheckBulletCollision(const Bullet& bullet)
	{
		bool is_overlap_x = bullet.position.x >= position.x && bullet.position.x <= position.x + FRAME_WIDTH;  // 水平方向碰撞检测
		bool is_overlap_y = bullet.position.y >= position.y && bullet.position.y <= position.y + FRAME_HEIGHT;  // 垂直方向碰撞检测
		return is_overlap_x && is_overlap_y;  // 返回是否碰撞
	}

	// 检测玩家碰撞
	bool CheckPlayerCollision(const Player& player)
	{
		// 将敌人中心位置等效为判断点, 检测判断点是否在玩家矩形内
		POINT check_position = { position.x + FRAME_WIDTH / 2, position.y + FRAME_HEIGHT / 2 };
		const POINT& player_position = player.GetPosition();  // 玩家位置
		bool is_overlap_x = (check_position.x >= player_position.x && check_position.x <= player_position.x + player.PLAYER_WIDTH); // 判断x轴重叠
		bool is_overlap_y = (check_position.y >= player_position.y && check_position.y <= player_position.y + player.PLAYER_HEIGHT); // 判断y轴重叠
		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)(normalized_x * SPEED);  // 更新敌人水平位置
			position.y += (int)(normalized_y * SPEED);  // 更新敌人垂直位置
		}

		if (dir_x < 0) // 如果敌人在玩家右侧
			facing_left = true; // 向左移动
		else
			facing_left = false; // 向右移动
	}

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

	// 绘制敌人
	void Draw(int delta)
	{
		int pos_shadow_x = position.x + (FRAME_WIDTH / 2 - SHADOW_WIDTH / 2);  // 阴影水平位置
		int pos_shadow_y = position.y + FRAME_HEIGHT - 35;  // 阴影垂直位置
		putimage_alpha(pos_shadow_x, pos_shadow_y, &img_shadow);  // 绘制阴影

		// 根据面向方向绘制动画
		if (facing_left)
			anim_left->Play(position.x, position.y, delta);
		else
			anim_right->Play(position.x, position.y, delta);
	}

	// 受到伤害
	void Hurt()
	{
		alive = false;  // 设置敌人状态为死亡
	}

	// 检查敌人是否存活
	bool CheckAlive()
	{
		return alive;  // 返回敌人是否存活
	}

private:
	const int SPEED = 2; // 敌人移动速度
	const int FRAME_WIDTH = 80;
	const int FRAME_HEIGHT = 80;
	const int SHADOW_WIDTH = 48;

private:
	IMAGE img_shadow;
	Animation* anim_left;
	Animation* anim_right;
	POINT position = { 0,0 };
	bool facing_left = false; // 敌人是否面向左
	bool alive = true; // 敌人是否存活
};

// 尝试生成敌人
void TryGenerateEnemy(std::vector<Enemy*>& enemy_list)
{
	const int INTERVAL = 50; // 生成敌人的时间间隔
	static int counter = 0;
	if ((++counter) % INTERVAL == 0) // 每隔一定时间生成一个敌人
		enemy_list.push_back(new Enemy()); // 将敌人加入列表
}

enum BulletType {
	CIRCLE,
	DOUBLE_RING_BULLET,
	FLOWER_BULLET
};

BulletType currentBulletType = CIRCLE;  // 默认圆形弹幕

// 切换弹幕类型
void ProcessBulletChangeKey() {
	if (GetAsyncKeyState('1') & 0x8000) {  // 按下1键
		currentBulletType = CIRCLE;
	}
	else if (GetAsyncKeyState('2') & 0x8000) {  // 按下2键
		currentBulletType = DOUBLE_RING_BULLET;
	}
	else if (GetAsyncKeyState('3') & 0x8000) {  // 按下3键
		currentBulletType = FLOWER_BULLET;
	}
}

// 更新子弹位置函数:根据时间与角度,控制子弹围绕玩家旋转并形成“呼吸感”的弹幕
void UpdateCircleBullets(std::vector<Bullet>& bullet_list, const Player& player)
{
	// 弹幕参数(可调)
	const double RADIAL_SPEED = 0.0045;   // 半径变化速度(决定“呼吸”快慢)
	const double TANGENT_SPEED = 0.0055;  // 弹幕绕圈的旋转速度(切向运动速度)

	// 圆上每颗子弹之间的间隔角度,确保每颗子弹均匀分布在圆周上
	double radian_interval = 2 * PI / bullet_list.size();

	// 获取玩家当前坐标(作为圆心)
	POINT player_position = player.GetPosition();

	// 计算当前时间(毫秒),用作动画驱动
	DWORD now = GetTickCount();

	// 通过 sin() 周期函数,让半径动态变化:实现“呼吸式”缩放效果
	double radius = 100 + 25 * sin(now * RADIAL_SPEED); // 半径在 [75, 125] 范围内变化

	// 遍历所有子弹,更新每一颗的位置
	for (size_t i = 0; i < bullet_list.size(); i++)
	{
		// 计算当前子弹的旋转角度
		double radian = now * TANGENT_SPEED + radian_interval * i;

		// 将极坐标 (radius, radian) 转换为直角坐标 (x, y)
		bullet_list[i].position.x = player_position.x + player.PLAYER_WIDTH / 2 + (int)(radius * cos(radian));
		bullet_list[i].position.y = player_position.y + player.PLAYER_HEIGHT / 2 + (int)(radius * sin(radian));
	}
}

// 更新花瓣弹幕函数:根据时间与角度,控制子弹围绕玩家旋转并形成“呼吸感”的弹幕
void UpdateFlowerBullets(std::vector<Bullet>& bullet_list, const Player& player) {
	DWORD now = GetTickCount();
	POINT p = player.GetPosition();
	int petal_count = 6;
	double radius_base = 100;

	for (size_t i = 0; i < bullet_list.size(); i++) {
		double angle = now * 0.003 + i * (2 * acos(-1) / bullet_list.size());
		double radius = radius_base * (1 + 0.3 * sin(petal_count * angle)); // 花瓣起伏

		bullet_list[i].position.x = p.x + player.PLAYER_WIDTH / 2 + (int)(radius * cos(angle));
		bullet_list[i].position.y = p.y + player.PLAYER_HEIGHT / 2 + (int)(radius * sin(angle));
	}
}

// 更新双环弹幕函数:根据时间与角度,控制子弹围绕玩家旋转并形成“呼吸感”的弹幕
void UpdateDoubleRingBullets(std::vector<Bullet>& bullet_list, const Player& player) {
	DWORD now = GetTickCount();
	POINT p = player.GetPosition();
	double radius = 120;
	double speed = 0.004;

	for (size_t i = 0; i < bullet_list.size(); i++) {
		bool clockwise = i % 2 == 0;
		double angle = now * (clockwise ? speed : -speed) + i * (2 * acos(-1) / bullet_list.size());
		bullet_list[i].position.x = p.x + player.PLAYER_WIDTH / 2 + (int)(radius * cos(angle));
		bullet_list[i].position.y = p.y + player.PLAYER_HEIGHT / 2 + (int)(radius * sin(angle));
	}
}

// 绘制玩家得分
void DrawPlayerScore(int score)
{
	static TCHAR text[128];  // 存储得分文本
	_stprintf_s(text, _T("当前玩家得分:%d"), score);  // 格式化得分文本

	setbkmode(TRANSPARENT);  // 设置背景模式为透明
	settextcolor(RGB(255, 85, 185));  // 设置文本颜色
	outtextxy(10, 10, text);  // 在窗口左上角绘制得分
}

int main()
{
	initgraph(WINDOW_WIDTH, WINDOW_HEIGHT); // 初始化图形模式,设置窗口大小为1280x720像素

	//初始化资产

	atlas_player_left = new Atlas(_T("img/player_left_%d.png"), 6);
	atlas_player_right = new Atlas(_T("img/player_right_%d.png"), 6);
	atlas_enemy_left = new Atlas(_T("img/enemy_left_%d.png"), 6);
	atlas_enemy_right = new Atlas(_T("img/enemy_right_%d.png"), 6);


	mciSendString(_T("open mus/bgm.mp3 alias bgm"), NULL, 0, NULL); // 将文件夹中名为bgm.mp3的音乐文件加载到别名bgm中
	mciSendString(_T("open mus/hit.wav alias hit"), NULL, 0, NULL); // 将文件夹中名为hit.wav的音乐文件加载到别名hit中

	int score = 0; // 玩家得分
	Player player; // 创建玩家对象
	ExMessage msg; // 消息结构体
	IMAGE img_menu; // 主菜单背景图
	IMAGE img_background; // 背景图片结构体
	std::vector<Enemy*> enemy_list; // 敌人列表
	std::vector<Bullet> bullet_list(4); // 子弹列表

	// 加载按钮 RECT表示矩形类型

	RECT startRegion, quitRegion;
	startRegion.left = (WINDOW_WIDTH - BUTTON_WIDTH) / 2;
	startRegion.right = startRegion.left + BUTTON_WIDTH;
	startRegion.top = 430;
	startRegion.bottom = startRegion.top + BUTTON_HEIGHT;

	quitRegion.left = (WINDOW_WIDTH - BUTTON_WIDTH) / 2;
	quitRegion.right = quitRegion.left + BUTTON_WIDTH;
	quitRegion.top = 550;
	quitRegion.bottom = quitRegion.top + BUTTON_HEIGHT;

	StartButton startButton{ startRegion , _T("img/ui_start_idle.png"),
		_T("img/ui_start_hovered.png"),_T("img/ui_start_pushed.png") };
	QuitButton quitButton{ quitRegion , _T("img/ui_quit_idle.png"),
		_T("img/ui_quit_hovered.png"),_T("img/ui_quit_pushed.png") };

	loadimage(&img_menu, _T("img/menu.png")); // 加载主菜单背景图
	loadimage(&img_background, _T("img/background.png")); // 加载背景图片

	BeginBatchDraw(); // 开始批量绘制

	while (is_running)
	{
		DWORD start_time = GetTickCount(); // 获取开始时间

		// 读取数据
		while (peekmessage(&msg))
		{
			if(is_game_start) // 如果开始游玩,则处理玩家类消息
				player.ProcessEvent(msg); // 处理玩家事件
			else // 如果游戏还没有开始游玩,则处理按钮消息
			{
				startButton.ProcessEvent(msg);
				quitButton.ProcessEvent(msg);
			}
		}

		// 处理数据
		if (is_game_start) // 如果游戏开始游玩,则处理玩家和敌人的相关逻辑——数据处理部分
		{
			ProcessBulletChangeKey(); // 切换弹幕类型

			player.Move(); // 移动玩家

			// 更新子弹
			switch (currentBulletType) {
			case CIRCLE:
				UpdateCircleBullets(bullet_list, player);  // 更新圆形弹幕
				break;
			case DOUBLE_RING_BULLET:
				UpdateDoubleRingBullets(bullet_list, player);  // 更新双环弹幕
				break;
			case FLOWER_BULLET:
				UpdateFlowerBullets(bullet_list, player);  // 更新花瓣弹幕
				break;
			}

			TryGenerateEnemy(enemy_list); // 尝试生成敌人
			for (auto& enemy : enemy_list)
				enemy->Move(player);

			// 检测碰撞
			for (auto& enemy : enemy_list)
			{
				if (enemy->CheckPlayerCollision(player)) // 检测玩家碰撞
				{
					static TCHAR text[128]; // 存储提示文本
					_stprintf_s(text, _T("最终得分:%d !"), score); // 格式化提示文本
					MessageBox(GetHWnd(), text, _T("游戏结束"), MB_OK); // 弹出消息框
					is_running = false; // 结束游戏循环
					break; // 退出循环
				}
			}

			for (auto& enemy : enemy_list)  // 遍历所有敌人
			{
				for (auto& bullet : bullet_list)  // 遍历所有子弹
				{
					if (enemy->CheckBulletCollision(bullet))  // 检测子弹碰撞
					{
						mciSendString(_T("play hit from 0"), NULL, 0, NULL); // 播放命中音效
						enemy->Hurt();  // 敌人受到伤害
						score++;  // 增加分数
						break; // 退出当前敌人剩余碰撞子弹检测循环,进入判断下一个敌人
					}
				}
			}

			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(); // 清除设备缓冲区

		if(is_game_start) // 如果游戏开始游玩,则绘制玩家和敌人还有游戏背景
		{
			putimage(0, 0, &img_background); // 绘制背景图片
			player.Draw(1000 / 144); //绘制当前人物动画
			for (auto& enemy : enemy_list)
				enemy->Draw(1000 / 144); // 绘制敌人动画
			for (auto& bullet : bullet_list)  // 遍历所有子弹
				bullet.Draw();  // 绘制子弹
			DrawPlayerScore(score);  // 绘制玩家得分
		}
		else // 如果还没有开始游玩,则绘制主菜单和按钮
		{
			putimage(0, 0, &img_menu);
			startButton.Draw();
			quitButton.Draw();
		}


		FlushBatchDraw(); // 将绘制的内容发送到设备

		DWORD end_time = GetTickCount(); // 获取结束时间
		DWORD delta_time = end_time - start_time; // 计算时间差
		if (delta_time < 1000 / 144) // 如果时间差小于144毫秒(约等于60FPS)
		{
			Sleep(1000 / 144 - delta_time); // 暂停一段时间,以达到目标帧率
		}
	}

	// 释放图集资源
	delete atlas_player_left;
	delete atlas_player_right;
	delete atlas_enemy_left;
	delete atlas_enemy_right;

	EndBatchDraw(); // 结束批量绘制

	return 0; // 程序正常退出
}

到这里已经全部写完了,按主键盘的1,2,3分别有三种不同的弹幕效果(感觉一个太单调了,然后就瞎折腾)

屏幕录制 2025-05-10 140337

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐