首页 > 解决方案 > 如何拥有可以被其他类C ++访问但不能更改的公共类成员

问题描述

以前可能已经问过这个问题,但是除了通用解决方案之外,我找不到其他东西private/public/const。基本上,我需要在创建 my 实例时将字体加载到数组中class text。该类text在我的text.h文件中,并在text.cpp. 在这两个文件中,它们还包含一个Fonts class,因此我希望我的fonts类将我的选择预加载到一个数组中,以便在创建第一个实例之后fonts我的类可以访问。我希望我的班级text能够访问text这些字体,但不能更改这些字体。我无法TTF_Font *Get_Font()在类中创建一个方法,fonts因为每次font被创建时,它加载需要手动关闭的内存,所以在它超出方法的范围后我无法完全关闭它,所以我想做一些类似的事情,例如,在创建一个角色时,调用TTF_RenderText_Blended(Fonts::arialFonts[10], "123", black);which例如,将选择字体类型arial in size 11

标签: c++sdl-2sdl-ttf

解决方案


我不确定您在哪种类型的应用程序中使用您的字体,但我已经使用 OpenGL 和 Vulkan 完成了一些 3D 图形编程。这可能会或可能不会帮助您,但应该为您提供一些关于游戏引擎开发中常用的框架结构的背景信息。

通常我们会有一个 Texture 类或结构,它有一个颜色三元组或四元组的向量,表示图像中每个像素的颜色数据。其他类将包含纹理将应用于的 UV 坐标...我们通常还具有加载纹理或图形文件的函数,例如 PNG、JPG、TGA 等。您可以相当简单地编写自己的,也可以使用许多如果您正在执行图形类型编程,则可以使用这些开源加载程序库。纹理类将包含其他属性,例如 mipmap、是否重复或镜像、其质量等......所以我们通常会加载纹理并为其分配 ID 值并将其存储到哈希表中。如果该纹理试图从文件中再次加载,代码将识别它存在并退出该函数调用,

由于大多数渲染文本是 2D 的,而不是创建 3D 模型并将其发送到渲染器以由模型-视图-投影矩阵处理……我们创建通常称为 Sprite 的东西。这是另一堂课。它有顶点来构成它的多边形边。通常一个精灵将有 4 个顶点,因为它是一个 QUAD。它还将具有与之关联的纹理坐标。我们不直接将纹理应用到精灵,因为我们想要实例化一个在内存中只有一个副本的精灵。我们通常会在这里做的是,我们会将它的引用连同通过 id 对纹理的引用发送给渲染器,并使用变换矩阵来改变它的形状、大小和世界位置。当 GPU 通过着色器处理它时,由于它是 2D 对象,我们使用正交投影。因此,这在顶点着色器中为每个顶点保存了矩阵乘法。基本上,它将由视图投影矩阵处理。在这里,我们的 Sprite 将与我们的纹理类似地存储在具有关联 ID 的哈希表中。

现在我们有一个精灵,它基本上是一个绘制到屏幕上的图形图像,一个简单的四边形,可以轻松调整大小并放置在任何地方。我们只需要内存中的一个四边形,但可以绘制成百上千个,因为它们是通过引用计数实例化的。

这对文本或字体有什么帮助?您希望您的 Text 类与您的 Font 类分开。文本类将包含您要绘制它的位置、要使用的字体、字体大小、要应用的颜色以及文本本身…… Font 类通常继承自基本 Sprite 类。

我们通常会创建一个单独的工具或迷你控制台程序,允许您按名称获取任何已知的 true type 字体或 windows 字体,这将为您生成 2 个文件。您会将标志与其他命令一起传递到程序的命令行参数中,例如 -all 用于所有字符或 -"abc123" 仅用于您想要的特定字符,以及 -12 用于字体大小,这样做会生成您需要的文件。第一个是我们称之为 Font Atlas 的单个纹理图像,它基本上是 Sprite Sheet 的一种特定形式,另一个文件将是一个 CSV 文本文件,其中生成了每个字符四边形的纹理定位值。

回到主项目,字体类将加载到这两个文件中,第一个很简单,因为它只是我们之前已经完成的纹理图像,后者是生成所有所需信息的 CSV 文件但是,在适当的四边形中,“字体”类确实需要执行相当多的复杂计算。同样,当一个字体被加载到内存中时,我们做的和以前一样。我们检查它是否已经通过文件名或 id 加载到内存中,如果不是,我们将其存储到带有生成的关联 ID 的哈希表中。

现在,当我们使用 Text 类来呈现我们的文本时,代码可能如下所示:

void Engine::loadAssets() {   
    // Textures
    assetManager_.loadTexture( "assets\textures\shells.png" );
    assetManager_.loadTexture( "assets\textures\blue_sky.jpg" );
    
    // Sprites
    assetManager_.loadSprite( "assets\sprites\jumping_jim.spr" );
    assetManager_.loadSprite( "assets\sprites\exploading_bomb.spr" );
    
    assetManager_.loadFont( "assets\fonts\"arial.png", 12 );
    // Same font as above, but the code structure requires a different font id for each different size that is used.
    assetManager_.loadFont( "assets\fonts\"arial.png", 16 ); 
    assetManager_.loadFont( "assets\fonts\"helvetica.png" );
}

现在,这些都作为单个实例存储在我们的 AssetManager 类中,该类为每种不同类型的资产包含多个哈希表。它的作用是管理他们的记忆和生命周期。每个只有一个实例,但我们可以引用它们 1,000 次......现在在代码的其他地方,我们可能有一个包含一堆独立文件的文件enumerations......

enum class FontType {
    ARIAL,
    HELVETICA,
};

然后在我们的渲染调用或 loadScene 函数中......

void Engine::loadScene() {
    fontManager_.drawText( ARIAL, 18, glm::vec3(-0.5, 1.0, 0.5), glm::vec4(245, 169, 108, 128), "Hello World!"); 
    fontManager_.drawText( ARIAL, 12, glm::vec3(128, 128, 0), glm::vec4(128, 255, 244, 80), "Good Bye!");
}

drawText 函数将获取ARIALid 并将引用获取到该存储字体的哈希表中。渲染器使用定位、颜色值、字体大小和字符串消息... ID 和大小用于检索适当的字体图集或 Sprite 表。然后将匹配消息字符串中的每个字符,并使用适当的纹理坐标将其应用于基于您指定的字体大小的适当大小的四边形。

所有的文件处理、打开、读取和关闭都已经在loadAssets函数中完成了。所有必需的信息都已经存储在一组哈希表中,我们通过实例化引用这些哈希表。此时无需担心内存管理或堆访问,这一切都是通过利用缓存来完成的。当我们将文本绘制到屏幕上时,我们只是通过矩阵变换通过着色器来操作像素。

引擎还有另一个主要组件尚未提及,但我们通常使用批处理和批处理管理器类来处理发送顶点、UV 坐标、颜色或纹理数据等的所有处理。 ..到视频卡。CPU 到 GPU 跨总线和/或 PCI-Express 通道的传输被认为很慢,我们不希望每帧发送 10,000、100,000 甚至 100 万个单独的渲染调用!因此,我们通常会创建一组具有优先级队列功能的批次,当所有存储桶都已满时,具有最高优先级值的存储桶或最满的存储桶将被发送到 GPU,然后清空。一个桶可以容纳 10,000 - 100,000 个图元...其中单个图元可以是点、线、三角形列表、三角形扇形等... 这使代码更加高效。很少使用堆。BatchManager、AssetManager、TextureManager、AudioManager、FontManager 等是存在于堆上的类,但它们所有存储的资产都通过引用使用,因此我们可以实例化一个对象一百万次!我希望这个解释有所帮助。


推荐阅读