如何拥有可以被其他类C++访问但不能更改的公共类成员



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

我不确定你在什么类型的应用程序中使用字体,但我已经使用OpenGL和Vulkan进行了一些3D图形编程。这可能对你有帮助,也可能没有帮助,但应该为你提供一些关于游戏引擎开发中常用的框架结构的上下文。

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

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

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

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

我们通常会创建一个单独的工具或迷你控制台程序,允许您按名称使用任何已知的真正类型字体或windows字体,为您生成2个文件。您将把标志与其他命令一起传递到程序的命令行参数中,例如-all for all character或-";abc123";只为您想要的特定字符,-12为字体大小,通过这样做,它将生成您需要的文件。第一个文件是一个单独的纹理图像,我们称之为字体图谱,它基本上是精灵表的一种特定形式,另一个文件是CSV文本文件,其中为每个角色的四边形的纹理定位生成了值。

回到主项目中,字体类将加载到这两个文件中——第一个文件很简单,因为它只是我们之前已经做过的纹理图像,而第二个文件是CSV文件,用于生成所有适当的四边形所需的信息,然而;字体"类确实有相当多的复杂计算需要执行。同样,当一个字体被加载到内存中时,我们会像以前一样进行操作。我们检查它是否已经通过文件名或id加载到内存中,如果不是,我们将其存储到具有生成的关联id的哈希表中。

现在,当我们使用Text类来呈现文本时,代码可能看起来像这样:

void Engine::loadAssets() {   
// Textures
assetManager_.loadTexture( "assetstexturesshells.png" );
assetManager_.loadTexture( "assetstexturesblue_sky.jpg" );

// Sprites
assetManager_.loadSprite( "assetsspritesjumping_jim.spr" );
assetManager_.loadSprite( "assetsspritesexploading_bomb.spr" );

assetManager_.loadFont( "assetsfonts"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( "assetsfonts"arial.png", 16 ); 
assetManager_.loadFont( "assetsfonts"helvetica.png" );
}

现在,这些都作为一个实例存储在我们的AssetManager类中,这个类为每种不同类型的资产容器了几个哈希表。它的作用是管理他们的记忆和生活。每个实例只有一个,但我们可以引用它们1000次。。。现在,在代码的其他地方,我们可能有一个文件,其中包含一堆独立的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和大小用于检索相应的Font Atlas或Sprite Sheet。然后,将匹配消息字符串中的每个字符,并使用适当的纹理坐标将其应用于基于您指定的字体大小的适当大小的四边形。

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

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

相关内容

最新更新