当前位置: 首页 > article >正文

【OpenGL】OpenGL游戏案例(二)

文章目录

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

特殊效果

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

数据结构

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.kler.cn/a/526778.html

相关文章:

  • WINDOWS安装eiseg遇到的问题和解决方法
  • 智能汽车网络安全威胁报告
  • 240. 搜索二维矩阵||
  • mybatis(134/134)完结
  • 【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南
  • 【信息系统项目管理师-选择真题】2007下半年综合知识答案和详解
  • Gurobi基础语法之打印模型
  • 本地部署 DeepSeek-R1 大模型实操
  • PHP中配置 variables_order详解
  • 爬虫基础(四)线程 和 进程 及相关知识点
  • 蓝桥杯例题五
  • 跨平台物联网漏洞挖掘算法评估框架设计与实现文献综述之GMN
  • doris:导入高可用性
  • 电脑要使用cuda需要进行什么配置
  • LitGPT - 20多个高性能LLM,具有预训练、微调和大规模部署的recipes
  • 电子电气架构 --- 在智能座舱基础上定义人机交互
  • 题单:冒泡排序1
  • Java根据端口范围关闭Appium服务
  • Java设计模式:行为型模式→责任链模式
  • 什么是Maxscript?为什么要学习Maxscript?
  • 数据结构之单链表(超详解)
  • 穷举vs暴搜vs深搜vs回溯vs剪枝系列一>解数独
  • 《一文读懂!Q-learning状态-动作值函数的直观理解》
  • win32汇编环境,窗口程序中使用滚动条控件的一般操作
  • AI 模型优化与性能调优
  • 芯片AI深度实战:进阶篇之vim内verilog实时基于AST的自定义检视