游戏引擎学习第63天

回顾如何获得初步使用代码以避免糟糕的设计

我们一开始做的一个大事就是要把足够的代码写下来,在一个原始的形式下,以便了解一切应该如何结构化。这样当我们开始编写代码时,能够避免陷入设计上的误区,避免过度设计或者重新设计错误的东西。很多时候,写使用代码是非常重要的,这样能够确保整个过程的正确性。因此,我们在整个过程中一直在这么做。

我一直在关注实体的一些更高级特性,比如低频和高频实体的设置,以及如何扩展到大量的实体等。假设我们有这个需求,但不太清楚如何构建代码以确保其美观和简洁。对我来说,这并不算问题,关键是要先写出足够的代码,以便可以慢慢地看到如何解决问题并逐步发展出好的解决方案。

高/低频实体的设计约束

在思考代码架构时,我们意识到有两个主要约束条件需要考虑。第一个约束是关于处理超大世界的能力。对于非常大的世界,单一浮点数的精度会迅速下降,因此需要特别处理浮点精度问题。这个问题并非每个引擎都必须解决,但我们希望展示如何在代码中处理它,因为这在设计大世界时是不可避免的。

第二个约束是关于大规模实体计数的能力。我们需要确保能够处理大量的实体。如果每个实体都单独处理并不断运行世界中的所有实体,系统将无法扩展,尤其在实体数量非常大时。

这两个问题看似互相矛盾,但它们实际上是相互联系的。在之前的思考中,虽然隐约意识到这两个问题的交集,但没有深入考虑它们如何共同影响架构。现在,意识到这两者需要同时得到解决,才能设计出一个既能处理大规模世界又能管理大量实体的良好架构。

大wolds的浮动精度问题

浮点精度问题主要是关于如何处理非常大的世界和大量的实体。在处理这些世界时,如果使用32位的浮点数,它的精度将不足以处理如此庞大的数据。因此,虽然使用64位的浮点数(即双精度浮点数)能够提供更高的精度,但它带来了一些问题。

首先,使用双精度浮点数会导致计算速度减慢,通常会是原来的一半,这在性能要求较高的场合并不理想。其次,一些平台可能并不完全支持64位浮点数,或者其支持方式并不高效。因此,尽管双精度浮点数可以解决精度问题,但在实际应用中可能并非最佳选择。

为了克服这些问题,使用32位浮点数进行计算时,通常会选择一个局部原点,通过相对于该原点的局部坐标来处理实体的位置。这样,即使是巨大的世界,也能通过局部化处理避免精度损失。

在实际实现中,低频实体和高频实体的处理也有所不同。高频实体的定位和计算更为精确,可以使用较高精度的数据,而低频实体则使用较简化的方式进行处理。总体上,采用局部坐标系统能够有效避免精度损失,同时保证性能的优化。

扩展到大量实体的难题

另一个问题是如何应对超大规模实体的缩放。尽管之前已经开始着手解决这个问题,但其核心是如何管理数量庞大的实体,特别是在处理低频和高频实体时。低频实体通常是以较低的频率进行处理的,它们包含的实体数据则是以简化的方式存储,而高频实体则需要更精确的数据和更频繁的处理。

具体来说,对于低频实体,系统希望能够继续在这些实体上执行一些操作,即使玩家不在这些实体附近。例如,如果一个生物或人物正在从某个地点取走物品并将其带回来,系统应该继续追踪这些操作,并且当玩家回到这些区域时,能够看到这些事件的后果,例如苹果被偷走并放到某个地方。

为了实现这一目标,系统将低频实体放在一个较低频率的集合中,并进行低速处理。这意味着,虽然这些实体的状态变化发生得较慢,但它们仍然可以在后台继续进行操作,而不需要实时更新。而高频实体则集中在玩家视野范围内,它们需要更频繁、更细致的计算和更新。

这种方法可以支持大规模世界中的动态事件,使玩家能够看到更具深度的世界变化,同时避免对性能的过度消耗。

意识到问题 - 错误的低频实体思考方式

在处理低频实体时,最初的想法是基于范围来进行处理,但这种思维方式被认为是错误的。这种方法要求为每个实体编写不同的代码,导致需要重复工作,这显得不太合理。最初的计划是在一个范围内一次处理一千个实体,但这种做法没有实际意义,因为它并没有提供任何优化或简化代码的好处。

后来意识到,实际上不需要这样基于范围的处理方式,因为没有必要按照实体的索引范围进行操作。这样做不仅会增加额外的复杂性,还可能导致代码重复。最终的目标是避免这种重复工作,并寻找更合理的方式来处理这些低频实体。

意识到解决方案 - 仿真区域

在处理低频实体时,原本考虑的方案是基于范围的处理,这意味着对每个实体的代码需要重复编写。这种方法被认为并不理想,因为它不仅增加了工作量,还会导致代码的重复和冗余。因此,转而采用了更简单的方案:使用一个虚拟相机,它在世界中不断移动,模拟不同区域的状态。

每一帧的处理都是基于能够看到的部分,重点模拟那些应该持续更新的区域,而其他部分则以较低的频率进行模拟。通过这种方式,避免了处理低频实体的复杂性,取而代之的是将其作为一种存储格式,存储实体的低细节信息。通过这种方式,实体在需要时可以从低频状态转回高频状态,而无需重复写代码来处理不同频率的实体。

这种方法带来了一种新的架构思路,即通过创建模拟区域来有效地管理和处理实体。每个模拟区域是自包含的,包含自身的高优先级列表和边界信息。模拟过程会在这些区域内进行,处理完后,再将数据映射回低频状态。这种方式不需要跨越帧边界,也避免了持续存储和冗余操作,从而简化了编程过程。

最终,这种结构能够保证所有实体的模拟处理以统一的方式进行,且不需要为低频和高频实体编写不同的代码,确保了系统的高效性和简洁性。这种架构不仅解决了之前的复杂问题,还为后续的优化和扩展提供了基础。

开始代码重组

在设计系统时,遇到了一些挑战,尤其是涉及到边界问题。在将实体映射到活动区域时,如果实体位于区域的边界附近,它们可能会越过边界。这种情况下,实体会被允许跨越边界,但在模拟过程中,边界外的实体无法进行碰撞检测,也无法被高频率模拟系统更新。为了处理这种情况,需要确保模拟只发生在一个保守的边界内,这个边界的范围小于实体的移动距离,防止实体越界。

为了解决这一问题,设计了一个二级边界,所有实体都被拉入这个边界以内。这样,处于边界外的实体将不会被允许移动,并且它们依然可以作为活跃实体存在,并在其他模拟区域中继续被处理。确保系统不会出现实体移动到边界之外的情况,是设计中的一个关键步骤。

尽管这个过程需要重新组织代码,可能会稍微拖慢进度,但通过这种方法,能够确保模拟区域的稳定性和高效性。整体来说,这个优化能有效避免实体越界并保持系统的流畅运行。

sim_region 结构体

首先,我们创建了一个game_sim_region.h,称为"game_sim_region",用于处理系统的模拟任务。这个区域将用于存储并操作相关实体。为避免不必要的字符引入,某些区域将不需要额外的字符支持。

在这个区域内,实体将被简单地称为"平面实体",这些实体将是模拟系统中的核心内容,主要在该区域内进行操作。每个实体都会有一个计数值,并被存储在一个数组中以供使用。为了优化结构,我们考虑到可能存在最大实体数量的限制,并可能为此预留一定的空间。

在实现过程中,我们可能不需要对这些结构进行过多修改,但为了预防未来的需求变化,先保持这些预留空间的设置。最终,所有这些将被整合到同一个区域内进行统一管理和操作。

如何更改 SetCamera

目前的设计将不再依赖于高频空间和低频空间之间的映射。每个实体会在每一帧中进进出出高频空间,而不需要额外的偏移或从高频空间转移到低频空间。这种处理方式即使当前可能效率较低,也计划在未来根据实际表现进行优化。

目前不考虑过早的优化,优先实现基本功能,并将在后续根据需要进行调整和优化。

通过将 sim_region 中的实体映射到低空间来EndSim

计划中将实体分配到低频空间,并通过映射将其存储在该区域。这个过程包括遍历所有能量计数,并为每个实体设置一个指针。每次迭代时,指针都会向前移动,并将实体从高频空间映射到低频空间。

实体将被存储在一个支持的结构中,类似于后备存储,并根据需要加载和存储。此过程将保持一致,每次都以相同的方式发生,避免人工操作的错误。通过这种方式,实体的存储和加载将被精确控制,确保稳定性。

通过将实体映射到 sim_region 来BeginSim

在这个过程的开始,我们将使用相同的概念,通过传递一个区域来启动模拟过程。具体来说,这个区域包含了所有相关的实体。模拟开始时,我们会传入该区域的边界(矩形)信息,然后进行区域内的实体处理。

每个区域都有一个中心位置和边界,模拟区域的平衡会参考相机平衡,并通过中心和区域边界来定义。这一过程需要特别注意高频率的位置,确保它们适应具体的模拟任务。

当区域开始时,模拟会基于这个区域内的实体进行处理,而这些实体是与当前区域或相机视图相关的。通过传入相应的矩形边界,系统可以准确地计算和处理需要更新的实体数据。

简化实体结构

在这个过程中,低实体不再需要指向高频实体,因为高频实体的处理已经不再相关。通过简化实体结构,系统将只处理一种类型的实体,避免了复杂的指针结构。

这样一来,我们不再需要双指针的机制,实体只需指向其存储位置。这个变化使得系统更加简洁和高效,也符合最初的目标,即更好地管理实体的存储和访问。

尽管系统已经变得更加清晰,但在进一步迁移代码时,仍需谨慎。虽然有些结构可能最终会被去除,但在完成迁移前,可能会暂时保留一些现有的结构,以防止过快变动导致不可预见的问题。

关于 BeginSim 的更多细节

在当前的系统中,我们不再需要检查实体是否需要被移动,因为所有的实体都会被拉进来,直到它们处于正确的状态。接下来,我们需要处理实体的位置,确保它们被有效地存储并可以快速访问。

关于内存划分,我们还不清楚如何最优地组织这些实体数组。是否需要将实体数组保持连续性尚不明确,但目前不打算过多考虑这个问题。我们优先考虑代码的执行速度,尤其是在进行模拟时。希望所有的代码能够尽可能高效运行。

我们通过实体数组遍历来访问每个实体的位置,并将其存储在合适的位置。我们使用指针来引用这些实体,并确保它们被正确地添加到所需的区域。这些区域可能与摄像机的视野有关,因此需要检查实体是否在摄像机的视野范围内,确保它们被正确处理。

最终,我们希望优化内存布局,以便在需要时能够快速处理这些实体,并确保它们在正确的位置上。

将实体添加到 sim_region

系统中的新实体会被添加到适当的区域,并进行验证以确保有足够的空间存储它们。我们进行断言检查,确保实体数量不超过允许的存储量,并考虑未来的灵活性。当前,实体数组的处理需要确保资源充足,以避免溢出。

实体被从列表中选取并添加到存储区域后,低压缩版本的实体会被处理。当前,简单地通过验证实体是否能够成功添加,并对其进行适当的清理。清理策略包括清除实体数组中的数据,以避免错误。

对于模拟中的实体,系统会处理存储实体和模拟实体的关系,确保在需要时能够正确转移。实体的存储和模拟位置之间的关系将根据需求进行调整,避免不必要的计算和错误。系统会确保实体的状态可以根据模拟环境的要求进行更新,同时会避免过度复杂化,保证代码简洁有效。

此外,系统还会考虑未来的扩展,允许更灵活地处理实体的存储和模拟。我们可能会根据实际需求调整实体的类型名称,并为新的实体分配适当的存储资源。在此过程中,确保实体在正确的状态下进行处理是至关重要的。

从相机空间位置切换到仿真空间位置

问题的核心是原先的相机空间不再符合需求。由于系统不再使用相机概念,因此通过获取相机空间位置(GetCameraSpaceP)的方式已经不再适用。之前相机空间是基于相机的视角来决定实体的位置,但现在,我们希望根据模拟区域的中心来确定位置,而非相机的概念。

为了满足新的需求,当前的目标是使得模拟实体的位置(P)与模拟区域的起源(origin)关联,而不是依赖于相机。区域的起源可以理解为模拟区域的"中心"位置,而不是由相机决定的位置。这种方式更加符合系统的整体架构,即实体的移动和位置是基于区域中心进行的。

通过使用区域的起源(origin),我们可以有效地定位实体的位置,而不再涉及任何相机的概念。起源(origin)会直接影响到实体的空间定位,使得该区域内的所有实体都能基于相同的参考点进行模拟。

此外,当模拟区域需要存储一个指向世界的指针时,区域会保存这个指针,以便能够获取世界的相关信息,并进行适当的转换。最终,模拟区域的空间位置(GetRegionSpaceP)将变得更加灵活和合理,符合新的需求。

总之,现在的目标是完全摆脱相机空间的依赖,转而通过区域的起源来计算实体的位置,这样能够更好地支持当前的模拟环境和未来可能的扩展。

调整实体结构

我们现在需要一种方法来处理当前的情况,把所有的东西整理回到一个合理的状态。我们清楚之前需要的一些内容,其中包括一个存储索引,这个索引能够告诉我们每个实体的来源。这些信息是针对同一区域的,但现在它的意义更明确了,不需要过多评论。我们可以在顶部添加一些注释,简要说明这些内容。

目前有一个动画相关的元素,我们暂时不需要增加更多内容。但显然有一些原本就应该被处理的东西,比如速度。速度需要被存储下来,并且在跨越帧边界时保持一致。同样,低频实体也需要存储速度,以确保持续性的逻辑一致性。这部分逻辑的存储需要被纳入低频实体中。

方向信息也需要遵循相同的逻辑处理方式。此外,我们暂时将一些次要内容放置到外部环境中,因为我们的重点将集中在设置这些基础问题上。

在处理这些问题时,我们注意到一些数据,比如zdz,需要进行一定程度的清理。我们已经引入了Vector3,但尚未以逻辑方式处理z的内容。尽管如此,当前处理还是分步骤进行,以免过多内容堆积。整体来说,这些步骤正在逐步推进,并且目前的状态是可接受的。

sim_region 的内存分配

当前需要将工作流程简化,首先清理现有代码,使其能够在设定的范围内正常运行。一个关键任务是分配相同区域的内存资源,并引入瞬态堆栈的概念,这将作为未来进一步优化的基础。目前我们将其作为占位符,稍后再详细处理。

在现有的内存管理中,使用了存储区域的分配方式,我们需要从这些区域中分配必要的空间来存储仿真系统的数据结构。在此过程中,需要初始化一些群体信息,包括区域和当前帧的实体边界。此外,还需要处理一些实体的区域配置,比如区域中的实体数量最大值。

初始化时,实体计数从零开始,根据实际需要逐步增加。由于这些配置具有暂时性,可以使用较大的临时内存池来支持动态需求,从而允许配置更高的最大值。

关于实体管理,现有的数组已经可以存储实体相关信息,因此可以直接利用这些数组来操作实际的实体数据。所有分配的内存都集中在一个特定区域进行管理,所有处理逻辑完成后释放相关内存。这种方式能够确保资源使用的清晰性和高效性。

通过上述过程,建立了一种统一的内存分配与管理机制,从而支持区域范围内的实体管理和仿真系统的运行。

在 EndSim 中保存回低频实体

当前需要实现对低频实体的存储管理,通过将处理后的数据写回低频实体数组来完成。由于已存在相应的代码结构,可以直接利用这些基础。首先需要从存储索引中提取信息,存储索引决定了低频实体的具体位置,不需要考虑高频实体的索引。

初始化时,通过存储索引将数据重新写入低频数组。这一过程确保了实体在每一帧处理后能够正确保存,支持下一帧的仿真。当前的存储机制将数据写入游戏状态,但可以优化为直接存储在世界对象中,以便更清晰地组织和访问数据。

为了实现这一点,需要调整实体存储的目标位置,将低频实体从游戏状态迁移到世界对象中。未来可能需要重命名某些结构体或变量以匹配新的逻辑,但为了迁移过程的便利性,可以暂时保持原有命名。

在这个过程中,操作逻辑包括以下几个步骤:

  1. 抓取存储索引:从存储索引中提取对应的实体信息,这一步确保了操作的正确性。
  2. 实体迁移:将低频实体存储在适当的数据结构中,这里暂时仍使用低实体结构以简化迁移。
  3. 变量调整:在适当的时候重命名变量,例如将"低实体"调整为"存储实体",以更好地反映其功能。

此外,需要注意的是,目前的代码并未关心高频实体的索引,因为目标仅限于低频实体。后续可以通过优化来减少存储操作的冗余或进一步提升执行效率。

以上逻辑的实现为未来的扩展提供了一个框架基础,例如动态调整存储位置或进一步优化数据组织结构。通过这种方式,整个系统可以逐步变得更加清晰和高效。

删除不再适用的代码

目前需要调整代码逻辑,主要集中在实体的更新与存储操作上。之前的代码中没有实现实体的打包,也没有对低频实体进行更新,因此当前逻辑部分已无法正常工作,需要修复。

调整和优化逻辑:

  1. 移除无用代码:

    • 之前的配对实体逻辑已经废弃,因为实体不再成对存在,而是通过指针单向关联。
    • 部分逻辑如强制高频处理已经失去意义,可以移除。
    • 相关旧代码段和概念,如按面积调整频率、未验证的存储等也已被更简化的逻辑替代。
  2. 明确实体位置:

    • 当前需要关注的是调整实体的位置更新逻辑。
    • 将存储索引关联的实体位置重新计算并应用。
    • 更新的重点是通过映射块空间的偏移量,推导出新的实体位置。
  3. 存储和位置映射:

    • 存储索引用于记录实体的初始状态,后续位置更新基于此索引展开。
    • 新位置的计算利用了区域映射和偏移逻辑,将实体从存储空间移动到世界空间的目标位置。
  4. 映射逻辑优化:

    • 从摄像机的参考位置切换到场景中的参考点,例如区域或军团的位置。
    • 通过新的偏移和映射关系,将实体位置与存储状态进行同步。
  5. 下一步改进方向:

    • 实体存储逻辑可以进一步优化,例如将存储的低频实体直接与全局位置关联。
    • 当前逻辑简单直观,但可以在未来通过压缩状态等技术提升性能。

主要步骤总结:

  • 清理冗余代码,移除已废弃的逻辑。
  • 通过存储索引获取实体的初始状态。
  • 计算实体的新位置,并与世界中的目标位置同步。
  • 调整实体存储与更新流程,使其适应新的区域参考框架。
  • 保持代码简洁,为未来优化和扩展打下基础。

当前代码逻辑经过整理后,可以更高效地支持实体的动态更新与低频处理。未来在扩展功能时,可以进一步增强存储结构和位置计算的准确性与灵活性。

将 SetCamera 替换为在主循环中获取 sim_region

我们发现设置摄像头的部分目前几乎没有任何实际作用。为了解决这个问题,首先回顾代码结构,得出结论某些部分实际上可以移除。我们只需要保留关于摄像机区域大小的概念即可。

为此,我们计划移动与摄像机相关的逻辑,简化代码,保留必要的数据。我们通过保存摄像机区域大小的关键信息,删除多余的计算和变量设置,集中处理模拟实体的逻辑。

具体步骤如下:

  1. 移除冗余代码,只保留核心逻辑,即定义和保存摄像机区域的大小。
  2. 定义一个函数,用于初始化模拟区域。我们将摄像机的位置作为原点,并结合先前计算出的区域边界,完成区域的绑定和模拟初始化。
  3. 整合初始化逻辑,使其更清晰地服务于主游戏循环,不再单独作为一个函数。
  4. 在模拟实体的逻辑中填充相关代码,以确保摄像机区域的计算和模拟是连贯的。

最终,我们的目标是将摄像机区域绑定的过程和模拟实体的逻辑结合起来,使其成为主游戏循环的一部分。通过这样简化的方式,代码变得更直接、更高效,同时移除了不必要的复杂性和重复操作

将相机位置更新从主循环移到 EndSim

当前处理逻辑中,我们需要更新摄像机的位置,使其准确反映实际应该跟随的区域或实体。为此,我们提出了一个关键点,即在模拟端执行时,确保摄像机的位置与最新状态保持一致。

具体步骤如下:

  1. 更新摄像机位置

    在模拟逻辑中,定位合适的执行点,使摄像机位置能与实时状态同步更新。该步骤需要在高频集(高更新频率的实体集合)处理完成前执行,从而保证所有相关状态已经更新完毕。

  2. 定义更新逻辑

    将更新摄像机位置的逻辑放置在合适的位置(如高频集合操作末尾),以确保摄像机能及时跟随目标实体或区域。

  3. 后续系统扩展

    如果未来需要实现摄像机更复杂的跟随机制,例如动态调整或智能跟踪多个目标,可以考虑将此功能扩展为一个通用的系统,便于处理更多场景。

  4. 简化当前需求

    在现阶段,重点是确保摄像机能在实体更新的过程中,始终准确地反映目标位置。这一位置的调整逻辑已被确认是放置的正确区域。

最终目标是在现有的框架中,完成摄像机更新的准确处理,并为未来扩展打下基础。当需要进一步优化或改进时,可以基于此逻辑进行扩展和调整。

调整仿真区域的渲染

我们当前的目标是优化模拟和渲染流程,同时清理实体相关的逻辑,使系统更高效并更容易扩展。以下是主要思路和步骤:

  1. 模拟与渲染协作

    模拟和渲染需要更紧密地结合。在渲染之前完成模拟,并确保渲染能够利用模拟的结果。这样可以通过共享区域和实体数据提高效率。

  2. 高实体与低实体的转换逻辑

    需要提供一种机制,使低频实体能够与高频实体相互引用。这可以通过映射或哈希表来实现,从而在不同频率的实体间建立关联,简化处理流程。

  3. 简化高实体管理

    将高实体索引的概念去除,使用更直接的指针或其他方法代替。这将减少指针遍历的复杂性,进一步优化性能。

  4. 临时处理低实体

    在实现完全的状态压缩和解压缩逻辑之前,低实体的部分逻辑会暂时保留,直到可以完全替换。

  5. 流动性与坐标更新

    保留与坐标相关的逻辑,如帧时间(dt)更新和流动性计算,以确保系统的动态性和稳定性。此部分的逻辑不受实体分组的影响,可以保持独立。

  6. 清理与重构绘图逻辑

    将绘图相关的调用明确分离,集中处理。同时更新与剑、熟悉物和其他特定对象的逻辑,确保其与新的实体管理方式兼容。

  7. 逐步优化实体区域处理

    检查并优化实体区域的循环和计算逻辑,确保模拟和渲染使用统一的区域数据。这不仅能提高效率,还能减少潜在的错误。

  8. 去除冗余组管理

    不再对小组进行管理和操作,专注于实体本身的逻辑。通过这种方式,简化了系统的复杂性,减少了不必要的耦合。

最终,我们的目标是通过优化和清理现有逻辑,构建一个高效、灵活的系统架构,使模拟和渲染能够无缝协作,同时为未来的扩展做好准备。

今天的总结 - 修复编译器错误

当前工作围绕优化和重构系统展开,重点在于改进实体更新、区域管理和哈希表映射的实现。以下是主要内容和方向:

  1. 实体更新的整合

    • 将某些代码从玩家部分移出并下移至适当位置,以便更好地集中管理。
    • 更新调用被认为是重构中的关键部分,需确保逻辑清晰并易于扩展。
  2. 区域相关文件的整合

    • 包含并调整与区域相关的文件,例如 sim_region.cppsim_region.h,确保区域管理逻辑的清晰性。
    • 定位与区域实体相关的潜在问题,并进行必要的调试。
  3. 低实体与游戏状态的关联

    • 游戏状态与低实体之间的关系需要进一步明确。目前,需要通过传递游戏状态来操作低实体,后续可能会移除这种依赖关系。
    • 未来可能优化为无须依赖游戏状态的设计。
  4. 区域边界与实体逻辑清理

    • 确保区域边界定义的一致性。当前区域边界和实体逻辑已经清理到一定程度。
    • 对于存储的低实体,暂时仍需保留,待后续实现完全的压缩与解压缩逻辑。
  5. 摄像机跟踪优化

    • 当前摄像机逻辑已不再需要依赖高实体的索引。计划通过哈希表实现低实体与模拟实体的映射,从而使摄像机逻辑更高效。
  6. 引入哈希表映射

    • 确定需要引入哈希表,将低实体索引映射到模拟实体。这将优化实体间的关联操作,并减少冗余逻辑。
    • 哈希表将成为系统中的重要结构,用于提高复杂实体操作的效率。
  7. 进一步清理和组织代码

    • 清理不必要的代码段,包括那些已经不再使用的小组逻辑。
    • 对于绘图和实体更新等调用进行分类管理,以提高代码的可读性和可维护性。
  8. 后续计划

    • 暂停当前工作,计划在明天进一步完善系统。需要组织现有逻辑,并对区域和实体映射进行更多的优化设计。
    • 保留未完成任务的注释,例如哈希表映射的具体实现,作为后续的工作重点。

当前工作已清理和优化了部分逻辑,但仍有许多任务需要完成,包括进一步调整实体与区域的管理方式,以及确保系统架构的稳定性和扩展性。

命名建议 - 你可以使用 stasis_entity 和 stasis_region 来代替存储实体;对于高频实体,你可以保持 sim_region 和 sim_entity。

讨论了是否应该为存储的实体使用"静止实体"和"静止区域"这一命名方式,建议保持高频率的区域不变,而不是存储实体本身。由于这些实体不是静止的,它们只是暂时存储,所以仍然可以视为活跃实体。存储的实体会被临时保存,直到需要再次取出。在命名上,还讨论了其他可能的选择,比如使用"存储"或"故事"作为术语,虽然对此尚未达成共识。总体来说,实体在被存储时仍有活跃的特性,等待需要时再被提取。

既然我们知道实体的速度是有界的,难道我们不能计算出一个带有边界的 sim_region,以确保实体在帧内不会移出它吗?这样,实体可以碰撞的任何东西都会被拉入 sim_region 吗?

讨论了一个关于模拟区域的有界性问题。假设我们从一个有界区域开始,并认为该区域内的所有实体都有一个已知的最大移动距离。当实体在框架内移动时,如果它与其他实体碰撞,它会被拉进同一个区域。接着需要重新计算并调整区域边界,直到没有更多实体能被拉入区域。问题是,这个过程如果没有限制,会导致区域不断扩展,最终可能包围整个世界,成为一个无界的问题。

为了解决这个问题,可以将实体分为"生命体"和"非生命体"两类,生命体可以继续移动,而非生命体则不受影响。这种方法可以简化计算,不需要特别复杂的代码,只需要更新那些在边界之外的实体。

此外,还讨论了如何使用一个第二个矩形来检查和设置实体是否可移动,并提出这一操作可能需要在特定位置进行。这一方案仍在考虑中,实际实现可能会稍后进行。

你还有多少个待办事项?

讨论了使用"TODO"注释和静态检查的方法,以确保在项目后期不遗漏重要的部分。虽然在项目早期很少关注"TODO"注释,但在项目的后期,它们可以用来检查是否有遗留的、未完成的工作。通过静态检查,可以找到并验证代码中所有的"TODO"项,确保没有忽略掉重要的事项。对于全局变量,静态检查可以帮助追踪它们的使用情况,从而避免潜在问题。

此外,提到"TODO"注释比评论更有优势,因为它们不会随着时间变得不准确或过时。通过这种方法,可以快速检查和处理那些已经不再相关的部分,而无需担心评论会给项目带来负面影响。

你如何处理屏幕外和屏幕内实体之间的更新速率差异?这重要吗?

讨论了如何处理实体更新率的差异,特别是屏幕内外实体之间的更新频率差异。希望通过编写高效的更新代码,能够在处理大量实体时保持较低的影响。对于屏幕内的实体,通常的更新频率是每秒16帧,而屏幕外的实体则可能有较低的更新频率,例如每秒5帧。尽管这种差异通常不会造成太大问题,但如果屏幕外实体的更新频率低至每5秒或每10秒一次,则可能需要额外的工作来支持这种情况。总的来说,具体如何处理这种差异,还需要在实际开发中根据情况调整。

sim_region 离玩家远时,是否会导致比正常更大的时间步长?

讨论了模拟世界时的时间步骤问题。对于远离玩家的区域,可能会使用较大的时间步骤进行模拟。目标仍然是尽可能快地模拟世界,因此尽量保持世界范围内的更新频率较高。虽然希望这种方法不会造成太大问题,但如果发现问题,就需要采取额外的措施,编写更高效的代码来更新这些区域中的实体。这可能涉及优化代码,确保在这些区域内的实体能够更加高效地进行更新。

我错过了这新系统是否基于离散房间或整数映射坐标。如果是基于房间,是否简单得可以考虑坐标而不是房间?

这个新系统并不是基于离散的房间或整数映射坐标。它是自由浮动的,使用一个相机区域。这个相机区域的位置是灵活的,可以在任何地方设置,而不是固定在房间或特定区域内。因此,它不依赖于房间的概念,也不需要以房间为基础进行思考。

如果你的 sim_region 和玩家位置在同一个地方,会发生什么?会出错吗?

如果模拟区域和玩家位置重叠,可能会出现一些问题。为了处理这种情况,可以使用像生成索引(generation index)这样的机制,记录每个实体经过了多少个模拟步骤。生成索引可以帮助确定实体的最新状态,从而避免重复计算。

在此基础上,可以存储关于实体更新的信息,例如记录它上次更新的时间。当摄像机区域进入一个尚未更新的实体时,系统需要知道该实体已经进行了多少步骤的更新。通过这种方式,系统可以更有效地管理实体的状态。

然而,这个过程仍然相对复杂,需要在处理标度问题和确保系统整洁方面进行一些优化和调整。虽然架构设计提供了很大帮助,但仍然需要解决一些具体的实现问题,以确保系统的顺利运行。

为什么低频- 高频系统如此重要?在小世界中创建基本的游戏规则和 AI 互动,不是更符合先做简单事情的理念吗?

低频系统的重要性在于,它能够支持大规模实体的管理和模拟,满足游戏中对大量实体的需求。在设计时,首先要实现基本的要求,比如能够处理大量的实体,这并不是一个可选项,而是一个必须满足的硬性需求。

实现这一目标时,首先从最简单的代码入手,逐步实现功能。虽然这个过程可能变得复杂,但通过不断的调整和优化,能够使系统变得更加干净和高效。重要的是,在编写代码时,要确保每个系统都能满足需求,而不是过早地进行过度设计。

此外,不同系统之间的关系也很关键。人工智能的交互和游戏规则需要单独设计,因为它们的工作方式与实体的管理和模拟无关。通过逐步完善这些系统,最终能够达到预期的架构和效果。

鉴于世界的稀疏性,如何更新低频区域?

在更新低频区域时,考虑到世界的稀疏性,更新过程可能会通过块状区域进行。具体来说,可能会遍历哈希表,筛选出没有更新过的区域(如城镇),并围绕这些区域进行更新。更新的过程可能会吸引到附近的跳跃区域,进而触发更多的更新操作。更新完成后,标记这些区域为已更新,并将更新的实体固定(锚定)。这样能够确保低频区域的有效更新,并避免不必要的资源浪费。

使用 'fors' 而不是 'whiles' 有什么主要优势吗?

使用 for 循环和 while 循环在代码实现上没有本质的区别,主要是语法上的不同。实际上,任何 for 循环都可以转换成 while 循环,反之亦然。选择使用 for 循环通常是因为它能够更清晰地表达循环的元素。特别是当有多个元素时,for 循环能更好地将这些元素捆绑在一起,增强代码的可读性。如果循环内部有很多内容,for 循环能够帮助开发者更容易地理解和追踪循环的结构,而不是让循环内容变得零散和难以理解。

屏幕上不可见的实体会做什么?我真的想不出需要更新的例子,尤其是当它们离玩家很远时。

对于那些不在屏幕上的实体,它们的行为可以根据游戏的需求而有所不同。例如,如果这些实体距离玩家很远,它们可能不需要频繁更新。具体而言,可以设计一些机制,比如让这些实体在世界的不同区域移动,执行任务或交互,甚至在某些情况下,玩家可以看到这些实体从一个地方走到另一个地方。尽管这样的功能在大多数游戏中不常见,但它能够展现出有趣的紧急行为,并且是一个值得实现的编程挑战。

这些设计并不旨在创建一个简单的游戏,而是为了展示如何应对复杂的游戏设计和建筑问题。这些问题远远超出常见的简单游戏,如平台游戏或克隆类游戏。通过解决这些更复杂的架构问题,能够更好地理解如何处理复杂的游戏设计,而不是仅仅处理最基础的编程任务。

"拧灯泡需要多少个低频实体?"

对于低频实体,只有当它们被映射到模拟中时,它们才会参与更新。如果谈论的是存储中的实体,它们并没有被纳入到模拟,因此并不会参与实际的操作。至于"低频模拟"这种情况,模拟这些实体的更新依赖于具体的实现需求和模拟机制,比如这些实体的更新是否依赖于某些特定的虚拟动作或需求。

例如,若涉及一个灯泡的情境,假设这个灯泡有生命值等属性,那么更新这些低频实体的数量将取决于需要处理的内容。至于其他的操作,如调整代码或重新排列逻辑,也需要根据需求来处理并编译。

对于稀疏存储,为什么不存储(x, y, z)坐标并通过对齐整数的位移将其用作字典或哈希映射的复合键?

在这个过程中,主要讨论了如何通过将x、y、z坐标值进行位移来生成一个行整数,并将其作为字典或哈希映射中的复合键。通过这种方式,可以有效地组织和查找数据。然而,尽管将位移的位组合在一起似乎可行,但并不是最优的方法。

为了解决哈希表的效率问题,实际应用中会使用质数来进行优化,而不是单纯地将比特位移。在构建更好的哈希函数时,还需要对这些数据进行分析,从而找到最佳的哈希方法,而不仅仅是将它们打包在一起。最终的目标是根据世界生成的需求,使用哈希表来实现合理的布局,以提高效率。

你是否以类似你的架构设计方式进行设计(例如基于探索的设计,设计本身自然出现等)?

在讨论游戏设计时,强调了自己并不是游戏设计师,而是专注于游戏编程。因此,无法对游戏设计本身发表评论或提供深入的见解。尽管如此,在游戏开发中所涉及的一些技术方面,如架构和编程,仍然是重点关注的内容,而不包括具体的设计决策。

多线程是该项目讨论范围内的内容吗?

项目中的线程化最初并不是为了提高性能,而是因为树莓派只有一个核心,因此多线程并没有显著的优势。最初的目的是为了渲染过程,但并不是设计的主要原因。然而,最近树莓派宣布更新,将配备具有四个核心的ARM Cortex处理器,这使得多线程变得更加可行。因此,在未来,可能会考虑在并行处理多个仿真区域时使用多线程。

如果远离的房间被更新,周围的房间也被更新,我们是否会遇到递归问题,即周围的房间也需要更新它们的邻居?

当远处的房间和其周围的房间被更新时,递归问题可以避免。周围的房间需要其邻居再次更新,以确保模拟过程的一致性。通过将实体移到特定区域并执行动作,可以调整这些房间的状态。然而,尽管可以进行这些操作,仍然可能引入问题。例如,在在线游戏中,这种做法可能会引发公平性等问题,但因为更新过程完全可控,所以如果出现问题可以通过调整解决。因此,不认为会有大的问题,虽然结果仍需观察。

将你正在制作的游戏从 2D 世界转变为 3D 世界需要多少工作量?

打开大门所需的工作量取决于具体的需求和上下文。如果是指将游戏从一个二维世界转移到三维世界,那么这是一个复杂的任务,涉及到大量的工作和设计调整。如果是指重新编写低温引擎或者其他引擎的重写,这也需要大量的开发和优化。而如果只是将从Maya中下载的3D模型导入游戏中,并没有改变游戏的设计或机制,那么所需的工作量会大大减少。两者之间有显著差异,具体工作量取决于实际目标。

https://gitee.com/mrxiao_com/2d_game

相关推荐
一棵开花的树,枝芽无限靠近你3 小时前
【PPTist】表格功能
前端·笔记·学习·编辑器·ppt·pptist
yuwinter4 小时前
鸿蒙HarmonyOS学习笔记(8)
笔记·学习
Ricciflows4 小时前
MIT线性代数教材:Linear Algebra and Its Applications
人工智能·学习·线性代数·机器学习·数学建模·矩阵
计科土狗5 小时前
离散数学第二章笔记
学习
美式小田5 小时前
Cadence学习笔记 12 PCB初始化设置
笔记·嵌入式硬件·学习·cadence
席万里5 小时前
【MySQL学习笔记】关于索引
笔记·学习·mysql
深蓝海拓6 小时前
使用sam进行零样本、零学习的分割实践
人工智能·深度学习·学习·目标检测·计算机视觉
滴_咕噜咕噜7 小时前
学习笔记(prism--视频【WPF-prism核心教程】)--待更新
笔记·学习·wpf
ghostwritten7 小时前
学习 Python 编程的规则与风格指南
python·学习
两水先木示7 小时前
【Unity3D】ECS入门学习(七)缓存区组件 IBufferElementData
学习·unity·ecs