提瓦特幸存者(四)
【代码】提瓦特幸存者(四)
·
#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
更多推荐
所有评论(0)