【OpenGL】OpenGL游戏案例(二)
文章目录
- 特殊效果
- 数据结构
- 生成逻辑
- 更新逻辑
- 文本渲染
- 类结构
- 构造函数
- 加载函数
- 渲染函数
特殊效果
为提高游戏的趣味性,在游戏中提供了六种特殊效果。
数据结构
PowerUp
类只存储存活数据,实际逻辑在游戏代码中通过Type字段来区分执行
class PowerUp : public GameObject
{
public:// powerup statestd::string Type;float Duration;bool Activated;// constructorPowerUp(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 chancethis->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 oftenthis->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中调用以下函数下落更新,并每帧检查是否与玩家发生碰撞
不同状态下能力内部标志位变化:
Destoryed | Activated | |
---|---|---|
下落状态 | false | false |
与玩家碰撞后 | true | true |
倒计时结束后 | true | false |
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 powerupActivatePowerUp(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 effectsif (powerUp.Type == "sticky"){if (!IsOtherPowerUpActive(this->PowerUps, "sticky")){ // only reset if no other PowerUp of type sticky is activeBall->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 activeBall->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 activeEffects->Confuse = false;}}else if (powerUp.Type == "chaos"){if (!IsOtherPowerUpActive(this->PowerUps, "chaos")){ // only reset if no other PowerUp of type chaos is activeEffects->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 activatedthis->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 shaderthis->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 quadsglGenVertexArrays(1, &this->VAO);glGenBuffers(1, &this->VBO);//绑定VAO,VBOglBindVertexArray(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 libraryFT_Library ft;if (FT_Init_FreeType(&ft))std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;// 将字符加载为faceFT_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 charactersstd::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 memoryglBindBuffer(GL_ARRAY_BUFFER, this->VBO);glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);glBindBuffer(GL_ARRAY_BUFFER, 0);// render quadglDrawArrays(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);
}
相关文章:

【OpenGL】OpenGL游戏案例(二)
文章目录 特殊效果数据结构生成逻辑更新逻辑 文本渲染类结构构造函数加载函数渲染函数 特殊效果 为提高游戏的趣味性,在游戏中提供了六种特殊效果。 数据结构 PowerUp 类只存储存活数据,实际逻辑在游戏代码中通过Type字段来区分执行 class PowerUp …...

28. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表定时器与报表数据修正
这篇文章是《.NET 8 实战–孢子记账–从单体到微服务》系列专栏的《单体应用》专栏的最后一片和开发有关的文章。在这片文章中我们一起来实现一个数据统计的功能:报表数据汇总。这个功能为用户查看月度、年度、季度报表提供数据支持。 一、需求 数据统计方面&…...

Java 泛型<? extends Object>
在 Java 泛型中,<? extends Object> 和 <?> 都表示未知类型,但它们在某些情况下有细微的差异。泛型的引入是为了消除运行时错误并增强类型安全性,使代码更具可读性和可维护性。 在 JDK 5 中引入了泛型,以消除编译时…...

FPGA|使用quartus II通过AS下载POF固件
1、将开发板设置到AS下载挡位,或者把下载线插入到AS端口 2、打开quartus II,选择Tools→Programmer→ Mode选择Active Serial Programming 3、点击左侧Add file…,选择 .pof 文件 →start 4、勾选program和verify(可选࿰…...

“新月之智”智能战术头盔系统(CITHS)
新月人物传记:人物传记之新月篇-CSDN博客 相关文章链接(更新): 星际战争模拟系统:新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 目录 一、引言 二、智能头盔控制系统概述 三、系统架…...

php:代码中怎么搭建一个类似linux系统的crontab服务
一、前言 最近使用自己搭建的php框架写一些东西,需要用到异步脚本任务的执行,但是是因为自己搭建的框架没有现成的机制,所以想自己搭建一个类似linux系统的crontab服务的功能。 因为如果直接使用linux crontab的服务配置起来很麻烦࿰…...

【LeetCode: 958. 二叉树的完全性检验 + bfs + 二叉树】
🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…...

MinDoc 安装与部署
下载可执行文件 mindoc mindoc_linux_amd64.zip 上传并解压压缩包 cd /opt mkdir mindoc cd mindocunzip mindoc_linux_amd64.zip 创建数据库 CREATE DATABASE mindoc_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; 配置数据库 将解压目录下 conf/app.conf.exam…...

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(基础组件实现)
目录 基础组件实现 如何将图像和文字显示到OLED上 如何绘制图像 如何绘制文字 如何获取字体? 如何正确的访问字体 如何抽象字体 如何绘制字符串 绘制方案 文本绘制 更加方便的绘制 字体附录 ascii 6x8字体 ascii 8 x 16字体 基础组件实现 我们现在离手…...

windows系统如何检查是否开启了mongodb服务
windows系统如何检查是否开启了mongodb服务!我们有很多软件开发,网站开发时候需要使用到这个mongodb数据库,下面我们看看,如何在windows系统内排查,是否已经启动了本地服务。 在 Windows 系统上,您可以通过…...

VS安卓仿真器下载失败怎么办?
如果网络不稳定,则VS的安卓仿真器很容易下载失败,如下 Downloaded file <USER_HOME>\AppData\Local\Temp\xamarin-android-sdk\x86_64-35_r08.zip not found for Android SDK archive https://dl.google.com/android/repository/sys-img/google_a…...

计算机网络一点事(24)
TCP可靠传输,流量控制 可靠传输:每字节对应一个序号 累计确认:收到ack则正确接收 返回ack推迟确认(不超过0.5s) 两种ack:专门确认(只有首部无数据) 捎带确认(带数据…...

视频拼接,拼接时长版本
目录 视频较长,分辨率较大,这个效果很好,不耗用内存 ffmpeg imageio,适合视频较短 视频较长,分辨率较大,这个效果很好,不耗用内存 ffmpeg import subprocess import glob import os from nats…...

制造企业的成本核算
一、生产成本与制造费用的区别 (1)生产成本,是直接用于产品生产,构成产品实体的材料成本。 包括企业在生产经营过程中实际消耗的原材料、辅助材料、备品备件、外购半成品、燃料、动力包装物以及其它直接材料,和直接参加产品生产的工人工资,以及按生产工人的工资总额和规…...

doris:高并发导入优化(Group Commit)
在高频小批量写入场景下,传统的导入方式存在以下问题: 每个导入都会创建一个独立的事务,都需要经过 FE 解析 SQL 和生成执行计划,影响整体性能每个导入都会生成一个新的版本,导致版本数快速增长,增加了后台…...

LLMs之WebRAG:STORM/Co-STORM的简介、安装和使用方法、案例应用之详细攻略
LLMs之WebRAG:STORM/Co-STORM的简介、安装和使用方法、案例应用之详细攻略 目录 STORM系统简介 1、Co-STORM 2、更新新闻 STORM系统安装和使用方法 1、安装 pip安装 直接克隆GitHub仓库 2、模型和数据集 两个数据集 FreshWiki数据集 WildSeek数据集 支持…...

鸿蒙HarmonyOS实战-ArkUI动画(页面转场动画)_鸿蒙arkui tab 切换动画
PageTransitionExit({type?: RouteType,duration?: number,curve?: Curve | string,delay?: number}) 在HarmonyOS中,PageTransitionEnter和PageTransitionExit是用于控制页面切换动画的参数。它们分别表示页面进入和退出时的动画。1. type(动画类型…...

图漾相机-ROS2-SDK-Ubuntu版本编译(新版本)
文章目录 前言1.Camport ROS2 SDK 介绍1.1 Camport ROS2 SDK源文件介绍1.2 Camport ROS2 SDK工作流程1.2.1 包含头文件1.2.2 2 初始化 ROS 2 节点1.2.3 创建节点对象1.2.4 创建发布者对象并实现发布逻辑1.2.5 启动 ROS 2 1.3 ROS2 SDK环境配置与编译1.3.1 Ubuntu 20.04 下ROS2 …...

小程序的协同工作与发布
1.小程序API的三大分类 2.小程序管理的概念,以及成员管理两个方面 3.开发者权限说明以及如何维护项目成员 4.小程序版本...

解锁维特比算法:探寻复杂系统的最优解密码
引言 在复杂的技术世界中,维特比算法以其独特的魅力和广泛的应用,成为通信、自然语言处理、生物信息学等领域的关键技术。今天,让我们一同深入探索维特比算法的奥秘。 一、维特比算法的诞生背景 维特比算法由安德鲁・维特比在 1967 年提出…...

计算机网络一点事(20)
IEEE802.11 无线局域网 分类有无基础设施 星型拓扑,基本服务集BSS一基站多移动站,服务集标识符SSID不超过32b,可接入802.3 漫游:移动站从一个基本服务集切换到另一个(类似换联WiFi) 802.11帧࿱…...

java求职学习day23
MySQL 单表 & 约束 & 事务 1. DQL操作单表 1.1 创建数据库,复制表 1) 创建一个新的数据库 db2 CREATE DATABASE db2 CHARACTER SET utf8; 2) 将 db1 数据库中的 emp 表 复制到当前 db2 数据库 1.2 排序 通过 ORDER BY 子句 , 可以将查询出的结果进行排序 ( 排序只…...

Vue-cli 脚手架搭建
安装node.js 官网下载node.js安装包,地址:Node.js — Download Node.js 先在node.js即将要安装的路径下创建两个文件夹:node_cache(缓存)、node_global(全局) 点击安装包…...

认识小程序的基本组成结构
1.基本组成结构 2.页面的组成部分 3.json配置文件 4.app.json文件(全局配置文件) 5.project.config.json文件 6.sitemap.json文件 7.页面的.json配置文件 通过window节点可以控制小程序的外观...

Spring Boot 热部署实现指南
在开发 Spring Bot 项目时,热部署功能能够显著提升开发效率,让开发者无需频繁重启服务器就能看到代码修改后的效果。下面为大家详细介绍一种实现 Spring Boot 热部署的方法,同时也欢迎大家补充其他实现形式。 步骤一、开启 IDEA 自动编译功能…...

深度学习编译器的演进:从计算图到跨硬件部署的自动化之路
第一章 问题的诞生——深度学习部署的硬件困境 1.1 计算图的理想化抽象 什么是计算图? 想象你正在组装乐高积木。每个积木块代表一个数学运算(如加法、乘法),积木之间的连接代表数据流动。深度学习框架正是用这种"积木拼接…...

【数据结构】_顺序表经典算法OJ(力扣版)
目录 1. 移除元素 1.1 题目描述及链接 1.2 解题思路 1.3 程序 2. 合并两个有序数组 1.1 原题链接及题目描述 1.2 解题思路 1.3 程序 1. 移除元素 1.1 题目描述及链接 原题链接:27. 移除元素 - 力扣(LeetCode) 题目描述:…...

数据结构:队列篇
图均为手绘,代码基于vs2022实现 系列文章目录 数据结构初探: 顺序表 数据结构初探:链表之单链表篇 数据结构初探:链表之双向链表篇 链表特别篇:链表经典算法问题 数据结构:栈篇 文章目录 系列文章目录前言一.队列的概念和结构1.1概念一、动态内存管理优势二、操作效率与安全性…...

第05章 17 Contour 过滤器介绍与例子
vtkContourFilter 是 VTK(Visualization Toolkit)中的一个关键类,用于从输入数据生成等值线或等值面。它是基于阈值的过滤器,可以从标量字段中提取等值线或等值面。vtkContourFilter 的核心功能是根据用户指定的值生成等值线或等值…...

【落羽的落羽 数据结构篇】顺序表
文章目录 一、线性表二、顺序表1. 概念与分类2. 准备工作3. 静态顺序表4. 动态顺序表4.1 定义顺序表结构4.2 顺序表的初始化4.3 检查空间是否足够4.3 尾部插入数据4.4 头部插入数据4.5 尾部删除数据4.6 头部删除数据4.7 在指定位置插入数据4.8 在指定位置删除数据4.9 顺序表的销…...