回顾和今天的计划
接下来进行编译,并设置编译目录,以便查看昨天的工作成果。可以看到,我们的界面上已经有了一些字体显示的内容。现在,可能是一个合适的时机关闭背景音乐,目前不需要音乐,相信大多数人也不会介意停止播放。
根据原始字形资源的大小缩放字体
当前的字体渲染状态可以看到,我们已经开始添加一些调试信息,这是昨天的主要目标之一。我们希望能够通过调试输出数据来分析字体信息,以便正确渲染字体。
从目前的情况来看,有几个明显需要改进的地方。如果希望让字体系统变得更完善,需要解决以下几个问题。
首先,所有字母的大小目前都是相同的。现在的渲染系统是按照指定的高度自动缩放字体,这样的方式虽然在某些情况下可行,但并不适用于真实的字体渲染。如果希望字体看起来更自然,必须让字体的实际显示尺寸与原始资源的像素大小保持比例关系。
这个调整可以基于已有的资源信息来完成,因为位图的像素尺寸已经在资产数据中存储了,因此不需要额外的信息即可计算出正确的比例关系。这个问题可以作为改进字体系统的第一步。
目前的调试输出中,可以看到测试行的渲染逻辑。在 Q&A 期间,有人提到希望支持嵌入转义代码来改变颜色或大小,所以加入了这部分功能。虽然这不是核心关注点,但如果有需要,可以继续保留以供调试和实验。
查看当前的输出代码,可以发现问题的关键在于 CharScale
变量。目前的渲染逻辑是直接指定高度,然后字体会按照该高度进行缩放。对于游戏中的大多数对象来说,这种方式是合理的,比如绘制一棵树时,可以直接指定高度,渲染系统会自动调整。但对于字体来说,情况有所不同。
字体需要保持相对比例,不应该被单独调整大小,而是应该按照原始像素大小进行等比例缩放,以保持一致性。查看 PushBitmap
代码,可以看到其宽高计算和对齐逻辑,目前的实现导致了字体缩放不当的问题。因此,需要找到一种方法,在必要时以更精确的像素方式处理这些信息,使字体渲染更加自然合理。
从 hha_asset 结构中获取位图尺寸
在加载位图之前,我们需要考虑是否能够提前获取相关信息。因为在 PushBitmap
过程中,位图不一定总是可用。例如,当尝试获取位图时,可能会遇到位图不存在的情况。这是因为字体是在后台资产加载过程中逐步加载的,因此位图可能尚未准备就绪。
关键问题是,我们真正需要的信息------即原始像素高度------是否可用,还是必须等到位图实际加载后才能获取。要解决这个问题,我们可以查看资产文件的存储格式。在资产文件中,HHA bitmap
结构中包含 Dim
(尺寸)字段,它是游戏启动时加载的表格数据的一部分。因此,即使位图本身尚未加载,我们仍然可以从该数据结构中获取其尺寸信息。
理想情况下,我们希望能够直接从该数据结构中获取位图信息,这样在需要时便可以直接使用。为此,我们需要访问位图信息的接口。目前的代码中可能已经有类似的功能,比如 bitmap_info
相关的接口,不过具体位置可能不太确定。有可能在之前的代码中已经实现了类似的功能,但由于代码量较大,可能已经遗忘了具体实现位置。同时,代码中曾经有 sound_info
相关的数据结构,因此很可能也有类似的 bitmap_info
结构。
实现 GetBitmapInfo
当前的问题在于我们没有获取位图信息的功能。如果我们添加一个获取位图信息的接口,并且使用 HHA bitmap
结构来存储相关数据,就可以在不增加额外开销的情况下获取所需信息。因为这只是一个简单的数组查找操作,不涉及昂贵的计算或额外的加载过程,因此可以高效完成。
在实现过程中,我们需要确保能够查询位图信息。调用时需要传递资产数据,以便让系统知道如何查找信息。为了获取位图的尺寸信息,我们需要提供位图的 ID
,这样就能根据 ID
获取相应的 HHA bitmap
结构,并读取其中的尺寸数据。这样,我们在不实际加载位图的情况下,就可以提前得知其具体尺寸,从而在后续处理中更高效地利用这些数据。


使用原始字体缩放绘制文本
在处理字符缩放时,我们决定暂时不使用基于 FontScale
进行缩放,而是直接采用字体的原生缩放比例。为了实现这一点,我们将缩放因子设为 1
,确保字体保持其原本的大小。接下来,我们将该值乘以字体的原始高度,以此来计算最终的缩放比例。
在实现过程中,我们使用 Dim
结构获取字体的高度信息,并利用其 y
维度来恢复字体的尺寸。这样,我们可以尝试调整字体的大小,使其更符合预期。不过,由于字体原始渲染时的大小可能过大,因此结果可能会超出预期。我们注意到当前字体过于巨大,但可以通过调整字符的间距来改善显示效果。
为了解决字体大小问题,我们可以使用 Dim
结构来计算字符的真实尺寸,并以此为基础进行字符的推进计算。尽管目前的调整方式可能并非最终方案,但它至少提供了一种初步的处理方法。此外,我们还需要处理默认的字符尺寸 CharDim
,这是字符度量信息的一部分,目前先临时设定一个值,稍后再进行优化。
通过这种方式,我们可以确保字符在推进时能够正确对齐,同时保持适当的间距。尽管当前的解决方案仍然是临时性的,但它可以帮助我们更清晰地理解问题所在,并逐步调整字体显示效果。目前,我们已经能够让字体的大小相对正常,字符之间的比例也更符合预期,例如大写字母明显高于小写字母。
由于当前使用的是调试字体,因此我们可能不希望它过于巨大。可以采取一些方式来控制其大小,比如调整 line height
(行高)或 FontScale
。现阶段,我们只是采用临时性方案,目的是逐步优化字体渲染,使其更加合理。同时,由于 y
方向的推进值仍然较小,我们需要进一步调整以匹配实际的字体大小。最终,我们的目标是确保字体的比例正确,字符之间的间距合理,并且整体布局符合预期。
目前的测试结果表明,我们正朝着正确的方向前进,尽管字体仍然过大,但大写字母和小写字母的相对比例已经正确。这表明我们的调整方法是有效的,接下来可以继续优化字符间距、行高以及整体布局,使字体最终达到最佳的可读性和美观度。



我们还需要修正垂直对齐
当前存在一个问题,即字符的对齐方式默认是居中的。这是由于我们的资产加载系统默认将未设置对齐方式的元素进行居中处理。结果导致所有字符沿着中心线排列,而这并不是字体对齐的正确方式。
从渲染结果可以观察到,整行文本都围绕某条中心线进行对齐,且该中心线的位置取决于字符的位图高度。这种对齐方式虽然符合默认行为,但在实际的字体渲染中是不合适的。例如,在标准的文本排版中,字符通常是基于基线对齐,而不是围绕中心线。因此,我们需要注意这一点,并进行调整,以确保文本的排版符合正常的视觉预期。
此外,这种居中对齐行为与字体的不同字符高度相关。因为每个字符的位图尺寸不同,它们的视觉对齐方式也受到影响。例如,较高的字符会显得更靠上,而较矮的字符会显得偏下,整体排列显得不自然。因此,当前的对齐逻辑需要进一步优化,以确保字符基于正确的基线对齐,而不是按照位图中心对齐。
目前,我们可以确定这种对齐方式符合系统的默认行为,也能在渲染结果中观察到这一点。然而,接下来需要思考如何调整对齐逻辑,以符合标准的字体排版规范。这可能涉及计算字符的基线位置,并根据该位置进行对齐,而不是简单地按照位图中心进行排列。这样可以确保不同高度的字符能够正确对齐,提高文本的可读性和视觉一致性。
理解导致水平推进行为异常的原因
当前的问题在于字符的排列和推进方式不正确,导致某些字符的间距过大。问题的根源在于,字符是围绕其中心进行绘制的,而不是基于字符的左侧或右侧进行对齐。当我们绘制一个字符时,字符会围绕其中心点进行绘制,这样一来,字符的宽度和位置就会受到影响,尤其是当字符本身的宽度差异较大时。
具体来说,当我们绘制一个宽字符时,比如一个较大的字母,它会占据较多的空间。而此时字符的推进是基于该字符的总宽度来计算的,因此,绘制下一个字符时,它的起始位置会提前推进一段距离。假如下一个字符是一个较窄的字符,比如字母"T",它的宽度很小,按照当前的推进方式,字符的位置会比预期要靠得更近。这是因为当前推进计算基于字符的宽度,但实际上,字符的中心已经超出了正确的起始位置,导致推进计算不准确。
这种推进方式是错误的,因为我们应该基于字符的实际起始位置来计算推进,而不是使用其总宽度。这样会导致字符之间的间距不一致,最终影响排版效果。
目前,绘制的字体仍然使用自然尺寸,字体看起来很大。虽然现在看到的效果可能并不合适,但这也是由于我们正在使用较大的调试字体。接下来,我们会调整字体的大小,使其更适合实际需求。虽然现在字体过大,但通过这个过程,我们能够更清晰地看到字符排列的问题,并为后续的调整做好准备。
总的来说,当前的字符推进方式并不正确,导致字符间距过大。我们需要重新调整字符对齐和推进方式,确保字符按照正确的逻辑排列,从而解决这一问题。
将垂直对齐调整到基线
现在已经到了一个关键点,所有内容看起来都已经正确渲染了,所以接下来我们需要讨论文本度量问题。我们的目标是使文本的排版看起来像一行正常的文本,而不是那种错位、间距不对或者是垂直方向居中的效果。虽然在某些情况下,垂直居中可能是想要达到的效果,但通常来说,特别是在屏幕上绘制文本时,文本应该按照正确的对齐方式显示。
首先,我们需要确保文本对齐得当,特别是垂直方向的对齐。就像之前提到的,字体排版需要根据具体的需求进行调整。为了做到这一点,首先要确保文本在垂直方向上的对齐方式正确。通常,文本会有不同的对齐方式,比如基线对齐或其他对齐方式,具体选择哪种方式取决于设计需求。
接下来,我们需要将这些对齐方式应用到字体文件中,并实际使用这些设置。通过调整字体文件中的参数,确保每个字符的排列符合预期的文本排版标准,避免出现不一致或不合理的间距。这意味着我们可能需要调整字体的基线、字符间距等信息,以便使文本在屏幕上呈现出更自然、正确的效果。
总的来说,接下来要做的就是确保文本的对齐正确,特别是垂直对齐,避免出现错误的居中效果或不规则的文本间距。通过调整字体文件中的文本度量信息,可以使最终的文本排版更加符合预期的视觉效果。
(黑板讲解)基线
在字体排版中,有一个重要的概念叫做"基线"(baseline)。基线是文本中字符对齐的参考线,它是一个水平的常量线,所有的字符都会根据这个基线进行对齐。不同的字母与基线的关系各不相同,我们通常可以看到:
- 无下行部分的小写字母:这些字母,如"a""e""o"之类,通常会在基线位置上对齐,或者说它们的底部触碰基线。
- 大写字母:几乎所有的大写字母都会在基线上对齐,除非是特殊的装饰字体,但一般来说它们会与基线平行。
- 带下行部分的小写字母:这些字母,如"g""j""y"等,会有一部分下行,超出基线以下。下行部分是字母的一部分,通常会向下延伸。
接下来的目标是通过引入基线的概念来正确对齐文本。具体来说,我们希望能够在任何给定的字符上,将其正确地定位到基线位置。这意味着在字体排版时,我们希望字符的纵向对齐点(即基线)能够与每个字符的正常位置一致。
为了实现这一目标,字体的位图需要能够基于字符的基线来进行调整。换句话说,位图应该根据正常对齐情况来设定字符的位置,使得每个字符的基线能够正确对齐。因此,接下来的任务是获取和计算每个字符的基线信息,并确保字体的位图能够按照这一标准进行排布。
通过这种方式,可以确保字符在排版时能够遵循标准的字体基线对齐,从而使文本看起来更加整齐、自然。

使用资源的 AlignPercentage 字段指定对齐点
我们的资产系统已经很方便地支持了字符对齐功能,因为它已经能够存储一个对齐点。这个对齐点是基于位图大小的对齐百分比,所以我们已经具备了指定对齐点位置的能力。具体来说,资产中有一个对齐百分比,可以决定位图中对齐点的位置。这意味着我们不需要在整个处理流程中做更多的工作,只需确保在资产处理器中设置一个合理的对齐百分比就能完成任务。
目前,问题在于对齐点的默认值设置为0.5(即位图的中心),这对于字符对齐并没有什么帮助。因此,我们需要调整这一点,使其能更有效地支持正确的字符对齐。
在加载位图时,当前的处理方式并没有设置对齐百分比,也没有给它指定一个有意义的值。默认情况下,对齐百分比是0.5,这意味着位图的对齐点是位于位图的中心。而我们希望能够设置一个更合理的对齐点,这样文本能够正确对齐。虽然加载的位图本身没有包含对齐百分比的具体信息,但通过修改资产处理器中的对齐设置,可以让对齐点更符合我们的需求。
总的来说,现有的资产系统已经具备了支持对齐功能的基础,只需要在资产处理器中调整对齐百分比,从而使得位图的渲染能够更加符合字符排版的要求。
LoadGlyphBitmap 将在测试资源构建器中写入对齐信息
回到这个问题,我们希望做的是将对齐百分比传递到加载位图的过程中,这样LoadGlyphBitmap
函数就可以在处理时将对齐百分比写入。如果这样做比较简单,这似乎是最直接的方式。
具体来说,我们希望在加载位图时,通过传递一个目标(比如一个HHA
资产对象)来让函数能够正确地写入对齐百分比。因此,接下来的步骤是查看LoadGlyphBitmap
的实现。这个函数应该接收一个HHA
类型的资产(hha_asset
),这是我们要操作的目标。
在查看资产构建器时,应该注意到加载位图函数会处理一个HHA
资产对象,这个对象包含了需要操作的数据。通过这种方式,可以确保对齐百分比被正确地传递和处理,使得位图渲染时能够使用正确的对齐设置。
对齐信息来源于文本度量指标
当我们进入这个流程时,应该能够将对齐百分比设置为有意义的值。为了做到这一点,最直接的方式可能是,我们要意识到当我们从Windows中提取字体时,我们可能需要采取略有不同的方式。我们将不得不维护两条路径,但这就是不可避免的情况。
从Windows提取字体时,Windows已经将字体大致对齐好,所以这些字体的对齐点应该是差不多的。接下来,我们可以使用text metrics
来获取每个字体对应的基准线位置。通过查找每个字体的像素与基准线的关系,然后在提取位图时,我们就可以确定这个像素相对于我们提取的矩形的位置,并记住这个信息。
我们可以查找text metrics
,这个结构应该能够提供我们需要的信息。根据MSDN的文档,它包含了许多有用的数据,如字体的高度、升降度(ascent和descent),这些信息应该足够帮助我们确定基准线的位置,并最终实现正确的对齐。
通过获取text metrics
中的这些信息,特别是基准线的位置,我们可以进一步提取位图时,确保它们能正确地对齐。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-textmetrica
(黑板讲解)描述我们将使用的文本度量指标
在这里,height
表示字符的总高度,ascent
表示基准线之上的单位高度,而descent
表示基准线之下的单位高度。也就是说,返回的基本信息会是这样的:height
是总高度,ascent
是升高的部分,descent
是下降的部分。因此,要找出基准线的位置,我们可以从顶部开始,减去ascent
,就会到达基准线;或者从底部开始,减去descent
,也能到达基准线。实际上,信息是冗余的,因为我们知道height
等于ascent
加上descent
,所以通过其中一个就足以确定基准线的位置。
也就是说,实际上我们只需要tmHeight
和tmBaseline
就可以确定基准线,其他的参数虽然有,但并不一定是必须的。虽然文档设计的方式包含了tmAscent
和tmDescent
,但它们并不是唯一需要的参数。
接下来,可能需要考虑的是,当前的设计在提取多个字体时可能存在问题。具体来说,使用静态变量的方式不适合处理多个字体。为了支持多种字体,我们需要确保在调用load with bitmap
时,传入字体信息,这样就可以把多个字体封装在一起。这并不是一个难以实现的改动,但这是一个需要注意的问题。

查找每个位图的对齐点
在处理文本时,我们知道可以访问到ascent
和descent
,因此可以用它们来计算字符的基准线。接下来,当我们获取get text extent point 32w
结果时,它会给出一个包含文本的矩形区域。我们需要确定这个矩形区域中哪些部分已经被填充,这样就可以得到一个边界框(bounding box)。目前我们不需要做额外的修正,因为修正操作已经完成了,我们已经处理了边缘问题。
现在我们要解决的问题是如何在位图中确定对齐点的位置。这是一个比较复杂的问题,因为我们之前已经遍历了整个位图,知道了最小的x和y坐标,确定了基准线的位置。由于我们使用的是自下而上的位图(bottom-up bitmap),这意味着位图的第一行(y = 0)是最底部的一行。因此,为了从底部行到达基准线,我们只需要通过descent
来计算,从底部向上移动多少个像素。
接下来,当我们填充位图时,我们需要使用Bitmap.AlignPercentage
来确保文本的对齐点设置正确。例如,如果我们有对齐百分比为0.5
或者1
,我们就需要调整对齐点的位置,以确保文本正确地展示在屏幕上。这需要在HHA asset
的处理流程中进行设置。
将水平对齐设置为字符的左侧
为了简化操作,当前将对齐百分比设置为20,使得字符的对齐点位于字符的左边缘。这样设置后,实际上是对齐到字符的"apron"(边缘),但我打算稍作调整,改为将对齐点设置为字符的第一个填充像素,这样会显得更合理。
为了实现这个调整,我需要将对齐点向内移动一个像素。这意味着在计算时,我需要除以像素总数,以使得对齐点正确定位到第一个填充像素的位置。这实际上是在标准化操作中进行的,即将像素数目转换为一个相对的百分比值。
虽然这个过程看起来比较简单,但在考虑是否要与上一个字符对齐时,可能会遇到一些复杂的情况。例如,最后一个像素的对齐范围需要非常仔细地计算,以确保结果的准确性,尤其是当出现"偏移一像素"的情况时。这种"偏移一像素"的问题是比较常见的,在实现时需要特别注意。
使用文本度量指标进行垂直对齐
为了处理Y轴的对齐问题,首先要注意,由于位图是从下往上的,所以需要根据字符的实际高度和下降量来确定正确的对齐方式。具体来说,我们可以利用字体的文本度量信息(如descent),并将其与字符的总高度进行比较来确定Y对齐的位置。但这样做并不完全准确,原因在于我们进行的是字符的提取操作。
例如,假设字符是小写字母"a",其位图高度只包括从基线到字母的下降部分。因此,位图保存的内容并不包括从基线到字符顶部的区域。所以,直接使用descent与总高度的比值会导致错误的对齐位置------它可能会把字符的位置放到过高的地方,这是不对的。
要解决这个问题,首先需要确定提取区域的最小X值(min X),即提取开始的位置。然后,从这个起点开始计算,并将字符的基线调整到正确的位置。具体来说,需要从基线向上调整,考虑到apron的像素(可以认为是位图的边缘),并根据descent值调整字符的位置。最终,基线应该从正确的Y轴位置开始,避免字符偏离原本应在的地方。
这需要重新构建文本资产,并确保对齐计算正确。
测试当前进展,仍然不正确
目前,我们的对齐问题已经有所改进,但还没有完全解决。虽然大部分字符的对齐已经有了明显的改进,但仍然存在一些问题。例如,字母"p"的对齐完全错误,而一些其他字符虽然看起来差不多正确,但依然存在微小的偏差,比如字母"U"稍微低了一些。
初步分析可能是因为我们在处理字符时,还没有完全理解和正确使用相关的度量值,尤其是在提取数据时存在一些偏差。我们现在的提取过程可能存在顶到底部与底部到顶的混淆,导致字符的对齐更倾向于字符的顶部,而不是应该对齐的底部。
为了进一步改进,可以尝试修正一些之前的错误,比如确保使用正确的变量名(例如将MINIX修正为MINY),并且调整位图提取的方式,确保字符对齐的准确性。此外,考虑到底部对齐的需求,可能还需要更细致地理解和调整提取过程中的方向问题和其他计算细节。
总之,尽管结果有所改善,但我们仍需进一步调整提取逻辑并重新审视计算过程,以确保对齐的正确性和一致性。



(黑板讲解)关于垂直对齐问题的一些理论
当前遇到的问题似乎与字符的"descend(下行部分)"处理有关。具体来说,出现了一个奇怪的情况。根据理解,问题出在从位图底部提取数据时,我们期望通过计算最小Y值和最大Y值之间的差来确定对齐点。但这个方法显然没有得到正确的结果,导致无法正确设置对齐点。
接下来需要确认的一个问题是,是否我们对"descend"的理解和提取区域的几何概念有误。为了更好地理解,打算深入检查一下"descend"的实际值,尤其是它是否有负值的问题。此外,想要确认在使用GetTextExtentPoint32W
时返回的内容是否正确,并理解它所提供的相关信息。
进一步的问题也包括,是否在提取过程中漏掉了某些信息,或者在文本的几何结构上存在误解。希望通过调试这些细节,能够更清晰地理解当前的错误,并找到合适的解决方案。
GetTextExtentPoint32 是否遵循基线?
在处理字体对齐问题时,当前的难点在于我们还不确定字体的对齐方式,尤其是在确定字符的精确位置时。我们需要知道如何正确地计算每个字符的基线位置,以便能够精确地对齐文本。然而,文档中并没有明确说明如何将文本的行和字符的基线对齐,尤其是在计算文本的宽度和高度时,也没有考虑到换行符或者字符的换行。
具体来说,计算文本宽度时,文档并没有提到如何处理字符的基线对齐。计算结果中没有考虑到基线位置,可能导致我们提取字符时无法正确对齐字符。而且,文档也未提及如何处理换行符等特殊字符,因此不清楚在字符对齐的过程中是否会受到这些因素的影响。
接下来的步骤是尝试获取更多关于单个字符的信息,看看能否通过获取每个字符的度量来确定它们的精确位置,而不是依赖可能无法正确对齐的文本行。如果获取字符的度量能够提供关于字符位置和基线的有用信息,那么这可能是一个更可靠的方向。
尝试获取关于字符位置的其他信息,例如字符宽度或位置可能会有所帮助。继续通过相关方法检查,以期找到能够解决对齐问题的有效数据。
进入代码检查度量值
在调试字体对齐问题时,首先需要确认文本度量数据的准确性。通过进入资产构建器,检查字体的"descend"和"height"值,发现这些值加起来是正确的。接下来,计划查看单个字符的详细信息,尤其是字符的代码点。这是因为某些字符,比如小写字母 "p",在对齐时表现异常,因此需要特别检查。
为了深入了解如何影响对齐,打算通过手动设置字符的代码点,查看小写字母 "p" 的表现,看看它在提取过程中是否存在问题。此外,还需要查看与字符相关的其他参数,如最小Y坐标、最大Y坐标以及最终结果的高度等,这些信息能够帮助进一步理解字符在字体提取和对齐时的问题。
通过这些步骤,旨在更清楚地了解字体度量数据的实际效果,以及字符在提取过程中可能存在的对齐错误。
正确计算 MaxY
在处理字体对齐时,核心问题是理解最大Y坐标(MaxY)和下降值(descent)之间的关系。最初的想法是将这两者直接结合起来,但发现由于最大Y坐标的方向问题,可能需要进行一些反向调整。具体来说,MaxY 是从底部向上计算的,而 tmDescent 是相对于字体的基线的值。
为了修正这一点,目标是找到一个正确的对齐位置。根据分析,发现应当从 BoundHeight 减去 tmDescent 来获得相对位置,然后再根据这个位置来进行对齐操作。这样可以确保对齐时的准确性。
在实际操作中,出现了一些问题。最初的尝试看似改变了位置,但字符"p"的对齐仍然不准确。通过反思,发现是计算方向错误,导致了不正确的值。真正应该做的是用 MaxY 减去相应的值,而不是相反。最终,应该调整代码,确保对齐值为正,并且从 MaxY 向上减去正确的值,从而获得更准确的字符位置。

垂直对齐现在正常了
调整后,结果看起来更符合预期了,字形的对齐也变得更准确了。现在可以看到文字在基线上的对齐,字体显示效果接近预期。虽然我们还有一些细节要调整,比如 kerning(字距调整),但这些内容可以留到下周再处理。现在的调整已经取得了显著的进展,整体效果看起来好多了。

更一致的水平间距
对字符的进展做了一些改进。之前的进展是按字符的高度来推进的,但现在意识到那种方式并不理想,应该根据字符的宽度来推进。这样可以更准确地调整字符之间的间距。现在,字符间距看起来更加一致了,但还没有进行字距调整(kerning),所以字符间的视觉效果还不理想。可以通过给字符间加点空隙,比如每个字符之间加上2像素,来稍微改善视觉效果,虽然这并不完美。
目前已经解决了文本基线对齐的问题,文本在左右方向上的对齐也变得更准确了。不过,仍然需要进行进一步的调整,特别是字距调整部分,我们计划下周再继续处理。

字体渲染和绘制相关的待办事项总结
目前还存在另一个问题,就是对行与行之间的间距没有任何了解。这也是为什么会丢失顶部的行,因为我们不知道应该把文本移到屏幕的多远,也不了解行与行之间的移动距离。我之前只是随便设置了一个值作为假设,但这样做并不准确。我们需要获取准确的行间距度量。
下周在继续处理字体相关问题时,应该重点关注这一部分。我们需要获得这些度量值,并以合理的方式使用它们,使得文本的显示效果更符合预期。
应用伽马校正以改善字体的 Alpha 混合效果
发现了一个问题,似乎是当前的提取没有使用预乘 alpha(premultiplied alpha)。注意到字母 "G" 上出现了一些不正常的部分,看起来不对劲,我怀疑这与我们没有正确使用预乘 alpha 相关,或者可能是我们做了某种处理但没有意识到。
问题的根源可能在于我们没有进行 gamma 校正。实际上,我们之前讨论过 gamma 校正并做了相关处理,因为它对 alpha 混合的正确性至关重要。如果没有这个步骤,可能会导致颜色没有正确解码。因此,我们需要按照以前的模式来处理:将颜色转换为线性空间,然后乘以 alpha,再转换回去。
为了修正这一点,可以编写一个小工具函数来进行这种颜色转换。具体来说,我们可以将颜色值除以 255(将其归一化),然后进行处理。按照这种方式进行转换后,应该能够解决黑色边缘的问题。
经过调整后,发现问题确实解决了,原本的黑色边缘消失了,显示变得更加干净。对图形处理而言,正确处理这些细节是非常重要的,因为任何小错误都可能导致明显的图像伪影。
另外,还注意到当前我们使用的是 Windows 32 的提取路径,而非 STB 库的提取路径,因此决定减缓粒子效果,避免不必要的输出,保留粒子效果,等到需要时再启用。

stb_truetype 是否提供这类文本度量信息?你会在直播中展示如何从 stb 库获取这些信息吗?
使用 stb 库获取文本度量信息并不复杂。通过 stb_truetype
库中的 stbtt_GetFontVMetrics
函数,可以获取所需的文本度量信息。这些信息与之前所用的信息基本相同,主要包括字形的上升高度、下降高度以及行间距等。
需要注意的是,STB 和 Windows 对于下降值的处理方式不同。STB 返回的下降值是负数,而 Windows 返回的是正数。因此,尽管获取到的信息基本相同,但在处理时需要注意方向上的差异,不可以直接复制这些值。
你会讲解从右至左文本的处理吗?
实际上,处理从右到左的文本和从左到右的文本并没有太大区别,关键在于在字符间隔的计算上是加还是减。对于这类文本,唯一需要做的就是调整字符推进方向,但考虑到游戏并不是以文本为核心,因此对右到左文本的处理并不需要特别关注,也没有进行深入的文本处理。
你能再讲解一下 AlignPercentage[1] 是如何计算的吗?
可以详细讲解一下如何计算行百分比。首先,假设我们在这里绘制了一些图形,来帮助理解。行百分比的计算依赖于某些特定的高度参数,这些高度参数用于计算每行文本的垂直位置。
(黑板讲解)计算 AlignPercentage[1]

在计算行百分比时,主要的数学公式是:
1 + MaxY − ( Height − Descent ) BitmapHeight 1 + \frac{\text{MaxY} - (\text{Height} - \text{Descent})}{\text{BitmapHeight}} 1+BitmapHeightMaxY−(Height−Descent)
接下来逐步解释这个公式:
-
BitmapHeight (字符存储的高度) :这是我们实际存储的字符的高度,比如一个小写字母"a",它会填满整个区域,除了一个像素的边缘(apron)。这个边缘是空白区域,不被字符占据,因此实际的字符高度是
BitmapHeight - 2
。 -
Height (整个提取区域的高度):这是字符所占据的区域的总高度,包括了一个像素的边缘,实际上它比字符的高度多两个像素(上面和下面的边缘各一个像素)。
-
提取区域的高度减去降至线的高度:公式中的这一部分表示从提取区域的顶部到降至线之间的距离。这里的"降至线"是指字符的最低点。需要计算的是,从提取区域顶部到这个降至线的距离。
-
最大高度(MaxY):这是字符的实际显示区域的顶部位置。这个位置和提取区域的顶部位置之间的差值(最大高度减去提取区域的高度)表示从字符顶部到降至线的距离。
-
计算百分比:我们最终需要将上面的差值除以整个位图的高度。位图的高度包括了字符的高度和边缘部分。由于我们额外添加了一个像素的边缘,公式中通过加1来调整计算。
最终的公式就是这样,通过加1来补偿边缘的影响,得到一个相对于整个位图高度的百分比。通过这个百分比,可以准确地表示字符在位图中的垂直位置。
这个过程有点复杂,尤其是需要处理额外的边缘和降至线的位置的关系,但这对于正确地计算和存储字体信息是非常重要的。
你们开发这个引擎是打算在这款游戏之外继续使用吗?
这个引擎的开发目标仅仅是为了这个游戏,不打算用于其他项目。它的设计完全针对这个游戏的需求,因此没有计划将其扩展到其他用途。
我们目前不是在使用等宽字体吗?那么在讨论完整的字距调整之前,仅通过固定的距离推进(而不是按位图宽度)应该会改善字母间距吧?
使用固定宽度字体时,按固定距离推进字符的方式有助于改善字母间距,即使在完全讨论字距调整(kerning)之前,可能也会显得更好。固定宽度字体通常会给人空间过多的感觉,因此提前进行一些改进可能会更好。但具体如何处理,最终还是要通过字距调整来决定,计划在下周进行进一步的字距调整。在此之前,文字的基本排列已经达到了一个初步的状态。
为什么不使用基于区块的精灵字体来调试,而要折腾 Windows 字体?
虽然使用基于块的字体进行调试可以避免Windows字体的问题,但并不认为这是一个特别有效的做法。原因是,游戏最终可能需要使用常规的字体系统,而不是仅仅写一个看起来比较粗糙的块状字体。块状字体看起来并不专业,除非整个游戏是像旧式像素风格那样采用位图字体的设计,但我们的游戏并没有这种复古风格。我们的游戏艺术风格比较画意,所以需要常规字体支持。因此,与其只为了调试写一个简单的块状字体,不如一次性做好字体系统,以后可以同时用于调试和游戏本身。这样做能更高效且符合整体设计需求。