回顾如何获得初步使用代码以避免糟糕的设计
我们一开始做的一个大事就是要把足够的代码写下来,在一个原始的形式下,以便了解一切应该如何结构化。这样当我们开始编写代码时,能够避免陷入设计上的误区,避免过度设计或者重新设计错误的东西。很多时候,写使用代码是非常重要的,这样能够确保整个过程的正确性。因此,我们在整个过程中一直在这么做。
我一直在关注实体的一些更高级特性,比如低频和高频实体的设置,以及如何扩展到大量的实体等。假设我们有这个需求,但不太清楚如何构建代码以确保其美观和简洁。对我来说,这并不算问题,关键是要先写出足够的代码,以便可以慢慢地看到如何解决问题并逐步发展出好的解决方案。
高/低频实体的设计约束
在思考代码架构时,我们意识到有两个主要约束条件需要考虑。第一个约束是关于处理超大世界的能力。对于非常大的世界,单一浮点数的精度会迅速下降,因此需要特别处理浮点精度问题。这个问题并非每个引擎都必须解决,但我们希望展示如何在代码中处理它,因为这在设计大世界时是不可避免的。
第二个约束是关于大规模实体计数的能力。我们需要确保能够处理大量的实体。如果每个实体都单独处理并不断运行世界中的所有实体,系统将无法扩展,尤其在实体数量非常大时。
这两个问题看似互相矛盾,但它们实际上是相互联系的。在之前的思考中,虽然隐约意识到这两个问题的交集,但没有深入考虑它们如何共同影响架构。现在,意识到这两者需要同时得到解决,才能设计出一个既能处理大规模世界又能管理大量实体的良好架构。
大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
)将变得更加灵活和合理,符合新的需求。
总之,现在的目标是完全摆脱相机空间的依赖,转而通过区域的起源来计算实体的位置,这样能够更好地支持当前的模拟环境和未来可能的扩展。
调整实体结构
我们现在需要一种方法来处理当前的情况,把所有的东西整理回到一个合理的状态。我们清楚之前需要的一些内容,其中包括一个存储索引,这个索引能够告诉我们每个实体的来源。这些信息是针对同一区域的,但现在它的意义更明确了,不需要过多评论。我们可以在顶部添加一些注释,简要说明这些内容。
目前有一个动画相关的元素,我们暂时不需要增加更多内容。但显然有一些原本就应该被处理的东西,比如速度。速度需要被存储下来,并且在跨越帧边界时保持一致。同样,低频实体也需要存储速度,以确保持续性的逻辑一致性。这部分逻辑的存储需要被纳入低频实体中。
方向信息也需要遵循相同的逻辑处理方式。此外,我们暂时将一些次要内容放置到外部环境中,因为我们的重点将集中在设置这些基础问题上。
在处理这些问题时,我们注意到一些数据,比如z
和dz
,需要进行一定程度的清理。我们已经引入了Vector3
,但尚未以逻辑方式处理z
的内容。尽管如此,当前处理还是分步骤进行,以免过多内容堆积。整体来说,这些步骤正在逐步推进,并且目前的状态是可接受的。
sim_region 的内存分配
当前需要将工作流程简化,首先清理现有代码,使其能够在设定的范围内正常运行。一个关键任务是分配相同区域的内存资源,并引入瞬态堆栈的概念,这将作为未来进一步优化的基础。目前我们将其作为占位符,稍后再详细处理。
在现有的内存管理中,使用了存储区域的分配方式,我们需要从这些区域中分配必要的空间来存储仿真系统的数据结构。在此过程中,需要初始化一些群体信息,包括区域和当前帧的实体边界。此外,还需要处理一些实体的区域配置,比如区域中的实体数量最大值。
初始化时,实体计数从零开始,根据实际需要逐步增加。由于这些配置具有暂时性,可以使用较大的临时内存池来支持动态需求,从而允许配置更高的最大值。
关于实体管理,现有的数组已经可以存储实体相关信息,因此可以直接利用这些数组来操作实际的实体数据。所有分配的内存都集中在一个特定区域进行管理,所有处理逻辑完成后释放相关内存。这种方式能够确保资源使用的清晰性和高效性。
通过上述过程,建立了一种统一的内存分配与管理机制,从而支持区域范围内的实体管理和仿真系统的运行。
在 EndSim 中保存回低频实体
当前需要实现对低频实体的存储管理,通过将处理后的数据写回低频实体数组来完成。由于已存在相应的代码结构,可以直接利用这些基础。首先需要从存储索引中提取信息,存储索引决定了低频实体的具体位置,不需要考虑高频实体的索引。
初始化时,通过存储索引将数据重新写入低频数组。这一过程确保了实体在每一帧处理后能够正确保存,支持下一帧的仿真。当前的存储机制将数据写入游戏状态,但可以优化为直接存储在世界对象中,以便更清晰地组织和访问数据。
为了实现这一点,需要调整实体存储的目标位置,将低频实体从游戏状态迁移到世界对象中。未来可能需要重命名某些结构体或变量以匹配新的逻辑,但为了迁移过程的便利性,可以暂时保持原有命名。
在这个过程中,操作逻辑包括以下几个步骤:
- 抓取存储索引:从存储索引中提取对应的实体信息,这一步确保了操作的正确性。
- 实体迁移:将低频实体存储在适当的数据结构中,这里暂时仍使用低实体结构以简化迁移。
- 变量调整:在适当的时候重命名变量,例如将"低实体"调整为"存储实体",以更好地反映其功能。
此外,需要注意的是,目前的代码并未关心高频实体的索引,因为目标仅限于低频实体。后续可以通过优化来减少存储操作的冗余或进一步提升执行效率。
以上逻辑的实现为未来的扩展提供了一个框架基础,例如动态调整存储位置或进一步优化数据组织结构。通过这种方式,整个系统可以逐步变得更加清晰和高效。
删除不再适用的代码
目前需要调整代码逻辑,主要集中在实体的更新与存储操作上。之前的代码中没有实现实体的打包,也没有对低频实体进行更新,因此当前逻辑部分已无法正常工作,需要修复。
调整和优化逻辑:
-
移除无用代码:
- 之前的配对实体逻辑已经废弃,因为实体不再成对存在,而是通过指针单向关联。
- 部分逻辑如强制高频处理已经失去意义,可以移除。
- 相关旧代码段和概念,如按面积调整频率、未验证的存储等也已被更简化的逻辑替代。
-
明确实体位置:
- 当前需要关注的是调整实体的位置更新逻辑。
- 将存储索引关联的实体位置重新计算并应用。
- 更新的重点是通过映射块空间的偏移量,推导出新的实体位置。
-
存储和位置映射:
- 存储索引用于记录实体的初始状态,后续位置更新基于此索引展开。
- 新位置的计算利用了区域映射和偏移逻辑,将实体从存储空间移动到世界空间的目标位置。
-
映射逻辑优化:
- 从摄像机的参考位置切换到场景中的参考点,例如区域或军团的位置。
- 通过新的偏移和映射关系,将实体位置与存储状态进行同步。
-
下一步改进方向:
- 实体存储逻辑可以进一步优化,例如将存储的低频实体直接与全局位置关联。
- 当前逻辑简单直观,但可以在未来通过压缩状态等技术提升性能。
主要步骤总结:
- 清理冗余代码,移除已废弃的逻辑。
- 通过存储索引获取实体的初始状态。
- 计算实体的新位置,并与世界中的目标位置同步。
- 调整实体存储与更新流程,使其适应新的区域参考框架。
- 保持代码简洁,为未来优化和扩展打下基础。
当前代码逻辑经过整理后,可以更高效地支持实体的动态更新与低频处理。未来在扩展功能时,可以进一步增强存储结构和位置计算的准确性与灵活性。
将 SetCamera 替换为在主循环中获取 sim_region
我们发现设置摄像头的部分目前几乎没有任何实际作用。为了解决这个问题,首先回顾代码结构,得出结论某些部分实际上可以移除。我们只需要保留关于摄像机区域大小的概念即可。
为此,我们计划移动与摄像机相关的逻辑,简化代码,保留必要的数据。我们通过保存摄像机区域大小的关键信息,删除多余的计算和变量设置,集中处理模拟实体的逻辑。
具体步骤如下:
- 移除冗余代码,只保留核心逻辑,即定义和保存摄像机区域的大小。
- 定义一个函数,用于初始化模拟区域。我们将摄像机的位置作为原点,并结合先前计算出的区域边界,完成区域的绑定和模拟初始化。
- 整合初始化逻辑,使其更清晰地服务于主游戏循环,不再单独作为一个函数。
- 在模拟实体的逻辑中填充相关代码,以确保摄像机区域的计算和模拟是连贯的。
最终,我们的目标是将摄像机区域绑定的过程和模拟实体的逻辑结合起来,使其成为主游戏循环的一部分。通过这样简化的方式,代码变得更直接、更高效,同时移除了不必要的复杂性和重复操作
。
将相机位置更新从主循环移到 EndSim
当前处理逻辑中,我们需要更新摄像机的位置,使其准确反映实际应该跟随的区域或实体。为此,我们提出了一个关键点,即在模拟端执行时,确保摄像机的位置与最新状态保持一致。
具体步骤如下:
-
更新摄像机位置
在模拟逻辑中,定位合适的执行点,使摄像机位置能与实时状态同步更新。该步骤需要在高频集(高更新频率的实体集合)处理完成前执行,从而保证所有相关状态已经更新完毕。
-
定义更新逻辑
将更新摄像机位置的逻辑放置在合适的位置(如高频集合操作末尾),以确保摄像机能及时跟随目标实体或区域。
-
后续系统扩展
如果未来需要实现摄像机更复杂的跟随机制,例如动态调整或智能跟踪多个目标,可以考虑将此功能扩展为一个通用的系统,便于处理更多场景。
-
简化当前需求
在现阶段,重点是确保摄像机能在实体更新的过程中,始终准确地反映目标位置。这一位置的调整逻辑已被确认是放置的正确区域。
最终目标是在现有的框架中,完成摄像机更新的准确处理,并为未来扩展打下基础。当需要进一步优化或改进时,可以基于此逻辑进行扩展和调整。
调整仿真区域的渲染
我们当前的目标是优化模拟和渲染流程,同时清理实体相关的逻辑,使系统更高效并更容易扩展。以下是主要思路和步骤:
-
模拟与渲染协作
模拟和渲染需要更紧密地结合。在渲染之前完成模拟,并确保渲染能够利用模拟的结果。这样可以通过共享区域和实体数据提高效率。
-
高实体与低实体的转换逻辑
需要提供一种机制,使低频实体能够与高频实体相互引用。这可以通过映射或哈希表来实现,从而在不同频率的实体间建立关联,简化处理流程。
-
简化高实体管理
将高实体索引的概念去除,使用更直接的指针或其他方法代替。这将减少指针遍历的复杂性,进一步优化性能。
-
临时处理低实体
在实现完全的状态压缩和解压缩逻辑之前,低实体的部分逻辑会暂时保留,直到可以完全替换。
-
流动性与坐标更新
保留与坐标相关的逻辑,如帧时间(dt)更新和流动性计算,以确保系统的动态性和稳定性。此部分的逻辑不受实体分组的影响,可以保持独立。
-
清理与重构绘图逻辑
将绘图相关的调用明确分离,集中处理。同时更新与剑、熟悉物和其他特定对象的逻辑,确保其与新的实体管理方式兼容。
-
逐步优化实体区域处理
检查并优化实体区域的循环和计算逻辑,确保模拟和渲染使用统一的区域数据。这不仅能提高效率,还能减少潜在的错误。
-
去除冗余组管理
不再对小组进行管理和操作,专注于实体本身的逻辑。通过这种方式,简化了系统的复杂性,减少了不必要的耦合。
最终,我们的目标是通过优化和清理现有逻辑,构建一个高效、灵活的系统架构,使模拟和渲染能够无缝协作,同时为未来的扩展做好准备。
今天的总结 - 修复编译器错误
当前工作围绕优化和重构系统展开,重点在于改进实体更新、区域管理和哈希表映射的实现。以下是主要内容和方向:
-
实体更新的整合
- 将某些代码从玩家部分移出并下移至适当位置,以便更好地集中管理。
- 更新调用被认为是重构中的关键部分,需确保逻辑清晰并易于扩展。
-
区域相关文件的整合
- 包含并调整与区域相关的文件,例如
sim_region.cpp
和sim_region.h
,确保区域管理逻辑的清晰性。 - 定位与区域实体相关的潜在问题,并进行必要的调试。
- 包含并调整与区域相关的文件,例如
-
低实体与游戏状态的关联
- 游戏状态与低实体之间的关系需要进一步明确。目前,需要通过传递游戏状态来操作低实体,后续可能会移除这种依赖关系。
- 未来可能优化为无须依赖游戏状态的设计。
-
区域边界与实体逻辑清理
- 确保区域边界定义的一致性。当前区域边界和实体逻辑已经清理到一定程度。
- 对于存储的低实体,暂时仍需保留,待后续实现完全的压缩与解压缩逻辑。
-
摄像机跟踪优化
- 当前摄像机逻辑已不再需要依赖高实体的索引。计划通过哈希表实现低实体与模拟实体的映射,从而使摄像机逻辑更高效。
-
引入哈希表映射
- 确定需要引入哈希表,将低实体索引映射到模拟实体。这将优化实体间的关联操作,并减少冗余逻辑。
- 哈希表将成为系统中的重要结构,用于提高复杂实体操作的效率。
-
进一步清理和组织代码
- 清理不必要的代码段,包括那些已经不再使用的小组逻辑。
- 对于绘图和实体更新等调用进行分类管理,以提高代码的可读性和可维护性。
-
后续计划
- 暂停当前工作,计划在明天进一步完善系统。需要组织现有逻辑,并对区域和实体映射进行更多的优化设计。
- 保留未完成任务的注释,例如哈希表映射的具体实现,作为后续的工作重点。
当前工作已清理和优化了部分逻辑,但仍有许多任务需要完成,包括进一步调整实体与区域的管理方式,以及确保系统架构的稳定性和扩展性。
命名建议 - 你可以使用 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模型导入游戏中,并没有改变游戏的设计或机制,那么所需的工作量会大大减少。两者之间有显著差异,具体工作量取决于实际目标。