CG计算机图形学2D大作业——OpenGL小球击碎砖块游戏(无依赖附源码)

本作业首次出现于 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+opengl https://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键退出程序。

  1. 主界面:用户单击Start Game(蓝色按钮)进入游戏,单击Help(绿色按钮)查看帮助,单击Exit(紫色按钮)退出程序。
  2. 进入游戏后,拖动左下角速度滑块调节初始速度,按空格键开始游戏。
  3. 开始游戏后,通过左键(←)右键(→)或者鼠标拖动挡板来控制挡板方向接球,球会根据与中心点的偏移位置进行反弹(如:击球点越靠近挡板两侧,反弹速度与水平面的夹角越小)。
  4. 每次游戏共有两次复活机会,临时“死亡”时按空格继续游戏;如复活机会耗尽后仍然未能击碎所有砖块,则游戏失败,提示Game Over;如耗尽前击碎所有砖块,则会提示Success
  5. 死亡后可以选择点击Restart(绿色按钮)重开一局,也可以点击Quit(蓝色按钮)退出游戏。

三、创意特色

  1. 使用了多种已学算法,(包括但不限于:基本图元的绘制、文本绘制、颜色更改、图形变换、图形运动、键盘相应、鼠标相应等)
  2. 交互逻辑丰富,可以通过鼠标或键盘任意一种方式控制挡板。
  3. 游戏难度渐进,随机性、可玩性强。每局游戏的初始化方式随机,无法通过暴力重复通关;且游戏中会通过增加小球速度、减小小球半径的方式提高难度。
  4. 采用了背景贴图技术与色彩丰富的游戏界面,每层砖块颜色不同,且设计了击碎特效,激发游戏兴趣。

四、界面说明

 五、项目代码

贴图的放置路径为:解决方案资源管理器-资源文件下,右键添加“现有项”并更换程序中的宏定义可以修改为你的贴图,如不进行处理则为白色背景

#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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值