当前位置: 首页 > article >正文

游戏引擎学习第154天

仓库:https://gitee.com/mrxiao_com/2d_game_3

回顾和今天的计划

我们的目标是完成一款游戏的开发。我们不会使用引擎或外部库,一切都是我们自己动手,从头开始编写代码。

在之前的工作中,我们完成了资产文件的处理部分。具体来说,我们将所有游戏的资产打包到资产文件中,这些文件可以作为整体单位进行分发。比如,如果想要发布一个游戏更新,里面添加了大量新的内容,我们可以通过更新可执行文件和新的资产包文件来实现;或者如果有人制作了游戏的mod,他们也可以将所有的资产打包成一个资产文件进行发布。

现在我们需要做的工作是,创建一种方法,使得游戏能够自动识别硬盘上有哪些资产文件。之前在测试时,我们将所有资产文件的路径硬编码到代码里,只加载了三个测试文件。现在我们希望将其改为平台层能自动列出所有游戏需要的资产文件,这样游戏就能灵活地加载所有文件,而无需手动指定。

具体而言,我们希望在平台层实现一种方法,能够遍历目录中的所有资产文件,并将这些文件传递给游戏。这样,我们就能更灵活地处理资产文件。如果我们在目录中放入了十个资产文件,程序能够自动识别并加载这十个文件,而不是手动指定。这个方法能使我们的游戏系统更加通用和灵活。

今天的工作就是回到平台层,改进这个功能,确保它能够满足游戏的需求。我们已经有一个初步的AI实现,但那只是第一版。接下来,我们将实现Windows平台下的版本,看看是否能够满足预期的功能,或者是否需要进行一些调整。

相关代码回顾

我所做的是先写了一些基本的代码框架。具体来说,当游戏启动并需要检查可用的资产时,平台层的代码会执行以下操作:首先,它会打开一个叫做“文件组”的结构,这个结构包含了某种类型的所有文件。在代码中,我们使用了一个方法“获取所有指定类型的文件”,然后它开始遍历这些文件,对它们进行处理。最后,当所有文件都处理完之后,程序会关闭文件,并进行必要的清理工作。

我之所以这样设计,是因为在我以往的编程经验中,迭代过程通常会占用一些资源。例如,操作系统可能会分配一些文件句柄等,这些资源在不再使用时需要关闭。因此,我在代码中预留了一个结束部分,用于清理这些资源。这样做是为了确保每次迭代后,能够正确释放已分配的内存或关闭打开的文件句柄,以免资源泄漏。

接下来,我们将进一步验证这一设计,看看是否符合预期。
在这里插入图片描述

在这里插入图片描述

去除硬编码的文件名

我们最初为了测试代码,采用了一种硬编码的方法。具体来说,我们并没有实际去检查驱动器中有哪些资源文件,而是直接返回了固定的值“3”。然后,我们观察代码在尝试加载文件时访问的索引,并人为地提供正确的文件名,以确保加载过程符合预期。

现在,我们的目标是将这个硬编码的实现转换为一种动态的方式,使其能够自动枚举目录中的所有相关文件,并将它们传递给游戏进行处理。我们希望,无论目录中存在哪些符合要求的文件,它们都能被正确识别和加载,而不是仅仅依赖于手动指定的文件列表。

为了更好地理解当前的目录结构,我们可以执行一个命令来列出所有符合特定后缀的文件。例如,我们可以运行 dir *.hha(或类似的命令)来显示所有以特定扩展名(如 .hha)结尾的文件。在示例目录中,我们可以看到多个文件,如 test1.hhatest2.hha 等。而在目前的硬编码实现中,这些文件名是手动指定的。

因此,在平台相关的代码部分,我们需要模拟这种文件枚举的行为,使得代码可以动态地获取当前目录下的所有目标文件,而不再依赖于硬编码的列表。这样,我们的程序就能够在不同的环境中正确运行,无需手动更新代码来适配不同的文件集合。
在这里插入图片描述

在Windows上列出文件名(FindFirstFile)

在 Windows 上,我们有几种方式可以遍历文件,但其实方法并不多,主要的选择之一是 FindFirstFileFindFirstFileEx。其中,FindFirstFile 是较为基础的 API,兼容性较好,适用于大多数 Windows 版本,包括 Windows XP。而 FindFirstFileEx 提供了一些额外的选项,但对于我们的需求来说,并没有太大的必要使用它。

如果考虑到向后兼容性,比如要兼容 Windows XP 及以上的系统,那么 FindFirstFile 是一个较好的选择。虽然 Windows 还提供 FindFirstFileTransacted,但它的适用范围较新,不支持 Windows XP,因此不适合作为首选方案。此外,Windows 2000 及更早的版本几乎已经无人使用,因此我们可以忽略对这些系统的兼容性要求。

Windows API 的调用方式是,我们调用 FindFirstFile 时,它会返回一个查找句柄(Find Handle)。调用时需要传入 lpFileName,它是一个字符串,并且涉及 ASCII 与 Unicode(宽字符)的区分。在 Windows API 中,凡是涉及字符串参数的函数,通常有两种变体:

  • A 结尾的版本(如 FindFirstFileA),表示接收的是 ANSI(ASCII)编码的字符串。
  • W 结尾的版本(如 FindFirstFileW),表示接收的是 Unicode(UTF-16)编码的字符串。

对于我们要处理的资源文件而言,并不需要考虑 Unicode 兼容性,因此可以直接使用 FindFirstFileA,即 ANSI 版本的 API,而无需支持复杂的多语言文件名。我们只需确保资源文件的命名符合 ANSI 规范,这样在处理时就不会遇到编码问题。

调用 FindFirstFileA 之后,会返回一个 WIN32_FIND_DATA 结构体,它是一个输出参数(out parameter)。在调用时,我们需要提供一个 WIN32_FIND_DATA 结构体的地址,API 会将查找到的第一个符合条件的文件信息填充到该结构体中。此外,FindFirstFileA 还会返回一个查找句柄,这个句柄用于后续遍历文件。通过这个方式,我们可以获取目标目录下所有符合指定格式的文件,并逐一传递给游戏进行处理。
在这里插入图片描述

lpFileName 参数可以包括通配符

lpFileName 参数可以包含通配符,例如 *?,从而允许我们匹配多个文件。具体来说,? 代表任意单个字符,而 * 代表任意数量的字符。因此,我们可以向 FindFirstFile 传递一个文件名模式,例如 *.hha,这样就能够枚举当前目录下所有符合该模式的文件。

当调用 FindFirstFile 时,如果 lpFileName 包含完整路径,它可以在指定目录中查找文件;否则,它将在当前执行目录下查找。调用后,FindFirstFile 会返回第一个匹配的文件,并将其详细信息存储在 WIN32_FIND_DATA 结构体中。

WIN32_FIND_DATA 结构体包含了多个有用的信息,其中包括:

  • cFileName:文件名称
  • cAlternateFileName:8.3 格式(短文件名)的文件名称
  • dwFileAttributes:文件属性,如是否为只读
  • ftCreationTimeftLastAccessTimeftLastWriteTime:文件的时间戳
  • nFileSizeHighnFileSizeLow:文件大小

实际上,我们只需要 cFileName 即可,因为我们的目标只是获取符合指定模式的文件名,并传递给游戏进行加载。因此,整个过程相对简单,主要步骤如下:

  1. 调用 FindFirstFileA("*.hha", &findData),获取第一个符合条件的文件信息。
  2. 读取 findData.cFileName,获取文件名。
  3. 使用 FindNextFileA 遍历所有匹配的文件,直到所有符合条件的文件都被找到。
  4. 关闭查找句柄,释放资源。

通过这种方式,我们可以动态获取目录中的所有目标文件,而不再依赖于手动指定的文件列表。
在这里插入图片描述

列出剩余的文件(FindNextFile)

在获取第一个符合条件的文件之后,我们需要继续获取其余的文件。这可以通过 FindNextFile 来实现。

FindNextFile 的作用是继续查找下一个匹配的文件,并将其信息存储在 WIN32_FIND_DATA 结构体中。它的返回值是一个布尔值,如果返回 TRUE,说明找到了下一个文件;如果返回 FALSE,说明已经没有更多的匹配文件,或者发生了某种错误。

如果 FindNextFile 返回 FALSE,通常意味着以下两种情况之一:

  1. 没有更多的匹配文件,此时可以通过 GetLastError 检查是否返回 ERROR_NO_MORE_FILES,表明所有符合条件的文件已经遍历完毕。
  2. 发生了其他错误,例如驱动器故障或者目录无法访问。在这种情况下,遍历无法继续,必须终止文件查找过程。

从代码逻辑上看,遍历文件的流程如下:

  1. 调用 FindFirstFileA("*.hha", &findData),获取第一个匹配的文件。
  2. 进入循环,调用 FindNextFileA(hFind, &findData) 获取下一个文件,直到返回 FALSE
  3. 使用 GetLastError 确认是否是 ERROR_NO_MORE_FILES,如果是,则说明所有文件都已找到;否则,可能是遇到了错误,需要适当处理。
  4. 关闭查找句柄 FindClose(hFind),释放资源。

在设计层面,这种 API 设计方式存在一定的问题,例如:

  • FindFirstFileFindNextFile 依赖于全局状态,而不是返回一个包含所有文件名的列表,这使得 API 使用起来更加繁琐。
  • 需要手动管理查找句柄,稍有不慎可能会导致资源泄露。
  • 错误处理依赖 GetLastError,而不是更直观的返回值,增加了额外的代码复杂度。

这种设计方式与现代编程范式(如面向对象编程)相悖,因此通常被认为是一个较差的 API 设计案例。在完成基本功能实现之后,可以进一步探讨如何用更现代化的方法封装这一 API,使其更加易用和健壮。
在这里插入图片描述

防止无效句柄值

在调用 FindFirstFile 之后,我们需要先检查返回的查找句柄是否有效。这是因为 FindFirstFile 并不保证一定能找到符合条件的文件,例如:

  • 目标目录下可能根本没有符合条件的文件。
  • 游戏可能被安装错误,或者用户可能从错误的目录运行游戏。
  • 用户可能修改了快捷方式的工作目录,导致游戏无法在正确的位置查找文件。
  • 资源文件可能被移动、删除或损坏,导致查找失败。

如果 FindFirstFile 返回的句柄无效,我们可以通过检查它是否等于 INVALID_HANDLE_VALUE 来判断。如果句柄是 INVALID_HANDLE_VALUE,说明查找失败,此时应该终止遍历过程并进行错误处理。

一旦确认 FindFirstFile 返回了有效的句柄,我们需要进入一个循环来获取所有匹配的文件:

  1. 处理第一个匹配的文件FindFirstFile 返回的 WIN32_FIND_DATA 结构体包含第一个符合条件的文件的信息,我们可以直接使用它进行处理。
  2. 遍历剩余的文件:使用 FindNextFile 继续查找下一个文件,并更新 WIN32_FIND_DATA 结构体的数据。
  3. 检查 FindNextFile 返回值
    • 如果返回 TRUE,说明找到下一个文件,继续处理。
    • 如果返回 FALSE,说明没有更多匹配的文件,或者发生错误。可以调用 GetLastError 判断是否是 ERROR_NO_MORE_FILES,如果是,则说明文件遍历完成。
  4. 关闭查找句柄:遍历完成后,调用 FindClose 释放查找句柄,避免资源泄漏。

整个流程可以总结如下:

  • 调用 FindFirstFile,如果返回 INVALID_HANDLE_VALUE,则终止。
  • 处理 WIN32_FIND_DATA 结构体中的第一个文件信息。
  • 进入循环,调用 FindNextFile 获取剩余的匹配文件,直到所有文件都处理完毕。
  • 关闭 FindClose,释放查找句柄。

通过这种方式,我们可以确保无论资源文件的数量是多少,都能够正确地获取并处理所有符合条件的文件,并避免潜在的错误情况。
在这里插入图片描述

调用 FindClose 来关闭由 FindFirstFile 返回的文件句柄

在使用 FindFirstFileFindNextFile 进行文件遍历时,还有一个额外的注意事项,即在查找结束后需要调用 FindClose 关闭查找句柄,以释放系统资源。

关于 FindClose 的使用

  • 只有在 FindFirstFile 返回了有效的句柄时,才需要调用 FindClose 进行清理。
  • 如果 FindFirstFile 失败并返回 INVALID_HANDLE_VALUE,则不应该调用 FindClose,因为此时并没有成功打开查找句柄。
  • FindClose 只能用于关闭 FindFirstFile 返回的查找句柄,而不能用于关闭其他类型的句柄(例如 CloseHandle 不能用于 FindFirstFile 生成的句柄)。

从官方文档的 FindFirstFile 说明部分来看,明确指出:

“当不再需要搜索句柄时,使用 FindClose 关闭它,而不是 CloseHandle。”

这一点表明,FindClose 仅适用于有效的查找句柄。如果 FindFirstFile 失败返回 INVALID_HANDLE_VALUE,则 FindClose 没有任何可以关闭的内容,因此调用它没有意义。

此外,如果错误地对 INVALID_HANDLE_VALUE 调用 FindClose,可能会导致未定义行为或错误,因此代码逻辑应该确保只有在 FindFirstFile 成功返回有效句柄时,才调用 FindClose

总结查找流程

  1. 调用 FindFirstFile 获取第一个匹配的文件,并检查返回的句柄是否有效。
  2. 如果句柄无效(INVALID_HANDLE_VALUE),则直接终止,无需调用 FindClose
  3. 进入循环,使用 FindNextFile 获取剩余的匹配文件。
  4. FindNextFile 返回 FALSE,说明文件遍历完成,退出循环。
  5. 只有在 FindFirstFile 返回了有效的句柄时,才调用 FindClose 关闭它,以释放系统资源。

这样可以确保文件查找的完整性,同时避免不必要的错误或资源泄漏。
在这里插入图片描述

使文件循环结构更加统一

在实现 FindFirstFileFindNextFile 进行文件遍历时,为了优化代码结构,使文件处理逻辑更加统一,可以将文件处理的代码集中在一个循环内,而不是在 FindFirstFileFindNextFile 处分别处理文件。

原始问题

  • FindFirstFile 返回第一个文件,需要处理它。
  • FindNextFile 在循环中继续获取后续文件,也需要处理它们。
  • 这样就导致文件处理逻辑存在两个地方,不够清晰,也容易出错。

优化后的结构

  • 先调用 FindFirstFile,如果返回有效的句柄,则进入 while 循环。
  • while 循环中,统一进行文件处理逻辑。
  • 每次循环结束时调用 FindNextFile 获取下一个文件,并检查是否还有更多匹配的文件。
  • 如果 FindNextFile 返回 FALSE,表示没有更多文件,跳出循环。
  • 在循环结束后,检查 FindFirstFile 是否返回了有效句柄,如果是,则调用 FindClose 释放资源。

代码逻辑示意

const char *FileName = "*.hha";
HANDLE FindHandle = FindFirstFileA(FileName, &FindData);
while (FindHandle != INVALID_HANDLE_VALUE) {
    {
    }
    if (!FindNextFile(FindHandle, &FindData)) {
        break;
    }
}
if (FindHandle != INVALID_HANDLE_VALUE) {
    FindClose(FindHandle);
}

优化点

  1. 只有一个循环:避免在 FindFirstFileFindNextFile 处分别写处理逻辑。
  2. 清晰的 while 结构:确保 FindFirstFile 发现的第一个文件和 FindNextFile 发现的后续文件都在同一块代码中处理。
  3. 减少冗余代码:避免重复判断文件是否有效,提高代码可读性和可维护性。
  4. 资源管理更清晰:只有 FindFirstFile 返回有效句柄时,才会调用 FindClose 释放资源,避免错误调用。

这种优化后的结构,使代码逻辑更清晰,减少了潜在的错误,提高了代码的可读性和可维护性。

在这里插入图片描述

(旁白)教材式糟糕的API设计

在遍历目录文件时,FindFirstFileFindNextFile 的 API 设计存在诸多不合理之处,使得编写代码变得繁琐且不直观。理想情况下,开发者希望能够以简洁直观的方式迭代匹配指定模式的文件,例如:

for(HANDLE FindHandle = BeginIteration(FindName);
    IterationIsValid(FindHandle);
    IterationAdvance(FindHandle)){
        process the file;
    }

然而,Windows API 并没有提供这样的直接支持,而是采用了一种不够直观且容易出错的方式:

  • FindFirstFile 用于获取第一个匹配的文件。
  • FindNextFile 用于获取后续的匹配文件。
  • 由于 FindFirstFileFindNextFile 机制不同,无法直接使用 forwhile 进行标准化迭代。
  • 需要手动调用 FindClose 关闭句柄,否则可能导致资源泄露。

问题分析

  1. API 设计不统一

    • FindFirstFileFindNextFile 采用了完全不同的方式获取文件列表,导致循环结构异常复杂。
    • 理想情况下,应该有一个统一的 FindNextFile 方式,不管是第一个文件还是后续文件,都用相同的 API 调用方式来获取。
  2. 需要手动管理句柄

    • API 要求手动调用 FindClose 释放句柄,即使 FindFirstFile 没有找到文件,也不能直接跳过,而必须检查 INVALID_HANDLE_VALUE,否则可能导致资源泄露。
    • 在循环结束后,仍需再一次检查句柄的有效性,避免在无效句柄上调用 FindClose
  3. 难以编写优雅的代码

    • 由于 API 设计的问题,标准的 forwhile 语句难以直接使用,而不得不采用 do-while 结构,增加了代码复杂度。
    • 代码需要手动管理多个 API 调用,并在不同阶段进行多次错误检查,使得代码结构变得混乱且不够直观。

优化后的结构

尽管 API 设计不合理,但仍然可以通过代码优化,使其逻辑更清晰:

const char *FileName = "*.hha";
HANDLE FindHandle = FindFirstFileA(FileName, &FindData);
while (FindHandle != INVALID_HANDLE_VALUE) {
    {
    }
    if (!FindNextFile(FindHandle, &FindData)) {
        break;
    }
}
if (FindHandle != INVALID_HANDLE_VALUE) {
    FindClose(FindHandle);
}

优化点

  1. 使用 while 结构

    • 这样可以确保 FindFirstFileFindNextFile 的逻辑被整合到同一个循环中,避免重复代码。
  2. 确保 FindClose 只在句柄有效时调用

    • FindClose 仅在 FindFirstFile 成功返回句柄时调用,避免错误地在无效句柄上调用 FindClose
  3. 减少逻辑分支,简化代码

    • 通过 while 结构,消除了 FindFirstFileFindNextFile 处理逻辑的冗余,使代码更加直观和清晰。

总结

Windows API 的 FindFirstFileFindNextFile 设计存在明显缺陷,使得遍历文件的代码难以编写得简洁易读。

  • 核心问题:API 结构不统一,导致遍历代码难以使用常规的 forwhile 结构。
  • 解决方案:通过 while 结构优化代码,使 FindFirstFileFindNextFile 的逻辑统一,并确保资源正确释放。

尽管 API 设计较为繁琐,但合理的代码结构可以在一定程度上减轻这一问题,提高可读性和可维护性。

问题:我们在遍历文件之前不知道有多少文件

在处理文件查找时,我们面临一个问题:如果需要为查找到的文件分配空间,我们无法提前知道有多少个文件。也就是说,无法确定会找到多少个符合条件的文件,因为在查找过程中,文件数量可能会发生变化,例如,可能会有文件在迭代期间被复制或者移动。

具体来说,问题是这样的:

  1. 由于文件的数量是动态的,因此无法知道在迭代过程中会找到多少个文件。这使得在为这些文件分配空间时变得困难。
  2. 可能会有文件在迭代过程中被复制或者移除。例如,如果在迭代和下一次迭代之间,有人向目录中添加了一个文件,或者删除了一个文件,这些都可能影响文件列表的准确性。
  3. 解决方法之一是,通过某种方式大致估算文件数量。例如,可以通过初步的扫描或者设置一个预估值来分配空间。但这种方法并不完美,因为在文件复制或删除时,无法保证精确找到所有的文件。
  4. 此外,也可以认为这些估算值本身就是不准确的。如果文件在迭代过程中被改变(例如移动或复制),也许某些文件无法被找到,但这对程序的整体功能影响不大,因此可以忽略不计。

综上所述,尽管这个问题看起来很棘手,但我们可以通过一定的策略来应对。例如,可以选择在迭代过程中不完全依赖准确的文件数量,而是以一种大致的方式进行处理,接受文件可能会被遗漏的事实。

在这里插入图片描述

修订文件API。没有必要对文件进行随机访问,顺序访问就足够,并且更容易实现

在处理文件访问时,存在一个问题,即代码中提到的“随机访问”功能。我们提到通过调用 FindFirstFileFindNextFile 来查找文件,这实际上涉及到文件索引的操作,意味着代码可能会根据文件的顺序或其他条件进行“随机”访问。

然而,这并不是一个必要的功能,因为代码并不需要随意跳跃到任意的文件位置。实际的需求是按顺序加载文件,而不是随机访问。因为这些文件只需要按顺序逐个处理,并不需要跳跃或随机选择特定文件,所以随机访问功能显得有些多余。

为了解决这个问题,可以考虑将当前的代码结构修改为一个更简单的顺序处理方式。例如,定义一个 OpenNextFile 函数,每次调用时打开列表中的下一个文件,而不需要使用复杂的随机访问机制。这样,代码只会按照顺序处理文件,更加简洁高效。

总的来说,当前的设计可能让文件访问显得过于复杂,并且引入了不必要的“随机访问”机制,而实际的需求只是按顺序处理文件,因此可以简化为逐个打开下一个文件的处理方式。

预先遍历文件

为了避免不必要的随机访问,可以采取一种相对简洁的做法,按顺序处理文件。在这种方法中,我们首先使用 FindFirstFile 函数获取第一个文件并初始化文件数据。接下来,我们通过迭代每一个文件,假设文件的初始位置是零,并在每次找到文件时增加一个计数器。这样,最终我们就能知道一共有多少个文件。

具体来说,步骤如下:

  1. 首先调用 FindFirstFile,获取第一个文件并初始化文件数据。
  2. 然后进入一个循环,通过 FindNextFile 继续迭代每个文件。在每次迭代时,简单地增加文件计数器。
  3. 当迭代结束时,文件计数器会告诉我们文件的数量。
  4. 最后,我们重新调用 FindFirstFile 来再次打开文件进行处理,这样我们可以再次访问文件并根据需要进行操作。

通过这种方法,文件的处理过程更加简洁,并避免了随机访问的复杂性。我们通过简单的顺序迭代来完成任务,确保代码更清晰且易于维护。
在这里插入图片描述

为 win32 的平台文件组结构分配空间

为了避免使用星号数据和平台文件组,我们需要为保存的数据分配一些空间。这些数据是由平台处理的,用于存储一些相关信息。为了实现这一点,我们需要为该数据分配空间,并确保其存在。

具体操作是这样的:为了处理文件组,我们将为文件组分配空间,这个空间将包含需要处理的相关数据。我们通过分配与平台相关的内存,为该平台特定的处理逻辑提供存储空间。这个空间包含了用于 FindFirstFileFindNextFile 等操作的文件句柄和数据结构。

这样做的目的是为了能够按照平台的需求有效地存储和处理文件信息,确保文件的查找和后续操作可以顺利进行。
在这里插入图片描述

讨论可扩展的平台无关类型。这种“为动态联合节省空间”的方法可以通过C++继承来实现,但我们会遇到一些其他类型无法实现的情况

在这个过程中,创建了一个平台无关的类型,供应用程序使用,并且为将来扩展留出了简单的方法。最初的想法是通过某种方式使这个平台无关的类型具有扩展性,尽管可能没必要这样做。这一结构也可以通过指针来简化,因为不一定非要将结构体直接传递,可以返回指针,处理起来会更加方便。

接下来,决定让平台的文件组处理方式变得对称,这样就能够更加简洁地处理数据。通过返回指针,代替直接传递数据结构本身,同时去掉了文件索引。这种方式让代码更简洁,也符合更加简洁的设计原则。

在这里,主要使用的是 C++ 中类继承的最简单形式,类似于为动态联合体节省空间。C++ 的类继承功能虽然较为简单,但也能很好地表示某些情况下所需要的类型结构,尤其是当一个结构体的顶部是相同的,而底部在不同平台上有所不同时。这种方法实际上可以用来表示多平台的文件句柄结构,尽管在这里使用继承并没有特别的必要,但它提供了一种结构上的便利,尤其是在需要节省内存空间时。

最终,这样的设计模式是通过利用类结构来避免浪费空间,并能在不同的平台上复用结构体的公共部分,同时避免在每个平台上都重复定义。
在这里插入图片描述

在这里插入图片描述

释放平台文件组

在这个过程中,目标是正确地管理文件的内存分配和释放。首先,创建了一个平台文件组,并为其分配内存。当文件组不再需要时,需要释放分配的内存。这与游戏中的其他文件不同,游戏文件会一直保持打开状态,因为游戏需要持续读取这些文件。因此,虽然文件处理时需要分配内存,但是游戏中的文件句柄不需要关闭或释放。

在具体实现时,首先需要为文件组分配内存,然后返回该文件组。当需要释放时,可以通过虚拟内存的释放函数 VirtualFree 来处理,确保不再使用分配的内存。如果内存不再需要,调用 VirtualFree 进行释放,避免内存泄漏。

此外,在文件处理过程中,检查文件句柄是否有效。如果有效,则需要关闭文件句柄,以确保资源被释放。文件处理过程中还会通过一些逻辑迭代来计算和跟踪有多少个文件,确保我们能够正确地管理内存和资源的分配。

最后,整体的设计是让平台代码在处理文件时尽量不暴露平台特定的细节。例如,通过对文件组进行类型转换和内存管理,确保平台代码尽可能保持通用和简洁。
在这里插入图片描述

第二次遍历加载文件,现在我们知道有多少文件

在文件处理过程中,首先需要进行两次文件遍历。第一次遍历的目的是为了统计文件的数量,这样应用程序才能为这些文件分配足够的空间。第一次遍历完成后,应用程序会知道大约有多少个文件,然后可以为这些文件进行适当的空间分配。

第二次遍历的目的是实际打开并处理这些文件。文件的打开过程使用的是 FindFirstFileA 来查找文件,之后会使用 FindNextFile 来获取下一个文件的数据。在每次文件打开时,都会通过文件句柄来加载文件数据。为了确保文件的正确处理,遍历过程中需要检查文件句柄是否有效。如果文件句柄无效,表示没有更多文件可供处理。

在文件打开之后,如果没有更多文件需要处理,应该关闭文件句柄,并确保每次迭代都能正确判断是否需要继续处理其他文件。为了防止下一次迭代重复打开同一个文件,需要在每次迭代结束时,检查是否能够成功找到下一个文件。如果无法找到下一个文件(即 FindNextFile 返回 false),则需要关闭文件句柄,并将文件句柄设置为无效,表示迭代结束。

同时,在文件处理过程中,文件名是动态获取的,可以通过文件迭代数据中的 FileName 来获得实际的文件名。这确保了文件名能够在运行时正确地被传递和使用,而无需依赖预设的文件路径或名称。

为了简化文件操作的接口,可以将文件操作的函数命名为 OpenNextFile,这样在调用时能够更加明确地表明正在进行的是“打开下一个文件”的操作,避免混淆。此函数会处理文件的打开和文件数据的读取。

总结来说,这个过程包括了两次文件遍历:第一次用于统计文件数量和分配内存,第二次用于实际打开文件并处理数据。每次迭代时都要确保文件句柄有效,不能重复打开文件,处理完成后要释放资源。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

选择系统调用和结构的ANSI版本

关于 FindFirstFile 的数据结构,假设有两种版本:一个是针对宽字符的版本(Unicode),另一个是针对非宽字符的版本(ANSI)。这两者的区别主要在于字符的编码方式。为了确保程序在不同字符集的系统上都能正常编译,需要特别注意使用正确的版本。

在检查Windows SDK时,发现结构体 WIN32_FIND_DATA 有多个版本,例如 WIN32_FIND_DATAA(用于ANSI)和 WIN32_FIND_DATAW(用于Unicode)。通常,Windows会提供 FindFirstFileFindNextFile 等函数,它们会使用相应的结构体进行文件操作。为了支持不同的字符集,可以显式选择使用ANSI版或Unicode版。

同时,FindNextFile 也需要相应的版本进行处理,而 FindClose 并不返回任何文本数据,因此它不会受到字符集的影响。这些细节需要确保在程序中正确地处理。

总体而言,通过仔细选择合适的结构体和函数版本,能够保证代码在不同字符集的环境中都能正常工作。如果有错误,其他开发者可能会在论坛中指出并进行修正。

获取文件名

现在我们需要做的就是获取文件名。实际上,获取文件名是非常简单的,只需要通过文件组中的 WIN32_FIND_DATA 结构体的 cFileName 字段,就能够得到所需的 ASCII 文件名。这样就能直接得到我们需要的文件名数据。
在这里插入图片描述

测试目前为止的代码

首先,我们进行了一些操作来初始化 WIN32_FIND_DATA 结构体,并通过 VirtualAlloc 分配内存。接着,我们通过 FindFirstFile 函数来执行第一次文件查找,这一步主要是为了统计文件的数量,而不是实际迭代。我们在这个过程中依次发现了 test1.hha, test2.hhatest3.hha 文件,并计算出一共有三个文件。

完成文件计数后,我们调用 FindClose 关闭了文件句柄。接下来,我们通过相同的方式开始实际的文件迭代。在这个过程中,我们将文件信息传递给平台独立层,平台独立层通过 FindFirstFile 获取文件句柄,然后通过 FindNextFile 获取下一个文件。每次我们会加载该文件,进行相关处理并尝试加载文件内容,加载成功后继续迭代下一个文件。

在迭代过程中,所有操作都按照预期执行,文件加载成功,迭代继续。当我们尝试访问下一个文件时,如果没有更多文件,FindNextFile 返回 false,此时我们关闭文件句柄,并将其设置为无效句柄值,退出循环。

最后,如果应用提前停止迭代,我们需要手动关闭文件句柄。无论如何,当迭代完成时,我们会释放 WIN32_FIND_DATA 所占的内存,确保不再占用操作系统的资源。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

参数化文件扩展名

现在,我们已经实现了按照指定目录中的文件数量来正确查找和处理文件,这正是我们所期望的。然而,目前的实现没有支持自定义文件扩展名,因为通配符是硬编码的。

接下来,我们希望让程序支持传入自定义的扩展名,因此我们需要稍微修改代码,使得通配符部分能够接受动态的扩展名输入。具体来说,我们希望做以下几点:

  1. 我们将通配符(wildcard)初始化为一个缓冲区,并且设置它以某个基础字符串开头。
  2. 接着,我们会将传入的扩展名追加到这个通配符字符串的末尾。
  3. 这个过程很简单,我们使用一个 for 循环,将传入的扩展名复制到通配符字符串的末尾,确保新的通配符能够正确地包含扩展名部分。

通过这样,我们能够将扩展名与原本的通配符字符串结合在一起,完成文件查找时支持自定义扩展名的需求。

如果你经常处理字符串,写一个自己的字符串系统

通常来说,处理字符串这类工作如果不是很多,我一般会手动编写这些代码。但是如果项目中有大量的字符串处理需求,那么就应该考虑编写一些字符串工具类,而不是每次都手动写这些代码。编写一个自己的字符串库并不难,也不会花费太长时间。如果项目中字符串处理的次数较多,最好停止每次重复编写,直接使用一个专门的字符串工具库,这样会更加高效。

如果需要处理的字符串不多,比如这里只有四五种操作,那么手动编写这些代码是可以接受的,但如果发现自己频繁编写类似的代码,那就应该考虑编写一个字符串处理库,这样以后就能更加方便地使用了。

例如,在处理字符串时,首先可以定义一个指向目标类型的指针,然后通过一个循环遍历字符串,直到遇到终止符('\0')。每次检查当前字符,如果不是终止符,就继续复制,直到遇到终止符。这种方式可以确保字符串总是以空字符结尾。

总之,虽然现在只是做了一些简单的操作,但在以后遇到更多的字符串操作时,可以考虑创建一个自己的字符串工具类库,这样能减少代码重复,提高开发效率。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

文件API的可能扩展

在这个过程中,我们已经完成了大部分工作,尤其是关于资产文件格式的部分。我们处理了扩展名相关的问题,并且我们也讨论了如何移除资产的功能。另外,我们提到了资产文件中关于优先级的概念,比如说哪个文件优先于其他文件的问题。

接下来,我们可能会加入一些更高级的功能,或者进一步完善现有的功能。比如说,支持更多复杂的功能,或者是让我们能够识别和处理更复杂的情况。虽然目前这些功能的需求不大,因为我们可以随时重新处理资产文件,也没有必要在游戏正式发布之前将这些文件锁定。然而,既然我们已经做了这部分工作,花上几天时间回顾和考虑是否还可以增加一些功能是值得的。

所以,也许我们可以在接下来的时间里,重新审视这部分工作,看看是否有什么地方可以改进,或者是否有额外的功能可以加入,确保我们没有在某些方面做出局限,影响到后续的开发。

保存文件是否现在需要使用这个系统?

关于保存文件是否需要使用现有的系统,目前的想法是保存文件可能不需要使用这个系统。事实上,保存文件通常不会像资产文件一样需要按部分加载。因此,保存文件的处理方式可能会有所不同。

目前的实现更多是针对资产加载的,因为资产加载通常需要按需加载不同的文件部分,而保存文件通常会一次性加载整个文件。由于保存文件一般是一次性加载完成的,所以不一定需要像资产加载那样处理。

虽然目前还没有处理保存状态的功能,但理论上如果需要,也可以利用现有的文件获取系统来处理保存文件。我们可以通过调用“获取所有指定类型的文件”来得到所有保存文件,然后直接打开这些文件。虽然这个想法是可行的,但目前还没有决定是否需要这么做,或者是否有必要按照资产加载的方式去处理保存文件。

完成这项工作后,你现在感觉如何?

完成这项工作后,感觉非常不错。感觉很高兴能完成一些事情,特别是资产系统的部分,效果非常好。尤其是标签查询系统,它的表现非常出色。尽管我们花费的时间并不多.
这个系统包括了标签库功能,还能够将资产文件合并。整体来说,这个系统的功能非常强大,要比很多游戏中随附的资产系统要好得多。很多游戏使用的资产系统甚至还不如这个系统,而我们只用了这么少的时间就做到了这一点,所以感觉非常棒。
当然,未来可能还需要做一些补充和修改,但目前为止,这个系统的效果已经非常不错,非常酷。

坚持认为 STL 就是语言,因此你应该使用 STL 中的“字符串”

关于 STL(标准模板库)和其中的字符串类,强烈建议不要使用 STL。事实上,STL 是一个非常糟糕的设计,尤其是它的字符串类(std::string)是最糟糕的一部分。很多资深程序员都会建议避免使用 STL,这不是我个人的偏见,而是长期以来的共识。

例如,早在 1998 年,我曾与其他程序员一起工作,那时我们使用 SCM 工具。编译速度非常慢,花费了很多时间。我调查之后发现,问题的根源在于使用了 STL 的 std::string 类。只要我们将 std::string 替换为我自己设计的一个简易字符串类,编译时间就大大缩短了。编译时间从十分钟减少到不到一分钟,简直让人难以置信。

STL 的设计非常糟糕,使用其中的任何一个组件,都会引入大量的模板,而模板会极大地拖慢编译速度。即使你只想用一个简单的字符指针(char*),STL 也会引入大量不必要的模板代码,导致编译器必须花费更多时间去处理这些模板,然后这些被处理的代码会填充到 C 文件中,最终导致链接时的瓶颈。

因此,绝对不要在生产代码中使用 STL。如果只是做实验或者学习,可以随便使用,但在真正的项目中,尤其是需要高效运行的代码中,使用 STL 是一个非常糟糕的选择,特别是不要使用其中的字符串类。

你能详细解释一下你提到的联合继承方式吗?同样,实体最终会成为联合体吗?

关于游戏中的实体系统,当前的设计非常独特,目的是为了展示如何处理一些困难的编程问题。游戏的实体设计并不像传统游戏那样简单地定义几种实体类型(如怪物、Blob 等),这在某种程度上是“基础”的设计。我故意选择了让设计变得非常复杂,目的是让大家面对实际的、困难的编程挑战,并通过这些挑战学到如何应对这些问题。

目前的设计并不是基于传统的“联合体”(union)继承,也不是完全不是联合体的设计,而是介于两者之间的“半联合体”式的设计。这种设计非常独特,因此目前还没有明确知道它是否会成功。如果它行不通,那么我们会尝试其他方案。关键是,这一系列的目的是让大家看到如何解决难题,而不仅仅是展示我已经知道的解决方案。

这个系列的目标不是让我教你们如何成为和我一样的程序员,而是让我展示如何解决那些我自己也不完全知道如何解决的问题。通过这个过程,大家将学会如何应对自己在游戏开发中遇到的独特问题。每个游戏都有独特的挑战,而如果我只教你们如何解决我已经知道的常见问题,那就没什么实际意义了。

因此,关于实体系统的设计,接下来会有很多挑战。在这一过程中,我可能会尝试多种不同的架构,并可能会失败很多次,但这正是解决难题的过程。通过这些实验,最终我们会找到解决方案,并且可以通过这个过程学习如何处理那些我们事先不知道答案的问题。这将是一个非常好的实践例子,教大家如何在面对未知时解决复杂的编程问题。

如果要更新资产文件,添加更多文件怎么办?你如何管理大量资产,知道每个资产的位置?

关于更新资产文件和管理大量资产的问题,当前的方式并不需要关心资产存放的位置。所有的操作都是基于标签查询的系统。举个例子,当你需要一个二十岁、有棕色头发的英雄角色时,只需要通过标签查询系统提出请求。如果存在符合条件的资产,系统就会返回它。如果没有完全符合的资产,系统会返回一个最接近的,比如一个有金色头发的角色。这是因为系统不要求在请求时明确知道资产的来源或者位置。

因此,游戏中的资产加载并不依赖于知道文件在哪里存放,只需通过标签来进行查询,系统会自动处理返回最符合要求的资产。所以,游戏不需要关心资产是从哪个具体文件或位置加载的,这种方式极大地简化了对资产的管理。

如果完整文件路径中包含Unicode字符,FindFirstFile的ASCII版本是否仍然有效?

目前的方式可以支持路径中包含Unicode字符,因为我们并不需要显式地传递路径给文件查找函数。文件查找功能是基于当前工作目录进行的,因此它不关心路径中是否包含Unicode字符。这种方式下,路径和Unicode字符不会影响功能。

然而,如果你采用另一种方式,需要显式地传递路径,那么可能会遇到问题。因为在这种情况下,路径中如果包含Unicode字符,可能就需要特别处理。针对这种情况,建议改为使用findnextfilew函数,它更好地支持Unicode路径。

预渲染的字体会包含到HHA文件中吗?

是的,预渲染的文件会被包含在aj文件中。这意味着所有的预渲染数据,包括相关的资源和文件,都将打包在aj文件内,以便在需要时能够方便地加载和使用。

这是新的《使命召唤》吗?

  1. 游戏版本:这款游戏被称为《使命召唤12》,并且游戏还在开发中,特别是引擎技术方面尚未最终完成。

  2. 角色扫描:提到了一个角色的面部扫描,尤其是一个使用了 “凯文·史派西” 的数字扫描模型。尽管扫描的模型在某些方面已经接近真实,但还是有些细节(例如脸部)显得不完全逼真。

  3. 后续修改:游戏开发团队计划通过再次扫描凯文·史派西的面部,确保最终角色的面部和真实人物更加一致。这个后续扫描将会在合同中进行,并且不需要额外支付高额费用。

  4. 技术完善:提到在引擎技术完成之后,角色的表现和外观将会更为真实和准确。

总体而言,这段话重点讨论了游戏开发中的技术细节,特别是如何通过数字化扫描技术进行角色建模,以及如何在开发过程中处理一些细节上的问题。

当你考虑到 FindFirstChangeNotification/FindNextChangeNotification/RefreshDirectory 是同一API的一部分时,FileClose() 的调用要求是否更有意义?

  1. 目录遍历API的要求find firstfind next这两个函数在目录遍历中的作用,以及它们是否有意义当考虑到find firstfind next文件变化通知和刷新目录时,要求调用find first的情况是有道理的,但实际上这些功能是在Windows XP之后才添加的。

  2. API功能的历史背景find first函数从Windows NT 5就已经存在,而目录变化通知功能大约是在2006年才被引入。这样,find first与目录变化通知并不是一开始就有的,它们是在后期版本中添加的。

  3. 对API设计的评价:虽然find first的调用看起来有些不必要,但它并不是最糟糕的部分,因此不认为它是一个问题。作者表达了对这一点的接受态度,认为它不是很严重的问题。

  4. 代码实现问题:实际上,find first函数允许传递一个无效的句柄而不会报错,这样的行为其实并不令人反感,尽管它并不是理想的做法。即使存在这种情况,代码仍然可以正常工作,这也让人觉得没有太大的问题。

  5. 关注点:更多的关注点,特别是关于实现中可能存在的两步验证版本的问题,以及无法实现清理资源的问题,认为这些问题比API设计本身更严重。

总体来说,这段内容探讨了文件遍历和目录变化通知API的设计历史,以及对API实现中某些行为(如无效句柄的处理)和设计决策的接受与反思。

关于在游戏中编写代码/其他Lineage游戏的评论是什么?Lineage 1几乎激发了我作为计算机科学家/程序员的整个职业生涯

我们曾为《Lineage Forever》编写过代码,但我们的贡献主要体现在提供技术支持和工具上,而不是直接参与游戏的开发过程。实际上,我们开发的代码被广泛应用于多个游戏项目中,因为我们曾为许多公司提供过游戏库和技术工具的授权。对于《Lineage Forever》,如果我们的名字出现在游戏的致谢名单上,那是因为我们编写了其中一部分技术,比如角色动画系统,这部分技术被他们授权并应用到游戏中。

然而,我们并未参与到游戏的创意设计、内容规划或是具体的游戏开发流程中,我们的作用仅限于提供一些技术支持。虽然我们曾有机会与《Lineage》的开发团队在韩国见面并交流过,但那只是一次短暂的交流,大家的英语沟通并不完全流利,也没有深入到游戏开发的具体细节。

从技术角度来看,我们开发的技术被许多其他游戏公司也使用,因此我们并不清楚自己开发的代码是否在某个特定的游戏中被使用。就像虚幻引擎一样,很多基于该引擎的游戏并没有与虚幻引擎的开发团队直接联系,开发团队只是提供了技术,游戏开发者使用这些技术来实现他们的游戏。

总的来说,虽然我们在技术层面上为《Lineage Forever》做出了一定贡献,但我们并未参与游戏的创意或内容开发,我们的工作更像是为游戏提供了实现所需的工具和技术。

资产文件加载之后是什么?

接下来要处理的内容是资产管理之后的虚拟内存管理。虚拟内存管理是一个复杂且具有挑战性的领域,尚未完全完成相关的虚拟内存管理单元。这个部分涉及到对内存的有效管理,确保系统能够正确处理大量数据的存储和访问,以便优化性能并防止内存溢出或崩溃。这个任务被认为是比较复杂的,需要深入的理解和精心设计。

为什么你使用 #define 来定义像 PLATFORM_GET_ALL_FILE_OF_TYPE_BEGIN 这样的东西,而不是使用普通的函数调用?

我们使用 #define 来定义像 platform_get_all_files_of_type 这样的函数,而不是直接用普通的函数调用,原因在于我们需要为这些函数创建类型定义。具体来说,我们不能仅仅通过普通的函数调用来实现这一点,至少需要先进行类型定义。

主要原因是我们需要将函数的指针存储起来。在平台层的实现中,像打开文件的操作(例如 OpenNextFile)需要使用指针,因为平台接口(或平台抽象层,Platform API)内部有一个调度表,存储了所有相关函数的指针。为了实现这个调度机制,我们需要用 typedef 定义这些函数的类型。

通过使用 #define 来定义函数指针的类型,我们能够让 typedef 使用这种宏定义,进而在实际的开发过程中,允许我们更容易地管理和修改函数类型。如果以后需要修改某个函数的实现,只需要在一个地方进行修改,就能够确保所有使用到该函数的地方都得到更新。

总结来说,使用 #define 来管理平台层的函数指针类型,能够让代码更加简洁且易于维护,尤其是在需要更改函数实现时,能在一个地方集中修改,避免了多处修改的麻烦。

有没有推荐的游戏编程或一般编程的书籍?

关于游戏编程或一般编程的书籍,实际上,现如今并不清楚有什么书籍值得推荐。自己在学习编程时,读过的书籍大多已经过时了,很多书的内容质量也不太好。例如,当时读过《C程序设计语言》这本书,这是一本经典的C语言学习书籍,至今看来,还是一本不错的书,用来学习C语言非常合适。

然而,关于C++的书籍,大多数质量并不高,自己在读那些书时并不清楚它们的质量问题。也曾读过一些如《设计模式》、《Effective C++》以及《LSD的书籍》等,这些书籍虽然在当时可能有影响,但现在回头看,很多书籍的内容并不好,甚至可以说是错误的,许多学习资料并没有真正传递出有效的信息。

很多当时读的书籍都可以说是无用的,可能只有《C语言程序设计》这本书值得一提。随着编程能力的提升,逐渐意识到那些书籍并没有太大帮助,反而浪费了时间。在编程水平有所提高后,自己开始更多关注一些具体的算法书籍,比如矩阵求解算法等,而不是一般的编程教材。

不过,也许现在仍然有一些优秀的编程书籍存在,但自己并不清楚具体有哪些。考虑到自己已经年纪不小,当时在学习编程时所接触到的书籍已经有些过时了,很多书籍也已经几十年历史。当时小时候读的书就是《C程序设计语言》,还读过一些如《深度工作》等书籍,但这些书籍当时也只是因为时下流行,而后来的学习中发现它们并不值得推荐,甚至内容质量不佳,根本不值得浪费时间去阅读。

你怎么看待 J. Gregory 的《游戏引擎架构》这本书?这本书看起来很棒

关于《游戏引擎架构》这本书,自己并没有读过,所以无法对它进行评价。虽然从书名和简介来看,它看起来是一本内容扎实的书,但由于没有阅读过,无法提供更多的看法或建议。如果有兴趣,可以查阅一些相关的书评或讨论,了解其他读过这本书的人是如何评价它的。

我的意思是,艺术家在制作文件时,你如何跟踪你添加了什么,什么没有添加?如何知道哪些资产已更改并需要更新?

在艺术家制作文件时,如何跟踪哪些资源已添加、哪些资源发生了变化并需要更新,这个问题涉及到资产管理和开发流程的问题。实际上,游戏的运行时包和开发管道是分开处理的,开发管道通常不是当前讨论的重点。大多数情况下,跟踪资产更改的工作是通过一个独立的系统来完成的,这个系统与游戏运行时并没有直接关系。它主要负责跟踪大量的与游戏开发无关的数据。

通常,资产管理系统是一个独立的基础设施,它需要跟踪和管理不同阶段的资源,并且该系统可能涉及到大量数据。某些团队可能会将这些内容合并,并在游戏发布时包含开发过程中使用的所有文件和资源,但这种做法并不推荐,因为它会导致游戏发布版本包含大量仅用于开发的无关资源。这种方法不太理想,因此在管理时应避免将这些东西混合在一起。

为了保持清晰和效率,通常会选择将运行时和开发管道的系统分开。例如,开发过程中使用的资源可以通过独立的管理系统来处理,确保游戏发布版本干净、精简,没有多余的开发数据。

对于某些项目,尤其是像“Game Hero”这样有较复杂开发需求的项目,确实需要使用这样的分开管理的系统。但对于目前正在开发的这个项目,由于它的资源需求和艺术工作相对简单,可能并不需要如此复杂的资产管理系统。这个项目的艺术资源相对较少,且更新频率也不高,所以没有必要建立完整的资产管理基础设施。对于较大的项目和较复杂的资源工作,使用更加精细化的管理系统会更加高效。

图形是双缓冲的吗?

关于图形是否使用双缓冲,实际上这个问题的回答取决于具体是指什么样的双缓冲技术。双缓冲有不同的定义和实现方式,因此在不同的上下文中,可能会有不同的理解。

在某些情况下,当前的图形渲染方式并不算真正意义上的双缓冲。在这种情况下,渲染是通过将图像合成到一个位图中完成的,然后再将这个位图复制到窗口上显示。这样做不会出现闪烁或撕裂现象,因为 Windows 的合成器会充当第二缓冲区,类似于双缓冲的效果。实际上,Windows 的实现方式可能使用的是三缓冲机制,而不是传统的双缓冲。这意味着虽然不能严格称为双缓冲,但也可以认为它实现了类似的效果。

另外,还有一种常见的双缓冲技术叫做“页面交换”(page flipping),这种方法并没有在当前的图形渲染中使用。因此,是否可以称之为双缓冲,取决于你对双缓冲的定义和期望。

总结来说,双缓冲的具体实现方式因平台和技术而异,这里使用的方式虽然不是传统意义上的双缓冲,但通过 Windows 的合成器机制,实现了类似双缓冲的效果。因此,是否算双缓冲,关键在于你所关注的缓冲技术和具体实现。

好吧,虽然人们可能知道如何破解加密,但在本系列中教授它并不一定是坏事

对于是否阻止玩家编辑保存文件的问题,实际上,这个问题是有争议的。虽然有些人可能认为,通过加密保存文件的内容可以防止玩家篡改进度,但这种做法可能会让玩家感到不被尊重,因为它给玩家带来了不必要的限制。加密的目的是为了防止作弊或修改存档,但这实际上会让玩家感到被对抗,因为他们购买游戏是为了享受其中的乐趣,而如果他们想要修改保存的游戏文件以获得不同的游戏体验,这本不该被阻止。

此外,从技术角度来看,这种加密措施其实是一场注定会失败的战斗。因为加密算法最终是运行在玩家的机器上的,任何有一定技术水平的玩家都可以通过逆向工程破解这个加密算法,一旦他们掌握了破解方法,就能轻松修改保存文件。因此,单纯依靠加密来保护保存文件并不是一个长期有效的解决方案。

如果真希望让玩家无法修改保存文件的内容,应该采取更好的方法,比如将游戏的保存进度转移到服务器端。这样,保存的游戏数据就存储在服务器上,玩家的设备无法直接访问这些数据,从而避免了本地修改的可能性。不过,这种做法可能会带来一定的反感,因为它可能会让玩家感觉他们失去了对游戏数据的控制,且需要依赖服务器。

总的来说,虽然加密保存文件看起来是一个保护游戏完整性的措施,但它可能会引起玩家的反感,因为他们希望能够自由地享受游戏,甚至修改自己的存档文件来满足个人需求。因此,这种做法虽然技术上可行,但可能不符合玩家的期望和游戏体验的初衷。

禁用Aero时会崩溃吗?

关于图形渲染的讨论,其中提到了双缓冲和垂直同步的实现以及如何处理显示效果。当前的图形渲染路径尚未使用最终的显示代码,主要还是通过简化的路径来进行显示。比如,图形渲染只是将图像直接合成到位图上,然后再复制到窗口,而不是通过 OpenGL 这样的标准路径进行渲染。这是为了让开发者不必学习 OpenGL 相关的基础代码,能更快速地进行开发。

至于是否会在 Aero 被禁用时出现撕裂现象,实际上很难确定,因为这依赖于 Windows 如何实现图像拉伸操作,尤其是在没有 OpenGL 支持时。由于不再使用 “stretch blit” 这样的技术,游戏的渲染方式可能会有所不同,尤其是当软件渲染模式下,依然会通过 OpenGL 来渲染图像,因为 OpenGL 是标准的、经过充分测试的路径,这样可以保证与驱动程序和硬件的最大兼容性,减少出现兼容性问题的风险。


http://www.kler.cn/a/584393.html

相关文章:

  • Hutool RedisDS:Java开发中的Redis极简集成与高阶应用
  • 云原生性能测试全解析:如何构建高效稳定的现代应用?
  • 一文了解JVM的垃圾回收
  • LLMs基础学习(二)涌现能力、Scaling Law 和 幻觉
  • Science Advances 视触觉传感机制的交互装置,可以实时测量来自手不同部位的分布力
  • 《苍穹外卖》SpringBoot后端开发项目核心知识点与常见问题整理(DAY1 to DAY3)
  • 2025年渗透测试面试题总结-阿里巴巴-阿里云安全 一面、三面(题目+回答)
  • 从零构建逻辑回归: sklearn 与自定义实现对比
  • HTML5(Web前端开发笔记第一期)
  • 【ubuntu】——wsl中使用windows中的adb
  • 《深度剖析:鸿蒙系统下智能NPC与游戏剧情的深度融合》
  • 第27周JavaSpringboot 前后端联调
  • 走进Java:类和对象的初步理解
  • latex问题汇总
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(二)
  • 【Linux学习笔记】Linux用户和文件权限的深度剖析
  • C++ 邻接矩阵(代码)
  • mapbox高阶,结合threejs(threebox)添加extrusion挤出几何体,并添加侧面窗户贴图和楼顶贴图,同时添加真实光照投影
  • ubuntu修改时区
  • idea更新git代码报错No Git Roots