游戏引擎学习第135天

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

回顾 game_asset.cpp 的创建

在开发过程中,不使用任何现成的游戏引擎或第三方库,而是直接基于 Windows 进行开发,因为 Windows 目前仍然是游戏的标准平台,因此首先在这个环境中进行教学。然而,代码是以高度可移植的方式编写的,社区的许多成员已经成功将其移植到 Linux 和 Mac 等系统上,甚至有人让它在 PlayStation VR 上运行,这一过程非常有趣。因此,在开发过程中始终保持代码的可移植性,以便最终能轻松将游戏移植到多个平台,这也是目标之一。

目前正在进行的是对游戏中资源(资产,assets)的扩展,包括如何存储资源、如何处理资源,以及如何对资源进行标记,以便最终能够创建一个资产包文件(pack file)。资产包文件的设计目标是让资源管理更加高效,使得能够批量导入资源,而不必手动逐个管理。在这一过程中,需要定义资源的组织方式、资源的查找机制,以及如何让整个资源系统更加灵活易用。

现在处于这一探索过程的后期阶段,预计到下周末之前,可以完成所有相关的探索工作,并开始对系统的细节进行最终的确定。目前仍然在做一些实验,因为尚未完全确定资源系统的所有需求,因此需要不断测试如何更高效地从资源系统中获取资源,以及如何管理资源,以确保初步设计是合理的。只有在确保合理之后,才会正式确定资产包文件的具体格式。

回顾昨天的进展,创建了一个新文件,虽然在 Game Hero 的开发过程中很少创建新文件,但为了更好地组织和管理资源相关的代码,决定新建一个文件来专门处理临时资源工作,以便更专注地优化资源系统,并将其转变为更加健壮的正式定义。目前一切运行良好,不过由于日常事务繁多,暂时不太记得昨天具体进行到哪里了。

回忆一下,昨天最后完成的工作是临时实现了一种方式,使得可以从指定的资产 ID 中获取第一个位图(bitmap)。这样可以测试资产表是否正常工作,测试结果表明它们运行得很好。因此,接下来的任务是进一步扩展这一概念。

目前有两个可能的方向:

  1. 处理数组(array)资源的管理
  2. 处理命名资源(named elements),即包含多个部分的复杂资源

首先,决定优先处理数组资源的管理。至于原因,暂时不太确定,但直觉上似乎可以用一种更直接的方式来处理这两种情况,而不需要分别进行特殊处理。因此,先从较简单的部分开始,也许能够找到一个通用的解决方案来处理所有类型的资源。

让数组化的资源通过资源系统

接下来将从数组化的资源管理入手,并让这些数组化的资源直接通过资产系统处理,而不是像现在这样单独提取出来。当前的实现方式是直接从资源数据中提取数组化的资源,而目标是让它们通过资产系统进行管理,以便更好地组织和优化资源加载流程。

为此,首先需要弄清楚如何实现这一点。回顾现有的资产类型,已经具备了管理多个资源的概念,因此理论上不需要对系统进行太多调整来支持多个资源的管理。只需要将这些资源正确地添加到资产表中,使其能够通过资产系统进行访问和管理。

目前的资源包括:

  • 资产 Grass(草地)
  • 资产 Tuft(草丛)
  • 资产 Stone(石头)

接下来,需要在资产系统的其他部分,将这些资源通过系统加载进来,而不是单独处理。可以开始尝试通过资产表将它们整合进系统,以确保所有资源都能统一通过资产系统进行访问和管理。
在这里插入图片描述

提供稳定的方式来定义 AlignXTopDownAlignY 以及位图对齐

在正式将数组化的资源管理集成到资产系统之前,有一个更基础的调整可以先进行,以便后续操作更加顺利。目前的代码仍然使用硬编码的方式来加载位图资源,依赖 switch 语句进行选择,而最终版本显然不希望继续使用这种方式。因此,下一步的目标是将这部分代码重构,使其更加稳定和可扩展。

当前的资源加载过程中,位图需要定义 AlignX(水平对齐)和 TopDownAlignY(垂直对齐),这些信息是必要的。为了让整个系统更加统一,需要替换掉这些固定的对齐方式,使用一个更通用的对齐方案。现有的代码存在一定的割裂性,比如有的资源对齐方式是 AlignPercentage = {0.5, 0.5},而有的资源使用其他方法进行对齐。之前这样做的原因是,默认希望位图的锚点在中心,而计算中心点的前提是需要先加载位图。但现在随着资产包系统的构建,这种方式需要调整。

新的资产包系统将允许在打包资源时提前计算并存储对齐信息,而不需要在加载时再进行额外计算。资产包的构建流程会包含一个资源处理管线,在打包时就可以预先定义 AlignPercentage,这样在运行时只需要直接读取这些预计算的对齐信息即可。

RenderGroup 相关代码中,位图数据实际需要的仅仅是 AlignPercentage,因此可以直接在资源加载时指定 AlignPercentage,彻底移除基于位图大小计算对齐的逻辑,避免额外的依赖关系。

为了完成这项调整,首先需要确定现有位图的对齐参数。最直接的方法是利用调试器,读取当前加载的资源的对齐值。由于某些位图的默认对齐已经是 {0.5, 0.5},因此重点需要关注的是那些使用特殊对齐方式的资源。

在进行调试时,需要先运行游戏,确保所有资源都被正确加载,然后再读取所有位图的对齐参数。这样可以确保所有数据都已经就绪,便于在后续代码调整时使用这些参数进行替换,从而完成 AlignXTopDownAlignY 的移除工作。
在这里插入图片描述

在这里插入图片描述

移除多余的资源类型

为了确保所有位图资源正确加载,还需要在最后额外调用 LoadAsset 来加载所有资源,避免遗漏。因此,需要为每个位图资源调用 LoadAsset,例如:Asset_TreeAsset_Rock、 和 Test 等。然而,检查代码时发现 TestBackground 资源已经不再使用,因此可以直接移除。此外,还发现 Asset_StairwellAsset_TestBackground 这两个资源似乎也未被使用,因此也可以清理掉。

最终,当前仍在使用的资源包括 AssetTreeAssetSword,其他的可能已经被移除或者未被引用。为了确保加载逻辑的正确性,需要确认 AssetSword 是否仍然在代码中引用。

在完成所有资源的加载后,可以查看它们的具体对齐参数。由于这些资源数量较少,可以直接逐个检查对齐值,并将其复制到代码中作为标准设定。这样,就可以在后续的资产管理系统中统一使用这些参数,确保对齐方式的一致性。
在这里插入图片描述

在这里插入图片描述

在调试器中读取位图对齐信息

现在需要对资产进行检查,确保所有位图正确加载。当前有 8 个位图资源,因此需要逐个查看它们的加载情况。

在检查过程中,发现其中一个位图未被加载,但这是 Asset_None,即表示“无资产”的占位符,符合预期。除此之外,所有其他位图均已成功加载,包括 Bitmap1Bitmap2Bitmap3。这些位图分别对应于 ShadowTreeSword 资源,与定义的资产名称相匹配,说明资源加载过程是正确的。

下一步可以继续调整这些资产的对齐参数,并确保所有资产都能通过新的系统正确访问和使用。
在这里插入图片描述

激活Sword 发现有段错误
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

AlignPercentage 替换 AlignXTopDownAlignY

现在可以为这些位图资源设置对齐参数(Align Percentage)。这些参数将直接从实际位图数据中提取,并用于确保正确的渲染位置。

首先,获取 Shadow 位图的对齐参数,其 X 轴对齐值为 0.5,而 Y 轴对齐值是一个较大的数值。这是合理的,因为阴影可能需要特殊的对齐方式。无论这个数值多么特殊,都不会影响整体处理。

然后,获取第二个位图(Tree)的对齐参数,并将其复制到合适的位置。之后,可以移除临时代码,因为这些数据已经被正确应用。

最后,提取 Sword 位图的对齐参数,并确保它与前面的数据格式一致。这是最后一个需要调整的对齐参数。

完成这些对齐参数的调整后,所有位图的 Align Percentage 设定就完成了,确保了它们在渲染时能够按照正确的位置进行对齐和显示。
在这里插入图片描述

在这里插入图片描述

移除 DEBUGLoadBMP 并修改之前的 DEBUGLoadBMP 以适应 AlignPercentage 变更

现在所有位图的对齐参数(Align Percentage)都已设置完毕。

接下来,可以移除之前的旧代码,因为现在已经有了标准的 Align Percentage 设定。不再需要手动计算对齐方式,只需使用 Align Percentage 即可。

一旦 Align Percentage 设置完成,之前的特殊对齐处理代码可以完全移除。例如,先前的 TopDownAlign 相关逻辑就可以删除,不再需要进行特殊的 Y 轴调整。

Align Percentage 设定后,上层代码可以直接使用它,并且 Align Percentage 的默认值可以设为 0.5, 0.5,以保证默认情况下图像以中心点为对齐基准。

所有位图的 Align Percentage 现在都可以直接赋值,不需要再进行额外的计算或调整,从而简化了整体代码逻辑。
在这里插入图片描述

在这里插入图片描述

删除 TopDownAlign

现在可以移除 TopDownAlign 相关的代码,因为 Align Percentage 已经统一处理对齐方式。

在移除 TopDownAlign 之后,重新编译时发现仍然有部分代码依赖 TopDownAlign,所以不能直接删除。因此,需要先调整 Bitmap Work 相关代码,将 HasAlignment 也一并移除,因为 Align Percentage 现在已经可以完全替代 HasAlignment 的功能。

调整 BitmapWork 代码后,可以清理 HasAlignment 相关的逻辑,并直接使用 Align Percentage 进行位图的对齐计算。同时,可以在适当的位置直接给 Align Percentage 赋默认值,因为现在对齐参数已经完全确定,不需要额外的判断逻辑。

清理 HasAlignment 相关的代码后,整体代码结构变得更加简洁,所有位图的默认对齐方式也变得一致,提高了代码的可读性和维护性。
在这里插入图片描述

运行游戏

代码中原先存在一些不够整洁的部分,这些部分的实现方式较为零散,因此需要进行优化和整理。通过识别这些代码区域并优化,使其更加简洁高效,同时也更易于维护和扩展。

经过调整后,整体代码变得更加经济合理,不再有冗余的逻辑,所有对齐方式也已经正确应用。现在 Align Percentage 统一处理对齐计算,移除了 TopDownAlignHasAlignment,大幅减少了额外的判断和复杂性。

最终,调整后的代码保持了正确性,所有的对齐计算均按照预期工作,整体逻辑更加清晰,便于后续进一步扩展和优化。

在没有打包文件的情况下,考虑如何清理当前代码

接下来需要进行的优化是消除 test_hero_shadow.bmpalign_percentage 相关的代码,使其更加简洁。不过,目前由于 pak 文件尚未实现,因此短期内可能无法彻底优化,只能进行一些过渡性的调整。

pak 文件最终实现后,文件名等信息将被移除,因此现在的处理方式只是临时方案。尽管如此,仍然可以先引入一个 bitmap_table 作为过渡,使得后续优化更加顺畅。

整体而言,虽然目前的调整仍然受到文件名管理的影响,但这不会是一个长期问题。在现有结构下,逐步优化代码的可读性和清晰度仍然是有意义的。

修改 game_asset.hasset_bitmap_info 结构

目前的代码设计中,有一个 asset_bitmap_info 表示加载的位图信息。为了暂时应对当前的需求,决定在代码中模拟出一个位图信息表。这张表与最终将会存在于 pak 文件中的版本略有不同,但目的是为了在当前阶段能使用相似的数据结构。

每个位图都包含位图信息,且数量相同。虽然可以将这些信息直接存储在 bitmap 结构或 assets slot 结构中,但目前并不打算这样做,因为还不确定这些数据是会一直加载在内存中,还是会按块加载出来。因此,决定将位图信息表作为一个单独的数组,并且使其与其他数据结构并行,以便保持灵活性。

这种设计方式能够确保代码在处理文件数据时更具灵活性,同时避免在不确定数据处理方式的情况下将其合并到其他结构中。
在这里插入图片描述

在这里插入图片描述

分配大量位图进行测试

目前的代码是为了测试目的,计划先手动创建和分配大量的位图。目的是为了确保能够处理大量的位图数据。虽然最终这段代码会被替换掉,数据将从磁盘加载,但目前的测试代码不关心实际的位图数量和加载方式,只是为了确保当前能处理和分配大量位图。
在这里插入图片描述

引入 DEBUGAddBitmapInfo

接下来,创建了一个调试用的函数 DEBUGAddBitmapInfo,用于模拟资产打包器的功能。这个函数会初始化一个名为 DEBUGUsedBitmapCount 的变量,初始值为零,用来模拟在打包过程中每次添加一个位图时的计数。

每次添加位图时,DEBUGAddBitmapInfo 会接收文件名和对齐百分比等信息,然后将这些信息用来构建位图的相关信息。位图的相关信息会存储在一个结构体中,包含了文件名和对齐百分比等必要数据,最终返回一个 ID,用来标识这个添加的位图。

该过程还包括一些检查,比如确保添加的位图数量不超过设定的最大位图数量。这些代码纯粹用于测试,因此不需要考虑实际的位图支持数量,最终会通过从包文件中加载数据来确定实际所需的位图数量。
在这里插入图片描述

在这里插入图片描述

AllocateGameAssets 中动态创建资源

现在,我们可以开始将资产动态地创建,模拟打包器的工作流程。为此,首先要做的是将资产的计数与位图的添加过程关联起来。具体来说,创建一个新的函数,该函数会在每次调用时,动态地处理资产的添加。

此过程会基于已有的资产,比如树的阴影和剑的位图,进行模拟。我们通过增加一个新的变量来追踪资产数量,这个数量应该与实际的资产计数一致。通过这种方式,系统可以根据实际的需要动态增加资产。

接下来,通过引入一个类似 AddBitmapAsset 的函数,每次添加位图时,我们都会传入位图的详细信息。这些信息会被传递给每个资产,确保每个资产能够正确地添加到系统中。通过这种方式,模拟过程会像实际从包文件加载资产一样,并且支持多次调用。

这个方法最终会使得每次调用 BeginAssetType 时,都能够添加一个新的资产并且更新相关的计数和结构,直到所有的资产都被正确加载和处理。这也为未来的资源打包过程提供了清晰的基础,尽管现在只是模拟这一过程。
在这里插入图片描述

在这里插入图片描述

提取 BeginAssetTypeAddBitmapAssetEndAssetType 为函数

现在,我需要定义一些函数来实现当前的功能。当前我们使用的调试方法非常有效,所以我决定继续沿用这种方法,直到我们能够加载一个实际的资产包文件。当包文件加载完成后,这些调试代码就可以去掉。

接下来,我的目标是使这些函数能够执行当前的操作。我们已经定义了一些函数,像 BeginAssetType,这些已经准备好了。接着,我们要确保在这些函数中传递正确的参数,尤其是文件名和对齐百分比的顺序,确保它们的一致性,避免混乱。

在接下来的实现过程中,我将把现有代码分解成多个步骤。首先,我们需要确认类型ID,确定资产的类型,并且要实现一个断言检查,确保在开始一个新类型之前,没有其他线程正在执行同样的操作。这样可以避免并发问题。

另外,有一点要特别注意,我之前花了时间去将某些变量清零,认为它们需要初始化为零,但实际上这些变量在程序中总是被初始化为零。所以我意识到我之前做的这一步是多余的,应该停止这种做法。这只是一个无意间做出的习惯性操作,实际上并不需要这样处理。

总的来说,这些代码的核心目的是为了模拟资产加载和管理的过程,并确保在真实的资产包文件加载之前,调试过程能正常运行,避免后续出现问题。
在这里插入图片描述

在这里插入图片描述

继续编写这些函数

在这段代码中,我主要在处理资产类型和调试相关的功能。首先,我计划在结束资产类型时进行一些断言,确保它的初始值为零,并且在结束时对其进行清理,从而可以在后续继续使用。接着,我注意到一个错误,资产类型不应该与位图计数相同。资产类型应该与调试用的资产计数相对应,因此我进行了相应的修正。

在初始化时,我将资产类型设置为调试用的资产计数,并且将其索引初始化为零,这意味着当前还没有实际的资产,但它已经做好了添加的准备。然后,在添加位图资产时,我们会确保获取并正确使用资产类型,同时更新调试用的资产计数。

在结束时,虽然没有特别的操作,但我们会确保在添加资产时,通过正确的索引和类型进行更新。同时,还需要设置正确的槽ID,这个槽ID指向的是添加的位图资源,以便于后续正确使用。

总的来说,整个流程确保了资产和位图信息的正确管理,尽管目前处于调试阶段,但这个方法可以模拟最终在加载资源包时的操作。
在这里插入图片描述

在这里插入图片描述

在调试器中单步执行这些函数

我们意识到这个过程有点复杂,所以即使代码里没有bug,我们也希望一步步地检查它,以便更清楚地了解它的运作方式。因此,我们决定在“AllocateGameAssets”(分配游戏资源)这个地方设置一个断点,来观察具体发生了什么。我们打开代码,开始逐步分析这个情况。为了更清楚地看到资源结构,我们先把调用栈的窗口缩小一点,这样能更好地查看资源相关的部分。

我们看到这里有几个关键的东西在起作用。首先,我们有一个“AssetTypes”(资源类型数组),用来存储不同类型的资源信息。然后,我们还有一个“Assets”(资源数组),用来存放具体的资源数据。此外,我们有两个与位图相关的数组:“Bitmaps”(位图数组)和“Bitmapinfos”(位图信息数组)。位图信息数组是我们正在填充的内容,而位图数组则是实际加载位图数据的地方。

接下来,我们开始检查这个流程。我们从资源类型数组中取出一个对应的资源类型,比如“shadow”(阴影),它的索引是1。我们获取这个资源类型时,会用到一个“DEBUGUsedBitmapCount”(已使用的资源计数),一开始这个计数是0。不过,我们觉得应该为它预留一个槽位(slot)。我们认为从0开始计数可能不太合适,所以决定让计数从1开始,把0号槽位留空作为一个空槽(null slot)。这样做成本不高,而且会让后续管理更简单。于是,我们调整了代码,把“DEBUGUsedBitmapCount”(位图计数)和“DEBUGUsedAssetCount”(资源计数)设置为从1开始,0号槽位就不占用。

调整之后,我们重新审视流程。我们从资源类型数组中取出阴影对应的资源类型,把它的“FirstAssetIndex”(第一个资源)的索引设置为当前的“DEBUGUsedAssetCount”(调试用的资源计数),也就是1。我们看到代码里把这个值设置好了,同时把“OnePastLastAssetIndex”(最后一个资源)也设为1。这表示当前还没有其他资源,但如果有的话,1号就是第一个资源。

然后,我们尝试添加一个位图资源。我们先取出资源数组中当前正在处理的最后一个资源的索引,把新的资源追加到后面,并把索引递增。我们为这个新资源设置了一些标签(tags),这个资源实际上是资源数组里的第1号资源(asset1)。它的初始值是0,我们清空了一些匹配信息(matches),以确保干净的状态。接着,我们为调试用的位图信息数组分配一个槽位ID(slot id),并在这个位图信息数组中添加一个新的位图信息。因为0号槽位被预留了,所以这个新位图信息被添加到1号位置。
在这里插入图片描述

我们继续看代码,取出了刚刚分配的位图信息的ID,设置了它的文件名(FileName)和对齐百分比(align percentage),然后返回了这个ID。这个ID会被记录下来,对应到刚刚设置的位图信息。我们检查下来,觉得这个过程看起来没问题。
在这里插入图片描述

最后,我们处理“end assets”(结束资源分配)的部分。这一步主要是完成整个流程。我们确保“debug asset count”(调试资源计数)至少等于我们添加的资源数量,然后把资源类型的计数重置为0,表示这个类型的资源处理完毕。
在这里插入图片描述

整个过程就是这样,我们通过逐步检查,确认了资源分配的逻辑是合理的,而且通过预留0号槽位让代码更清晰。现在我们觉得这个实现应该能正常工作。

在这里插入图片描述

运行游戏

现在可以运行程序了。经过测试,程序运行正常,我们的目标已经实现,场景中已经成功加载了小羊和小树,正如我们所期望的那样,一切重新加载工作正常。
接下来,准备继续推动工作进展,利用现在已经具备的功能,继续进行后续的开发和测试。
在这里插入图片描述

GrassTuftStone 位图使用新系统

现在我们进行下一步工作,目标是将草地(tufts)加入到系统中。我打算开始一个资产类型,并访问未处理的草地资产类型(grass)。接着,我会用我们已经实现的系统来处理这个草地图,给它命名并添加。

这一次,我会添加两个草地图,因为同一个资产类型下有两种不同的草地图,而不像之前的情况只涉及到一个草地图。我会让系统支持这个需求,并确保它能正常工作。

之后,我会将草地图的处理从这里移除,并将其转化为需要的格式,因为我们已经在资产中加入了草地资产。这将促使编译器对函数进行正确处理。
在这里插入图片描述

game.cpp 中引入基于 ID 选择资源的方法

接下来,我们将让编译器强制执行一些操作,并回到之前处理草地资产的地方。我们当时做了一个随机选择的操作,这次我们不会做特别复杂的处理,而是稍微简单一些。具体来说,我会做以下几点:

  1. 我先将石材相关的代码注释掉,然后将草地图的部分改成使用随机选择的资产ID。
  2. 我打算从游戏资产中随机选择一个资产ID,具体来说,就是从所有草地图资产中选择一个。
  3. 为了实现这一点,我会使用一个随机数生成器,它可以从游戏资产中选择一个资产,这些资产必须符合草地类型的条件。
  4. 然后,绘制时,我们将使用基于ID的方式来渲染,而不再使用基于位图的渲染方法。

通过这种方式,我们将从可用的草地资产中随机选择一个,并使用其ID进行渲染。
在这里插入图片描述

game_asset.cpp 引入 RandomAssetFrom

接下来,我们需要将随机选择的资产ID转换为能够加载资产的机制。这基本上类似于之前用来获取第一个位图的操作,但这次我们是根据随机选择的方式来获取资产。

具体来说:

  1. 首先,我们将从资产类型中选择一个随机的资产。原本的做法是直接选择第一个位图,而现在我们要做的是从草地资产类型中随机挑选一个。
  2. 为了实现这个功能,我决定使用一个随机选择的函数,并传入合适的参数,这样我们就能够从草地资产的范围内随机选择一个。
  3. 通过给定一个范围,我能够选择一个随机的值,在这个范围内查找并返回对应的资产ID。
  4. 最后,我们会将选择的随机资产ID作为槽ID返回,并用于后续的操作。

通过这种方式,我们能够在草地类型的资产中进行随机选择,并将其ID作为槽ID进行渲染。
在这里插入图片描述

忘记去掉之前的代码了
在这里插入图片描述

运行游戏

在执行过程中,程序确实成功选择了一个资产,但很明显并不是正确的资产。当前的问题是,草地资产的部分竟然点亮了树木,而不是正确的草地资产,这显然是不对的。

需要进行调试,找出为什么会发生这样的错误。尽管 bug 可能会让人感到挫败,但有时候它们也会带来一些有趣和滑稽的情况。这次的错误属于后者,看起来相当有趣,不过仍然需要修复它。

下一步的计划是深入排查,为何随机选择的资产没有正确落在草地资产范围内。可能是索引计算错误,或者是随机选择逻辑的问题。通过设置断点,并检查调试信息,能够更快找到问题的根源并进行修复。
在这里插入图片描述

RandomAssetFrom 中添加第一个资源

目前使用的随机数生成方法 random.nextInt()random.choice() 的范围包括了最大值,这其实并不是我们想要的效果。所以考虑到这一点,决定保持现有的方式,但需要明确随机数生成的范围应该是 [min, max],且最大值会被包括在内。

为了调整这个问题,可能需要调整随机数生成的范围,确保它符合预期的逻辑,而不是导致错误的资产选择。

在调试器中查看资源表

为了确保当前的逻辑没有明显的错误,我们决定先查看当前的表格数据,以便确认是否存在显而易见的问题,而不是直接进行复杂的带宽调试。

目前,我们已经确认 asset 这一部分的数据,其中草地 (grass) 资产位于编号 5 的位置。我们按照索引 0, 1, 2, 3, 4, 5 进行了检查,并确定草地的 FirstAssetIndex 是正确的。

接下来,我们需要确保所有相关的数据结构和索引计算逻辑没有问题,以便正确处理 FirstAssetIndex,并避免不必要的错误。
在这里插入图片描述

在这里插入图片描述

RandomAssetFrom 中添加第一个资源

我们已经发现了代码中的一个错误,而且甚至不需要进一步单步调试就能确定问题所在。

当前的代码逻辑中,我们需要确保仍然正确地添加第一个被随机选中的资产,并且要保证这个选择在正确的范围内。我们正在使用 random between 这一函数来生成随机值,而 random between 的实现方式如下:

min + random_next() % (max + 1 - min)

这一计算方式实际上是包含了 max 值的,而这并不是我们想要的行为。我们本希望 random between(min, max) 生成的值是 [min, max-1],但现在它的范围是 [min, max],即 max 也可能被选中。

为了解决这个问题,我们可以保留当前的 random between 逻辑不变,但在使用时手动调整范围,例如改为:

random between(min, max - 1)

这样可以确保不会超出预期的索引范围,并避免因 max 过大而导致访问非法内存或错误映射资产。
在这里插入图片描述

在这里插入图片描述

运行游戏,注意到树消失了

目前的情况是,场景中的树完全消失了,这显然是不对的。我们需要找出原因,弄清楚到底发生了什么。

首先,代码运行后,我们观察到树的纹理没有正确渲染出来,它们彻底从游戏画面中消失了。这是一个比较奇怪的现象,因为按理来说,树应该依然存在,代码逻辑没有涉及移除树的部分。

目前的怀疑点在于资产管理系统的某个部分可能影响了树的加载或渲染。我们最初认为可能是某个尚未实现的功能导致问题,但回头检查后,发现这个功能并没有被启用,因此可以排除这个可能性。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

game.cpp 调查树缺失的问题

首先,我们尝试了一个新的调试方法:暂时移除地面块的渲染,看看是否影响树的显示情况。结果表明,当不渲染地面块时,树就能够正常显示。这说明问题并不出在树本身,而是与地面块的渲染过程有关。

进一步分析发现,问题可能出在获取随机资产并推送到位图缓冲区的过程中。原本的假设是,如果不进行 PushBitmap 操作,一切应该都会正常运行。然而,实际情况表明,执行 PushBitmap 之后,加载资产的逻辑被触发,而这可能导致了树的显示问题。

具体来说,我们怀疑地面块的渲染可能影响了树的渲染流程,导致树的位图数据未正确加载或被覆盖。以下是可能的原因:

  1. 地面块的渲染逻辑影响了树的绘制顺序,可能导致树的图层被地面块错误覆盖。
  2. PushBitmap 操作影响了资产加载,导致树的纹理被错误地替换或移除。
  3. 某些数据结构的错误修改,可能在执行 PushBitmap 时,导致树的相关数据发生了变化。

下一步,我们需要深入检查 PushBitmap 的实现,确定它是否影响了树的渲染流程。可以尝试:

  • 手动检查资产加载顺序,确保树的纹理仍然存在于资产列表中。
  • 逐步执行 PushBitmap 操作,观察哪一步导致树的消失,以确定根本原因。
  • 调整绘制顺序,先渲染树,再渲染地面,看看是否能够恢复正常显示。

总的来说,问题的核心似乎是地面块的渲染与树的渲染存在某种冲突,导致树在某个阶段被错误移除或覆盖。

确定 LoadBitmap 是否是线程安全的

仔细回顾代码后,我们开始思考一个新的问题:加载资产的过程是否是线程安全的?最初并没有特别关注这个问题,因此需要重新检查代码,确保在多线程环境下不会导致竞争条件或数据损坏。

我们首先查找了 load asset 的调用情况,发现它在渲染过程中被调用。在分析 load asset 相关代码时,注意到其中涉及 LoadBitmap 这一函数。进一步跟踪调用链后,发现 LoadBitmap 可能是导致问题的关键。

为了确保多线程友好性,检查了 LoadBitmap 的实现,发现其中调用了 PlatformAddEntry 来处理位图加载。从代码结构来看,这部分逻辑似乎已经做了线程安全处理,因此理论上不应该引发并发问题。

不过,为了进一步确认,我们需要考虑以下几点:

  1. 是否有其他代码路径调用 LoadBitmap 而未进行线程同步
  2. PlatformAddEntry 是否真正保证了线程安全?是否可能存在资源竞争?
  3. 如果多个线程尝试同时加载相同的资产,是否可能导致渲染异常

接下来的调试重点是:

  • 添加日志或断点,检查 LoadBitmap 在多线程环境下的行为,确认是否存在并发问题。
  • 测试不同的加载顺序,看是否影响渲染结果,尤其是地面块与树的渲染顺序
  • 尝试手动同步,如添加锁或使用线程安全的数据结构,观察是否改善问题。

目前的初步结论是:代码已经尝试了线程安全处理,但仍需进一步验证,确保 LoadBitmap 在所有情况下都能正确运行。
在这里插入图片描述

使用 asset_bitmap_info

在回顾代码后,发现一个关键问题:虽然之前已经确定了我们想要的位图 ID,但并没有真正存储它。这意味着虽然所有逻辑都已经实现,但最终并没有正确地应用所获取的数据。

在这里插入图片描述

问题分析

  1. 我们确定了位图 ID,但没有真正将其存储或使用。
  2. 代码中仍然保留了对 AlignPercentageFileName 的引用,但实际上这些信息应该来自 bitmap info table,不再需要额外存储。
  3. 忘记移除旧逻辑,导致无效数据仍在代码中存在,从而影响了渲染流程。

修正方法

  1. 移除不必要的变量,例如 AlignPercentageFileName,因为这些信息已经存储在 bitmap info table 中,不需要重复存储。
  2. 直接使用 bitmap info table 来获取位图信息,而不是依赖旧的逻辑。
  3. 确保正确存储并使用 bitmap ID,以便后续可以正确地渲染对应的位图。

反思

这次错误的本质是没有完全清理旧代码,导致逻辑虽然更新了,但仍然受旧代码的影响,最终使得正确的数据没有被使用。这提醒我们,在重构或优化代码时,不仅要加入新逻辑,还要确保旧逻辑彻底清理,避免混乱和无效的计算。
在这里插入图片描述

在这里插入图片描述

重新启用 PushBitmap

在这里插入图片描述

RandomAssetFrom 中使用 Choice 而非 Count

目前发现了一个逻辑错误,导致程序错误地访问了无效内存区域。

问题分析

  1. 错误操作:错误地增加了 count

    • 代码逻辑本来是group_assets 中随机选择一个索引 choice,然后使用这个索引来获取 SlotID
    • 但是,在计算 SlotID 时,错误地加上了 count,这会导致索引超出数组范围,进入未定义行为的区域(“no man’s land”)。
  2. 错误的后果

    • 访问数组时超出了合法范围,可能导致:
      • 读取到未初始化的内存,数据错误。
      • 发生非法访问,导致程序崩溃。
    • 这个错误让之前所有的随机选择逻辑都变得无效。

修正方案

  1. 去掉 count 偏移

    • 只使用 choice 直接索引 group_assets,而不是 choice + count
    • 确保 choice 的范围是 0count-1 之间,避免溢出问题。
  2. 增加边界检查

    • 在使用 choice 时,添加 assert(choice >= 0 && choice < count),确保索引合法。
    • group_assets 访问时,确保数据结构中确实有足够的元素。
  3. 添加调试信息

    • 输出 choicecount,以便检查它们是否在合理范围内:
      printf("Choice: %d, Count: %d\n", choice, count);
      

总结

之前的错误是因为在计算 SlotID 时,错误地增加了 count,导致数组访问超出范围。修正方法是直接使用 choice 作为索引,同时增加边界检查,确保数据索引在合法范围内,以防止出现未定义行为。

在这里插入图片描述

在这里插入图片描述

运行游戏,疑惑为何失败

目前的问题似乎还没有完全解决,尽管做了一些修正。具体来说,某些资产的 SlotID 始终为零,这导致了对这些资产的处理不正常。

问题分析

  1. SlotID 为零的现象

    • 本来应该随机从可用的资产中选择一个,但现在某些资产的 SlotID 一直为零。
    • 这意味着这些资产的选择过程中存在错误,可能是计算方式不正确,或者数据访问时发生了意外的行为。
  2. 为什么会失败

    • SlotID 为零时,程序会尝试加载一个无效的位图(bitmap)。这种情况下,load bitmap 会立即失败,因为没有有效的图像数据。
    • 但这并不解释为什么所有的 get first bitmap id 调用都会失败,或者为什么会影响到整个加载过程。

假设的原因

  1. 无效资产导致加载失败

    • 如果尝试加载的资产无效(例如,SlotID 为零),加载过程就会失败。虽然理论上这应该不会影响队列的其他操作,但可能存在某些资源或数据加载的机制相互影响,导致队列未能正确处理。
  2. 队列未被正确填充

    • 当加载无效位图时,可能导致队列没有按预期添加其他有效的加载任务。因此,虽然没有数据被加载到队列,但队列本身可能在某种情况下未能正常工作。
  3. 其它潜在的线程问题

    • 如果涉及到多线程操作,可能会有线程同步问题,导致某些任务被跳过或错误执行。

下一步行动

  • 检查 SlotID 的来源和计算过程,确保其值在合法范围内,不会总是为零。
  • 在加载位图时,添加更多的调试信息,输出加载失败时的状态,检查是否存在无效资产导致的失败。
  • 测试队列和线程机制,确保在加载失败时,其他任务仍然能够正常进行,并且队列能够正确管理和处理任务。

总结

目前的问题仍然与 SlotID 为零以及随之而来的加载失败有关,可能与无效数据或队列机制相关。接下来需要对这些方面进行详细的调试和测试,以确保程序能够在各种情况下正确运行。

完成资源数组设置

首先,需要完成软件的修复工作,以确保系统能够正常运行,特别是在处理资产映射时。通过修复和调整相关的代码,现在可以确保资产(例如石头等)能够正确加载并映射。这样一来,程序就能正常运行,所有的功能也可以顺利操作,确保这些资产在游戏中能够正确显示并发挥作用。
在这里插入图片描述

game.cpp 设置 Stamp 使其从 RandomAssetFrom 获取

现在,程序可以正常运行,并且我们能够正确地选择随机资产来进行渲染。通过使用随机选择,我们可以从草地数组中选择一个随机的资产作为“stamp”,否则就从石头数组中选择。这段代码实现了随机选择,确保了程序能够根据需要选择草地或石头的资产。接下来,“stamp”将会使用这些随机选择的资产,确保能够渲染不同的草地或石头类型。同时,程序中的每个步骤也都正确地通过随机选择方式进行处理,确保了功能正常。
在这里插入图片描述

在这里插入图片描述

运行游戏,注意地面块缺失

现在,我们已经正确地翻译了所有内容,但依然存在一个bug,问题可能出在没有正确刷新某些数据,或者做了一些不该做的操作。虽然大部分功能正常,但这个问题仍然需要解决。即使这样,有时会很幸运,第一次尝试就能顺利运行,但大多数时候还是需要调试和修复一些小问题。时间虽然所剩不多,但问题已经明确,接下来需要进一步检查和修复,以确保一切正常工作。

game.cpp 编写 AllResourcesPresent 的失败情况处理

问题出在我们处理任务时出现了一个明显的错误。当加载失败且任务尚未完成时,我们错误地继续保留了这个任务。实际上,我们应该在任务失败时中止它,而不是让任务继续"伪运行"。这种情况导致任务堆积,从而无法加载任何新内容。关键问题在于任务没有被正确中止或清除,造成了资源的浪费和后续无法加载其他内容的情况。所以,正确的做法是在失败时立即终止任务,防止任务继续占用资源。
在这里插入图片描述

在调试器中检查 FillGroundChunk

在这里插入图片描述

我们正在检查任务是否能正确结束并释放内存。我们尝试调用任务相关的内存清理功能,但机器报告说无法完成此操作。于是我们开始进行一些调试,尝试通过设置断点来观察任务是否按预期执行,看看问题出在哪里。发现目前任务尚未正确设置,所以问题可能出在任务的初始化阶段。

FillGroundChunk 顶部设置 RenderGroupBufferTask

我们发现了一个问题,之前没有正确设置任务的相关信息。现在决定在渲染时正确设置这些信息,确保缓冲区任务可以被正确处理。实际上,在调用平台的add entry之前,这些信息并不会被检查,因此现在决定在这之前就设置好它们。这样做应该能够避免之前的问题,确保任务可以顺利执行,并且提升代码的安全性,防止其他潜在的问题。

在这里插入图片描述

在这里插入图片描述

地面块设置的有问题
修复
在这里插入图片描述

在这里插入图片描述

“你提到的‘先写用法代码’是什么意思?此外,Asset 0 是否有特殊处理?”

在编写代码时,应该先编写使用该系统的代码,而不是先编写系统本身的 API 及其结构。先编写使用代码的目的是为了观察用户希望如何使用这个系统,然后再去实现该系统。这样,整个开发过程会以实际使用需求为导向,而不是凭空想象一个接口的设计方式。

如果一开始就设计和实现系统的 API,可能会导致 API 的使用方式不够合理,最终在代码的各个使用场景中都会遇到不便或不直观的问题。而如果先编写使用代码,就可以从实际需求出发,使 API 的设计更符合使用者的直觉,从而提高代码的可读性和易用性。

在软件开发中,尤其是在游戏开发中,一个小的功能可能只需要实现一次,但在整个游戏代码库中可能会被成百上千次地调用。如果 API 设计不合理,开发者在每个使用场景中都会面临额外的复杂性,而如果 API 设计合理,所有使用它的代码都会变得更简洁、易读。因此,最佳做法是先编写多个实际的使用示例,看看在不同场景下如何调用该功能,然后再设计实现该功能本身,这样能确保 API 设计对绝大多数使用场景都是友好的。

至于对资产(asset)编号为 0 的特殊处理,目的是让 0 代表“无资产”(nothing)。这样,在代码中提及某个资产时,可以用 0 来表示“没有资产”,从而在逻辑上保持一致性。例如,在某个游戏逻辑中,需要指定玩家获胜时显示的资产,如果该值为 0,则意味着不需要显示任何资产。这种处理方式可以使代码更清晰,也更容易理解。

“你会不会故意降低游戏画质,以制造争议,让游戏在 NeoGAF 和 Reddit 上被讨论?”

在游戏开发和推广过程中,可以通过制造争议来吸引更多关注,因为“没有坏的宣传”。为了引发讨论和热议,可以采取多种方式来制造话题和争议,其中之一就是故意降低游戏的画质,以引发玩家的不满和讨论,让游戏成为热门话题,在社交平台和论坛上引发广泛关注。

除此之外,还可以通过其他方式进一步制造争议。例如,可以接受某个厂商的资金支持,使游戏成为独占作品,从而激怒其他平台的玩家。比如,可以与某个主机厂商签订协议,推迟 PC 版的发行时间,以此引发 PC 玩家群体的不满,让他们在社交平台上掀起讨论。

此外,还可以在游戏内容上采取一些更具争议性的做法,例如在角色选择方面进行某种歧视性的设定,或者在游戏剧情、角色设定等方面故意制造争议。所有这些做法的目的,都是为了在社交媒体、论坛和新闻报道中引发热议,从而扩大游戏的曝光度,吸引更多的关注和讨论。

可以整理一份详细的“争议制造策略”清单,列出所有可能引发讨论和争议的做法,并有计划地实施,以最大程度地提高游戏的知名度和讨论热度。

“哪些游戏性改动会需要修改资源标签?”

在讨论游戏机制的改动时,需要考虑这些改动是否会影响到标签(tags)的使用。然而,标签的主要作用是描述游戏中的资源(assets),而不是直接反映游戏玩法的变化。因此,游戏机制的改动未必会直接导致标签的变化,除非新的机制引入了需要额外描述的新资源。

如果游戏增加了一些新的玩法,而这些玩法涉及到新的资产描述方式,那么可能就需要新增或调整标签。例如,如果游戏新增了一种特殊的交互方式,需要对某些资源进行特定的标记,以便正确调用或显示,那么这就需要对标签系统进行调整。但总体而言,游戏机制和标签系统并不是完全紧密相关的概念,两者的关联性可能并不强。

“长时间调试 bug 会让你犯困吗?”

在调试代码时,如果花费了很长时间解决一个错误,可能会感到困倦。但这种困倦感并不是因为错误本身难以解决,而是因为本身已经感到疲惫,导致解决问题的效率降低。

有时,由于困倦,思维变得迟缓,导致调试过程变得更加缓慢,解决问题的时间也因此被拉长。但并不能确定是因为调试时间过长才导致了困倦,更多时候,困倦本身就是影响解决问题效率的原因。

“你会在直播中展示资源管线工具的编程过程吗?”

当前的重点仍然是游戏本身的编程,因此不会涉及资产(asset)流水线工具的展示。之前已经讨论过,出于管理范围的考虑,编程工作被限定在游戏开发本身,而资产流水线属于完全不同的编程领域,与游戏代码的核心部分关系不大,因此不会纳入当前讨论范围。

目前的工作重点是制定数据文件格式的规范,这也是当前正在进行的任务之一。完成这一步之后,会单独处理与该格式兼容的 pak 文件的生成,就像在实际开发过程中,资产管理流程通常由专门负责资产流水线的人员来完成一样。资产文件的打包和管理将作为一个独立的流程进行,而不会与当前的游戏代码开发直接交叉。

“为什么要用 size_t 而不是 int32uint32?”

在编程中,使用 size_t 的主要原因是为了在不同的系统架构(如 32 位或 64 位)上正确表示内存大小,而不依赖具体的整数类型,如 int32_tint64_t

int32_tint64_t 分别代表 32 位和 64 位的整数类型,但它们并不随平台的不同而自动调整。因此,在编写需要处理内存大小的代码时,如果直接使用 int32_tint64_t,可能会导致跨平台兼容性问题。例如,在 64 位系统上,使用 int32_t 可能无法正确存储较大的地址或内存偏移量,而使用 int64_t 在 32 位系统上则可能会浪费存储空间。

size_t 解决了这个问题。它的大小会根据平台的架构自动调整,在 32 位系统上,它是 32 位的,在 64 位系统上,它是 64 位的。这样,就可以确保它足够大,能够表示系统允许的最大内存大小。

使用 size_t 主要用于涉及内存分配、数组索引、指针运算等场景,因为它能够保证变量的大小适应不同平台的内存模型,避免因整数类型大小不同而引发的错误。

“你会支持 HTC Vive 和 Lighthouse 吗?”

目前没有计划支持 HTC Vive 或其他类似的 VR 设备,主要原因是当前项目是一个 2D 游戏,而 VR 设备通常用于 3D 体验。如果没有明确的 3D 交互方式或视觉呈现方案,支持 VR 设备的意义并不大。

VR 设备的核心价值在于沉浸式的 3D 体验,包括深度感知、空间交互以及头部追踪等功能。而对于 2D 游戏而言,这些功能并没有明显的应用场景。因此,在没有清晰的 3D 设计目标的情况下,增加对 HTC Vive 等设备的支持并不合理,也无法充分发挥 VR 设备的优势。

“如果不在直播中做资源管线,你还会公开源码吗?”

资产流水线并不会成为产品的一部分,实际上,它只是用于生成数据集,而这个数据集最终会成为游戏的一部分。至于如何生成这些数据集,实际上并没有承诺它会在其他人的机器上编译运行,因为这一部分的实现是相对独立的,只是根据需要进行的操作,以确保生成的数据文件符合预定的格式和描述。

虽然生成数据集的过程可能并不会公开,但会有一个屏幕上的描述,任何有兴趣的人都可以根据这个描述构建自己的 pack 文件。这并不是特别复杂,实际上就是将多个数据拼接在一起,制作文件的过程并不难。

不过,可能会使用一些专有的工具或代码(比如自己编写的 Photoshop 解析器),这些工具和代码并不属于公共领域,因此它们不会包含在发布的版本中,也不会公开给其他人使用。这些工具主要是为了生成符合要求的文件格式,但它们并不直接影响游戏的核心内容或开放给用户的部分。

:“你能承认色差 (Chromatic Aberration) 是最棒的后期处理技术吗?”

不会接受色差(chromatic aberration)作为最好的后期处理效果。在我的观点中,色差是一种不好的视觉效果。色差和其他类似的摄像机视觉效果(如镜头光晕)通常不是为了增强画面效果,而是因为镜头本身的缺陷,常常是不希望出现的,除非是在非常特殊的情况下。大多数时候,专业的摄像师会尽量避免出现这些效果。

色差、镜头光晕和渐晕(vignetting)是属于这些“避免”的视觉效果范畴。虽然有时为了某种非常特殊的效果,可能会刻意使用这些视觉效果,但通常来说,优秀的电影摄影师在绝大多数情况下是尽量避免它们的。相反,像景深(depth of field)和虚化(bokeh)这些效果,是专业摄像师常常使用的,能够帮助他们在每个镜头中实现特定的视觉效果,并增强图像的质量。

所以,景深和虚化等后期效果是我认为值得加入到工作流程中的,它们能在图像中产生特定的艺术效果,帮助提升整体画面质量,而色差则完全不在这个范畴内。

“为什么不支持 DirectInput8,让旧 USB 手柄能用,而只支持 Xbox360 手柄?”

目前,可能会支持 DirectInput 8 来兼容一些旧款的美国控制器,而不仅仅是 XInput 来支持 Xbox 控制器。虽然平台层的工作暂时处于待完成状态,但一旦游戏开发完成并进入发布阶段,就会回到平台层的工作上。

目前,平台层还不支持一些功能,而这些功能可能在未来的工作中会被添加进来。因此,预计会支持 DirectInput 8,并且可能会支持其他相关的输入控制方式。

吐槽:API 和 Dependency Walker

在讨论 API 依赖时,提到有些程序在运行时需要非常复杂的依赖关系,特别是在 Windows 系统上。以前,程序的依赖关系是非常简洁明了的,通常只需要少数几个系统服务和库,比如内核和用户界面相关的调用。然而,随着微软不断添加新的功能和复杂性,像是“并排程序集”和“API 资产”等概念被引入,导致了程序依赖关系的复杂化。

例如,运行一个非常简单的程序(像是一个游戏),本来只需要几个基础的依赖项,但现在程序会涉及到大量的依赖,很多时候甚至无法清楚地知道程序在运行时具体依赖了什么。这让软件维护变得极其困难,很多依赖在程序运行时可能出现问题,导致程序崩溃或无法启动。

有一个例子是,某些开发者在他们的电脑上运行程序时一切正常,但当尝试在其他机器上运行时,往往会因为某些缺失的依赖或者版本不匹配而无法运行。这种情况下,有时只能通过额外安装依赖包或者重新配置才能解决问题,而这已经不是一个简单的复制文件就能解决的事情了。

尤其在 Windows 系统中,依赖问题变得更加复杂,很多软件和游戏都需要安装额外的 Redistributables(例如 VC 重新分发包)或者 DirectX 等依赖才能正常运行。这种情况使得在 Windows 上运行程序变得非常不可靠,尤其是对于商业软件来说,安装程序往往会捆绑大量的依赖包,但这会造成程序的运行环境变得非常复杂,甚至一旦依赖版本不一致,就可能导致程序无法运行。

为了避免这些问题,某些程序设计时非常小心,不依赖于复杂的机制,尽量保持程序的简洁性。这样,即使没有安装特定的依赖包,程序仍然可以运行。这样的方法虽然可以避免依赖问题,但在现代操作系统中,依赖的复杂性越来越大,导致程序之间的兼容性和可维护性变得更加困难。

总结来说,这种复杂的依赖关系和不可靠的环境给软件开发和维护带来了很大的挑战,特别是在 Windows 系统上,依赖的管理和版本控制变得越来越复杂,导致软件开发变得不再像以前那样简单直接。

Dependency Walker 已经过时新的代替的软件

https://github.com/lucasg/Dependencies
在这里插入图片描述

“感觉微软内部没人知道 Windows 为什么还能正常运行。”

在讨论 Windows 操作系统的变化时,感到很遗憾的是,微软曾经的很多优秀设计和技术,现在似乎被逐渐复杂化和低效的做法所压制。尽管微软内部依然可能有一些非常出色的开发人员,特别是负责内核的团队,但如今操作系统的整体质量已经大不如前,很多优秀的设计思想被逐渐淘汰或者没有得到充分的利用。

以前的 Windows NT 内核设计得非常智能、可靠,在很多方面都做得很好。然而,随着时间的推移,微软在操作系统上不断增加复杂的层级和功能,导致了很多不必要的复杂性。这些复杂性使得操作系统的稳定性和性能大打折扣,尤其是用户层面的一些低效编程和不精细的实现,直接影响了整个操作系统的质量。

虽然操作系统的内核部分仍然可能是高质量的,但如果操作系统的其他部分(如用户界面和依赖管理等)做得不好,那么再好的内核也无济于事。举个例子,Windows 注册表的问题,以及越来越复杂的依赖管理,都成为了软件无法正常运行的原因。即使内核很好,也无法修复这些软件层面的硬伤,最终导致操作系统变得非常不稳定,用户体验差。

此外,操作系统的开发需要非常细致的关注每个阶段的工作,保证从内核到用户层每个部分的质量。但微软显然没有能够做到这一点,导致了一些曾经优秀的部分被逐渐扭曲和丧失。这种局面让人感到非常惋惜,因为如果微软能够保持对细节的关注,Windows 本来可以是一个非常优秀且稳定的操作系统,但现在的情况却远未达到那个理想状态。

对于曾经使用过早期版本的 Windows NT(例如 NT 3.51)的用户来说,这种变化特别令人失望,因为当时的操作系统是非常出色的,而如今看到它的衰退,不禁让人感到更加遗憾。很多时候,人们并不在意一个操作系统从未好过,但如果一个操作系统曾经很优秀,却因不断的改动和添加复杂功能变得糟糕,那种失落感是更深刻的。

“现代桌面 OS 这么多依赖项,不是不可避免的吗?”

复杂的软件依赖和现代操作系统中额外的生产力工具,并不意味着这些问题是不可避免的。很多时候,操作系统在增加功能的同时引入了过多的复杂性,而这种复杂性往往远超实际需要的功能增量。举个例子,当比较 Windows NT 3.51 和现代 Windows 时,可以看到操作系统在功能上的提升远远没有带来与之匹配的复杂度。实际上,新增的复杂性几乎是成倍增加的,而所获得的功能提升却相对较小。这就引发了一个问题:是否真的无法在不让操作系统变得更加复杂的情况下,达到同样的功能?

在实际情况中,这种复杂性并非不可避免。很多人认为,微软本来完全有能力做得更好,避免将操作系统变得如此复杂。过去的 Windows NT 版本在稳定性和简洁性上有着很高的水准,并且可以扩展新的功能,而不会导致操作系统变得难以管理。显然,后来的一些改动并不一定是为了提升功能,而更多的是由于缺乏足够的组织和管理,导致了很多复杂性的引入。

问题的根源很可能在于微软的开发团队文化。微软内部并没有建立起足够严格的控制机制,确保每一个新的功能和 API 都能经过严格的审核,只有在真正有必要的情况下才会被纳入操作系统核心功能中。相反,很多团队由于政治上的资本或者内部的松懈,能够将自己的功能随意发布,这导致了大量的冗余和不必要的复杂性不断被引入操作系统。像 ActiveX 和 DirectShow 这样的技术,实际上并没有为操作系统带来显著的好处,但却增加了极大的复杂性和维护成本。

如果微软当时能够更好地控制开发过程,只有那些具备足够经验和技能的团队才有机会对操作系统核心进行修改,那么今天的操作系统可能会更加简洁高效,不会面临如此多的问题。控制系统变更的文化至关重要。如果开发团队能够对每一个新功能的引入保持极高的标准,避免无关紧要的功能过度膨胀,那么操作系统将能够保持更好的稳定性和可靠性,而不会被不必要的复杂性拖慢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/981389.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】冯诺依曼体系结构-操作系统

一.冯诺依曼体系结构 我们所使用的计算机&#xff0c;如笔记本等都是按照冯诺依曼来设计的&#xff1a; 截止目前&#xff0c;我们所知道的计算机都是由一个一个的硬件组装起来的&#xff0c;这些硬件又由于功能的不同被分为了输入设备&#xff0c;输出设备&#xff0c;存储器…

[liorf_localization_imuPreintegration-2] process has died

使用liorf&#xff0c;编译没报错&#xff0c;但是roslaunch报错如下&#xff1a; 解决方法&#xff1a; step1: 如果你之前没有安装 GTSAM&#xff0c;可以尝试安装它 step2: 检查是否缺少依赖库 ldd /home/zz/1210/devel/lib/liorf_localization/liorf_localization_imuPr…

模块11_面向对象

文章目录 模块11_面向对象模块十回顾&&模块十一重点 第一章.接口1.接口的介绍2.接口的定义以及使用3.接口中的成员3.1抽象方法3.2默认方法3.3静态方法3.4成员变量3.4成员变量 4.接口的特点5.接口和抽象类的区别 第二章.多态1.多态的介绍2.多态的基本使用3.多态的条件下…

常见webshell工具的流量特征

1、蚁剑 1.1、蚁剑webshell静态特征 蚁剑中php使用assert、eval执行&#xff1b;asp只有eval执行&#xff1b;在jsp使用的是Java类加载&#xff08;ClassLoader&#xff09;&#xff0c;同时会带有base64编码解码等字符特征。 1.2、蚁剑webshell动态特征 查看流量分析会发现…

03标准IO接口

一、系统与标准IO的区别 相同点:系统IO与标准IO都可以操作linux系统下的文件。 ⭐不同点: 系统IO&#xff1a;打开文件得到的是一个整数&#xff0c;称为文件描述符。 标准IO&#xff1a;打开文件得到的是一个指针&#xff0c;称为文件指针。系统IO&#xff1a;可以访问linux…

Axure高保真Element框架元件库

点击下载《Axure高保真Element框架元件库》 原型效果&#xff1a;https://axhub.im/ax9/9da2109b9c68749a/#g1 摘要 本文详细阐述了在 Axure 环境下打造的一套高度还原 Element 框架的组件元件集。通过对 Element 框架组件的深入剖析&#xff0c;结合 Axure 的强大功能&#…

【Linux】进程信号——信号保存和信号捕捉

文章目录 信号保存信号相关的概念信号是如何保存的呢&#xff1f;有关信号保存的系统调用sigprocmask信号的增删查改查看pending表验证接口 信号捕捉用户态与内核态信号捕捉流程 总结 信号保存 信号相关的概念 信号递达&#xff1a;指 操作系统 将一个信号&#xff08;Signal…

【FL0090】基于SSM和微信小程序的球馆预约系统

&#x1f9d1;‍&#x1f4bb;博主介绍&#x1f9d1;‍&#x1f4bb; 全网粉丝10W,CSDN全栈领域优质创作者&#xff0c;博客之星、掘金/知乎/b站/华为云/阿里云等平台优质作者、专注于Java、小程序/APP、python、大数据等技术领域和毕业项目实战&#xff0c;以及程序定制化开发…

因子分析讲解

一、定义 因子分析&#xff08;Factor Analysis&#xff09;是一种常用于多变量统计分析的方法&#xff0c;主要用于数据降维、识别潜在的结构、理解变量间的关系。它通过将一组观察变量&#xff08;通常是高度相关的变量&#xff09;转化为一组较少的、互不相关的因子&#x…

从 JVM 源码(HotSpot)看 synchronized 原理

大家好&#xff0c;我是此林。 不知道大家有没有这样一种感觉&#xff0c;网上对于一些 Java 框架和类的原理实现众说纷纭&#xff0c;看了总是不明白、不透彻。常常会想&#xff1a;真的是这样吗&#xff1f; 今天我们就从 HotSpot 源码级别去看 synchronized 的实现原理。全…

DeepSeek搭配Excel,制作自定义按钮,实现办公自动化!

今天跟大家分享下我们如何将DeepSeek生成的VBA代码&#xff0c;做成按钮&#xff0c;将其永久保存在我们的Excel表格中&#xff0c;下次遇到类似的问题&#xff0c;直接在Excel中点击按钮&#xff0c;就能10秒搞定&#xff0c;操作也非常的简单. 一、代码准备 代码可以直接询问…

Metal学习笔记十一:贴图和材质

在上一章中&#xff0c;您设置了一个简单的 Phong 光照模型。近年来&#xff0c;研究人员在基于物理的渲染 &#xff08;PBR&#xff09; 方面取得了长足的进步。PBR 尝试准确表示真实世界的着色&#xff0c;真实世界中离开表面的光量小于表面接收的光量。在现实世界中&#xf…

zabbix“专家坐诊”第277期问答

在线答疑:乐维社区 问题一 Q&#xff1a;这个怎么解决呢&#xff1f; A&#xff1a;缺少这个依赖。 Q&#xff1a;就一直装不上。 A&#xff1a;装 zabbix-agent2-7.0.0-releasel.el7.x86 64 需要前面提示的那个依赖才可以装。 问题二 Q&#xff1a;大佬&#xff0c;如果agen…

让单链表不再云里雾里

一日不见&#xff0c;如三月兮&#xff01;接下来与我一起创建单链表吧&#xff01; 目录 单链表的结构&#xff1a; 创建单链表&#xff1a; 增加结点&#xff1a; 插入结点&#xff1a; 删除结点&#xff1a; 打印单链表&#xff1a; 单链表查找&#xff1a; 单链表…

图像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image

图像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image 文章目录 图像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image主要创新点模型架构图生成器生成器源码 判别器判别器源码 损失函数需要源码讲解的私信我 S…

指纹细节提取(Matlab实现)

指纹细节提取概述指纹作为人体生物特征识别领域中应用最为广泛的特征之一&#xff0c;具有独特性、稳定性和便利性。指纹细节特征对于指纹识别的准确性和可靠性起着关键作用。指纹细节提取&#xff0c;即从指纹图像中精确地提取出能够表征指纹唯一性的关键特征点&#xff0c;是…

泵吸式激光可燃气体监测仪:快速精准守护燃气管网安全

在城市化进程加速的今天&#xff0c;燃气泄漏、地下管网老化等问题时刻威胁着城市安全。如何实现精准、高效的可燃气体监测&#xff0c;守护“城市生命线”&#xff0c;成为新型基础设施建设的核心课题。泵吸式激光可燃气体监测仪&#xff0c;以创新科技赋能安全监测&#xff0…

HTML label 标签使用

点击 <label> 标签通常会使与之关联的表单控件获得焦点或被激活。 通过正确使用 <label> 标签&#xff0c;可以使表单更加友好和易于使用&#xff0c;同时提高整体的可访问性。 基本用法 <label> 标签通过 for 属性与 id 为 username 的 <input> 元素…

数字万用表的使用教程

福禄克经济型数字万用表前面板按键功能介绍示意图 1. 万用表简单介绍 万用表是一种带有整流器的、可以测量交、直流电流、电压及电阻等多种电学参量的磁电式仪表。分为数字万用表&#xff0c;钳形万用表&#xff0c; &#xff08;1&#xff09;表笔分为红、黑二只。使用时黑色…

Python 爬取唐诗宋词三百首

你可以使用 requests 和 BeautifulSoup 来爬取《唐诗三百首》和《宋词三百首》的数据。以下是一个基本的 Python 爬虫示例&#xff0c;它从 中华诗词网 或类似的网站获取数据并保存为 JSON 文件。 import requests from bs4 import BeautifulSoup import json import time# 爬取…