【OpenGL】OpenGL游戏案例(二)

news2025/2/1 19:57:56

文章目录

    • 特殊效果
      • 数据结构
      • 生成逻辑
      • 更新逻辑
    • 文本渲染
      • 类结构
      • 构造函数
      • 加载函数
      • 渲染函数

特殊效果

为提高游戏的趣味性,在游戏中提供了六种特殊效果。

数据结构

PowerUp

类只存储存活数据,实际逻辑在游戏代码中通过Type字段来区分执行

class PowerUp : public GameObject
{
public:
    // powerup state
    std::string Type;
    float       Duration;
    bool        Activated;
    // constructor
    PowerUp(std::string type, glm::vec3 color, float duration, glm::vec2 position, Texture2D texture)
        : GameObject(position, POWERUP_SIZE, texture, color, VELOCITY), Type(type), Duration(duration), Activated() { }
};

Game中存在容器存储目前存活的PowerUp对象

std::vector<PowerUp>  PowerUps;

生成逻辑

当玩家消灭一个方块后,按固定概率随机生成或不生成一个随机能力的砖块。

//Game::DoCollision()

// destroy block if not solid
if (!box.IsSolid)
{
    box.Destroyed = true;
    this->SpawnPowerUps(box);
}

生成策略

bool ShouldSpawn(unsigned int chance)
{
    unsigned int random = rand() % chance;
    return random == 0;
}
void Game::SpawnPowerUps(GameObject& block)
{
    if (ShouldSpawn(75)) // 1 in 75 chance
        this->PowerUps.push_back(PowerUp("speed", glm::vec3(0.5f, 0.5f, 1.0f), 0.0f, block.Position, ResourceManager::GetTexture("powerup_speed")));
    if (ShouldSpawn(75))
        this->PowerUps.push_back(PowerUp("sticky", glm::vec3(1.0f, 0.5f, 1.0f), 20.0f, block.Position, ResourceManager::GetTexture("powerup_sticky")));
    if (ShouldSpawn(75))
        this->PowerUps.push_back(PowerUp("pass-through", glm::vec3(0.5f, 1.0f, 0.5f), 10.0f, block.Position, ResourceManager::GetTexture("powerup_passthrough")));
    if (ShouldSpawn(75))
        this->PowerUps.push_back(PowerUp("pad-size-increase", glm::vec3(1.0f, 0.6f, 0.4), 0.0f, block.Position, ResourceManager::GetTexture("powerup_increase")));
    if (ShouldSpawn(15)) // Negative powerups should spawn more often
        this->PowerUps.push_back(PowerUp("confuse", glm::vec3(1.0f, 0.3f, 0.3f), 15.0f, block.Position, ResourceManager::GetTexture("powerup_confuse")));
    if (ShouldSpawn(15))
        this->PowerUps.push_back(PowerUp("chaos", glm::vec3(0.9f, 0.25f, 0.25f), 15.0f, block.Position, ResourceManager::GetTexture("powerup_chaos")));
}

更新逻辑

生成完后会在Update中调用以下函数下落更新,并每帧检查是否与玩家发生碰撞

不同状态下能力内部标志位变化:

DestoryedActivated
下落状态falsefalse
与玩家碰撞后truetrue
倒计时结束后truefalse

Destroyed决定是否要渲染其图像,Activated决定是否要启用其计时器,最后的状态则决定了是否要将其移除出列表

刚碰撞时根据Type字段执行附加能力的逻辑,倒计时结束后执行取消附加能力的逻辑。流程完毕后移除出数组

//绘制
for (PowerUp& powerUp : this->PowerUps)
    if (!powerUp.Destroyed)
        powerUp.Draw(*Renderer);

//检查碰撞
for (PowerUp& powerUp : this->PowerUps)
{
    if (!powerUp.Destroyed)
    {
        if (powerUp.Position.y >= this->Height)
            powerUp.Destroyed = true;
        if (CheckCollision(*Player, powerUp))
        {	// collided with player, now activate powerup
            ActivatePowerUp(powerUp);
            powerUp.Destroyed = true;
            powerUp.Activated = true;
        }
    }
}

//启用能力
void ActivatePowerUp(PowerUp& powerUp)
{
    if (powerUp.Type == "speed")
    {
        Ball->Velocity *= 1.2;
    }
    else if (powerUp.Type == "sticky")
    {
        Ball->Sticky = true;
        Player->Color = glm::vec3(1.0f, 0.5f, 1.0f);
    }
    else if (powerUp.Type == "pass-through")
    {
        Ball->PassThrough = true;
        Ball->Color = glm::vec3(1.0f, 0.5f, 0.5f);
    }
    else if (powerUp.Type == "pad-size-increase")
    {
        Player->Size.x += 50;
    }
    else if (powerUp.Type == "confuse")
    {
        if (!Effects->Chaos)
            Effects->Confuse = true; // only activate if chaos wasn't already active
    }
    else if (powerUp.Type == "chaos")
    {
        if (!Effects->Confuse)
            Effects->Chaos = true;
    }
}

//每帧更新,轮询何时撤销能力
void Game::UpdatePowerUps(float dt)
{
    for (PowerUp& powerUp : this->PowerUps)
    {
        powerUp.Position += powerUp.Velocity * dt;
        if (powerUp.Activated)
        {
            powerUp.Duration -= dt;

            if (powerUp.Duration <= 0.0f)
            {
                // remove powerup from list (will later be removed)
                powerUp.Activated = false;
                // deactivate effects
                if (powerUp.Type == "sticky")
                {
                    if (!IsOtherPowerUpActive(this->PowerUps, "sticky"))
                    {	// only reset if no other PowerUp of type sticky is active
                        Ball->Sticky = false;
                        Player->Color = glm::vec3(1.0f);
                    }
                }
                else if (powerUp.Type == "pass-through")
                {
                    if (!IsOtherPowerUpActive(this->PowerUps, "pass-through"))
                    {	// only reset if no other PowerUp of type pass-through is active
                        Ball->PassThrough = false;
                        Ball->Color = glm::vec3(1.0f);
                    }
                }
                else if (powerUp.Type == "confuse")
                {
                    if (!IsOtherPowerUpActive(this->PowerUps, "confuse"))
                    {	// only reset if no other PowerUp of type confuse is active
                        Effects->Confuse = false;
                    }
                }
                else if (powerUp.Type == "chaos")
                {
                    if (!IsOtherPowerUpActive(this->PowerUps, "chaos"))
                    {	// only reset if no other PowerUp of type chaos is active
                        Effects->Chaos = false;
                    }
                }
            }
        }
    }
    // Remove all PowerUps from vector that are destroyed AND !activated (thus either off the map or finished)
    // Note we use a lambda expression to remove each PowerUp which is destroyed and not activated
    this->PowerUps.erase(std::remove_if(this->PowerUps.begin(), this->PowerUps.end(),
        [](const PowerUp& powerUp) { return powerUp.Destroyed && !powerUp.Activated; }
    ), this->PowerUps.end());
}

文本渲染

文本渲染部分依赖于一个库FreeType,在项目中只拿来读取ttf生成像素数据,然后记录在自定义的Character结构中。

// 宽度、高度、左上角偏移、水平距离
struct Character
{
	unsigned int TextureID;
	glm::ivec2    Size;
	glm::ivec2    Bearing;
	unsigned int  Advance;
};

创建文字渲染器类TextRenderer来负责文字的渲染

类结构

class TextRenderer
{
public:
    // 预处理记录的需要的字符内容(借助FreeType)
    std::map<char, Character> Characters;
    
    // 文字渲染Shader,就是渲染一个四边形,然后采样纹理并渲染上去
    Shader TextShader;
    
    TextRenderer(unsigned int width, unsigned int height);
    void Load(std::string font, unsigned int fontSize);
    void RenderText(std::string text, float x, float y, float scale, glm::vec3 color = glm::vec3(1.0f));
    
private:
    unsigned int VAO, VBO;
};

构造函数

构造函数中负责初始化shader参数,初始化VAO和VBO,但VBO的具体顶点数据会在绘制时动态计算出来

TextRenderer::TextRenderer(unsigned int width, unsigned int height)
{
    // load and configure shader
    this->TextShader = ResourceManager::LoadShader("res/shaders/text.vs", "res/shaders/text.frag", nullptr, "text");
    this->TextShader.SetMatrix4("projection", glm::ortho(0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f), true);
    this->TextShader.SetInteger("text", 0);

    // configure VAO/VBO for texture quads
    glGenVertexArrays(1, &this->VAO);
    glGenBuffers(1, &this->VBO);

    //绑定VAO,VBO
    glBindVertexArray(this->VAO);
    glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
    //填充VBO数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
    //规划VBO数据布局
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

    //解绑
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

加载函数

外部提供字体文件(.ttf)路径和字体大小,内部利用FreeType将前128字符转换为使用Characters存储

void TextRenderer::Load(std::string font, unsigned int fontSize)
{
    // 如果之前已经Load过,清除
    this->Characters.clear();

    // 初始化并加载 FreeType library
    FT_Library ft;
    if (FT_Init_FreeType(&ft))
        std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;

    // 将字符加载为face
    FT_Face face;
    if (FT_New_Face(ft, font.c_str(), 0, &face))
        std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;

    // 设置加载字体的大小,这里是设置为 fontSize 参数指定的大小。第一个参数为 0,表示不限制宽度,第二个参数是目标字体大小
    FT_Set_Pixel_Sizes(face, 0, fontSize);

    // 关闭了 OpenGL 中的字节对齐限制,使得字节数据可以按 1 字节对齐,这样可以避免在纹理生成时出现内存填充问题
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // 通过循环加载前 128 个 ASCII 字符。
    for (GLubyte c = 0; c < 128; c++)
    {
        // FT_Load_Char 用来加载字符的字形信息,如果加载失败,则输出错误信息并跳过该字符
        if (FT_Load_Char(face, c, FT_LOAD_RENDER))
        {
            std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
            continue;
        }

        // 为每个字符生成一个纹理
        unsigned int texture;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        //使用 GL_RED 格式来存储单通道的灰度数据
        glTexImage2D(
            GL_TEXTURE_2D,
            0,
            GL_RED,
            face->glyph->bitmap.width,
            face->glyph->bitmap.rows,
            0,
            GL_RED,
            GL_UNSIGNED_BYTE,
            face->glyph->bitmap.buffer
        );

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        //将原始数据转换为Character数组
        Character character = {
            texture,
            glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
            glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
            face->glyph->advance.x
        };
        Characters.insert(std::pair<char, Character>(c, character));
    }

    // 清理资源
    glBindTexture(GL_TEXTURE_2D, 0);
    FT_Done_Face(face);
    FT_Done_FreeType(ft);
}

渲染函数

  • 使用并设置文字shader,使用当前的VAO
  • 遍历所有提供的字符,在Characters中取出对应的数据
  • 计算位置长宽,更新顶点缓存,绑定记录的纹理
  • 绘制完后偏移光标位置
void TextRenderer::RenderText(std::string text, float x, float y, float scale, glm::vec3 color)
{
    // activate corresponding render state	
    this->TextShader.Use();
    this->TextShader.SetVector3f("textColor", color);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(this->VAO);

    // iterate through all characters
    std::string::const_iterator c;
    for (c = text.begin(); c != text.end(); c++)
    {
        Character ch = Characters[*c];

        // 计算位置和长宽
        float xpos = x + ch.Bearing.x * scale;
        float ypos = y + (this->Characters['H'].Bearing.y - ch.Bearing.y) * scale;
        float w = ch.Size.x * scale;
        float h = ch.Size.y * scale;

        // 为每个字符更新对应的VBO缓存
        float vertices[6][4] = {
            { xpos,     ypos + h,   0.0f, 1.0f },
            { xpos + w, ypos,       1.0f, 0.0f },
            { xpos,     ypos,       0.0f, 0.0f },

            { xpos,     ypos + h,   0.0f, 1.0f },
            { xpos + w, ypos + h,   1.0f, 1.0f },
            { xpos + w, ypos,       1.0f, 0.0f }
        };

        // 绑定指定的纹理
        glBindTexture(GL_TEXTURE_2D, ch.TextureID);

        // update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        // render quad
        glDrawArrays(GL_TRIANGLES, 0, 6);

        // 更新光标位置
        x += (ch.Advance >> 6) * scale; // bitshift by 6 to get value in pixels (1/64th times 2^6 = 64)
    }

    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2289438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DeepSeek本地版安装简易教程(windows)

第一步&#xff1a;下载 第二步&#xff1a;安装 先安装ollama&#xff0c;安装完毕保持ollama运行&#xff0c;设置ollama通过防火墙&#xff0c;再安装deepseek&#xff0c;7b代表下载的r1版本&#xff0c;版本越高消耗资源越大 第三步&#xff1a;开放windows防火墙 第四步…

RK3568使用QT搭建TCP服务器和客户端

文章目录 一、让RK3568开发板先连接上wifi二、客户端代码1. `widget.h` 文件2. `widget.cpp` 文件**详细讲解**1. **`Widget` 类构造函数 (`Widget::Widget`)**2. **UI 布局 (`setupUI`)**3. **连接按钮的槽函数 (`onConnectClicked`)**4. **发送消息按钮的槽函数 (`onSendMess…

Python爬虫之——Cookie存储器

目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&#x1f308; 博客主页&#xff1a;请点击——> 一晌小贪欢的博客主页求关注 &…

蓝桥杯刷题DAY1:前缀和

所谓刷题&#xff0c;讲究的就是细心 帕鲁服务器崩坏【算法赛】 “那个帕鲁我已经观察你很久了&#xff0c;我对你是有些失望的&#xff0c;进了这个营地&#xff0c;不是把事情做好就可以的&#xff0c;你需要有体系化思考的能力。” 《幻兽帕鲁》火遍全网&#xff0c;成为…

Hive:窗口函数(1)

窗口函数 窗口函数OVER()用于定义一个窗口&#xff0c;该窗口指定了函数应用的数据范围 对窗口数据进行分区 partition by 必须和over () 一起使用, distribute by经常和sort by 一起使用,可以不和over() 一起使用.DISTRIBUTE BY决定了数据如何分布到不同的Reducer上&#xf…

OpenCV:SIFT关键点检测与描述子计算

目录 1. 什么是 SIFT&#xff1f; 2. SIFT 的核心步骤 2.1 尺度空间构建 2.2 关键点检测与精细化 2.3 方向分配 2.4 计算特征描述子 3. OpenCV SIFT API 介绍 3.1 cv2.SIFT_create() 3.2 sift.detect() 3.3 sift.compute() 3.4 sift.detectAndCompute() 4. SIFT 关…

爬虫基础(一)HTTP协议 :请求与响应

前言 爬虫需要基础知识&#xff0c;HTTP协议只是个开始&#xff0c;除此之外还有很多&#xff0c;我们慢慢来记录。 今天的HTTP协议&#xff0c;会有助于我们更好的了解网络。 一、什么是HTTP协议 &#xff08;1&#xff09;定义 HTTP&#xff08;超文本传输协议&#xff…

【4Day创客实践入门教程】Day1 工具箱构建——开发环境的构建

Day1 工具箱构建——开发环境的构建 目录 Day1 工具箱构建——开发环境的构建1.元件选型2.准备工具3. 开发板准备焊接排针具体步骤注意事项与技巧 4. 软件环境配置与固件烧录Thonny IDE软件环境配置配置Micropython环境与烧录固件**问题&#xff1a;**买的是4M/16M&#xff0c;…

如何让一个用户具备创建审批流程的权限

最近碰到一个问题&#xff0c;两个sandbox&#xff0c;照理用户的权限应该是一样的&#xff0c;结果开发环境里面我可以左右的做各种管理工作&#xff0c;但是使用change set上传后&#xff0c;另一个环境的同一个用户&#xff0c;没有相对于的权限&#xff0c;权限不足。 当时…

本地部署DeepSeek开源多模态大模型Janus-Pro-7B实操

本地部署DeepSeek开源多模态大模型Janus-Pro-7B实操 Janus-Pro-7B介绍 Janus-Pro-7B 是由 DeepSeek 开发的多模态 AI 模型&#xff0c;它在理解和生成方面取得了显著的进步。这意味着它不仅可以处理文本&#xff0c;还可以处理图像等其他模态的信息。 模型主要特点:Permalink…

【详细教程】如何在Mac部署Deepseek R1?

DeepSeek是目前最火的国产大模型&#xff0c;官方App用户太多服务经常出现卡顿&#xff0c;部署一个本地DeepSeek R1可以方便使用。 1.系统最低要求 macOS 11 Big Sur 或更新 2.下载ollama https://ollama.com/ 3.安装DeepSeek R1 打开终端 运行命令 ollama run deepseek-…

DeepSeek能下围棋吗?(续)

休息了一下&#xff0c;接着琢磨围棋&#xff0c;其实前面一篇里的规则有个漏洞的&#xff0c;就是邻居关系定义有问题&#xff0c;先回顾一下游戏规则&#xff1a; 游戏规则 定义&#xff1a; 1.数字对&#xff0c;是指两个1到9之间的整数组成的有序集合。可与记为(m,n)&…

【产品经理学习案例——AI翻译棒出海业务】

前言&#xff1a; 本文主要讲述了硬件产品在出海过程中&#xff0c;翻译质量、翻译速度和本地化落地策略是硬件产品规划需要考虑的核心因素。针对不同国家&#xff0c;需要优化翻译质量和算法&#xff0c;关注市场需求和文化差异&#xff0c;以便更好地满足当地用户的需求。同…

被裁与人生的意义--春节随想

还有两个月就要被迫离开工作了十多年的公司了&#xff0c;不过有幸安安稳稳的过了一个春节&#xff0c;很知足! 我是最后一批要离开的&#xff0c;一百多号同事都没“活到”蛇年。看着一批批仁人志士被“秋后斩首”&#xff0c;马上轮到我们十来个&#xff0c;个中滋味很难言清…

4-图像梯度计算

文章目录 4.图像梯度计算(1)Sobel算子(2)梯度计算方法(3)Scharr与Laplacian算子4.图像梯度计算 (1)Sobel算子 图像梯度-Sobel算子 Sobel算子是一种经典的图像边缘检测算子,广泛应用于图像处理和计算机视觉领域。以下是关于Sobel算子的详细介绍: 基本原理 Sobel算子…

【算法设计与分析】实验5:贪心算法—装载及背包问题

目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 掌握贪心算法求解问题的思想&#xff1b;针对不同问题&#xff0c;会利用贪心算法进行问题建模、求解以及时间复杂度分析&#x…

MySQL为什么默认引擎是InnoDB ?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL为什么默认引擎是InnoDB &#xff1f;】面试题。希望对大家有帮助&#xff1b; MySQL为什么默认引擎是InnoDB &#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL 默认引擎是 InnoDB&#xff0c;主要…

【AIGC专栏】AI在自然语言中的应用场景

ChatGPT出来以后&#xff0c;突然间整个世界都非常的为之一惊。很多人大喊AI即将读懂人类&#xff0c;虽然这是一句夸大其词的话&#xff0c;但是经过未来几十年的迭代&#xff0c;ChatGPT会变成什么样我们还真的很难说。在当前生成式内容来说&#xff0c;ChatGPT毫无疑问在当前…

docker安装nacos2.2.4详解(含:nacos容器启动参数、环境变量、常见问题整理)

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull nacos:2.2.4 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜像到…

使用 postman 测试思源笔记接口

思源笔记 API 权鉴 官方文档-中文&#xff1a;https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md 权鉴相关介绍截图&#xff1a; 对应的xxx&#xff0c;在软件中查看 如上图&#xff1a;在每次发送 API 请求时&#xff0c;需要在 Header 中添加 以下键值对&a…