游戏引擎学习第162天

server/2025/3/17 16:12:50/

回顾与即将进行的调试工作概述

有人提了一个关于 C 运行时库的问题,所以画面有些不同。不过现在已经调整回正常的画面,大家看到的就是平时熟悉的界面了。

今天的内容是继续在不使用任何引擎和库的情况下,编写一个完整的游戏。

上周已经完成了一部分通用分配器的工作,比预想的要快很多。即便只是初步的版本,也没有花太长时间。而优化版本的实现需要等到对资源管理系统施加更大压力之后,才能真正观察到可能的问题,以及是否有优化的必要。因此,暂时不需要对分配器进行进一步调整,它的基础功能已经完成了。

接下来,最合理的工作方向应该是调试代码。目前引擎开发已经进入到一个阶段,需要开始整理、完善和优化代码,使其达到更高的质量标准。在开发过程中,已经积累了一些已知的 bug 以及某些尚未达到期望水平的部分,这些问题之前都做了记录。因此,接下来的目标是让引擎的基础层足够稳定,这样在未来开发游戏时,就可以确保所有已经实现的系统是可靠的,不需要频繁进行调试或修改。

如果能够让引擎层保持稳定,后续的引擎开发工作就可以完全围绕游戏需求来进行,而不是被各种维护问题困扰。因此,今天的计划是围绕这个目标展开。

今天的计划

接下来的计划是开始研究字体的处理方式,因为在编写调试代码时,我们需要在屏幕上显示文本,以便打印各种数值以及其他需要可视化的信息。因此,今天的重点将是字体处理。首先,会在黑板上讲解一些关于字体的基础知识,然后进入代码编写环节。

在之前的开发中,并没有遗留什么问题需要立即解决,唯一需要注意的是,通用分配器的优化工作需要等到后续有更大的负载压力后再进行。因此,现在的重点不在于优化分配器,而是确保之前的代码逻辑是正确的。

目前来看,代码状态良好,没有发现新的问题。例如,现阶段还没有实现对资源的缓存,因此某些操作在速度较慢时会导致短暂的闪烁现象。但这个问题主要与资源系统的调用方式有关,而资源系统本身已经具备了支持预缓存的能力。因此,这部分的优化可以在后续进行调整,现在暂时不需要特别关注。

接下来的目标是实现更完善的调试信息显示,包括编写更好的调试界面,并在屏幕上展示必要的信息。这样可以更方便地查看代码运行状态,增加对程序执行情况的可视化反馈。很多时候,能够直观地看到关键数值和状态变化,对于调试工作至关重要。因此,现在的主要任务是探索合适的方式,将这些调试信息呈现在屏幕上。

测试驱动 vs. 可视化驱动的游戏开发

经常有人会问对测试驱动开发(TDD)的看法。然而,在游戏开发中,最难调试的问题通常并不适用于测试驱动开发。测试驱动开发主要适用于解决一些相对简单的问题,而这些问题往往不会占据游戏开发的大部分时间。因此,在游戏开发中,比测试驱动开发更重要的通常是可视化驱动开发,也就是通过可视化工具来辅助调试。

随着游戏复杂度的增加,仅仅观察最终的运行结果通常不足以判断是否一切正常。代码中可能存在许多复杂的错误,而这些错误很难通过单元测试或直接观察最终画面来发现。因此,在游戏开发中,常见的做法是编写各种可视化工具,如HUD(显示)叠加信息面板等。这些工具可以显示游戏运行的核心状态信息,使开发人员能够凭借直觉和技术经验发现潜在的问题,并进一步调查和解决那些隐藏得更深、难以察觉的 Bug。

因此,在当前的开发过程中,需要尽早实现对字体的支持。虽然游戏本身可能不会大量使用文本输出,但在调试过程中,文本显示对于可视化工具来说至关重要。因此,现在的目标是研究字体的基本工作原理,并探讨如何在游戏中实现字体渲染。

今天是开发的主题是字体(Fonts)。接下来的内容将涉及字体的基本概念、它们的结构以及如何将字体集成到游戏引擎中。

字体的基础知识:什么是字体文件?

当讨论字体时,需要了解字体系统包含的内容。从最基础的概念开始,无论是否了解**排版(Typography)**或相关知识,都可以逐步理解字体的构成和工作原理。

所有计算机用户都会对字体有所了解,它决定了文本在屏幕上的显示方式。常见的字体文件格式包括TTF(TrueType Font),这种格式在 Windows 系统中广泛使用。此外,还有Adobe Type 1,这种字体格式曾导致微软遭遇严重的安全漏洞,攻击者可以通过网页中的 Adobe Type 1 字体文件入侵系统。其他字体格式还包括MetaFont(用于 LaTeX),以及多种不同的字体文件格式。

这些字体文件的主要作用是描述字符的外观,通常采用轮廓(outline)的方式进行定义。例如,在TrueTypeAdobe Type 1字体文件中,字符的形状通常由矢量曲线表示,而不是固定的像素点。如果使用过Adobe Illustrator等矢量绘图软件,就会对这种表示方式有所了解。

字体文件的核心——轮廓描述

在字体文件中,每个字符的形状通常由一组顶点(vertices)和连接它们的线段或曲线来描述。例如,字母“T”可以表示为一组点和直线,形成填充的区域。同样,像“C”这样的曲线字符,则需要使用**贝塞尔曲线(Bezier Curves)**来描述。

贝塞尔曲线是一种常见的数学曲线,由控制点(control points)和控制手柄(handles)来定义。这些手柄决定了曲线的弯曲方式,调整手柄的位置可以改变曲线的形态。这种方法广泛用于字体、矢量图形和计算机图形学领域。

字体的分辨率独立性

字体的描述方式使其具有分辨率独立性(Resolution Independence)。与基于像素的图像(如 Photoshop 中的位图)不同,矢量字体可以任意缩放,而不会丢失细节或变得模糊。这意味着同一个字体文件可以用于不同的字号,例如 20 点、30 点等,而不会损失清晰度。这种特性使得字体在不同设备和分辨率下都能保持高质量的显示效果。

尽管 TrueType 和 OpenType 字体文件中可能包含一些**字体微调(Hinting)**信息,用于优化小字号的显示效果,但基本概念仍然是基于矢量的轮廓描述。

目标:在游戏中实现字体渲染

了解了字体的基本概念后,接下来的任务是如何在游戏引擎中实现字体渲染。游戏开发通常需要在调试过程中显示大量文本信息,因此尽早实现字体系统可以为后续开发提供便利。在后续的实现中,需要研究如何解析字体文件、生成字符的位图表示,并高效地渲染到屏幕上。

字体描述了从字符集编码到轮廓形状的映射

字体文件的第二个关键功能是将字符集编码映射到对应的轮廓形状

字符集编码的作用

字符集编码的目的是提供一种标准化的方式,将文本中的字符(如字母“T”或汉字“肉”)映射到字体文件中的具体字形(Glyph)。例如,英文字母“T”或汉字“肉”都有其固定的形状,而字体文件中存储的只是字形本身。为了确保计算机能够正确显示字符,必须有一种方式来告诉系统:“某个字符对应的是字体文件中的哪个字形。”

字符映射的挑战

单纯地在字体文件中为每个字形编号(例如“字形 17”)并不足够,因为计算机在显示文本时需要知道如何找到正确的字形。例如,当程序需要显示汉字“肉”时,它不能直接假设“肉”就是字体文件中的“字形 17”,因为不同的字体文件可能使用不同的编号方式。因此,必须有一套标准规则,确保所有系统都能一致地找到对应的字形。

字符编码标准

计算机科学界为解决这个问题,制定了一些字符编码标准,其中最基础的一种是ASCII 编码。在 ASCII 编码中,每个英文字母、数字和符号都被分配了一个固定的数值。例如:

  • 字母“A” 对应数值 65
  • 字母“B” 对应数值 66
  • 字母“T” 对应数值 84

这样,当程序遇到 ASCII 码 84 时,就知道它需要在字体文件中找到对应的字形,并渲染出字母“T”。

然而,ASCII 只能表示128 个字符,仅适用于基本的英文字符,而无法涵盖其他语言的字符(如中文、日文、韩文等)。因此,后来发展出了扩展 ASCII(支持 256 个字符)、Unicode(支持全球所有字符),以及UTF-8、UTF-16等编码格式,以便在全球范围内正确显示各种语言的文本。

字符映射与字体文件的结合

在字体文件中,通常会包含一个字符映射表,用于存储“字符编码 → 字形编号”的对应关系。例如:

  • Unicode 编码 U+0041(对应字母“A”)→ 字形编号 17
  • Unicode 编码 U+8089(对应汉字“肉”)→ 字形编号 2315

当计算机需要显示文本时,它会:

  1. 读取文本的字符编码(如 UTF-8)。
  2. 在字体文件的字符映射表中查找对应的字形编号。
  3. 根据字形编号找到字体文件中的轮廓描述,并将其渲染到屏幕上。

这种字符映射机制保证了文本能够在不同的系统、应用和语言环境中正确显示,而不会因为编码差异导致乱码或错误的字符显示。

ASCII/ANSI 编码

字符集编码与ANSI编码

字符集编码是计算机用来表示文本字符的一种标准方式,不同的字符集编码决定了计算机可以识别和存储哪些字符。例如,ASCII(美国信息交换标准代码) 是最基本的一种字符编码,它主要用于表示英语及部分西欧语言的字符。

ASCII 编码的基本原理

ASCII 使用 7 位(二进制 7 位) 表示字符,总共可容纳 128 个字符。其中包括:

  • 数字(0-9)
  • 大写字母(A-Z)
  • 小写字母(a-z)
  • 基本标点符号(如! @ # $ % ^ & *等)
  • 控制字符(如换行符、回车符等)

在 ASCII 编码中,每个字符都有一个固定的数值映射,例如:

  • 字母“T” 对应 ASCII 码 84
  • 字母“O” 对应 ASCII 码 79
  • 字母“P” 对应 ASCII 码 80

如果一个字体文件采用了 ASCII 编码,它就可以通过这些数值来正确映射字符。例如,字体文件会明确规定:“字形 84 对应字母 ‘T’”,这样,计算机就能在显示文本时找到正确的字符。


扩展 ASCII(ANSI 编码)

由于 ASCII 只能表示 128 个字符,无法满足一些欧洲语言(如法语、西班牙语、德语)中的特殊字符需求,因此后来扩展出了 ANSI 编码(也称为 Extended ASCII),它将字符范围扩展到了 256 个字符(8 位编码),增加了:

  • 带重音符的字母(如é, ü, ñ)
  • 货币符号(如€、¥、£)
  • 额外的数学符号和绘图符号

但是 ANSI 编码依然不足以表示中文、日文、韩文等语言,因为这些语言的字符数量远超 256 个。例如,汉字的常用字符就超过 3000 个,如果要完整表示所有汉字,可能需要 3-4 万个字符


字符集编码的局限性与应对方案

由于 ASCII 和 ANSI 编码的字符数量有限,无法涵盖全球所有语言,因此开发者需要寻找其他方式来支持更多字符:

  1. 使用代码页(Code Pages)
    代码页是一种“可切换的字符集编码方案”,不同的代码页可以映射不同的字符集。例如:

    • Windows-1252 代码页用于西欧语言
    • Windows-936 代码页用于简体中文(GB2312 编码)
    • Windows-932 代码页用于日文(Shift JIS 编码)

    但是代码页的缺点是:它们彼此不兼容,比如一个用 Windows-1252 代码页保存的文本文件,在 Windows-936 代码页下可能会出现乱码。

  2. 直接在游戏中自定义字符映射
    开发者可以手动修改扩展 ASCII 里的未使用字符,将它们映射为游戏中需要的特殊符号。例如,可以把 ASCII 码 223(░)修改为“肉”字,这样在游戏内部就可以正确显示汉字。但是,这种方法非常不通用,只适用于特定的游戏环境,不能保证跨平台兼容。

  3. 使用更先进的 Unicode 编码
    为了彻底解决字符映射问题,现代计算机系统普遍采用 Unicode 编码,它可以表示全球几乎所有语言的字符,包括:

    • UTF-8(可变长度编码,适合存储文本)
    • UTF-16(16 位编码,适合处理东亚字符)
    • UTF-32(固定 32 位编码,但占用内存较大)

    Unicode 允许开发者在同一个文件或程序中使用多种语言字符,例如可以在同一个游戏里同时显示:

    • 英文字母(A-Z)
    • 数字(0-9)
    • 法语字符(é, ç)
    • 中文汉字(肉, 你好)
    • 日文(こんにちは)

总结
  • ASCII 编码 只能表示 128 个字符,仅适用于英文。
  • ANSI 编码(扩展 ASCII) 能表示 256 个字符,适用于西欧语言,但仍然无法表示中文、日文等语言。
  • 代码页(Code Pages) 允许切换不同语言字符集,但不兼容,容易产生乱码问题。
  • Unicode 编码(UTF-8, UTF-16, UTF-32) 是目前全球通用的标准,可以表示全球所有语言的字符,解决了跨语言文本显示的问题。

在现代游戏开发中,为了确保支持多语言文本(如在游戏 UI 或对话框中显示中英文字幕),通常都会采用 Unicode 编码,而不会使用 ASCII 或 ANSI

https://theasciicode.com.ar/extended-ascii-code/minuscule-c-cedilla-lowercase-ascii-code-135.html
在这里插入图片描述

在这里插入图片描述

Unicode

Unicode 编码与 UTF-8、UTF-16

Unicode 的发展背景

随着计算机的普及,不同语言的文字需要在同一系统中共存。然而,早期的 ASCIIANSI 编码方案都存在严重的局限性:

  • ASCII 仅支持 128 个字符,仅适用于英语。
  • ANSI(扩展 ASCII)支持 256 个字符,但仍然无法覆盖中文、日文、韩文等语言。
  • 代码页(Code Pages) 允许切换字符集,但不同代码页之间不兼容,容易导致乱码问题。

为了解决这些问题,计算机行业逐步转向 Unicode 编码,它提供了一种通用的字符集,能够支持世界上几乎所有语言,并在不同系统之间保持一致的字符映射。


Unicode 编码的基本概念

Unicode 采用了更大的编码空间,最初的标准版本使用 16 位(2 字节)存储每个字符,能够表示 65,536(2¹⁶)个字符,远远超过 ASCII 和 ANSI。

但是,由于部分语言(如中文)包含大量字符,甚至可能超过 6 万个,Unicode 后来又扩展了支持范围,引入了 不同的编码方案,包括 UTF-8UTF-16


UTF-16 编码
  • 基本存储单位:16 位(2 字节)
  • 标准 Unicode 采用 UTF-16 作为默认编码,例如 Windows 操作系统默认使用 UTF-16。
  • 可扩展至 21 位(支持超过 100 万个字符):
    • 基本字符(前 65,536 个)使用 2 字节(16 位)存储
    • 超出范围的字符使用 4 字节(两个 16 位单元)存储
  • 适合东亚文字,因为许多汉字、日文、韩文字符可以直接用 16 位编码,节省空间。

UTF-8 编码
  • 基本存储单位:8 位(1 字节),兼容 ASCII。
  • 采用可变长度编码,不同字符使用不同字节数存储:
    • ASCII 兼容(0-127 的字符):占用 1 字节(与 ASCII 完全相同)。
    • 扩展字符(128-2047):占用 2 字节
    • 复杂字符(如大部分汉字):占用 3 字节
    • 极端字符(稀有语言或特殊符号):占用 4 字节
  • 存储效率高
    • 英文文本仍然使用 1 字节,不会增加额外存储开销。
    • 东亚字符平均 3 字节,比 UTF-16 稍占空间,但兼容性更好。
  • 广泛用于互联网,如 HTML、JSON、数据库等,因为它兼容 ASCII,且可以灵活扩展。

UTF-8 VS UTF-16
特性UTF-8UTF-16
字节长度可变(1~4 字节)固定 2 字节(部分 4 字节)
ASCII 兼容性完全兼容需要转换
存储效率适合英语,非英语稍大适合东亚语言
使用场景网页、网络通信、数据库、LinuxWindows、某些东亚文字处理
占用空间(英语)小(1 字节/字符)大(2 字节/字符)
占用空间(汉字)大(3 字节/字符)小(2 字节/字符)

为什么 UTF-8 更流行?
  1. 兼容 ASCII

    • ASCII 是最早的字符编码方案,很多老旧系统仍然依赖 ASCII。
    • UTF-8 前 128 个字符与 ASCII 完全一致,因此它能够与现有系统无缝兼容,减少转换成本。
  2. 灵活的存储方式(可变长度编码)

    • 在存储英文文本时,UTF-8 占用空间最小(1 字节/字符)。
    • 在存储汉字、日文、韩文时,UTF-8 虽然比 UTF-16 占用更多空间(3 字节/字符 vs 2 字节/字符),但由于互联网和文件传输对兼容性要求更高,存储效率并不是主要问题
  3. 广泛用于互联网和现代软件

    • HTML、XML、JSON、Python、JavaScript、MySQL 等都默认使用 UTF-8
    • Linux 和 macOS 默认采用 UTF-8,而 Windows 主要使用 UTF-16,但在跨平台应用时,UTF-8 更具优势。

总结

  • Unicode 统一了字符集编码,解决了 ASCII 和 ANSI 不能表示全球所有语言的问题
  • UTF-16 采用 16 位编码,适合东亚语言,在 Windows 系统上广泛使用。
  • UTF-8目前最流行的编码方案,因其 兼容 ASCII、存储效率高、适合互联网和跨平台开发,被广泛用于 网页、数据库、现代软件

在实际应用中:

  • 如果开发 Web 应用、数据库、跨平台软件,使用 UTF-8
  • 如果开发 Windows 本地应用、东亚语言软件,使用 UTF-16

Unicode 的优缺点

Unicode 的优缺点与字体映射机制

Unicode 的优点:全球通用的字符集

Unicode 统一了全球所有语言的字符编码,使得每个字符都有唯一的标识符,确保不同语言的字符不会冲突。无论是现代语言还是已经消失的古代文字,都可以在 Unicode 编码中找到对应的编号。

这带来的一个重要优势是:

  • 一个字体文件可以包含所有人类历史上使用过的字符,理论上支持所有语言,无需切换编码方式。
  • 标准化字符集 使得不同操作系统、软件和平台之间的文本数据更具兼容性,避免乱码问题。

Unicode 的缺点:存储和查询效率问题

尽管 Unicode 解决了字符编码不统一的问题,但它也引入了一些实际应用上的挑战:

  1. 占用空间过大

    • Unicode 采用 16 位、24 位甚至更长的编码方案,而 ASCII 仅占 8 位(1 字节)。
    • 如果直接使用 Unicode 编码,存储一个文本会比 ASCII 版本大 2-3 倍,影响存储和传输效率。
    • 在游戏或其他应用中,不可能为每个字符都保留 65,536(2¹⁶)或更大范围的映射表,否则会造成极大的存储浪费。
  2. 查询速度低

    • 在传统的 ASCII 或 ANSI 编码中,字符可以直接作为数组索引访问,比如 glyphs[char],查询速度非常快。
    • Unicode 范围太大,不能直接作为数组索引,否则会有大量未使用的空间。因此,使用 Unicode 进行字符查询时,通常需要进行额外的映射计算,降低访问速度。
  3. Unicode 仅是字符编码,不等于字体支持

    • Unicode 只是字符编号表,并不包含字符的具体外观信息
    • 即使 Unicode 编码中包含某个字符,也需要字体文件支持该字符的实际渲染,否则仍然无法正确显示。
    • 例如,某个 Unicode 字符在 Arial 字体中可能无法显示,但在 SimSun(宋体)字体中可以正常显示。

Unicode 在字体中的应用:映射机制

由于 Unicode 不能直接用于高效索引字体,通常需要使用一种映射机制,将 Unicode 编码转换为实际的字体索引。

  1. Unicode → 字体索引映射

    • 当系统或应用程序接收到 Unicode 编码的文本时,不会直接在字体文件中查找对应的 Unicode 编码
    • 相反,会先通过映射表找到该字符在当前字体中的索引,然后再进行渲染。
  2. 构建紧凑的字体映射表

    • 一个常见的优化方式是仅存储当前语言需要的字符,而不是整个 Unicode 表。
    • 例如,某款游戏仅支持英语和日语,那么字体文件中只包含 A-Z0-9、基本标点符号,以及日语汉字和假名,而不包括其他 60,000 多个字符。
    • 这样可以大幅减少字体文件的体积,提高渲染效率。
  3. Unicode 映射的典型流程

    • 应用程序接收 Unicode 字符(如 U+4EBA,表示“人”)。
    • 通过 Unicode 到字体索引的映射表,找到该字符在字体文件中的具体位置。
    • 渲染引擎加载对应的字形数据,最终将字符绘制到屏幕上。

实际应用中的 Unicode 处理

在实际开发中,大多数程序支持 Unicode 作为输入和存储格式,但不会直接使用 Unicode 编号进行渲染,而是采用更紧凑的方式存储和查询字体数据。例如:

  • 网页:HTML 和 CSS 默认使用 Unicode 进行文本编码,但字体文件(如 woffttf)通常只包含部分字符,以减小文件大小。
  • 游戏引擎:通常只加载当前使用语言的字符集,并使用 Unicode 映射表进行转换,以提高性能。
  • 操作系统:Windows、macOS 和 Linux 内部使用 Unicode,但在渲染时会根据字体文件提供的字符进行映射。

总结

  • Unicode 提供了全球通用的字符集,确保所有语言的兼容性。
  • 但由于 Unicode 编码空间巨大,直接使用会导致存储浪费和查询效率低下。
  • 实际应用中,Unicode 主要作为字符存储标准,而不是直接用于字体索引。
  • 字体渲染时,通常会构建紧凑的字符映射表,以提高查询和渲染效率。
  • 程序通常接受 Unicode 输入,但在渲染时会转换为更高效的索引方式。

Unicode 码点

在字体文件中,最终存储的内容通常包括了字符的轮廓(即字形的几何描述)和该字形对应的 Unicode 代码点。每个字符都有一个唯一的 Unicode 代码点,代码点是指该字符在 Unicode 编码表中预留的数字位置。例如,“T” 这个字母在 Unicode 中有一个特定的数字位置,这个数字位置就是代码点。

Unicode 代码点

  • Unicode 代码点指的是字符在 Unicode 表中的位置。每个字符、符号或字形都对应一个唯一的代码点,像数字 63,452 就是某个具体符号(比如一个汉字或者字母)的代码点。
  • 这个代码点可以帮助系统识别不同语言中的字符。比如,当程序读取到一个字形时,它可以通过代码点来理解该字形表示的是哪个符号,确保在不同的设备和环境中能够正确显示。

字体文件的基本结构

一个字体文件中的内容通常包括:

  1. 字符轮廓(即每个字形的几何形状),这些轮廓通常是通过坐标点、曲线等方式来描述的。
  2. 对应的 Unicode 代码点,它告诉程序这个轮廓是属于哪个字符。

代码点的作用

  • 字符的轮廓(字形)本身是无法直接告知我们这个符号属于哪种语言或字符。例如,轮廓可能是一个"字母T"的形状,系统需要通过 Unicode 代码点来告诉我们这个轮廓是代表字母“T”。
  • 代码点在字体文件中起到了将符号的形状(轮廓)与实际的语言字符相连接的作用,它弥补了字形和字符意义之间的空白

代码点和实际显示

  • 即使字体文件中有了字符的轮廓和对应的代码点,仍然不够完全。因为我们需要知道如何将这些字符按照特定的顺序(比如单词、句子)进行排列显示。

总结

  • 字体文件包含字符的轮廓和对应的 Unicode 代码点,用以指示每个字形对应的字符。
  • Unicode 代码点使得系统能够将字符形状与其实际意义联系起来,并确保在不同语言和环境下显示正确的字符。
  • 然而,光有代码点和字形还不足以完成一个完整的字体文件,接下来需要进一步解决如何组合多个字符以形成实际的文字内容。

基础排版学(基线、最大上升部和下降部、行高)

在字体排印学中,核心概念涉及字符形状的实心部分和空白部分,以及它们之间的相互关系。排版师花费大量时间思考这些元素如何交互,并且有很多关于字体的特性需要考虑。虽然本文不深入讲解字体设计的细节,但提供一些基本的排版知识,有助于理解字体系统的设计。

字体的基本元素

  • 基线:字体中的字符通常相对于一条“基线”进行排列。基线是指大部分字符底部的对齐线。很多字体文件中会包含有关基线的定义,描述字符在基线上的位置以及它们是如何相对于基线进行排列的。

  • 升部和降部

    • 升部(Ascender):一些字符的部分会高出基线,称为升部。例如字母“H”中的竖直部分超出基线的部分。
    • 降部(Descender):与升部相对,有些字符的部分会低于基线,形成降部。例如字母“g”或“y”的下半部分会低于基线。
  • 字符的垂直跨度:除了基线,还需要考虑字符在垂直方向上的跨度。不同字符的升部和降部高度可能不同。因此,字体文件需要描述哪些字符有最大升部,哪些字符有最大降部,这有助于确定字体在垂直方向上的整体空间利用。

字体文件中的其他信息

  • 行高(Line Height):行高描述了在排版时,相邻两行文本之间的垂直距离。排版师会决定标准行高,以确保文字在行间有合适的间距,使得文本可读性更高。

  • 字符位置:对于每个字符,字体文件会定义它的具体位置。比如,字符“g”的降部需要向下移动,因此字体文件中会包含该字符的垂直位置数据。这是字体排版中的一个重要部分,帮助字符在屏幕上正确对齐。

字体文件的设计目标

尽管许多字体文件已经包含了以上基本信息,但并不是所有字体都能很好地实现这些功能。理想情况下,字体文件应该包含这些重要的排版信息,以便字体在实际使用时能适应各种排版需求。

字距调整(Kerning)

在字体设计中,字距调整(Kerning) 是一种通过调整字母对之间的间距来优化文字排版的方式。字距调整的目标是让某些字母组合看起来更加和谐和均衡,因为某些字母对之间的默认间距可能会显得过大或过小。

字距调整的作用

  • 调整字母间距:字母的形状不同,某些字母的部分可能会向左或向右扩展,导致字母对之间的空间不均匀。字距调整就是为了优化这些不一致的间距,使得字母组合看起来更自然。

  • 根据字母组合调整:字距调整是针对特定的字母对进行优化的。例如,字母“V”和“A”放在一起时,它们之间的默认空隙可能过大,通过调整字距,可以让这两个字母的组合更紧凑,视觉效果更佳。

字距调整的重要性

  • 提升可读性:合理的字距调整能使文本看起来更整齐,增强可读性,尤其是在标题或强调文本中,适当的字距调整能帮助文字更加突出。

  • 美学效果:字距调整不仅仅是为了可读性,还为了达到特定的视觉效果。不同的字母组合可能会通过细微的调整,来实现更加和谐的外观,符合设计师的审美标准。

简而言之,字距调整是字体设计中非常重要的一个方面,通过它,能够让不同字母之间的距离更加均匀,从而提升整体排版的美观性和可读性。

等宽字体(Monospace Fonts)

要理解字体中的单倍间距(Mono)和比例间距(Proportional)的区别,首先需要了解这两者的基本概念。

单倍间距字体(Mono Spaced)

单倍间距字体,顾名思义,每个字符占用的空间是相同的,无论是宽字符(如“W”)还是窄字符(如“I”),它们都会占用相同的水平空间。早期的打字机和一些终端设备使用这种字体。由于设备的限制,无法根据每个字符的实际宽度动态调整字符间的空隙,所以每个字符都被强制调整为相同的宽度。这种字体的一个缺点是,字符间距的固定性可能导致某些字母看起来不自然。例如,“G”和“i”在一个单倍间距字体中占用相同宽度时,导致字母“i”周围的空白区域过大,显得不对称和不美观。这种字体在视觉上往往显得很不协调,看起来“笨拙”,因为没有根据字符本身的形状来优化空间分配。

比例间距字体(Proportional Spaced)

与单倍间距字体不同,比例间距字体会根据每个字符的实际宽度来分配空间。每个字符根据其形状和大小,占用不同的空间。比如字母“i”会占用较少的空间,而字母“W”则占用更多的空间。比例间距字体看起来更自然,因为它能够根据字符的大小进行合理的排版。它能够帮助区分字母之间的空隙和单词之间的空隙,这对提高阅读体验非常重要。合理的间距使得读者可以轻松识别单词,并且帮助他们将一系列字符聚集成有意义的单元,从而提高阅读流畅性和理解力。

小结

  • 单倍间距字体:所有字符占用相同的空间,导致字符间距不均,可能显得不自然,特别是在阅读时容易让人感到不舒服。
  • 比例间距字体:每个字符根据其实际宽度占用不同空间,排版更加自然,能够有效区分字母和单词之间的空隙,提升可读性。

这两种字体在不同的场景中有不同的应用,比例间距字体在大多数现代排版中使用,而单倍间距字体通常用于某些编程环境或特定设计中。

比例字体(Proportional Fonts)

为了理解字母间距的调整,我们需要先了解比例字体的基本概念。在早期的计算机系统中,由于硬件的限制,大多数使用的字体都是单倍间距字体(Mono Spaced),每个字符占用相同的水平空间,这种字体对可读性并不友好,因为它没有根据字母本身的形状来调整间距,导致某些字母之间的空隙看起来不自然。因此,比例字体(Proportional Spaced)逐渐成为了更受欢迎的选择,因为它提供了更自然、更易读的排版效果。

比例字体的进化

在现代字体排版中,比例字体的引入使得每个字母根据其实际宽度占据不同的空间。例如,字母“i”会占用较少的空间,而字母“W”则占用更多的空间,这样的排版方式比单倍间距字体更加灵活和美观。计算机系统也希望能够支持这种更复杂的字体排版,以便生产出更符合传统印刷排版风格的输出结果。

问题的复杂性

然而,虽然比例字体解决了字符宽度不同的问题,但它仍然面临一个挑战:字符间的间距并不是固定的。一个简单的比例字体实现可能会假定每个字母之间的间距是固定的,但这并不能完全解决问题,尤其是当两个字母相互靠近时,所需要的间距可能会有所不同。

例如,当字母“G”和“J”靠在一起时,我们希望它们之间有一个合理的空隙,因为“G”会占据较大的空间,而“J”有一个较长的下部延伸,两个字母之间的空隙需要适当增加。而如果是“i”和“J”紧挨着放置,由于“J”的下部延伸可以和“i”重叠,字母间的空隙可以适当减少。

字母对之间的间距调整(Kerning)

为了精确调整不同字母对之间的间距,我们引入了字距调整(Kerning)的概念。字距调整是指根据特定字母对的组合,动态地调整它们之间的空隙。不同字母组合的间距调整是非常重要的,因为不同字母对之间的形状和布局可能会导致它们看起来更紧凑或更宽松。

举个例子,当字母“F”和“H”组合时,由于“F”通常有一个很长的水平部分,而“H”有一个向上的延伸部分,这两个字母之间需要保持较大的空隙,才能让它们看起来不拥挤。而如果是字母“i”后跟字母“F”,它们可以紧密地排列,因为“i”占据的空间较小,而“F”也不需要特别多的间隙。

连字(Ligature)

在某些情况下,特定的字母对组合非常常见,以至于排版人员会为这些字母对创建一个单一的统一字形,这种字形被称为“连字”(Ligature)。例如,“F”和“I”常常会被设计成一个新的字形,用以更自然地展示这两个字母的组合。

小结

字距调整(Kerning)是排版设计中非常重要的一环,它使得不同字母对之间的空隙能够更加合理、自然。通过对字母对的间距进行精确调整,能够提高文本的可读性和美观性,避免看起来生硬和不协调。

字距调整信息

在字体文件中,通常会有一个用于调整字符间距的额外编码信息,这种信息用于处理字距调整(Kerning)。这种信息的表示方式通常是通过一种编码机制来实现,而不是直接使用查找表。举个例子,字体文件中可能会有类似以下的内容:如果字符G和J紧挨着,调整后的间距值可能是5;如果是字母I和字母J紧挨着,调整后的间距可能是1。

字距调整的实现

字距调整通常并不会直接记录总的间距值,而是记录间距的变化量(Delta)。通常,字体文件会先为每个字符指定一个默认的字符间距(例如,字母G可能会预设一个宽度为10个单位的间距)。然后,如果某一对字母之间的间距需要调整(例如,G和J之间的间距需要减少),字体文件会记录该调整量(例如减少5个单位),而不是重新编码调整后的总间距。为了节省空间,字体文件通常只记录字距调整的差值,而不是每对字母的完整间距。

为什么采用这种方式?

这种方法的优势在于节省了存储空间。如果直接为每一对字符记录所有可能的间距变化(即使用一个n×n的表格,n是字体中字符的总数),那么对于每一对字母就需要记录一个条目,这样的存储需求会非常庞大。而通过记录默认的字符间距并仅为需要调整的字母对指定调整值,可以大大减少存储空间的消耗。

字体文件的组成

一个完整的字体文件通常包含以下内容:

  1. 字形(Glyphs):字体文件中的每个字符(或字形)都有一个表示其形状的数据。
  2. 字体度量(Font Metrics):这些是一些基本的数字信息,用于描述字体的布局。例如,包括基线(Baseline)、降部(Descender)、升部(Ascender)等度量数据。
  3. 字距调整表(Kerning Table):这个表格记录了特定字母对之间的调整量,用于修改默认的字符间距,使得字符之间的排版更加美观。

字距调整的缺陷

遗憾的是,很多字体文件中的字距调整表并不完善,有时这些表格甚至会做得非常糟糕,导致显示效果不理想。虽然这些问题让人感到烦恼,但也只能尽量接受并进行改进。

小结

字体文件通常包含字符的字形、字体度量信息以及字距调整信息。字距调整通过记录字母对之间的间距差值,而不是每一对字母的总间距,来节省存储空间。通过这种方式,可以在排版过程中实现更加精确的字符间距调整,提升字体的可读性和美观性。然而,许多字体文件的字距调整表不完善,导致字体显示效果并不理想。

字体微调(Hinting)

字体文件中通常还包含一个称为“提示(Hinting)”的功能,提示主要是针对在非常小的屏幕上显示字体时,确保字体清晰可读的一种技术。如果字体被渲染在一个非常小的网格上,尤其是在低分辨率的情况下,字体的显示可能会变得模糊或失真,影响其可读性。

提示的作用

提示的目的是确保字体的笔画(如字母T的竖线)能够精确地与像素网格对齐。当字体被渲染到非常小的分辨率时,如果没有经过适当的提示,可能会出现字体笔画在网格中只占用一部分像素,导致边缘变得模糊。例如,在一个非常小的8x8像素的网格中,如果字母T的竖线没有精确对齐,它可能在某些地方显得像是半个像素宽度,这样就会导致显示出来的T字形变得模糊并且不清晰,影响阅读体验。

提示的工作原理

提示通过确保字符的主要笔画(如竖线或横线)能够和网格的像素边界对齐,从而避免这种模糊的效果。比如,对于字母T,它的竖线应该尽量和网格的水平和垂直像素线对齐,而不是落在像素的中间位置,防止渲染时出现半像素效果。

现代高分辨率显示器与提示

在现代高分辨率显示器(如Retina显示器)上,由于像素密度足够高,即使没有经过提示,字体也能够非常清晰地显示出来,因为每个像素可以精确地表示字符的细节。因此,在这种高分辨率显示环境中,提示的作用变得不那么重要。只有在极低分辨率或非常小的显示区域中,像8x8的字体网格才会显得尤其重要。

总结

字体文件中的提示技术主要是为了在低分辨率的情况下保证字体的可读性,它通过调整字形的笔画使其与像素网格对齐,从而避免字体渲染时出现模糊或失真的问题。虽然在现代高分辨率显示器上提示不再那么必要,但在低分辨率的显示场合(如小型显示屏或低像素的图形显示)中,提示仍然是确保字体清晰可读的重要技术。

当前游戏中使用字体的方式:

接下来,我们希望构建一种方法,可以将字体文件加载到程序中并在应用中使用。为了实现这一点,有几种可行的方式。关键是要选择一种高效且易于渲染的方式,毕竟在某些应用中,字体并不是核心内容,因此不需要花费太多时间在字体处理上,而是要找到一种快速且简便的实现方法。

可能的实现方法

我们可以通过不同的方式来加载和使用字体,最重要的是要考虑到哪些方式对当前项目最有效。由于此项目并不是一个专注于文本的游戏或应用,因此对字体的需求相对较少,我们的目标是确保字体的处理既简洁又高效,不会成为性能上的瓶颈。

1) 加载 TTF 并将字形镶嵌成由三角形构成的轮廓

在这个方案中,我们可以通过加载 TTF(TrueType Font)文件来实现字体的使用,并将其转化为可以渲染的形状。具体来说,有两种可能的方式来处理这个问题。

方案一:将字体转化为三角形

第一种方式是将字体的字形(glyphs)加载进来,然后将这些字形转化为三角形。这个过程通常叫做“细分”(tessellation),它通过将复杂的形状转化为三角形来简化渲染。转化后的字体将是一个轮廓版本,可以用于渲染。

然而,这种方法也有一个问题。如果只处理一次并直接使用这种格式的字体,当渲染像字母“S”这样的字符时,可能会出现失真问题。例如,字母“S”的曲线在细分时可能变成了直线,导致放大后图形失去原有的流畅度。因此,可能需要在渲染时根据需要动态调整细分度,确保字符在不同大小下仍然清晰。

方案二:直接使用 TTF 文件进行渲染

另一种方式是直接加载 TTF 文件,并使用文件中内置的信息进行渲染。这种方法可以省去动态细分的复杂性,但需要确保字体的每个字形在各种显示条件下都能够正确显示,尤其是在不同的缩放级别下。

总结来说,以上两种方案都可以实现加载和渲染字体,只是每种方法的处理方式不同,各有其优缺点。

2) 加载 TTF 并进行隐式光栅化

在这段内容中,讨论了如何将字体文件进行栅格化(rasterization),虽然这一过程并没有深入探讨,但已经在其他部分的工作中实现过。栅格化的过程可以通过“隐式栅格化”(implicit rasterization)来完成,这种方法类似于之前在填充四边形时使用的方式。

隐式栅格化

隐式栅格化是一种方法,它通过某种函数来确定一个点是否位于形状内。例如,在之前的四边形填充中,我们通过计算每个像素是否在四边形的边界内来决定是否填充该像素。对于字体的栅格化,我们可以使用类似的方法,将每个字体的字形(如字母“S”)转化为一个边界函数,并判断每个点是否在字形内。具体来说,使用贝塞尔曲线(Bezier curves)来描述字形轮廓,然后检查某个像素是否位于这些曲线内部。这可以通过一种叫做“线交测试”(line test)的方式实现,判断该点是否在边界内部。

“线交测试”原理是:每次沿着一个线段穿过字形边界时,计数增加,如果穿过的次数是奇数,则点在字形内;如果是偶数,则在字形外。这种方法在不同的分辨率下都能保持一致,不论是缩放还是变形,字形都能保持平滑和准确。

然而,这种方法虽然精确,但对于大多数游戏来说其实是过度设计的。大多数游戏不会依赖如此复杂的字体形状处理,因为大部分游戏已经预设好了字体的显示大小,或者字体的显示在固定的分辨率下是稳定的。因此,花费大量时间去开发这样复杂的字体渲染系统是不必要的,反而会浪费时间,影响其他功能的开发。

典型的做法

因此,大部分游戏开发中通常选择更简单、更高效的字体渲染方案,避免使用过于复杂的栅格化方法。这也意味着对于大多数游戏来说,字体的处理需要做到简单而有效,而不必为每个细节进行复杂的计算。

3) 使用预光栅化字体

预栅格化字体是一个非常有效且简单的解决方案,其优点在于将字体转化为游戏已经能够处理的格式。通过预先栅格化字体,字体变成了一个已经解决的问题,因为游戏已经具备了处理位图的能力。例如,我们已经能够将位图渲染到屏幕上,并且知道如何对它们进行缩放和变换,就像我们处理主角精灵和背景精灵一样。

预栅格化字体的优点:

  1. 简化了字体处理:
    预栅格化字体意味着字体已经转化为位图,这个过程在开发时完成,因此在游戏运行时不需要额外的计算。我们只需将这些已经栅格化的字体直接显示在屏幕上,像处理任何其他位图一样,过程简单直接。

  2. 性能考虑:
    使用预栅格化字体时,我们无需担心运行时的性能问题。因为字体已经被转换成位图,所以不需要在游戏运行时进行复杂的字体渲染计算。无论字体的形状有多复杂,预栅格化字体都不会影响性能。我们只需通过标准的位图渲染流程将其显示在屏幕上。

  3. 灵活性:
    预栅格化的字体不受复杂度的限制,无论字体的设计如何,都可以通过这种方式进行处理。我们只需为目标分辨率(如1920x1080)栅格化字体,并确保它们的高度(例如40像素)。一旦栅格化完成,我们可以将所有字形保存为单独的位图或一个包含所有字形的打包位图。

  4. 便于显示:
    一旦字体被预栅格化,就可以像处理任何其他位图一样将其绘制到屏幕上。这种方式非常直观,并且性能稳定,不会像实时计算字体那样产生额外的性能开销。

实现方式:

实现预栅格化字体的方式是首先选择一个目标分辨率,然后在此分辨率下将字体栅格化成位图。例如,对于目标分辨率1920x1080,如果希望字体显示大约40像素高,就将字体按照此大小栅格化。接下来,保存栅格化后的字形到位图中,这些字形可以放在一个打包的位图中。最终,在游戏运行时,字体的显示就是简单地从这些位图中“复制”出来,不需要任何复杂的计算。

总结来说,预栅格化字体通过将字体转化为简单的位图,极大地简化了字体渲染过程,并且避免了运行时复杂计算带来的性能压力。这种方式不仅容易实现,而且能够确保在各种分辨率下都有良好的显示效果,适用于大多数游戏需求。

处理预光栅化字体的方法

要进行预栅格化字体的处理,通常的做法是从位图开始,将字体字形(glyphs)一个个地打包到位图中。具体步骤如下:

1. 创建位图并打包字形:

  • 首先,我们需要一个位图来存储所有的字体字形。我们可以从字母A开始,接着是B,依此类推,将所有字形依次放入位图中。每个字形都会占据位图中的一个区域,最终这些字形就会组成一个完整的字体位图。
  • 如果需要,我们可以将这些字形单独存储为不同的资源文件,而不是打包在一个位图中,这取决于实际需求和实现方式。

2. 栅格化字形:

  • 栅格化时,我们不需要存储颜色信息,因为通常字体的颜色会在显示时动态设置。大多数字体不需要复杂的颜色,所以栅格化时,可以选择使用单色位图,只存储字形的轮廓信息。
  • 在栅格化的过程中,使用8位的灰度值来表示每个像素的覆盖情况(例如,反走样的灰度值)。这种方法只记录每个字形的反走样效果,而不是色彩信息,适用于大多数情况。

3. 存储信息:

除了位图外,我们还需要一些其他数据来支持字体的正确渲染。主要有以下几种信息:

  • 字符映射表(Code Points):
    需要将每个字符与相应的字形位图进行映射。例如,字母A对应的字形所在位图的区域或位图ID。这是为了能够通过字符找到正确的字形。

  • 定位信息(Positioning Information):
    每个字形在位图中的位置可能并不完全按照字形的尺寸排列。为了确保字形可以正确显示,我们需要知道每个字形的具体位置,特别是对于不同大小的字形(例如,小写字母a与大写字母A的尺寸不同)。这部分信息帮助确定字形相对基线的位置。

  • 字距调整表(Kerning Table):
    字距调整是指在两个字符之间的空隙调整,确保文本显示时的字母间距自然、均匀。我们需要一个字距调整表来定义每对字符之间的间距。

4. 总结:

在处理字体时,预栅格化字体的关键在于将字形转化为位图并存储字形、字符映射、定位信息以及字距调整等数据。这些信息可以确保字体在加载后能够正确地渲染和显示,且不需要在运行时进行复杂的计算,提升了渲染效率。

下一集的计划

明天的任务是开始构建一个预栅格化字体的系统,将其集成到资源包文件中,确保包含所有必要的信息,比如字体字形和其他相关数据。目标是让这个字体处理系统能够适应我们的需求,并且能在游戏中正常使用。

1. 扩展资源系统:

我们已经做了很多工作来确保资源系统的可扩展性。为了加入字体资源,实际上并不需要太大的改动。最简单的方法就是将字体作为一种新的资源类型加入到现有的资源管理系统中。如果需要处理特殊的字体资源格式,我们可以做一些定制的处理,但总体来说不会很复杂。

2. 字体加载与使用:

我们可能会考虑通过标签匹配系统来提取字体代码点,这样做也能轻松管理字体资源的加载。尽管这种方法可能看起来有些有趣,但它是可行的,可以简化字体的管理和使用。

3. 实际操作:

明天的工作重点是开始处理字体的预栅格化,将字体字形转换为位图并存储在资源包文件中。这一过程本身比较繁琐,但并不复杂,关键是要将字体转换为合适的格式并将所有相关数据(如字符位置、字距调整等)打包起来。

4. 字体生成的方式:

关于如何生成字体,我们可以通过不同的方式实现:

  • Windows API:可以通过Windows系统的API来提取和处理字体。
  • 其他操作系统:可以使用其他操作系统的字体处理功能,提取字体数据。
  • STB TrueType 库:还可以使用STB TrueType这样的第三方库,提供简洁且易用的接口来处理TrueType字体。这个库代码量小,功能强大,非常适合用来生成需要的字体。

5. 总结:

总体来说,明天的工作将是把之前讨论的字体系统具体实现出来,处理字体的加载、预栅格化、以及相关的位图存储等任务。这部分工作虽然可能比较单调,但它是实现字体在游戏中正确渲染的基础。因此,明天将重点处理如何将字体数据转化为可以在游戏中使用的格式,并确保它们的正确性和高效性。

字体的授权问题是否值得关注?

关于字体的许可问题,实际上取决于你选择的实现方式。不同的字体处理方法会有不同的法律风险和要求。

1. 动态生成字体的许可问题:

如果选择动态生成字体(如通过加载并渲染TTF或OTF文件),你通常需要为所使用的字体获取许可,即使你不打算将字体文件本身(如TTF文件)发布在游戏中。这是因为在某些情况下,字体文件本身会被视为计算机程序。具体来说,有法律判例认为字体文件(如TTF文件)是计算机指令,能够在不同分辨率下生成字体,因此它们被视为软件。根据版权法,软件是可以受到保护的,即使你只是加载字体文件并将其转化为不同的格式(例如:转换为其他字体形式),依然可能违反版权。因此,即使不分发字体文件本身,你使用这类方法时,仍然需要获得相应的字体许可。

2. 预栅格化字体的许可问题:

然而,如果你选择预栅格化字体(即将字体转换为位图图像),这种方法通常没有版权问题,因为你最终得到的是字体的图像而不是软件指令。在美国,字体的形状本身是无法受到版权保护的,只有计算机程序(如字体文件)才可能受到保护。因此,如果将字体转换为位图后用于游戏,理论上没有版权侵权的风险,因为字体的图像并不属于受版权保护的“软件”范畴。

3. 其他地区的版权问题:

如果你在美国以外的地方使用这种方法,版权问题可能会有所不同。在一些地区,如欧洲,字体的版权可能会受到更多的保护。因此,具体的法律情况可能会因地区而异,但在美国,预栅格化字体通常不会涉及版权问题。

4. 结论:

总体来说,使用预栅格化字体是一个比较安全的选择,因为你只是使用字体的图像而非其软件指令。这样,你不需要担心版权问题。然而,如果你选择动态生成或加载字体,尤其是使用原始的字体文件(如TTF文件),那么你就需要获得字体的许可。这在美国是必须的,而在其他地区可能会更加严格。因此,如果要确保完全安全,获取字体许可总是一个更稳妥的做法,尽管它们通常价格不高。

你会支持 Unicode 吗?

在游戏中,是否支持Unicode取决于设计需求。为了支持Unicode,可以通过处理不同语言和符号来增强游戏的文本显示能力。Unicode 是一种字符编码标准,涵盖了全球几乎所有书写系统,因此在全球范围内使用的文本和符号都可以被识别和显示。

为了实现这一目标,可以采取以下步骤:

  1. 字体支持: 游戏需要加载和渲染包含Unicode字符的字体。通过加载具有Unicode字符集的字体文件,可以确保支持多种语言字符。
  2. 字符映射: 游戏将需要一个字符映射系统,将Unicode字符映射到相应的图形或位图。可以通过预先栅格化字体,将其转换为位图进行存储,从而避免在游戏运行时实时计算和渲染字符。
  3. 输入和显示: 如果游戏允许玩家输入文本,输入系统也需要支持Unicode字符输入,确保多语言输入的兼容性。

通过这些措施,游戏可以提供更丰富的文本支持,使其能够适应全球不同的语言和符号需求。因此,在游戏中集成Unicode功能是一项有益的选择,尤其是在需要处理多语言文本的情况下。

我们是否可以在运行时,根据分辨率变化来预光栅化字体?

在游戏中,是否在分辨率改变时动态预栅格化字体是可以的,但通常不推荐这样做。这样做会增加游戏的复杂性,因为需要在游戏中加入一个字体栅格化器。这意味着游戏必须处理更多的内容,可能会带来更多的潜在问题。如果可以避免这种情况,通常会选择将字体视作常规的艺术资源来处理。

如果必须在运行时动态调整字体,可能需要考虑一些额外的因素,例如性能和额外的计算开销。在这些情况下,预先将字体栅格化成位图并将其作为静态资源使用,会更为简单和高效。这种方法可以避免运行时的复杂性和潜在的错误,因此更倾向于将字体当做静态资源处理,除非有特别的需求需要动态生成。

在编写字体读取代码之前,你会先用该字体写点什么吗?

在实现字体系统时,首先并不需要编写实际的字体读取代码。我们只需要获取字体数据,确保它可以正确地嵌入到游戏中。至于如何打印文字,确实需要先定义一个字体API,决定如何处理字体的显示。然后,我们可以根据这个API的设计来实现打印文本的功能。这意味着在设计字体渲染系统时,首先需要确定如何通过API进行文本输出,之后再根据这个API的需求来实现字体文件的读取和处理。

经典的位图字体在未来是否仍然有价值?还是说我们会完全转向矢量字体?

经典的位图字体可能在未来几年依然会有一定的存在,尤其是在复古风格的游戏中,位图字体会保持其独特的魅力。然而,整体趋势是矢量字体(Vector Fonts)将成为主流,因为它们可以根据需要进行缩放和调整,灵活性更高。虽然矢量字体通常会被栅格化为位图来显示,但手工制作位图字体已经不再是大多数开发者的首选,除非是为了复古风格的游戏。虽然复古风格的游戏可能会长期存在,甚至可能永远不会消失,但对位图字体的需求总体上会逐渐减少。

你在研究量子力学吗?

量子力学不是我了解的领域,因为我并不懂得任何与之相关的对立机制。

你有没有尝试过自己设计一款字体?

确实,在小时候自己做过一个字体,但那是一个位图字体,并不是像iPhone那样的矢量字体。

你的游戏会使用专门设计的字体吗?

可能会为游戏制作独特的字体,这取决于具体需求。如果需要使用多种字体风格,可以考虑添加不同的字体家族。

如果要预光栅化多个字体家族,是否可以将它们合并到一个文件中?

在预先光栅化字体的情况下,字体是可以被存储在一个文件中的。具体能否将它们存放在一个文件里,取决于所使用的资产系统是否支持这种方式。如果资产系统支持,就可以将它们打包在一个文件里。

我能搬去跟你住,让你辅导我吗?

没有空余的卧室,所以无法提供住宿。

一些语言的 Unicode 组合字符是如何处理的?

在这款游戏中,可能不需要特别担心 Unicode 字符的组合问题。实际上,我也不记得曾经在这方面做过特别的处理。

黑板演示:组合字形

在 Unicode 规范中,字体的表示有两种方式。其中一种方式是通过 Unicode 代码点来表示字符,比如说,某个代码点(如 87 或 80)对应字母 “P”。在这种情况下,我们可以直接将其显示为字母 “P”。这是大多数情况下的处理方式,也是游戏中的处理方式。

另外,还有一种更复杂的情况叫做“组合字符”,它表示的是一些字形的组合。这意味着,不是简单地将每个字符作为独立的图形,而是将一个字符分解成多个部分,并且这些部分可以通过多个代码点表示。例如,字母 “i” 可能被分成上半部分和下半部分,并分别用两个不同的代码点表示。当引擎看到这两个代码点时,它需要知道如何将它们组合在一起(比如上半部分放在下半部分上方)。

这种组合字符的处理方式在 Unicode 规范中是存在的,但我没有深入研究过这个部分的规范,也不清楚如何在实现中处理。具体来说,我不确定在处理组合字符时是否需要特别的代码,或者是否可以通过调整字距表(kerning table)来解决。因为它涉及到字符组合的特性,可能需要进行更复杂的处理,或者可能只需要检查后续的代码点,判断是否存在组合关系。总的来说,这部分的实现相对复杂,我并没有深入研究过它的具体要求。

自从我开始随时光栅化字体,我变得快乐多了,这种方式太棒了。

一旦开始实时生成字体,我变得更开心了。意思是你会在绘制每个字符时,实时栅格化字体吗?或者你指的是其他的方式呢?这有点偏题,但究竟是什么让代码变得更有趣呢?

(题外话):代码“可发布”意味着什么?你说你不会发布当前的 Win32 平台层,这是为什么?

目前的 Win32 平台层并不适合发布或交付使用,主要是因为它并不支持创建一个强大、稳定的用户体验。具体来说,它没有通过 3D 硬件层,这意味着帧率可能不稳定,且没有垂直同步 (vsync),这会影响图形表现。此外,Win32 层也没有正确处理应用程序激活(比如在后台运行时的状态)。这些问题都表明,该平台层还没有达到适合发布的质量。

为了确保最终版本能够顺利发布,需要回到 Win32 平台层,逐一完善其中的所有问题,增强其稳健性。这包括对代码路径的优化、测试,并且需要在不同的机器上进行实际测试,以确保最终产品在更多的设备上能够稳定运行。

你会自己从零开始编写预光栅化工具,还是使用第三方工具?

我们将完全使用 Windows 来实现字体的预栅格化功能,这样的实现相对简单,不需要使用第三方工具。至于具体在哪一集实现这个功能,我们会在后续的内容中逐步展开。

哪一集会实现 Unicode 皮肤色调的表情符号修饰符?

皮肤色调表情符号修改器显然是最重要的事情之一。如果我们的表情符号不能覆盖所有可能的肤色,那我们还能称其为一个统一的世界吗?答案显然是否定的。为了实现这个目标,我们必须确保能够支持所有的肤色,包括那些尚未出现的肤色。

如果游戏中有战利品历史或故事线历史,你提到的 40px UI,如果允许用户调整该 UI,是否仍会使用预光栅化字体?在哪种情况下你会选择非光栅化版本?

对于是否使用预栅格化的字体,这基本上是一个判断问题。如果选择使用预栅格化字体,就需要确保能够有效覆盖不同的使用场景。因此,是否包括非栅格化版本字体取决于质量要求和需要的字体大小范围。当需要的字体质量和尺寸变得更复杂,超过了预栅格化字体的覆盖能力时,可能需要考虑使用非栅格化版本。

你在粒子系统中使用的欧拉方法是将物质从高密度移动到低密度的单元,对吧?这种方法可以反过来使用吗,比如创建引力,使低密度物质被吸引?此外,欧拉方法是否可以用于 AI 系统,比如让不同角色对人群的密度产生吸引或排斥?

欧拉方法,或者说离散化方法,通常指的是在固定规则网格上进行离散化,而拉格朗日方法则指的是将系统离散化为自由浮动的点。关键是,欧拉方法不仅仅是关于网格的离散化,而是通过选择合适的物理方程来解决相应的系统。虽然没有深入探讨,实际上如果进一步模拟,可以使用欧拉方法模拟任何物理系统,比如模拟液体、表面张力等。为了实现这些,需要先为所要模拟的系统编写物理方程,然后将这些方程离散化到网格中,最后通过解算器进行求解。

对于引力模拟,完全可以实现,问题在于网格的精细度以及如何捕捉所需的物理效果。这类问题属于连续介质动力学领域,若对此感兴趣,可以深入研究该领域的知识。对于引力模拟来说,欧拉方法可以处理复杂的动力学模拟,只要选择合适的方程和网格即可。这个领域非常庞大,很多人一生都可以从事相关的模拟工作。总的来说,想要做任何复杂的模拟都可以,但问题在于所需的计算成本和资源。

在游戏中滥用文本是否是糟糕的游戏设计?

游戏中的恶劣文本是否构成糟糕的游戏设计,这个问题本身取决于游戏设计的核心理念。如果游戏的重点不在于文本,那么显然在游戏中使用恶劣或不当的文本设计就是一种糟糕的设计。但如果游戏的重点正是围绕文字展开,那么游戏设计就需要考虑文本的准确性和适当性。在这种情况下,文本的使用和设计将取决于游戏的整体目标和需求。总的来说,游戏设计是否糟糕,取决于是否能够与游戏的核心主题和玩法相匹配。

在代码中你会使用正确的排版术语,还是会随意一些?

游戏中对于排版术语的使用可能会比较随意,并不会特别讲究准确性。虽然排版术语有一定的专业性,但在游戏设计中,使用这些术语并不会带来太大的好处。实际上,排版学的术语并不多,所以即使不完全遵循严格的排版术语,也不会影响整体效果。如果有需要,当然可以参考专业排版师的建议,但在游戏中并不需要过于拘泥于这些细节。

随着引擎的复杂性不断增加,你会等到 FPS 下降到一定程度才开始优化,还是有其他标准?

在游戏开发中,增加引擎的复杂度时,是否会在帧率下降到一定程度时才开始优化,或者是在其他条件下开始优化呢?通常,优化不仅仅是最重要的部分,尽管它确实在游戏开发中占有重要地位。事实上,有两个原因会促使我们使用调试视图。一个是优化,另一个是发现和修复bug。

在复杂的游戏中,可能会有许多bug存在,这些bug可能会影响到游戏的表现和体验,尤其是那些无法通过设计单元测试或者事先检查代码发现的bug。这些bug可能看起来不会直接影响游戏的运行,但它们会导致一些不愉快的游戏体验。例如,AI可能会因为一个bug做出不合理的决定,导致玩家遇到一个比预期更弱的敌人,这样的错误通常是无法在开发过程中被察觉的。为了及时发现这些bug,必须要有可视化工具,帮助开发者在游戏中实时观察程序的行为,并且直观地了解发生了什么,从而能及时发现问题。

即使当前代码已经很成熟,也可能仍然有一些bug我们并没有发现,直到更多的功能或视图被实现时,才会被显现出来。比如说,在多线程的实现中,可能存在一些bug,我们在可视化线程运行时可能会立即察觉到。游戏本身是一个随着时间进行的系统,因此很多bug是无法通过传统的测试方法完全排除的,这也是为什么可视化如此重要的原因。它不仅是优化的一部分,更是增强游戏稳定性和提升质量的关键工具。

总的来说,开发过程中的可视化工作,不仅有助于发现性能瓶颈,还能够帮助开发者更好地理解和修复复杂系统中的潜在问题。

在编程方面,你目前遇到的最大挑战是什么?

目前编程中最大的挑战之一是工具和环境的质量问题。随着Windows系统和开发工具的质量逐渐下降,使用的工具,如Emacs,变得越来越慢,很多开发环境变得不堪使用,给开发者带来了很大的困扰。对于编程工具,很多时候我们感到很孤独,因为这些工具的开发者似乎并没有真正关心开发者的需求,而是更多关注如何插入广告或如何为特定的开发者群体(如Java开发者)优化工具,这让人感到非常沮丧。

对于开发者来说,工具的质量影响极大,甚至可能影响到他们的编程体验和效率。曾经,开发者们在使用早期的工具时,感到非常兴奋和满意,甚至在低性能的系统上也能够体验到良好的编程体验。然而,现在许多现代工具无法提供这种体验,特别是在硬件性能强大的情况下,文本编辑器和编译器的反应速度也无法满足开发者的需求。尤其是,很多开发工具的设计者似乎并不理解编程的核心需求,导致了开发工具的使用体验逐渐恶化。

随着对这种现状的不满越来越强烈,有些开发者开始考虑自己重新设计和开发更好的工具,甚至希望能做出一款新的操作系统或编程语言来改变这一局面。这种思维激发了一些开发者的积极性,像某些开发者便开始尝试开发新的编程语言,希望能为未来的编程体验带来一些改变。

尽管这种想法可能被认为不够实用,甚至有些不切实际,但事实上,正是因为现有的工具和技术没有真正解决问题,才让开发者意识到自己有能力并且需要去改变这一局面。开发者们希望通过自己的努力,开发出更好的工具,从而为未来的编程工作提供更好的支持。

这种对于改善工具和技术环境的渴望,尤其是在发现有更多的开发者开始动手做类似项目时,给人带来了希望。像是有人在模仿制作文本编辑器,或者尝试开发新的编程工具等,都是积极的信号。虽然这些项目可能需要一到两年的时间来实现,但并不是无法完成的任务,只要有足够的人关注和投入其中,这些工具的质量就能逐渐得到改善。

因此,虽然当前的编程工具可能让人感到沮丧,但看到一些开发者通过自己的努力来改变现状,给未来的编程环境带来希望,还是让人感到鼓舞。通过这些项目的推动,未来的编程体验有可能会迎来一种新的转机,带来更高效、更适合开发者需求的工具。

最糟糕的是,你指出这些工具有多糟糕,但似乎没人理解你的意思,大家都觉得“这是什么意思?”。

有时候,很难让别人理解开发工具的糟糕之处,因为很多人并没有亲身体验过更好的工具。就像如果一个人只吃过麦当劳的鱼柳汉堡,他很难理解真正高质量的寿司有多好。你没见过更好的东西,就很难意识到现有的东西有多差。

很多时候,直到你拿到一个真正优秀的工具,才会突然明白差距有多大。这就像是,可能很多人并不觉得Windows手机差,直到他们拿到了iPhone,滑动屏幕的时候才会觉得“天啊,这简直是魔法”。这种改变是非常直观的,因为你亲身体验过才会知道什么才是好的。

目前,我所抱怨的工具问题,未来可能会发生变化。也许在五十年后,我们都生活在一个虚拟现实中,通过思想就能让代码自动生成,那个时候我现在认为好的工具可能也会显得很差。所以,即使我现在对工具的不满,未来的标准可能会完全不同。

因此,当前最重要的事情就是开始构建一些好的工具,展示给大家,告诉他们:其实Visual Studio这种工具是不够好的。我们需要展示给大家,好的调试器到底应该是什么样的,真正高效的开发工具应该具备什么特性。

你是否考虑过使用有符号距离场(SDF)进行文本渲染?

使用带符号的距离场(Signed Distance Fields,SDF)进行文本渲染时,会面临一些挑战。首先,带符号的距离场本质上是通过对像素进行四舍五入来计算的,因此它无法生成硬边缘的效果。如果直接使用SDF渲染文本,结果可能会导致字体看起来像是模糊的“斑块”,因为SDF并不适合处理清晰的边界。

为了避免这种情况,必须使用一种更复杂的编码方式。在SDF中,如果想要得到清晰的字体渲染效果,需要将字体的不同区域分成多个SDF区域,类似于Voronoi区域的划分方式。这意味着需要为每个字体区域创建多个距离场,这种方法虽然可以得到更清晰的渲染效果,但也非常昂贵,计算成本较高。

因此,除非确实需要使用带符号的距离场,否则这种方法并不是最优选择。需要权衡其高昂的成本与其他更简单的渲染方法的优势。

你是否对不研究低级开发的人不够公平?我个人热爱低级开发,但为了支付账单,我不得不使用 WPF 和 C#,因为如果从低级做起,开发速度会慢 10 倍,我别无选择。

这并不是一个关于公平与否的问题,而是关于工具质量的问题。当前使用的工具质量让人不满意,尽管可以理解一些工具可能因为时间或资源不足而没有做到最好,但这并不能改变这些工具的不足。比如,尽管知道 Visual Studio 可以做得更好,但现有的工具依然存在很多问题,这些问题直接影响工作效率和质量。

对于每个人来说,如何通过工具完成工作是个人的选择。如果有些人为了生计必须依赖更高层次的工具如 WPF 和 C#,这完全可以理解,毕竟每个人都需要在现实中挣钱。然而,这并不意味着现有的工具就已经足够好。问题在于整个行业没有提供满足开发者需求的工具。

希望将来能有更多的人投入时间和精力,去开发更好的开发工具。尽管在商业上可能存在一定的挑战,但真正高质量的开发工具仍然值得去追求,并且可能会开创出一个新的市场,帮助提升整个行业的工作效率和开发者的体验。

仅仅见过一个好工具并不足以理解现代工具的缺点。我觉得你必须是一个非常优秀的程序员,否则不会真正感受到它们的问题。你同意吗?

并不是只有很高水平的程序员才能感受到现代工具的不足。即使是普通的程序员,当看到一些真正好的工具时,也能明显感受到当前工具的差距。比如,如果有一个调试器,它能够直观地展示所有的结构,自动更新并绘制出相关的图形,比如显示三维向量并可以旋转查看,任何人都能立刻意识到当前使用的工具有多差,都会想要使用这种高效的工具。即使是新手,看到这种工具也会觉得它简洁易用,远比现有的工具强大。

如果开发者能像谷歌在广告投放上投入那么多精力和时间来打造一个高效的调试工具,那么这个调试器的效果会远超目前我们使用的工具,像 Visual Studio 这样的平台就会显得远远不够用。问题在于,现有的工具并没有投入足够的精力去提升其功能和用户体验,而是将重点放在了其他地方。

总的来说,如果开发者能够真正专注于打造优秀的开发工具,而不是仅仅满足于现有的状态,那么任何人都能感受到工具的巨大进步和提升。

如果黑手党给你一个完美版本的 Emacs 和 Visual Studio,你会接受他们的工作吗?

如果黑帮给出一个完美版本的 Emacs 和 Visual Studio,是否会考虑接受这个工作?有趣的是,提问者提到自己是意大利人,并且询问具体是哪个黑帮。如果是西西里黑帮,他认为非西西里人也可能被接纳到一定程度,暗示这是一个轻松的回答。


http://www.ppmy.cn/server/175730.html

相关文章

Spring 事务失效的 8 种场景!

在日常工作中,如果对Spring的事务管理功能使用不当,则会造成Spring事务不生效的问题。而针对Spring事务不生效的问题,也是在跳槽面试中被问的比较频繁的一个问题。 点击上方卡片关注我 今天,我们就一起梳理下有哪些场景会导致Sp…

Android 自定义数字键盘实现教程

在 Android 应用中,系统默认的键盘可能无法满足特定需求(如仅支持数字输入、自定义布局等)。本文将详细介绍如何实现一个自定义数字键盘,并提供完整的代码示例。 实现步骤 1. 创建自定义键盘布局 首先,我们需要定义一…

爬虫逆向:Hook 技术原理与实战

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 1. Hook 技术概述1.1 Hook 技术作用1.2 Hook 技术在爬虫逆向中的应用场景1.3 常用工具与库1.4 hook实施步骤2. Hook 技术原理2.1 函数拦截2.2 代码注入3. Hook 技术实战3.1 工具准备3.2 实战步骤4. 实战案例分析4.1 拦截…

Hive高级SQL技巧及实际应用场景

Hive高级SQL技巧及实际应用场景 引言 Apache Hive 是一个建立在Hadoop之上的数据仓库基础设施,它提供了一个用于查询和管理分布式存储中的大型数据集的机制。通过使用类似于SQL(称为HiveQL)的语言,Hive使得数据分析变得更加简单…

编程语言的几种常见的分类方法

一、 按照编程范式分类 命令式编程语言 强调通过语句来改变程序状态,如 C、Pascal、Fortran 等。 面向对象编程语言 基于对象和类的概念,支持封装、继承和多态,如 Java、C、Python、Ruby 等。 函数式编程语言 注重不可变性和纯函数&#xf…

用python代码将excel中的数据批量写入Json中的某个字段,生成新的Json文件

需求 需求: 1.将execl文件中的A列赋值给json中的TrackId,B列赋值给json中的OId 要求 execl的每一行,对应json中的每一个OId json 如下: {"List": [{"BatchNumber": "181-{{var}}",// "Bat…

使用爬虫获取自定义API操作API接口

1. 引言 在现代Web开发中,API(应用程序接口)是前后端通信的桥梁。通过API,前端可以从后端获取数据,进行各种操作。而爬虫是一种自动化工具,用于从网站上提取数据。本文将详细介绍如何使用爬虫获取自定义AP…

ADB报错:daemon not running...

ADB报错:daemon not running… 解决步骤: ADB【问题】程序报错:daemon not running; starting now at tcp:5037 【原因】5037端口被占用 【方法】找出5037端口占用的应用,关闭掉该应用进程 【解决方案】打开cmd命令窗口,首先找出占…