本作业首次出现于 BNU 25Spring [SAI22002]计算机图形学课程,
原封不动搬运请注意风险,
助教老师别误伤。
一、项目依赖
基于Visual Studio 2022 社区版, GLUT 3.7、GLFW3.4以及GLEW2.2进行开发。
关于如何配置OpenGL库请参考Windows10+VS2019+OpenGL安装配置详解_win10+vs2019+opengl-CSDN博客文章浏览阅读1.9w次,点赞50次,收藏270次。零基础入门OpenGL之安装配置Windows10+VS2019+OpenGL安装配置步骤Windows10+VS2019+OpenGL安装配置步骤**零基础入门OpenGL之安装配置**VS2019简介VS2019的分类1.个人版:Visual Studio Community2.专业版:Visual Studio Professional3.企业版:Visual Studio EnterpriseOpenGL简介一、VS2019下载与安装1.Visual Studio 2019下载2.相关配置安_win10+vs2019+openglhttps://blog.csdn.net/Hi123_/article/details/119116152本代码中背景贴图用到了“stb_image.h”头文件,配置非常简单,(也可以选择不使用贴图注释掉相关代码/利用OpenGL自己的api贴图)如何配置请参考引擎开发二: stb_image库及使用_stbimage下载-CSDN博客文章浏览阅读1.5w次,点赞14次,收藏30次。 stb_image 是一个简单易用的图像解码库。安装及使用环境:win7 VS20151. 下载stb_image :github地址:https://github.com/nothings/stb2. opengl项目配置: 因为stb_image库实现都写在头文件中,不需要编译成库,项目中直接引用头文件目录即可。a. 项目属性 ----> C/C++ —> 附..._stbimage下载
https://blog.csdn.net/u012278016/article/details/105784912
二、操作说明
任意时刻都可以通过Esc键退出程序。
- 主界面:用户单击Start Game(蓝色按钮)进入游戏,单击Help(绿色按钮)查看帮助,单击Exit(紫色按钮)退出程序。
- 进入游戏后,拖动左下角速度滑块调节初始速度,按空格键开始游戏。
- 开始游戏后,通过左键(←)右键(→)或者鼠标拖动挡板来控制挡板方向接球,球会根据与中心点的偏移位置进行反弹(如:击球点越靠近挡板两侧,反弹速度与水平面的夹角越小)。
- 每次游戏共有两次复活机会,临时“死亡”时按空格继续游戏;如复活机会耗尽后仍然未能击碎所有砖块,则游戏失败,提示Game Over;如耗尽前击碎所有砖块,则会提示Success
- 死亡后可以选择点击Restart(绿色按钮)重开一局,也可以点击Quit(蓝色按钮)退出游戏。
三、创意特色
- 使用了多种已学算法,(包括但不限于:基本图元的绘制、文本绘制、颜色更改、图形变换、图形运动、键盘相应、鼠标相应等)
- 交互逻辑丰富,可以通过鼠标或键盘任意一种方式控制挡板。
- 游戏难度渐进,随机性、可玩性强。每局游戏的初始化方式随机,无法通过暴力重复通关;且游戏中会通过增加小球速度、减小小球半径的方式提高难度。
- 采用了背景贴图技术与色彩丰富的游戏界面,每层砖块颜色不同,且设计了击碎特效,激发游戏兴趣。
四、界面说明
五、项目代码
贴图的放置路径为:解决方案资源管理器-资源文件下,右键添加“现有项”并更换程序中的宏定义可以修改为你的贴图,如不进行处理则为白色背景
#include "framework.h"
#include <GL/glut.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <cstdio>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// 滑动条参数定义
#define SLIDER_WIDTH 200 // 滑动条宽度
#define SLIDER_HEIGHT 20 // 滑动条高度
#define SLIDER_MIN_X 100 // 滑动条最小X坐标
#define SLIDER_MAX_X (SLIDER_MIN_X + SLIDER_WIDTH) // 滑动条最大X坐标
#define SLIDER_Y 150 // 滑动条Y坐标
float sliderPos = SLIDER_MIN_X; // 滑块当前位置
float speedLevel = 0.01f; // 速度等级,范围 0.10-1.00
// 游戏相关常量定义
#define TEXTURE_PATH "texture/goldwood.jpg" //底纹相对路径
#define MAX_ROWS 9 // 砖块最大行数
#define MIN_BRICKS 3 // 每行最少砖块数
#define MAX_BRICKS 10 // 每行最多砖块数
#define BRICK_HEIGHT 20 // 砖块高度
#define PADDLE_WIDTH 90 // 挡板宽度
#define PADDLE_HEIGHT 10 // 挡板高度
#define INIT_BALL_RADIUS 10 // 小球初始半径
#define BALL_RADIUS_MIN 5 // 小球最小半径
#define INIT_LIVES 3 // 初始生命数
#define PADDLE_SPEED 10 // 挡板移动步长
float colors[MAX_ROWS][3]; // 存储每行砖块颜色(RGB值)
// 按钮尺寸和位置定义
#define BUTTON_WIDTH 100 // 按钮宽度
#define BUTTON_HEIGHT 40 // 按钮高度
// 砖块结构体定义
typedef struct {
float x; // 砖块X坐标
float y; // 砖块Y坐标
float width; // 砖块宽度
bool active; // 砖块是否激活
} Brick;
// 碎片结构体定义
typedef struct {
float x;
float y;
float dx;
float dy;
float r, g, b;
bool active;
} Fragment;
#define MAX_FRAGMENTS 1000
Fragment fragments[MAX_FRAGMENTS];
int fragmentCount = 0;
int ww, hh; // 窗口宽度和高度
float ballX, ballY; // 小球X和Y坐标
float ballDX = 0.0f, ballDY = 0.0f; // 小球X和Y方向速度
float paddleX; // 挡板X坐标
Brick bricks[MAX_ROWS][MAX_BRICKS]; // 砖块二维数组
int brickCounts[MAX_ROWS]; // 每行砖块数量数组
float ballRadius = INIT_BALL_RADIUS; // 小球当前半径
float colorHue = 0.0f; // 小球颜色色调
bool isPaused = false; // 游戏暂停标志
bool isStartPage = true; // 开始页面标志
int score = 0; // 游戏得分
int lives = INIT_LIVES; // 剩余生命数
bool gameStarted = false; // 游戏是否开始标志
bool gameOver = false; // 游戏是否结束标志
bool isDraggingPaddle = false; // 标记是否正在拖动挡板
// 纹理ID
unsigned int textureID;
// 函数声明
void Myinit(bool start);
void Reshape(int w, int h);
void myKeyboard(unsigned char key, int x, int y);
void mySpecial(int key, int x, int y);
void Display(void);
void drawBricks();
void drawPaddle();
void drawBall();
void updateGame();
void initBricks();
void drawText(const char* text, int x, int y);
bool checkCollision();
void drawSlider();
void updateSpeedLevel();
void hsv2rgb(float h, float s, float v, float* r, float* g, float* b);
void drawStartPage();
void handleStartButtonClick(int x, int y);
void updateFragments();
void drawFragments();
void createFragments(float x, float y, float width, float r, float g, float b);
// 初始化游戏状态
// backToStartPage: 是否返回开始页面,默认为 true
void Myinit(bool backToStartPage = true) {
srand((unsigned int)time(NULL)); // 初始化随机数种子
glClearColor(0.0, 0.0, 0.0, 0.0); // 设置清除颜色为黑色
score = 0; // 重置得分
lives = INIT_LIVES; // 重置生命数
gameStarted = false; // 标记游戏未开始
gameOver = false; // 标记游戏未结束
ballRadius = INIT_BALL_RADIUS; // 重置小球半径
colorHue = 0.0f; // 重置小球颜色色调
isPaused = false; // 标记游戏未暂停
if (backToStartPage) {
isStartPage = true; // 返回开始页面
}
sliderPos = SLIDER_MIN_X; // 重置滑块位置
speedLevel = 0.01f; // 重置速度等级
initBricks(); // 初始化砖块
fragmentCount = 0;
// 加载纹理
int width, height, nrChannels;
unsigned char* data = stbi_load(TEXTURE_PATH, &width, &height, &nrChannels, 0);
if (data) {
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GLenum format;
if (nrChannels == 3) {
format = GL_RGB;
}
else if (nrChannels == 4) {
format = GL_RGBA;
}
// 使用 gluBuild2DMipmaps 生成 Mipmap
if (gluBuild2DMipmaps(GL_TEXTURE_2D, format, width, height, format, GL_UNSIGNED_BYTE, data) != GL_NO_ERROR) {
printf("Failed to generate mipmaps\n");
}
stbi_image_free(data);
}
else {
printf("Failed to load texture\n");
}
}
// r, g, b: 指向存储 RGB 值的指针
void hsv2rgb(float h, float s, float v, float* r, float* g, float* b) {
int i = (int)(h * 6); // 计算色调索引
float f = h * 6 - i; // 计算色调小数部分
float p = v * (1 - s); // 计算中间值 p
float q = v * (1 - s * f); // 计算中间值 q
float t = v * (1 - s * (1 - f));// 计算中间值 t
switch (i % 6) {
case 0: *r = v; *g = t; *b = p; break;
case 1: *r = q; *g = v; *b = p; break;
case 2: *r = p; *g = v; *b = t; break;
case 3: *r = p; *g = q; *b = v; break;
case 4: *r = t; *g = p; *b = v; break;
case 5: *r = v; *g = p; *b = q; break;
}
}
// 为每行砖块生成随机颜色
void generateRandomColors(float colors[MAX_ROWS][3]) {
srand(static_cast<unsigned int>(time(nullptr)));
for (int i = 0; i < MAX_ROWS; ++i) {
float hue = static_cast<float>(rand()) / RAND_MAX; // 随机生成色调
float saturation = 0.7f + static_cast<float>(rand()) / RAND_MAX * 0.3f; // 随机生成饱和度
float value = 0.8f + static_cast<float>(rand()) / RAND_MAX * 0.2f; // 随机生成亮度
hsv2rgb(hue, saturation, value, &colors[i][0], &colors[i][1], &colors[i][2]); // 转换为 RGB
}
}
// 初始化砖块的位置、数量和激活状态
void initBricks() {
generateRandomColors(colors); // 生成随机颜色
for (int row = 0; row < MAX_ROWS; row++) {
brickCounts[row] = MIN_BRICKS + rand() % (MAX_BRICKS - MIN_BRICKS + 1); // 随机确定每行砖块数量
float totalWidth = ww - 2 * INIT_BALL_RADIUS; // 计算可用宽度
float spacing = (totalWidth - brickCounts[row] * (BRICK_HEIGHT * 2)) / (brickCounts[row] + 1); // 计算砖块间距
for (int i = 0; i < brickCounts[row]; i++) {
bricks[row][i].width = BRICK_HEIGHT * 2; // 设置砖块宽度
bricks[row][i].x = INIT_BALL_RADIUS + spacing + i * (bricks[row][i].width + spacing); // 设置砖块 X 坐标
bricks[row][i].y = hh - (row + 1) * (BRICK_HEIGHT + 5); // 设置砖块 Y 坐标
bricks[row][i].active = true; // 激活砖块
}
}
}
// 处理窗口大小改变事件
void Reshape(int w, int h) {
glMatrixMode(GL_PROJECTION); // 设置投影矩阵模式
glLoadIdentity(); // 重置投影矩阵
glViewport(0, 0, w, h); // 设置视口大小
gluOrtho2D(0, w, 0, h); // 设置正交投影
ww = w; // 更新窗口宽度
hh = h; // 更新窗口高度
paddleX = (ww - PADDLE_WIDTH) / 2; // 计算挡板初始 X 坐标
ballX = paddleX + PADDLE_WIDTH / 2; // 计算挡板初始 X 坐标
ballY = PADDLE_HEIGHT + INIT_BALL_RADIUS + 20; // 计算小球初始 Y 坐标
ballRadius = INIT_BALL_RADIUS; // 重置小球半径
initBricks(); // 重新初始化砖块
}
// 处理特殊按键(如方向键)事件
void mySpecial(int key, int x, int y) {
if (gameOver || isPaused || isStartPage) return; // 游戏结束、暂停或在开始页面时不处理
int delta = PADDLE_SPEED; // 挡板移动增量
switch (key) {
case GLUT_KEY_LEFT: // 左方向键
paddleX = max(0.0f, paddleX - delta); // 限制挡板左移不超出边界
if (!gameStarted) {
ballX = paddleX + PADDLE_WIDTH / 2; // 游戏未开始时,小球随挡板移动
}
break;
case GLUT_KEY_RIGHT: // 右方向键
paddleX = min((float)ww - PADDLE_WIDTH, paddleX + delta); // 限制挡板右移不超出边界
if (!gameStarted) {
ballX = paddleX + PADDLE_WIDTH / 2; // 游戏未开始时,小球随挡板移动
}
break;
}
glutPostRedisplay(); // 标记窗口需要重新绘制
}
// 更新游戏状态
void updateGame() {
if (!gameStarted || gameOver || isPaused || isStartPage) return; // 游戏未开始、结束、暂停或在开始页面时不更新
// 更新小球颜色
colorHue += 0.001f;
if (colorHue >= 1.0f) colorHue -= 1.0f;
ballX += ballDX; // 更新小球 X 坐标
ballY += ballDY; // 更新小球 Y 坐标
// 边界检测
if (ballX - ballRadius < 0 || ballX + ballRadius > ww) ballDX *= -1; // 左右边界反弹
if (ballY + ballRadius > hh) ballDY *= -1; // 上边界反弹
// 挡板碰撞检测
if (ballY - ballRadius < PADDLE_HEIGHT + 20 &&
ballX >= paddleX && ballX <= paddleX + PADDLE_WIDTH) {
ballDY *= -1.1f; // 反弹并增加速度
float offset = (ballX - (paddleX + PADDLE_WIDTH / 2)) / (PADDLE_WIDTH / 2);
ballDX += offset * 0.05f; // 根据碰撞位置调整 X 方向速度
}
// 砖块碰撞检测
checkCollision();
// 底部边界检测
if (ballY - ballRadius < 0) {
lives--; // 失去一条生命
if (lives <= 0) {
gameOver = true; // 生命耗尽,游戏结束
}
else {
gameStarted = false; // 游戏重新开始
ballX = paddleX + PADDLE_WIDTH / 2; // 重置小球位置
ballY = PADDLE_HEIGHT + INIT_BALL_RADIUS + 20;
ballDX = 0;
ballDY = 0;
ballRadius = INIT_BALL_RADIUS; // 重置小球半径
}
}
// 检查是否所有砖块都被摧毁
bool allDestroyed = true;
for (int row = 0; row < MAX_ROWS; row++) {
for (int i = 0; i < brickCounts[row]; i++) {
if (bricks[row][i].active) {
allDestroyed = false;
break;
}
}
}
if (allDestroyed) {
gameOver = true; // 所有砖块被摧毁,游戏结束
}
updateFragments();
glutPostRedisplay(); // 标记窗口需要重新绘制
}
// 绘制游戏画面
void Display(void) {
glClear(GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区
// 启用纹理映射
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureID);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex2f(0, 0);
glTexCoord2f(1.0f, 0.0f); glVertex2f(ww, 0);
glTexCoord2f(1.0f, 1.0f); glVertex2f(ww, hh);
glTexCoord2f(0.0f, 1.0f); glVertex2f(0, hh);
glEnd();
glDisable(GL_TEXTURE_2D);
if (isStartPage) {
drawStartPage(); // 绘制开始页面
}
else {
if (!gameStarted && !gameOver) {
drawSlider(); // 绘制滑动条
char buffer[32];
sprintf_s(buffer, "Speed: %.1fx", speedLevel * 10); // 格式化速度显示字符串
drawText(buffer, (ww - SLIDER_WIDTH) / 2 + 50, SLIDER_Y - 30); // 显示速度
}
drawBricks(); // 绘制砖块
drawPaddle(); // 绘制挡板
drawBall(); // 绘制小球
drawFragments();
// 绘制 UI
char buffer[32];
sprintf_s(buffer, "Score: %04d", score); // 得分显示
drawText(buffer, 10, hh - 25);
sprintf_s(buffer, "Lives: %d", lives); // 生命数显示
drawText(buffer, ww - 120, hh - 25);
if (!gameStarted && !gameOver) {
drawText("Press SPACE to start", ww / 2 - 80, hh / 2); // 提示开始游戏
}
if (isPaused) {
drawText("Game Paused", ww / 2 - 50, hh / 2); // 显示暂停提示
}
if (gameOver) {
glColor3f(1.0, 0.0, 0.0);
if (lives <= 0) {
drawText("GAME OVER", ww / 2 - 35, hh / 2 + 60); // 显示游戏结束提示
int restartButtonX = (ww / 2 - BUTTON_WIDTH - 20);
int quitButtonX = (ww / 2 + 20);
int buttonY = (hh / 2 - BUTTON_HEIGHT / 2);
// 绘制 Restart 按钮
glColor3f(0.1, 0.4, 0.2);
glBegin(GL_QUADS);
glVertex2f(restartButtonX, buttonY);
glVertex2f(restartButtonX + BUTTON_WIDTH, buttonY);
glVertex2f(restartButtonX + BUTTON_WIDTH, buttonY + BUTTON_HEIGHT);
glVertex2f(restartButtonX, buttonY + BUTTON_HEIGHT);
glEnd();
glColor3f(0.0, 0.0, 0.0);
drawText("Restart", restartButtonX + 20, buttonY + 18);
// 绘制 Quit 按钮
glColor3f(0.2, 0.2, 0.5);
glBegin(GL_QUADS);
glVertex2f(quitButtonX, buttonY);
glVertex2f(quitButtonX + BUTTON_WIDTH, buttonY);
glVertex2f(quitButtonX + BUTTON_WIDTH, buttonY + BUTTON_HEIGHT);
glVertex2f(quitButtonX, buttonY + BUTTON_HEIGHT);
glEnd();
glColor3f(0.0, 0.0, 0.0);
drawText("Quit", quitButtonX + 35, buttonY + 18);
}
else {
drawText("YOU WIN!", ww / 2 - 50, hh / 2); // 显示胜利提示
}
}
}
glutSwapBuffers();
}
// 绘制开始页面
void drawStartPage() {
glColor3f(1.0, 1.0, 1.0);
drawText("2D BRICK - BALL", ww / 2 - 60, hh * 3 / 4);
drawText("", ww / 2 - 90, hh * 1 / 4 - 60);
int buttonWidth = 200;
int buttonHeight = 50;
int buttonX = (ww - buttonWidth) / 2;
int buttonY = (hh - buttonHeight) / 2 + 50;
// 绘制开始按钮
glColor3f(0.0, 0.5, 1.0);
glBegin(GL_QUADS);
glVertex2f(buttonX, buttonY);
glVertex2f(buttonX + buttonWidth, buttonY);
glVertex2f(buttonX + buttonWidth, buttonY + buttonHeight);
glVertex2f(buttonX, buttonY + buttonHeight);
glEnd();
glColor3f(1.0, 1.0, 1.0);
drawText("Start Game", buttonX + 60, buttonY + 20);
// 绘制帮助按钮
glColor3f(0.3, 0.8, 0.2);
glBegin(GL_QUADS);
glVertex2f(buttonX, buttonY - 80);
glVertex2f(buttonX + buttonWidth, buttonY - 80);
glVertex2f(buttonX + buttonWidth, buttonY + buttonHeight - 80);
glVertex2f(buttonX, buttonY + buttonHeight - 80);
glEnd();
glColor3f(1.0, 1.0, 1.0);
drawText("Help", buttonX + 85, buttonY - 60);
// 绘制退出按钮
glColor3f(0.7, 0.32, 1.0);
glBegin(GL_QUADS);
glVertex2f(buttonX, buttonY - 160);
glVertex2f(buttonX + buttonWidth, buttonY - 160);
glVertex2f(buttonX + buttonWidth, buttonY + buttonHeight - 160);
glVertex2f(buttonX, buttonY + buttonHeight - 160);
glEnd();
glColor3f(1.0, 1.0, 1.0);
drawText("Exit", buttonX + 85, buttonY - 140);
}
// 处理开始页面按钮点击事件
void handleStartButtonClick(int x, int y) {
int buttonWidth = 200;
int buttonHeight = 50;
int buttonX = (ww - buttonWidth) / 2;
int startButtonY = (hh - buttonHeight) / 2 + 50;
int helpButtonY = startButtonY - 80;
int exitButtonY = helpButtonY - 80;
// 检测开始按钮点击
if (x >= buttonX && x <= buttonX + buttonWidth &&
y >= startButtonY && y <= startButtonY + buttonHeight) {
isStartPage = false; // 进入游戏
}
// 检测退出按钮点击
else if (x >= buttonX && x <= buttonX + buttonWidth &&
y >= exitButtonY && y <= exitButtonY + buttonHeight) {
exit(0); // 退出游戏
}
}
// 处理鼠标点击事件
void mouse(int button, int state, int x, int y) {
y = hh - y; // 转换坐标系
if (button == GLUT_LEFT_BUTTON) {
if (state == GLUT_DOWN) {
if (isStartPage) {
handleStartButtonClick(x, y); // 处理开始页面按钮点击
}
else if (gameOver && lives <= 0) {
int restartButtonX = (ww / 2 - BUTTON_WIDTH - 20);
int quitButtonX = (ww / 2 + 20);
int buttonY = (hh / 2 - BUTTON_HEIGHT / 2);
// 检测 Restart 按钮点击
if (x >= restartButtonX && x <= restartButtonX + BUTTON_WIDTH &&
y >= buttonY && y <= buttonY + BUTTON_HEIGHT) {
Myinit(false); // 重置游戏
Reshape(ww, hh);
gameOver = false;
}
// 检测 Quit 按钮点击
else if (x >= quitButtonX && x <= quitButtonX + BUTTON_WIDTH &&
y >= buttonY && y <= buttonY + BUTTON_HEIGHT) {
exit(0); // 退出游戏
}
}
else if (!gameStarted && !gameOver && !isPaused) {
if (y >= SLIDER_Y && y <= SLIDER_Y + SLIDER_HEIGHT &&
x >= SLIDER_MIN_X && x <= SLIDER_MAX_X) {
sliderPos = x; // 更新滑块位置
updateSpeedLevel(); // 更新速度等级
}
}
// 检查是否点击在挡板上
if (y >= 20 && y <= 20 + PADDLE_HEIGHT &&
x >= paddleX && x <= paddleX + PADDLE_WIDTH) {
isDraggingPaddle = true;
}
}
else if (state == GLUT_UP) {
isDraggingPaddle = false;
}
}
glutPostRedisplay(); // 标记窗口需要重新绘制
}
// 绘制滑动条
void drawSlider() {
// 绘制滑动条背景
glColor3f(0.5, 0.5, 0.5);
glBegin(GL_QUADS);
glVertex2f(SLIDER_MIN_X, SLIDER_Y);
glVertex2f(SLIDER_MAX_X, SLIDER_Y);
glVertex2f(SLIDER_MAX_X, SLIDER_Y + SLIDER_HEIGHT);
glVertex2f(SLIDER_MIN_X, SLIDER_Y + SLIDER_HEIGHT);
glEnd();
// 绘制滑块
glColor3f(1.0, 1.0, 0.0);
float sliderWidth = 20;
glBegin(GL_QUADS);
glVertex2f(sliderPos - sliderWidth / 2, SLIDER_Y);
glVertex2f(sliderPos + sliderWidth / 2, SLIDER_Y);
glVertex2f(sliderPos + sliderWidth / 2, SLIDER_Y + SLIDER_HEIGHT);
glVertex2f(sliderPos - sliderWidth / 2, SLIDER_Y + SLIDER_HEIGHT);
glEnd();
}
// 处理滑动条拖动事件和挡板拖动事件
void motion(int x, int y) {
if (!gameStarted && !gameOver && !isPaused && !isStartPage) {
y = hh - y;
if (y >= SLIDER_Y && y <= SLIDER_Y + SLIDER_HEIGHT &&
x >= SLIDER_MIN_X && x <= SLIDER_MAX_X) {
sliderPos = x; // 更新滑块位置
updateSpeedLevel(); // 更新速度等级
}
}
if (isDraggingPaddle) {
paddleX = max(0.0f, min((float)ww - PADDLE_WIDTH, x - PADDLE_WIDTH / 2));
if (!gameStarted) {
ballX = paddleX + PADDLE_WIDTH / 2;
}
glutPostRedisplay();
}
}
// 根据滑块位置更新速度等级
void updateSpeedLevel() {
float range = SLIDER_MAX_X - SLIDER_MIN_X;
float pos = sliderPos - SLIDER_MIN_X;
speedLevel = 0.01f + (pos / range) * 0.09f; // 速度等级范围 0.1-1.0
}
// 处理键盘按键事件
void myKeyboard(unsigned char key, int x, int y) {
switch (key) {
case ' ':
if (gameStarted && !gameOver) {
isPaused = !isPaused; // 切换暂停状态
}
else if (!gameStarted && !gameOver && !isStartPage) {
ballDX = 3.0f * speedLevel; // 开始游戏,设置小球速度
ballDY = 5.0f * speedLevel;
gameStarted = true;
}
break;
case 27: // ESC 键
exit(0); // 退出游戏
}
}
// 绘制砖块
void drawBricks() {
for (int row = 0; row < MAX_ROWS; row++) {
glColor3fv(colors[row]);
for (int i = 0; i < brickCounts[row]; i++) {
if (bricks[row][i].active) {
glBegin(GL_QUADS);
glVertex2f(bricks[row][i].x, bricks[row][i].y);
glVertex2f(bricks[row][i].x + bricks[row][i].width, bricks[row][i].y);
glVertex2f(bricks[row][i].x + bricks[row][i].width, bricks[row][i].y + BRICK_HEIGHT);
glVertex2f(bricks[row][i].x, bricks[row][i].y + BRICK_HEIGHT);
glEnd();
}
}
}
}
// 绘制挡板
void drawPaddle() {
glColor3f(1.0, 0.5, 0.0);
glBegin(GL_QUADS);
glVertex2f(paddleX, 20);
glVertex2f(paddleX + PADDLE_WIDTH, 20);
glVertex2f(paddleX + PADDLE_WIDTH, 20 + PADDLE_HEIGHT);
glVertex2f(paddleX, 20 + PADDLE_HEIGHT);
glEnd();
}
// 绘制小球
void drawBall() {
float r, g, b;
hsv2rgb(colorHue, 1.0f, 1.0f, &r, &g, &b); // 转换颜色
glColor3f(r, g, b);
glBegin(GL_TRIANGLE_FAN);
for (int i = 0; i < 360; i++) {
float angle = i * 3.1415926 / 180.0;
glVertex2f(ballX + ballRadius * cos(angle), ballY + ballRadius * sin(angle));
}
glEnd();
}
// 检测小球与砖块的碰撞
bool checkCollision() {
for (int row = 0; row < MAX_ROWS; row++) {
for (int i = 0; i < brickCounts[row]; i++) {
if (bricks[row][i].active) {
float bx = bricks[row][i].x;
float by = bricks[row][i].y;
float bw = bricks[row][i].width;
if (ballX + ballRadius > bx &&
ballX - ballRadius < bx + bw &&
ballY + ballRadius > by &&
ballY - ballRadius < by + BRICK_HEIGHT) {
bricks[row][i].active = false; // 砖块被击中,设置为非激活状态
score += 10; // 得分增加
// 创建碎片
createFragments(bx, by, bw, colors[row][0], colors[row][1], colors[row][2]);
// 检查得分是否达到100的倍数
if (score % 100 == 0) {
ballDX *= 1.1; // 速度加快10%
ballDY *= 1.1;
if (ballRadius * 0.9 > BALL_RADIUS_MIN) {
ballRadius *= 0.9; // 球半径减小10%
}
}
ballDY *= -1; // 小球反弹
return true;
}
}
}
}
return false;
}
// 在指定位置绘制文本
void drawText(const char* text, int x, int y) {
glColor3f(1.0, 1.0, 1.0);
glRasterPos2f(x, y);
while (*text) {
glutBitmapCharacter(GLUT_BITMAP_8_BY_13, *text++);
}
}
// 创建碎片
void createFragments(float x, float y, float width, float r, float g, float b) {
int numFragments = 20;
for (int i = 0; i < numFragments && fragmentCount < MAX_FRAGMENTS; i++) {
fragments[fragmentCount].x = x + rand() % (int)width;
fragments[fragmentCount].y = y;
// 减小碎片的初始速度
fragments[fragmentCount].dx = (rand() % 6 - 3) * 0.2;
fragments[fragmentCount].dy = -(rand() % 4 + 3);
fragments[fragmentCount].r = r;
fragments[fragmentCount].g = g;
fragments[fragmentCount].b = b;
fragments[fragmentCount].active = true;
fragmentCount++;
}
}
// 更新碎片状态
void updateFragments() {
for (int i = 0; i < fragmentCount; i++) {
fragments[i].x += fragments[i].dx;
fragments[i].y += fragments[i].dy;
// 减小重力效果
fragments[i].dy += 0.05;
// 移除超出边界的碎片
if (fragments[i].x < 0 || fragments[i].x > ww || fragments[i].y < 0) {
fragments[i] = fragments[--fragmentCount];
i--;
}
}
}
// 绘制碎片
void drawFragments() {
for (int i = 0; i < fragmentCount; i++) {
glColor3f(fragments[i].r, fragments[i].g, fragments[i].b);
// 增大碎片的大小
glBegin(GL_QUADS);
glVertex2f(fragments[i].x, fragments[i].y);
glVertex2f(fragments[i].x + 4, fragments[i].y);
glVertex2f(fragments[i].x + 4, fragments[i].y + 4);
glVertex2f(fragments[i].x, fragments[i].y + 4);
glEnd();
}
}
// 程序入口
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
char* argv[] = { (char*)"Brick Game", (char*)" " };
int argc = 2;
glutInit(&argc, argv);
glutInitWindowSize(800, 600);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutCreateWindow("2D弹球砖块碰撞游戏");
Myinit();
glutDisplayFunc(Display);
glutReshapeFunc(Reshape);
glutKeyboardFunc(myKeyboard);
glutSpecialFunc(mySpecial);
glutIdleFunc(updateGame);
glutMouseFunc(mouse);
glutMotionFunc(motion);
glutMainLoop();
return 0;
}