甘肃建网站,长沙微信网站建设,垂直电商平台有哪些?,管理咨询公司名称仓库:https://gitee.com/mrxiao_com/2d_game_3
回顾 game_asset.cpp 的创建
在开发过程中#xff0c;不使用任何现成的游戏引擎或第三方库#xff0c;而是直接基于 Windows 进行开发#xff0c;因为 Windows 目前仍然是游戏的标准平台#xff0c;因此首先在这个环境中进行…仓库:https://gitee.com/mrxiao_com/2d_game_3
回顾 game_asset.cpp 的创建
在开发过程中不使用任何现成的游戏引擎或第三方库而是直接基于 Windows 进行开发因为 Windows 目前仍然是游戏的标准平台因此首先在这个环境中进行教学。然而代码是以高度可移植的方式编写的社区的许多成员已经成功将其移植到 Linux 和 Mac 等系统上甚至有人让它在 PlayStation VR 上运行这一过程非常有趣。因此在开发过程中始终保持代码的可移植性以便最终能轻松将游戏移植到多个平台这也是目标之一。
目前正在进行的是对游戏中资源资产assets的扩展包括如何存储资源、如何处理资源以及如何对资源进行标记以便最终能够创建一个资产包文件pack file。资产包文件的设计目标是让资源管理更加高效使得能够批量导入资源而不必手动逐个管理。在这一过程中需要定义资源的组织方式、资源的查找机制以及如何让整个资源系统更加灵活易用。
现在处于这一探索过程的后期阶段预计到下周末之前可以完成所有相关的探索工作并开始对系统的细节进行最终的确定。目前仍然在做一些实验因为尚未完全确定资源系统的所有需求因此需要不断测试如何更高效地从资源系统中获取资源以及如何管理资源以确保初步设计是合理的。只有在确保合理之后才会正式确定资产包文件的具体格式。
回顾昨天的进展创建了一个新文件虽然在 Game Hero 的开发过程中很少创建新文件但为了更好地组织和管理资源相关的代码决定新建一个文件来专门处理临时资源工作以便更专注地优化资源系统并将其转变为更加健壮的正式定义。目前一切运行良好不过由于日常事务繁多暂时不太记得昨天具体进行到哪里了。
回忆一下昨天最后完成的工作是临时实现了一种方式使得可以从指定的资产 ID 中获取第一个位图bitmap。这样可以测试资产表是否正常工作测试结果表明它们运行得很好。因此接下来的任务是进一步扩展这一概念。
目前有两个可能的方向
处理数组array资源的管理处理命名资源named elements即包含多个部分的复杂资源
首先决定优先处理数组资源的管理。至于原因暂时不太确定但直觉上似乎可以用一种更直接的方式来处理这两种情况而不需要分别进行特殊处理。因此先从较简单的部分开始也许能够找到一个通用的解决方案来处理所有类型的资源。
让数组化的资源通过资源系统
接下来将从数组化的资源管理入手并让这些数组化的资源直接通过资产系统处理而不是像现在这样单独提取出来。当前的实现方式是直接从资源数据中提取数组化的资源而目标是让它们通过资产系统进行管理以便更好地组织和优化资源加载流程。
为此首先需要弄清楚如何实现这一点。回顾现有的资产类型已经具备了管理多个资源的概念因此理论上不需要对系统进行太多调整来支持多个资源的管理。只需要将这些资源正确地添加到资产表中使其能够通过资产系统进行访问和管理。
目前的资源包括
资产 Grass草地资产 Tuft草丛资产 Stone石头
接下来需要在资产系统的其他部分将这些资源通过系统加载进来而不是单独处理。可以开始尝试通过资产表将它们整合进系统以确保所有资源都能统一通过资产系统进行访问和管理。
提供稳定的方式来定义 AlignX 和 TopDownAlignY 以及位图对齐
在正式将数组化的资源管理集成到资产系统之前有一个更基础的调整可以先进行以便后续操作更加顺利。目前的代码仍然使用硬编码的方式来加载位图资源依赖 switch 语句进行选择而最终版本显然不希望继续使用这种方式。因此下一步的目标是将这部分代码重构使其更加稳定和可扩展。
当前的资源加载过程中位图需要定义 AlignX水平对齐和 TopDownAlignY垂直对齐这些信息是必要的。为了让整个系统更加统一需要替换掉这些固定的对齐方式使用一个更通用的对齐方案。现有的代码存在一定的割裂性比如有的资源对齐方式是 AlignPercentage {0.5, 0.5}而有的资源使用其他方法进行对齐。之前这样做的原因是默认希望位图的锚点在中心而计算中心点的前提是需要先加载位图。但现在随着资产包系统的构建这种方式需要调整。
新的资产包系统将允许在打包资源时提前计算并存储对齐信息而不需要在加载时再进行额外计算。资产包的构建流程会包含一个资源处理管线在打包时就可以预先定义 AlignPercentage这样在运行时只需要直接读取这些预计算的对齐信息即可。
在 RenderGroup 相关代码中位图数据实际需要的仅仅是 AlignPercentage因此可以直接在资源加载时指定 AlignPercentage彻底移除基于位图大小计算对齐的逻辑避免额外的依赖关系。
为了完成这项调整首先需要确定现有位图的对齐参数。最直接的方法是利用调试器读取当前加载的资源的对齐值。由于某些位图的默认对齐已经是 {0.5, 0.5}因此重点需要关注的是那些使用特殊对齐方式的资源。
在进行调试时需要先运行游戏确保所有资源都被正确加载然后再读取所有位图的对齐参数。这样可以确保所有数据都已经就绪便于在后续代码调整时使用这些参数进行替换从而完成 AlignX 和 TopDownAlignY 的移除工作。 移除多余的资源类型
为了确保所有位图资源正确加载还需要在最后额外调用 LoadAsset 来加载所有资源避免遗漏。因此需要为每个位图资源调用 LoadAsset例如Asset_Tree、Asset_Rock、 和 Test 等。然而检查代码时发现 TestBackground 资源已经不再使用因此可以直接移除。此外还发现 Asset_Stairwell 和 Asset_TestBackground 这两个资源似乎也未被使用因此也可以清理掉。
最终当前仍在使用的资源包括 AssetTree 和 AssetSword其他的可能已经被移除或者未被引用。为了确保加载逻辑的正确性需要确认 AssetSword 是否仍然在代码中引用。
在完成所有资源的加载后可以查看它们的具体对齐参数。由于这些资源数量较少可以直接逐个检查对齐值并将其复制到代码中作为标准设定。这样就可以在后续的资产管理系统中统一使用这些参数确保对齐方式的一致性。 在调试器中读取位图对齐信息
现在需要对资产进行检查确保所有位图正确加载。当前有 8 个位图资源因此需要逐个查看它们的加载情况。
在检查过程中发现其中一个位图未被加载但这是 Asset_None即表示“无资产”的占位符符合预期。除此之外所有其他位图均已成功加载包括 Bitmap1、Bitmap2 和 Bitmap3。这些位图分别对应于 Shadow、Tree 和 Sword 资源与定义的资产名称相匹配说明资源加载过程是正确的。
下一步可以继续调整这些资产的对齐参数并确保所有资产都能通过新的系统正确访问和使用。
激活Sword 发现有段错误 用 AlignPercentage 替换 AlignX 和 TopDownAlignY
现在可以为这些位图资源设置对齐参数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 统一处理对齐计算移除了 TopDownAlign 和 HasAlignment大幅减少了额外的判断和复杂性。
最终调整后的代码保持了正确性所有的对齐计算均按照预期工作整体逻辑更加清晰便于后续进一步扩展和优化。
在没有打包文件的情况下考虑如何清理当前代码
接下来需要进行的优化是消除 test_hero_shadow.bmp 和 align_percentage 相关的代码使其更加简洁。不过目前由于 pak 文件尚未实现因此短期内可能无法彻底优化只能进行一些过渡性的调整。
在 pak 文件最终实现后文件名等信息将被移除因此现在的处理方式只是临时方案。尽管如此仍然可以先引入一个 bitmap_table 作为过渡使得后续优化更加顺畅。
整体而言虽然目前的调整仍然受到文件名管理的影响但这不会是一个长期问题。在现有结构下逐步优化代码的可读性和清晰度仍然是有意义的。
修改 game_asset.h 中 asset_bitmap_info 结构
目前的代码设计中有一个 asset_bitmap_info 表示加载的位图信息。为了暂时应对当前的需求决定在代码中模拟出一个位图信息表。这张表与最终将会存在于 pak 文件中的版本略有不同但目的是为了在当前阶段能使用相似的数据结构。
每个位图都包含位图信息且数量相同。虽然可以将这些信息直接存储在 bitmap 结构或 assets slot 结构中但目前并不打算这样做因为还不确定这些数据是会一直加载在内存中还是会按块加载出来。因此决定将位图信息表作为一个单独的数组并且使其与其他数据结构并行以便保持灵活性。
这种设计方式能够确保代码在处理文件数据时更具灵活性同时避免在不确定数据处理方式的情况下将其合并到其他结构中。 分配大量位图进行测试
目前的代码是为了测试目的计划先手动创建和分配大量的位图。目的是为了确保能够处理大量的位图数据。虽然最终这段代码会被替换掉数据将从磁盘加载但目前的测试代码不关心实际的位图数量和加载方式只是为了确保当前能处理和分配大量位图。
引入 DEBUGAddBitmapInfo
接下来创建了一个调试用的函数 DEBUGAddBitmapInfo用于模拟资产打包器的功能。这个函数会初始化一个名为 DEBUGUsedBitmapCount 的变量初始值为零用来模拟在打包过程中每次添加一个位图时的计数。
每次添加位图时DEBUGAddBitmapInfo 会接收文件名和对齐百分比等信息然后将这些信息用来构建位图的相关信息。位图的相关信息会存储在一个结构体中包含了文件名和对齐百分比等必要数据最终返回一个 ID用来标识这个添加的位图。
该过程还包括一些检查比如确保添加的位图数量不超过设定的最大位图数量。这些代码纯粹用于测试因此不需要考虑实际的位图支持数量最终会通过从包文件中加载数据来确定实际所需的位图数量。 在 AllocateGameAssets 中动态创建资源
现在我们可以开始将资产动态地创建模拟打包器的工作流程。为此首先要做的是将资产的计数与位图的添加过程关联起来。具体来说创建一个新的函数该函数会在每次调用时动态地处理资产的添加。
此过程会基于已有的资产比如树的阴影和剑的位图进行模拟。我们通过增加一个新的变量来追踪资产数量这个数量应该与实际的资产计数一致。通过这种方式系统可以根据实际的需要动态增加资产。
接下来通过引入一个类似 AddBitmapAsset 的函数每次添加位图时我们都会传入位图的详细信息。这些信息会被传递给每个资产确保每个资产能够正确地添加到系统中。通过这种方式模拟过程会像实际从包文件加载资产一样并且支持多次调用。
这个方法最终会使得每次调用 BeginAssetType 时都能够添加一个新的资产并且更新相关的计数和结构直到所有的资产都被正确加载和处理。这也为未来的资源打包过程提供了清晰的基础尽管现在只是模拟这一过程。 提取 BeginAssetType、AddBitmapAsset 和 EndAssetType 为函数
现在我需要定义一些函数来实现当前的功能。当前我们使用的调试方法非常有效所以我决定继续沿用这种方法直到我们能够加载一个实际的资产包文件。当包文件加载完成后这些调试代码就可以去掉。
接下来我的目标是使这些函数能够执行当前的操作。我们已经定义了一些函数像 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以确保干净的状态。接着我们为调试用的位图信息数组分配一个槽位IDslot id并在这个位图信息数组中添加一个新的位图信息。因为0号槽位被预留了所以这个新位图信息被添加到1号位置。
我们继续看代码取出了刚刚分配的位图信息的ID设置了它的文件名FileName和对齐百分比align percentage然后返回了这个ID。这个ID会被记录下来对应到刚刚设置的位图信息。我们检查下来觉得这个过程看起来没问题。
最后我们处理“end assets”结束资源分配的部分。这一步主要是完成整个流程。我们确保“debug asset count”调试资源计数至少等于我们添加的资源数量然后把资源类型的计数重置为0表示这个类型的资源处理完毕。
整个过程就是这样我们通过逐步检查确认了资源分配的逻辑是合理的而且通过预留0号槽位让代码更清晰。现在我们觉得这个实现应该能正常工作。 运行游戏
现在可以运行程序了。经过测试程序运行正常我们的目标已经实现场景中已经成功加载了小羊和小树正如我们所期望的那样一切重新加载工作正常。 接下来准备继续推动工作进展利用现在已经具备的功能继续进行后续的开发和测试。
让 Grass、Tuft 和 Stone 位图使用新系统
现在我们进行下一步工作目标是将草地tufts加入到系统中。我打算开始一个资产类型并访问未处理的草地资产类型grass。接着我会用我们已经实现的系统来处理这个草地图给它命名并添加。
这一次我会添加两个草地图因为同一个资产类型下有两种不同的草地图而不像之前的情况只涉及到一个草地图。我会让系统支持这个需求并确保它能正常工作。
之后我会将草地图的处理从这里移除并将其转化为需要的格式因为我们已经在资产中加入了草地资产。这将促使编译器对函数进行正确处理。
在 game.cpp 中引入基于 ID 选择资源的方法
接下来我们将让编译器强制执行一些操作并回到之前处理草地资产的地方。我们当时做了一个随机选择的操作这次我们不会做特别复杂的处理而是稍微简单一些。具体来说我会做以下几点
我先将石材相关的代码注释掉然后将草地图的部分改成使用随机选择的资产ID。我打算从游戏资产中随机选择一个资产ID具体来说就是从所有草地图资产中选择一个。为了实现这一点我会使用一个随机数生成器它可以从游戏资产中选择一个资产这些资产必须符合草地类型的条件。然后绘制时我们将使用基于ID的方式来渲染而不再使用基于位图的渲染方法。
通过这种方式我们将从可用的草地资产中随机选择一个并使用其ID进行渲染。
在 game_asset.cpp 引入 RandomAssetFrom
接下来我们需要将随机选择的资产ID转换为能够加载资产的机制。这基本上类似于之前用来获取第一个位图的操作但这次我们是根据随机选择的方式来获取资产。
具体来说
首先我们将从资产类型中选择一个随机的资产。原本的做法是直接选择第一个位图而现在我们要做的是从草地资产类型中随机挑选一个。为了实现这个功能我决定使用一个随机选择的函数并传入合适的参数这样我们就能够从草地资产的范围内随机选择一个。通过给定一个范围我能够选择一个随机的值在这个范围内查找并返回对应的资产ID。最后我们会将选择的随机资产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 之后加载资产的逻辑被触发而这可能导致了树的显示问题。
具体来说我们怀疑地面块的渲染可能影响了树的渲染流程导致树的位图数据未正确加载或被覆盖。以下是可能的原因
地面块的渲染逻辑影响了树的绘制顺序可能导致树的图层被地面块错误覆盖。PushBitmap 操作影响了资产加载导致树的纹理被错误地替换或移除。某些数据结构的错误修改可能在执行 PushBitmap 时导致树的相关数据发生了变化。
下一步我们需要深入检查 PushBitmap 的实现确定它是否影响了树的渲染流程。可以尝试
手动检查资产加载顺序确保树的纹理仍然存在于资产列表中。逐步执行 PushBitmap 操作观察哪一步导致树的消失以确定根本原因。调整绘制顺序先渲染树再渲染地面看看是否能够恢复正常显示。
总的来说问题的核心似乎是地面块的渲染与树的渲染存在某种冲突导致树在某个阶段被错误移除或覆盖。
确定 LoadBitmap 是否是线程安全的
仔细回顾代码后我们开始思考一个新的问题加载资产的过程是否是线程安全的最初并没有特别关注这个问题因此需要重新检查代码确保在多线程环境下不会导致竞争条件或数据损坏。
我们首先查找了 load asset 的调用情况发现它在渲染过程中被调用。在分析 load asset 相关代码时注意到其中涉及 LoadBitmap 这一函数。进一步跟踪调用链后发现 LoadBitmap 可能是导致问题的关键。
为了确保多线程友好性检查了 LoadBitmap 的实现发现其中调用了 PlatformAddEntry 来处理位图加载。从代码结构来看这部分逻辑似乎已经做了线程安全处理因此理论上不应该引发并发问题。
不过为了进一步确认我们需要考虑以下几点
是否有其他代码路径调用 LoadBitmap 而未进行线程同步PlatformAddEntry 是否真正保证了线程安全是否可能存在资源竞争如果多个线程尝试同时加载相同的资产是否可能导致渲染异常
接下来的调试重点是
添加日志或断点检查 LoadBitmap 在多线程环境下的行为确认是否存在并发问题。测试不同的加载顺序看是否影响渲染结果尤其是地面块与树的渲染顺序。尝试手动同步如添加锁或使用线程安全的数据结构观察是否改善问题。
目前的初步结论是代码已经尝试了线程安全处理但仍需进一步验证确保 LoadBitmap 在所有情况下都能正确运行。
使用 asset_bitmap_info 表
在回顾代码后发现一个关键问题虽然之前已经确定了我们想要的位图 ID但并没有真正存储它。这意味着虽然所有逻辑都已经实现但最终并没有正确地应用所获取的数据。 问题分析
我们确定了位图 ID但没有真正将其存储或使用。代码中仍然保留了对 AlignPercentage 和 FileName 的引用但实际上这些信息应该来自 bitmap info table不再需要额外存储。忘记移除旧逻辑导致无效数据仍在代码中存在从而影响了渲染流程。
修正方法
移除不必要的变量例如 AlignPercentage 和 FileName因为这些信息已经存储在 bitmap info table 中不需要重复存储。直接使用 bitmap info table 来获取位图信息而不是依赖旧的逻辑。确保正确存储并使用 bitmap ID以便后续可以正确地渲染对应的位图。
反思
这次错误的本质是没有完全清理旧代码导致逻辑虽然更新了但仍然受旧代码的影响最终使得正确的数据没有被使用。这提醒我们在重构或优化代码时不仅要加入新逻辑还要确保旧逻辑彻底清理避免混乱和无效的计算。 重新启用 PushBitmap 在 RandomAssetFrom 中使用 Choice 而非 Count
目前发现了一个逻辑错误导致程序错误地访问了无效内存区域。
问题分析 错误操作错误地增加了 count 值 代码逻辑本来是从 group_assets 中随机选择一个索引 choice然后使用这个索引来获取 SlotID。但是在计算 SlotID 时错误地加上了 count这会导致索引超出数组范围进入未定义行为的区域“no man’s land”。 错误的后果 访问数组时超出了合法范围可能导致 读取到未初始化的内存数据错误。发生非法访问导致程序崩溃。 这个错误让之前所有的随机选择逻辑都变得无效。
修正方案 去掉 count 偏移 只使用 choice 直接索引 group_assets而不是 choice count。确保 choice 的范围是 0 到 count-1 之间避免溢出问题。 增加边界检查 在使用 choice 时添加 assert(choice 0 choice count)确保索引合法。在 group_assets 访问时确保数据结构中确实有足够的元素。 添加调试信息 输出 choice 和 count以便检查它们是否在合理范围内printf(Choice: %d, Count: %d\n, choice, count);总结
之前的错误是因为在计算 SlotID 时错误地增加了 count导致数组访问超出范围。修正方法是直接使用 choice 作为索引同时增加边界检查确保数据索引在合法范围内以防止出现未定义行为。 运行游戏疑惑为何失败
目前的问题似乎还没有完全解决尽管做了一些修正。具体来说某些资产的 SlotID 始终为零这导致了对这些资产的处理不正常。
问题分析 SlotID 为零的现象 本来应该随机从可用的资产中选择一个但现在某些资产的 SlotID 一直为零。这意味着这些资产的选择过程中存在错误可能是计算方式不正确或者数据访问时发生了意外的行为。 为什么会失败 当 SlotID 为零时程序会尝试加载一个无效的位图bitmap。这种情况下load bitmap 会立即失败因为没有有效的图像数据。但这并不解释为什么所有的 get first bitmap id 调用都会失败或者为什么会影响到整个加载过程。
假设的原因 无效资产导致加载失败 如果尝试加载的资产无效例如SlotID 为零加载过程就会失败。虽然理论上这应该不会影响队列的其他操作但可能存在某些资源或数据加载的机制相互影响导致队列未能正确处理。 队列未被正确填充 当加载无效位图时可能导致队列没有按预期添加其他有效的加载任务。因此虽然没有数据被加载到队列但队列本身可能在某种情况下未能正常工作。 其它潜在的线程问题 如果涉及到多线程操作可能会有线程同步问题导致某些任务被跳过或错误执行。
下一步行动
检查 SlotID 的来源和计算过程确保其值在合法范围内不会总是为零。在加载位图时添加更多的调试信息输出加载失败时的状态检查是否存在无效资产导致的失败。测试队列和线程机制确保在加载失败时其他任务仍然能够正常进行并且队列能够正确管理和处理任务。
总结
目前的问题仍然与 SlotID 为零以及随之而来的加载失败有关可能与无效数据或队列机制相关。接下来需要对这些方面进行详细的调试和测试以确保程序能够在各种情况下正确运行。
完成资源数组设置
首先需要完成软件的修复工作以确保系统能够正常运行特别是在处理资产映射时。通过修复和调整相关的代码现在可以确保资产例如石头等能够正确加载并映射。这样一来程序就能正常运行所有的功能也可以顺利操作确保这些资产在游戏中能够正确显示并发挥作用。
在 game.cpp 设置 Stamp 使其从 RandomAssetFrom 获取
现在程序可以正常运行并且我们能够正确地选择随机资产来进行渲染。通过使用随机选择我们可以从草地数组中选择一个随机的资产作为“stamp”否则就从石头数组中选择。这段代码实现了随机选择确保了程序能够根据需要选择草地或石头的资产。接下来“stamp”将会使用这些随机选择的资产确保能够渲染不同的草地或石头类型。同时程序中的每个步骤也都正确地通过随机选择方式进行处理确保了功能正常。 运行游戏注意地面块缺失
现在我们已经正确地翻译了所有内容但依然存在一个bug问题可能出在没有正确刷新某些数据或者做了一些不该做的操作。虽然大部分功能正常但这个问题仍然需要解决。即使这样有时会很幸运第一次尝试就能顺利运行但大多数时候还是需要调试和修复一些小问题。时间虽然所剩不多但问题已经明确接下来需要进一步检查和修复以确保一切正常工作。
在 game.cpp 编写 AllResourcesPresent 的失败情况处理
问题出在我们处理任务时出现了一个明显的错误。当加载失败且任务尚未完成时我们错误地继续保留了这个任务。实际上我们应该在任务失败时中止它而不是让任务继续伪运行。这种情况导致任务堆积从而无法加载任何新内容。关键问题在于任务没有被正确中止或清除造成了资源的浪费和后续无法加载其他内容的情况。所以正确的做法是在失败时立即终止任务防止任务继续占用资源。
在调试器中检查 FillGroundChunk 我们正在检查任务是否能正确结束并释放内存。我们尝试调用任务相关的内存清理功能但机器报告说无法完成此操作。于是我们开始进行一些调试尝试通过设置断点来观察任务是否按预期执行看看问题出在哪里。发现目前任务尚未正确设置所以问题可能出在任务的初始化阶段。
在 FillGroundChunk 顶部设置 RenderGroup、Buffer 和 Task
我们发现了一个问题之前没有正确设置任务的相关信息。现在决定在渲染时正确设置这些信息确保缓冲区任务可以被正确处理。实际上在调用平台的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 而不是 int32 或 uint32”
在编程中使用 size_t 的主要原因是为了在不同的系统架构如 32 位或 64 位上正确表示内存大小而不依赖具体的整数类型如 int32_t 或 int64_t。
int32_t 和 int64_t 分别代表 32 位和 64 位的整数类型但它们并不随平台的不同而自动调整。因此在编写需要处理内存大小的代码时如果直接使用 int32_t 或 int64_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 这样的技术实际上并没有为操作系统带来显著的好处但却增加了极大的复杂性和维护成本。
如果微软当时能够更好地控制开发过程只有那些具备足够经验和技能的团队才有机会对操作系统核心进行修改那么今天的操作系统可能会更加简洁高效不会面临如此多的问题。控制系统变更的文化至关重要。如果开发团队能够对每一个新功能的引入保持极高的标准避免无关紧要的功能过度膨胀那么操作系统将能够保持更好的稳定性和可靠性而不会被不必要的复杂性拖慢。