Bootstrap

游戏引擎学习第78天

Blackboard: Position != Collision “网格”

昨天想到的一个点,可能本来就应该想到,但有时反而不立即思考这些问题也能带来一些好处。节目是周期性的,每天不需要全程关注,通常只是在晚上思考,因此有时我们可能不能那么快想到解决方案,这样就能看到自己做的不太好的一部分,之后再朝更好的方向改进。我们已经在很多地方看到过类似的情况,比如关于区域和瓦片块的设计,这些都是我们逐步改进的结果。

有时候,一开始的设计故意保持简单,而不是一开始就去猜测最终的理想方案,目的是展示一个过程。其他时候,我们也会尝试做一些设计,之后才发现其实有更好的做法。昨天我们讨论了关于“地面点”的概念,想象一个实体在空间中占据一个位置。如果我们使用包围盒来做碰撞检测,当前的碰撞检测机制已经能够正常工作,但遇到的问题是,如果我们将角色的位置作为包围盒的中心点,那么随着角色的移动,包围盒就会下沉到地面之下,这显然不是我们希望看到的结果。

于是我们引入了“地面点”的概念,这样可以让包围盒在地面上正确定位。但在后续的讨论中,有人提出了一个建议,能否简化处理,避免每次都重新计算地面点。这个建议的核心是,为什么不将地面点作为常驻使用的点,这样可以避免多次计算。

我们考虑了这个问题,但实际上,使用包围盒时,我们更倾向于保持对称性。如果只把包围盒的最小角作为参考点,它就会导致很多不自然的行为。我们通常更倾向于将角色的中心作为参考点,尤其是在行走时,角色的腿部位置更接近包围盒的中心,而不是靠近外部边缘。因此,直接使用最小角来做碰撞检测会显得很别扭。

直到后来,才意识到有一个明显的解决方法,我们可以借鉴三维游戏中的处理方式。实际上,角色的“位置”和“碰撞”是两个不同的概念。我们可以将位置看作角色的逻辑位置,而碰撞则可以通过一个独立的碰撞网格来表示。在三维游戏中,我们通常会有多个碰撞区域来代表角色的不同部分,比如头部、躯干和四肢,每个部分都有独立的碰撞体积,这样就能更精确地进行物理计算。

因此,我们可以将角色的位置和碰撞体积分开处理。角色的中心位置可以保持在地面上,这样它就能够沿地面移动。而碰撞体积可以是多个区域,针对这些区域进行独立的碰撞检测,最后将检测结果综合起来,决定角色的移动。这样一来,我们不仅解决了地面点的问题,还增加了更多的功能,比如支持多个碰撞区域,能够处理更复杂的物理交互。

最终,这种做法既解决了最初看似无法解决的问题,又为引擎增加了更多的灵活性,能够处理更复杂的角色碰撞体积。通过这种方式,我们能够做到兼顾精确性和扩展性,达到了一个理想的平衡。

game_sim_region.h: 引入 sim_entity_collision_volume

从目前的情况来看,基本的运动逻辑已经有了,然后我们还需要考虑一些维度。接下来,我的目标是让这些维度能够偏移,以便更好地处理碰撞检测。首先,由于目前还不需要多个碰撞体积,我打算先实现一个碰撞体积,等到未来需要的时候,再根据情况扩展支持多个碰撞体积。

现在,我决定暂时只使用一个碰撞体积,并在未来根据需要扩展功能。这是一个简单的步骤,可以让系统保持灵活,适应将来可能的变化。

另外,有一个问题需要思考:是否需要将实体的存储与碰撞体积直接关联,或者使用某种实体基础的存储方式来处理这个问题?这个问题没有明确答案,因此还需要进一步思考。

在这里插入图片描述

Blackboard: 存储此碰撞体积数据的两种方式

让我们以一个例子来讨论这个问题,假设我们有一个角色,这个角色的形状是通过碰撞体积来表示的。我们有两种方式来存储这个数据:

第一种方式是在实体结构中直接存储碰撞体积,第二种方式是让实体结构中包含一个指向碰撞体积列表的指针。也就是说,我们可以选择直接将碰撞体积的数据嵌入到实体结构中,或者通过一个指针引用外部的碰撞体积数据。

如果我们选择第二种方式,即通过指针来指向碰撞体积列表,问题就变成了:我们是否需要每个实体都有自己的碰撞体积,还是可以共享相同的碰撞体积?比如,如果我们有多个实体,它们的碰撞体积完全相同(比如一些敌人或怪物),那么我们是否可以让它们共享同一个碰撞体积列表,而不必为每个实体单独存储一份?

如果每个实体的碰撞体积不完全相同,比如某些怪物的碰撞体积稍大或者稍小,或者形状上有不同(比如不同大小的身体部件),那么共享就不适用了。在这种情况下,每个实体需要自己独立的碰撞体积数据。

目前来看,可能大多数情况下实体的碰撞体积可以共享,除非有特殊需求需要独立的碰撞体积。因此,初步的设计可以考虑通过指针共享数据,未来如果出现需要独立碰撞体积的情况,再根据实际需求进行调整。

game_sim_region.h: 引入 sim_entity_collision_volume_group

当前的设计中,决定使用一个简单的结构来存储碰撞体积的数据。这结构包含了两个主要部分:一个是碰撞体积的数量(volume counts),另一个是碰撞体积的具体数据(collision volumes)。碰撞体积的数据可以是一个列表,代表一个实体所拥有的所有碰撞体积。虽然目前使用了两个字段来存储数据,但这其实是优化层面的问题,未来可以决定是否将它们合并。

为了使结构更加灵活,原先的尺寸(dimension)字段被替换成了一个碰撞组的列表。每个实体都可以指向一个碰撞组,碰撞组存储了与该实体类型相关的碰撞体积信息。在每个碰撞体积的基础上,还需要一个偏移量(offset),用于描述碰撞体积与实体实际位置之间的相对位置,从而确定如何进行碰撞检测。

目前,决定让所有的碰撞体积指向一个统一的碰撞组,该碰撞组是根据实体的类型构建的。这样做是为了方便管理和共享碰撞体积数据,避免为每个实体创建独立的碰撞数据。未来,如果需要支持更多不同的碰撞体积(例如不同大小的实体),可以再进行调整。这个结构的好处在于它能让实体的碰撞体积更加灵活,允许对不同类型的实体进行尺寸调整,直到决定是否将其进一步简化或优化。
在这里插入图片描述

编译并通过编译错误引导来替换 Dim

用户正在采用一种编程技巧,通过编译错误来指导代码替换的过程。在进行替换时,他们会先进行替换,并让编译错误指引他们,标出需要修改的地方。这是一种常见的有效编程方法,可以帮助定位需要修改的部分。

game_sim_region.h: 在 sim_entity 中添加 WalkableDim

这段讨论主要围绕楼梯的碰撞处理进行。首先,目标是确定楼梯的“地面”位置。为了实现这一点,需要检查角色当前位置是否位于某个楼梯的碰撞区域。如果楼梯由多个碰撞区域组成,那么就需要判断角色处于哪个具体区域,并基于此获取正确的“地面”位置。

随后,提出了一种稍微不同的处理方法,特别是对于楼梯来说,不仅要关心楼梯的碰撞区域本身,还要考虑楼梯的“Walkable”,即其占据的二维区域。楼梯的碰撞检测与其“Walkable”无关,因此可以专门处理这一区域,而不需要关联到具体的碰撞体。

进一步讨论指出,可能为楼梯定义一个额外的矩形区域来表示可行走的区域,这个区域不会涉及到传统的碰撞检测,而是描述如何在楼梯中移动。这个区域可以帮助了解楼梯的具体空间结构,虽然现在看来不一定需要这样做。

最后,虽然讨论了可能为楼梯定义一个三维矩形区域来表示楼梯的体积,但这部分的实现并不是当前的优先任务,可以在后续根据需求进一步考虑。
在这里插入图片描述

game_entity.h: 在 GetStairGround 中使用 WalkableDim

这段讨论主要涉及楼梯的可行走区域和如何处理地面点的计算。首先,提到的矩形区域实际上是一个偏移矩形,代表楼梯的可行走区域。这个矩形是基于楼梯中心点(Entity->P.XY)和楼梯的尺寸(WalkableDim)来定义的,表示楼梯的可行走区域。

接着,提到可以通过获取该矩形的重心坐标来计算楼梯上的位置。使用重心坐标中的Y值可以确定角色在楼梯上的实际高度。通过这种方式,可以根据角色位置和楼梯区域的特征来计算角色应该站在哪个高度。

进一步讨论中提到,可以考虑让楼梯朝不同方向倾斜,甚至创建更奇怪的斜面。这种情况下,可以通过采样点来确定角色的位置,并进行相应的处理,虽然这种设计在当前阶段并未深入考虑。

讨论还提到,获取的地面点(AtGroundPoint)实际上用于计算角色应处的高度,从而得出角色在楼梯上的具体位置。矩形的定义不再需要过于复杂,它只需要表示楼梯的可行走区域,因此可以简化为只用尺寸信息(WalkableDim)即可。

最后,提到使用重心坐标来限制输出的范围在0到1之间,从而确保位置计算的准确性,并准备继续完善后续的步骤。

在这里插入图片描述

game_math.h: 为 rectangle2 引入 GetBarycentric

如何为矩形实现一个“GetBarycentric”的方法。首先提到需要处理一个简单的操作,重点是减少下载的数据并避免使用z轴。接着提到一个比例相关的操作,应该在文件顶部定义以便所有部分都能使用,这些操作本质上是标量操作,类似于clamplerp等常见函数.
在这里插入图片描述

game_math.h: 为 v2 引入 Clamp01

在这段内容中,讨论了模板使用和编程方式的选择。首先,提到通过“Clamp01”操作来获取中心,这涉及到三个特殊函数的使用,但如果使用模板的话,这些操作会自动完成,不需要手动编写。

接着,讨论了使用模板的优缺点。有一种方式是手动复制粘贴,或者直接按编程格式进行,而不依赖模板。即便选择使用模板,理想情况下模板会正确生成所需的代码。如果模板配置正确,就不需要做额外工作;假设模板没有问题,就能够节省很多时间和精力。然而,如果模板有问题,可能需要做一些调整,但如果配置得当,就可以避免额外的操作。

总的来说,讨论的重点是模板的自动化功能与手动编程的选择。模板使用得当可以减少工作量,但也有些人更倾向于直接编写代码。

game_sim_region.h: 向 sim_entity_collision_volume_group 添加 sim_entity_collision_volume 的 TotalVolume

核心内容围绕碰撞检测展开,主要涉及实体与边界区域的重叠检测,以及性能优化和设计思路。以下是总结内容:

  1. 检测需求
    实现一种方法,用于判断实体是否与指定的边界区域发生重叠。关键在于测试这些区域是否符合重叠条件。

  2. 性能和复杂度的权衡
    在检测实体是否与矩形区域重叠时,可以考虑性能优化问题。如果检测目标仅是判断实体是否位于区域内,而非精确验证每个复杂形状的部分,则可以采用更快速的检测方法。

    • 区域检测的重点在于收集处于同一区域的实体,精确度要求较低,适合使用简单的检测逻辑。
    • 碰撞检测涉及游戏可玩性和交互行为,要求更高的精确度,需要对复杂形状进行详细的计算。
  3. 体积的分组和存储

    • 引入总碰撞体积子碰撞体积的概念。
      • 总碰撞体积:是一个简单的边界体积,能够完全包围实体的所有部分,适用于快速区域检测。
      • 子碰撞体积:用于描述实体的更复杂的形状部分,在需要精确检测时逐一进行判断。
    • 设计一个存储结构,允许每个实体管理自己的总碰撞体积和子碰撞体积集合。
    • 当子碰撞体积的数量为零时,表示实体形状较为简单,仅使用总碰撞体积即可完成检测。
  4. 检测逻辑的分离

    • 在区域检测中,仅需判断总碰撞体积是否与边界重叠,无需深入到子碰撞体积的细节。
    • 在精确碰撞检测中,需要逐一检查子碰撞体积,以确保复杂形状部分的精确重叠关系。
    • 这种逻辑分离有助于在性能和功能之间取得平衡。
  5. 优化理由
    区域检测的目标是快速筛选出位于指定区域内的实体,并不需要考虑边缘部分是否完全重叠。例如,一个实体的小凸出部分不在区域内并不会影响该检测的目标,但在碰撞检测中,这种细节可能会影响游戏的体验。因此,区域检测可以使用更粗略的总碰撞体积,而碰撞检测则需要更精确的计算。

  6. 设计思路

    • 引入一个“碰撞组”或类似的结构,用于管理实体的碰撞体积信息。
    • 区域检测方法调用总碰撞体积进行快速判断。
    • 在复杂检测中,再调用子碰撞体积进行详细计算。
    • 这种分层设计减少了不必要的计算开销,同时保留了碰撞检测的精确性。

通过以上设计,可以在保持性能优化的同时,满足不同场景下的检测需求。总碰撞体积用于快速检测,子碰撞体积保证了精确性,两者相辅相成。
在这里插入图片描述

game_sim_region.cpp: 将 sim_entity_collision_volume 传递给 EntityOverlapsRectangle

内容主要描述了在碰撞检测逻辑中如何处理实体的碰撞体积(总碰撞体积和子碰撞体积),并结合代码设计对矩形范围进行扩展和检测的过程。以下是详细总结:

  1. 传递碰撞体积
    在实体与矩形区域重叠检测的逻辑中,会传递一个碰撞体积作为参数,用于检测是否发生重叠。

    • 碰撞体积可以按值传递,尽管这种方式可能显得体积较大,但对性能影响有限。
    • 碰撞体积在检测中用来计算实际检测点的位置以及扩展后的矩形范围。
  2. 扩展矩形范围
    在检测过程中,需要根据碰撞体积来扩展矩形范围。

    • 使用碰撞体积的偏移量计算新的检测位置。
    • 偏移后的位置是实体位置与碰撞体积偏移的叠加值。
    • 扩展后的矩形范围用于更精确地判断实体是否位于指定区域内。
  3. 子碰撞体积的检测
    在完成对总碰撞体积的初步检测后,会进入一个循环,对每个子碰撞体积分别进行测试。

    • 每个子碰撞体积的检测逻辑与总碰撞体积类似,但更加精细。
    • 通过遍历所有子碰撞体积,可以确保复杂实体的每个部分都被正确检测到。
  4. 错误处理

    • 代码中出现了一些问题,例如传递维度时未正确定义,导致错误。这些错误需要通过修正数据结构或补充参数来解决。
    • 遇到的部分问题是由于缺少预定义的成员变量或方法,例如未正确实现的“维度”属性。
  5. 代码执行逻辑

    • 检测逻辑包含在一个循环中,对所有需要检测的体积逐一进行重叠测试。
    • 通过扩展矩形范围,结合每个碰撞体积的偏移量,逐步完成区域检测和碰撞判断。
    • 这种设计能够兼顾性能和精确性。
  6. 总体设计目标

    • 先用总碰撞体积进行快速筛选,降低复杂度。
    • 对通过初步筛选的区域,使用子碰撞体积进行更精确的检测。
    • 通过分层检测方式优化性能,同时确保复杂实体的精确性。

总结来看,逻辑核心是将碰撞检测分为总量检测和细化检测两部分,利用矩形扩展和偏移计算实现区域判断,同时通过循环对复杂形状进行逐步检测。这种设计既能够减少计算开销,也能够确保复杂场景的精确碰撞判断。

在这里插入图片描述

game_sim_region.h: 指定如果实体有任何体积,VolumeCount 始终大于 0

内容主要讨论了在碰撞检测设计中关于总碰撞体积和子碰撞体积的优化处理逻辑,并提到了一种可能的简化方案,但暂时不打算实现。以下是详细总结:


  1. 总碰撞体积的存储问题

    • 当前设计中,总碰撞体积和子碰撞体积是分开存储的。
    • 如果没有子碰撞体积,总碰撞体积可能会重复存储一次。尽管如此,当前仍然选择保留这种设计。
  2. 优化设想

    • 一种可能的优化方法是,当子碰撞体积数量为零时,直接使用总碰撞体积作为实体的唯一碰撞体积进行检测。
    • 这种优化可以避免子碰撞体积为空时的额外存储和计算,但需要额外的逻辑来判断和切换检测方式。
  3. 当前处理方式

    • 当前设计中,子碰撞体积数量(volume count)始终要求大于零,表示只要实体存在碰撞体积,就必须包含至少一个有效的碰撞体积。
    • 即使子碰撞体积为空,总碰撞体积依然被视为附加的独立数据,而不是完全替代的唯一数据。
  4. 未来可能的优化方向

    • 在需要压缩数据或优化性能时,可以考虑允许子碰撞体积数量为零的情况。
    • 在这种情况下,逻辑上将总碰撞体积视为实体的唯一有效碰撞体积,用于所有检测任务。
  5. 暂不实现优化的原因

    • 当前阶段选择不引入这种短路优化(short circuit)。
    • 原因可能包括简化实现、减少逻辑分支、保证设计的一致性等。未来如果需要进一步优化,再考虑实现这一逻辑。
  6. 设计目标

    • 保持子碰撞体积和总碰撞体积的独立性,确保检测逻辑的完整性。
    • 保证当前碰撞检测逻辑对所有实体类型都能正确处理,而无需引入额外的判断条件。

总结来看,当前逻辑偏向于简单明了,通过明确要求 volume count 始终大于零,避免了复杂的判断逻辑。而在未来需要优化存储或性能时,可以引入更灵活的设计,将总碰撞体积作为唯一有效体积进行检测。这种渐进式的设计思路确保了系统的稳定性,同时为后续优化留出了空间。

game_sim_region.cpp: 遍历 Volumes

内容主要讨论了在碰撞检测中涉及的多实体多碰撞体积情况下的处理复杂性。以下是详细总结:


  1. 循环迭代的结构调整

    • 碰撞检测逻辑需要嵌套循环来处理多个实体和它们各自的碰撞体积。
    • 首先对所有实体的碰撞体积进行一次迭代,添加了新的循环变量,用于遍历体积索引。
  2. 复杂性分析

    • 碰撞检测需要考虑两个实体之间的所有碰撞体积组合。
    • 每个实体可能有多个碰撞体积,因此这类检测逻辑实际上是一个 n² 配对问题
    • 每个实体的碰撞体积数量 n 都会成倍增加整体运算的复杂性。
  3. 碰撞体积配对逻辑

    • 为了正确地检测两实体间的碰撞,需要对两实体的所有碰撞体积进行两两配对。
    • 这种逻辑不可避免地引入嵌套循环,每一层循环对应一个实体的碰撞体积。
  4. 计算量的不可避免性

    • 由于每个实体可能有多个碰撞体积,完整的检测过程需要检查所有可能的配对组合。
    • 这种 n² 级的复杂性是碰撞检测逻辑在处理多体积碰撞时的固有特性。
  5. 优化可能性

    • 当前讨论未涉及优化,仅描述了实现这种逻辑的必要性和复杂性。
    • 在未来可以考虑通过分区或简化体积检查的方式减少计算负担,例如通过提前过滤不可能碰撞的区域或体积。
  6. 设计目标

    • 确保碰撞检测的准确性,即使在多实体、多碰撞体积的情况下,也要能够完整覆盖所有可能的碰撞组合。
    • 逐步调整实现以支持更复杂的场景,同时为后续优化奠定基础。

总结来看,该讨论描述了碰撞检测在多实体、多体积场景下的必然复杂性,以及实现这种逻辑所需的嵌套循环结构。这种设计虽然计算量较大,但可以准确地检测所有可能的碰撞组合,为后续优化提供了清晰的实现路径。

在这里插入图片描述

Blackboard: 碰撞检测是 O(n^2) 问题

内容主要讨论了碰撞检测的复杂性及其性能问题,以下是详细总结:


  1. 碰撞检测的本质

    • 碰撞检测本质上是一个 O(n²) 的问题。
    • 计算中需要将每个对象与其他所有对象进行配对,以判断是否发生碰撞。
    • 对于每个对象,需要检查其与其余所有对象的碰撞状态,因此计算量随着对象数量呈平方增长。
  2. 成对检查的机制

    • 计算机在一次计算中只能处理两个对象之间的关系。
    • 检查完成一个对象与另一个对象的碰撞后,无法直接得知与其他对象的关系,需要逐一检查。
    • 这种成对比较的机制导致计算复杂度快速增加。
  3. 复杂度的影响

    • 碰撞检测的性能直接受到场景中对象数量的限制。
    • 对于小规模对象数量(如几十到上百个),O(n²) 的计算成本尚可接受。
    • 当对象数量达到数千甚至上百万时,计算成本将极为庞大,导致程序无法在合理时间内完成运行。
  4. 优化策略的必要性

    • 由于碰撞检测的平方复杂度特性,优化过程需要集中在尽可能减少需要比较的对象对数上。
    • 通过预处理或空间划分(如四叉树、八叉树或网格分区),可以大幅减少需要进入详细碰撞检测的对象数量。
    • 这些优化旨在快速排除不可能发生碰撞的对象,从而降低整体计算量。
  5. 大规模场景的挑战

    • 在大规模场景中,对象数量的增加会迅速导致性能问题。
    • 即使通过优化策略减少了一些对象的比较,仍需要针对剩余对象进行 O(n²) 的检测,因此需要权衡场景规模和计算资源。
  6. 复杂度的数学描述

    • O(n²) 是复杂度的一个表示法,描述了计算量如何随着输入规模增加而变化。
    • 这一表示法揭示了随着对象数量增加,计算需求呈非线性增长的趋势。
    • 对于数量增长至一定规模的场景,碰撞检测可能难以实时完成。
  7. 循环的显性表现

    • 碰撞检测逻辑中,嵌套循环显式地体现了 O(n²) 的特性。
    • 外层循环遍历所有对象,内层循环逐一检查每个对象与其他对象的关系。
    • 这种双层循环结构是碰撞检测复杂度的核心来源。

总结来看,碰撞检测作为游戏开发中的核心技术,其平方复杂度问题对性能提出了极高的要求。通过优化策略减少比较次数、分区管理对象范围等方法,可以在一定程度上缓解性能瓶颈,但根本上仍需面对其固有的复杂性。

game_sim_region.cpp: 继续编写此循环

内容主要讨论了碰撞检测过程中对实体体积进行配对测试的机制以及优化方法,以下是详细总结:


  1. 碰撞检测中的双层循环

    • 实体具有多个碰撞体积(volume),需要分别遍历实体的所有碰撞体积和测试对象的所有碰撞体积。
    • 这意味着在碰撞检测中,每对实体的所有体积都需要进行成对测试,形成一个嵌套循环。
    • 当前实现中,由于每个实体通常只有一个碰撞体积,因此不会增加额外的计算成本。但如果未来体积数量增加,将显著提高计算复杂度。
  2. 复杂度与性能的挑战

    • 如果体积数量增加,嵌套循环的工作量会成倍增长,使其变得更加昂贵。
    • 当前情况下,实体的碰撞体积数量较少(通常为3到4个),因此总体计算成本较低。
    • 对于大量实体的场景,仍需考虑优化以避免O(n²)的性能瓶颈。
  3. 优化碰撞检测的必要性

    • 嵌套循环结构需要针对所有实体进行比较,即使这些实体之间距离较远,不太可能发生碰撞。
    • 为减少不必要的计算,可以通过将实体按区域划分来限制比较范围。例如,在同一区域内进行进一步的分区。
    • 这些优化可以进一步降低“n”的值,从而减轻嵌套循环带来的性能压力。
  4. 当前引擎的现状与规划

    • 当前引擎的规模足够小,不会受到大规模碰撞检测问题的影响。
    • 在设计中已经通过将全局实体数量限制在较小范围(例如从10万减少到数百或数千)来避免性能瓶颈。
    • 对于更细粒度的分区处理,未来仍需要进一步优化以确保最终版本的性能表现。
  5. 最终优化目标

    • 嵌套循环的现状被认为是临时实现,最终产品中不打算保留这些直接的循环处理。
    • 通过分析性能分析器(Profiler)数据来决定是否需要更进一步的优化。
    • 在最终版本中,可能会引入更加高效的分区算法来替代当前的嵌套循环。
  6. 碰撞体积测试的实现细节

    • 为了测试两个体积是否发生碰撞,代码通过索引提取实体的碰撞体积,并进行逐一比较。
    • 将实体的相关体积提取为变量,使用这些变量计算碰撞结果,而非直接使用实体数据。
    • 此过程通过分离实体与体积的具体数据处理,简化了后续的逻辑。

总结来看,此段内容强调了碰撞检测的复杂性及其优化方向。在初期阶段,通过区域划分降低全局计算成本;而在最终实现中,将依赖更高效的分区算法,以在大规模场景中实现高性能的碰撞检测。
在这里插入图片描述

“不要问我为什么要重命名这个”


  1. 重命名与调整

    • 对变量进行了重命名操作,目的是为生成正确的碰撞体积直径(diameter)。
    • 确保生成的最小值和最大值(min 和 max)以测试体积为中心,与之前的逻辑一致。
  2. 位置偏移的处理

    • 修正了实体的碰撞位置,原始的实体位置变量(Entity->P)需要加上碰撞体积的偏移量(Volume->OffsetP)。
    • 计算测试实体的位置时,同样需要加上测试体积的偏移量(TestVolume->OffsetP)。
    • 通过添加这些偏移量,生成了两个实体的包围盒(bounding box),用于后续的碰撞检测。
  3. 碰撞检测的逻辑调整

    • 包围盒生成完成后,继续沿用之前的碰撞检测算法进行相交测试。
    • 碰撞检测逻辑保持不变,只是改动了实体位置的计算方式,以便支持带有偏移量的体积。
  4. 其他细节检查

    • 在当前实现中,所有碰撞检测的逻辑都已经调整为基于偏移量计算的包围盒,确保检测结果的准确性。
    • 整体逻辑经过调整后,应该可以正确覆盖所有相关的碰撞检测场景。
  5. 处理楼梯区域的碰撞检测

    • 确定实体的矩形或椭圆是否与楼梯区域发生重叠。
    • 针对楼梯的特殊情况,单独处理相关的碰撞检测,以支持复杂的地形交互场景。

总结来看,这段内容主要完成了从实体位置到碰撞体积偏移的计算转换,并通过包围盒检测保证了与楼梯区域的碰撞处理。所有调整旨在支持更复杂的场景,同时保持现有碰撞检测逻辑的有效性。
在这里插入图片描述

game.h: 向 game_state 添加 sim_entity_collision_volume_groups

在游戏开发过程中,创建并使用不同的碰撞体积组(Volume Groups)用于不同的实体(如剑、玩家、楼梯、墙壁等)。为了简化工作流程,这些碰撞体积组将作为模板直接用于创建实体,而不是每次手动配置。以下是具体步骤和说明:

  1. 创建碰撞体积组

    • 为不同的实体(如剑、楼梯、玩家、墙壁等)创建碰撞体积组指针。
    • 这些碰撞体积组将作为模板,在后续创建实体时直接使用。
    • 使用简化的函数来初始化这些碰撞体积组,并在创建实体时应用它们。
  2. 使用简化函数

    • 为每个实体(如剑)调用一个函数来创建简单的碰撞体积(makeSimpleCollision),传递相应的尺寸(如剑的x、y、z维度)。
    • 对于需要偏移的实体(如地面上的实体),使用makeSimpleGroundedCollision函数,自动将实体偏移半个z轴高度。
  3. 碰撞体积类型

    • 对于一些不需要碰撞的实体,创建一个空的碰撞体积组,这些实体将不与其他实体发生碰撞。
  4. 使用游戏状态来管理碰撞

    • 游戏状态(gameState)将用于管理和存储这些碰撞体积组。
    • 在实体创建时,将其相应的碰撞体积组与实体关联。
  5. 优化工作流程

    • 使用这些碰撞体积组模板避免了多次重复创建,简化了碰撞体积的管理。
    • 每个实体(如玩家、怪物、剑、墙壁等)都可以通过相应的碰撞体积组进行初始化,确保碰撞检测的准确性和一致性。
  6. 简化的碰撞系统

    • 在整个过程中,使用了简化的碰撞体积创建方法,使得每个实体的碰撞检测更加清晰和一致。
    • 通过这些方法,可以减少手动配置碰撞体积的工作量,提高开发效率。
  7. 进一步的清理工作

    • 在开发过程中,有些不再需要的代码被清理,简化了碰撞体积的处理,并确保所有实体的碰撞体积都被正确地初始化和应用。

通过这样的改进,碰撞体积的创建和管理变得更加高效,减少了冗余代码,并确保了游戏中的碰撞检测能够顺利进行。
在这里插入图片描述

在这里插入图片描述

game.cpp: 使用 WalkableDim 绘制楼梯

在处理楼梯(stairwell)的绘制时,所绘制的内容与之前有所不同。具体步骤如下:

  1. 绘制楼梯时的变化

    • 现在要绘制的是与楼梯的“可走区域”相关的维度,即定义楼梯的“可行走高度”。
    • 在之前的绘制过程中,使用的是碰撞体积(collision volumes)来定义楼梯的形态,但现在转而使用“可走维度”来表示楼梯的高度。
  2. 楼梯的z值

    • 楼梯的z值用于确定楼梯在空间中的偏移量,该值表示楼梯离地面的高度。
    • 通过这个高度来确定楼梯的具体位置,确保其正确显示在游戏空间中。
  3. 更新绘制方式

    • 现在绘制楼梯时,不再使用之前的碰撞体积定义,而是直接绘制与“可走维度”相关的内容。
    • 使用这些新的维度来确定楼梯的外观,确保玩家能够清晰看到楼梯的行走区域。

通过这种方式,楼梯的绘制更加准确,符合游戏中“可走区域”的表现,使得玩家的交互体验更加直观和自然。

在这里插入图片描述

game.cpp: 实现 MakeSimpleGroundedCollision

在处理碰撞体积和内存分配时,进行了以下步骤:

  1. 临时禁用某些功能

    • 暂时禁用了某些功能,以便集中精力解决其他问题。计划在稍后的时间内再处理这些问题。
  2. Arena 初始化

    • 当前的实体系统没有为实体专门初始化arena(竞技场),而是将其应用于世界(world)。为了正确初始化,需要先进行一些调整,将arena设置为适合实体的形式。
    • 这个部分涉及对内存空间的划分,暂时使用世界arena,但将来可能会重新调整为更合适的内存区域。
  3. 内存分配和碰撞体积组

    • 将碰撞体积组推入arena中,以便管理所有的碰撞体积。每个组将包含一个体积,体积的总数目前为1。
    • 每个体积使用一个简单的维度(dim)和偏移量(offset),偏移量根据需要进行设置。
  4. 碰撞体积的设置

    • 体积的维度直接传入,偏移量简单地设置为半个高度。此时,每个体积的定义都相对简单。
  5. 未来的调整

    • 当前的设计假设只使用一个体积,因此碰撞体积组中的总体积(total volume)被设置为一个体积。随着系统的完善,可能需要支持多个体积。
  6. 潜在的Bug

    • 预计在这个过程中会遇到一些bug,需要在后续进行修复和调整。

总体来说,当前的实现目标是通过简化的设置来快速推进碰撞系统的构建,但仍需注意未来可能出现的问题,并为进一步的调整做好准备。
在这里插入图片描述

game.cpp: 提前初始化 World

在这里插入图片描述

为了引用地砖的尺寸,首先需要初始化游戏状态相关的内容。当前未进行初始化,因此需要设置一些基础的元素,比如地砖等。这个过程需要在项目开始时进行初始化,确保一切准备就绪。现在的任务是处理这些基本的设置,以便后续的操作能够顺利进行。
在这里插入图片描述

运行游戏并发现我们没完全崩溃

在这里插入图片描述

碰撞检测仍然正常工作,但楼梯部分明显缺失,需要处理。虽然已经注释掉了楼梯的代码,但它本应该仍然绘制出来,所以这是一个需要解决的问题。其他部分看起来都正常。目前剩下的时间较少,但仍然有可能在剩余的时间内完成楼梯的处理。

game.h: 飞行物体现在可以越过低墙

增加了一个额外的功能,现在飞行实体的碰撞框与其实际位置有偏移,它们仍然可以像其他实体一样在地面上行走。这使得飞行实体能够越过低墙,因为它们的碰撞框不会与墙体发生碰撞。这项改动已经完成,可以从待办事项中删除。

game.cpp: 在 AddStair 中初始化 WalkableDim

遇到的问题可能是没有正确初始化WalkableDim。为了修复这个问题,需要将WalkableDim设置为应该碰撞到的区域。维度的大小应与楼梯碰撞区域的总体积一致,应该定义为一个矩形,这样更为合理。
在这里插入图片描述

game_math.h: 引入 ToRectangleXY

为了将一个三维矩形转换为二维矩形,设计了一个简单的函数,该函数接收一个三维矩形作为输入,并返回一个对应的二维矩形。转换过程基于矩形的最小值和最大值,通过切片操作提取二维矩形的x和y值。这个函数的目的是将三维空间的矩形简化为二维,以便更高效地进行碰撞检测等操作。
在这里插入图片描述

在这里插入图片描述

运行游戏,看到楼梯回来了

目前已经恢复了楼梯功能,并且楼梯的碰撞检测能够正常工作:角色会碰到楼梯,并正确通过它。然而,重叠检测问题没有得到处理,因为相关的设置被清除掉了。因此,需要修复这一问题。

game_sim_region.cpp: 重新启用楼梯碰撞

在当前的工作中,需要对区域内的代码进行重命名。为了简化工作,可以通过测试总碰撞体积来实现,这样做非常快捷。具体来说,测试的内容涉及实体碰撞的总体积,并通过相关函数来完成这个任务。也许可以创建一个专门的函数来获取碰撞体积,稍后再深入考虑如何实现。类似的测试也会在其他地方进行,比如对于某些特定维度的检测,使用相应的碰撞体积函数来进行测试。
在这里插入图片描述

运行游戏并尝试跳跃过楼梯

目前,已经可以通过跳跃越过楼梯,尽管需要足够的高度才能清除楼梯。如果高度不够,可以通过双重跳跃来完成这个动作,能够成功跳过楼梯。在时间有限的情况下,已经接近完成了这部分工作。接下来,需要清理和处理体积相关的内容,并为不同体积之间的重叠测试做准备。这涉及到进行N平方次的测试,即对每个体积与其他体积之间进行比较,检查是否存在重叠。
在这里插入图片描述

game.cpp: 实现 MakeNullCollision

接下来,计划完成一个功能,处理无碰撞区域的初始化。目标是确保每个实体的碰撞始终被初始化为某种形式,以避免出现未初始化碰撞指针的问题,避免因检查指针是否为零而导致崩溃。为了实现这一点,将创建一个专门用于无碰撞区域的结构,表示一个完全没有碰撞的区域。这个结构会将体积、指针、偏移量和维度都设置为零,确保区域内没有任何内容。尚不确定是否应该将某些值设为负数,但这是下一步需要考虑的内容。
在这里插入图片描述

运行游戏并注意到在楼梯上掉下来的重力效果很奇怪

目前,遇到了一个问题,当从楼梯的底部跳跃时无法跳过去,因为楼梯本身是固体的。然而,跳到楼梯上方时,楼梯变成了地板上的一个洞,这时就能跳过去,表现得很好。关于浮空下落时的表现,出现了一些不理解的情况,看起来重力似乎没有按预期速度作用,导致下落速度不正常。这可能是与碰撞处理相关的某些问题。初步猜测,可能是因为在3D环境中应用了拖拽效果,导致跳跃时受到拖拽影响,从而影响了跳跃的速度。接下来需要检查这个问题。
在这里插入图片描述

game_sim_region.cpp: 设置 Drag.Z = 0.0f

目前,正在讨论Drag效果对跳跃的影响,计划调整在z轴上的Drag计算。初步想法是取消z轴方向的Drag,以确保跳跃的重力表现更加符合预期。
在这里插入图片描述

运行游戏并从楼梯上摔下来

目标是确保跳跃后角色能正确地掉进楼梯口。这是预期的效果,角色应当顺利落下并停留在楼梯上。
在这里插入图片描述

运行并四处跳跃

  1. 跳跃机制的调整:提到游戏中的跳跃机制需要进一步优化,尤其是在多层结构中,确保角色无法轻易跳跃到最高层。例如,目前已经能够实现跳跃到较高层次,但存在角色跳跃过高后,部分区域的内容超出了可见范围的问题。
  2. 地面层级的处理:明确指出,地面层级需要进一步修正,以确保跳跃或层级切换时的逻辑正确。这被列为未来需要解决的主要任务之一。
  3. 整体进度:项目的3D功能已经接近完成,整体效果令人满意,开发过程中感到非常兴奋。
  4. 讨论的氛围:讨论中没有收到提问,表明工作流程顺利,且对当前进展表示肯定和满意。
    总的来说,内容涉及编程任务的优化、游戏机制的完善以及当前项目的积极进展。

我们能不能提前看看基础类型区域?

可以创建一个基本类型的内存分配池(arena),用于管理和组织内存资源。该内存分配池的设计目标是高效、灵活且易于扩展,能够满足基本类型或对象的动态分配需求。以下是关于内存分配池的详细设计总结:

  1. 内存分配方式

    • 内存池通过预分配一块连续的内存区域,避免频繁的动态内存分配,提升性能。
    • 通过固定大小的块或分层分配机制,支持不同大小的内存请求。
  2. 基本功能

    • 提供初始化和销毁方法,确保内存池的生命周期管理。
    • 提供分配和回收方法,支持动态分配的同时能够有效地回收未使用内存。
    • 支持边界检查和错误检测,避免内存泄漏或越界问题。
  3. 设计特点

    • 可定制性:支持对内存块大小、对齐方式等参数的灵活配置,以适应多种应用场景。
    • 性能优化:通过减少内存碎片和避免频繁调用系统内存分配函数,提升整体运行效率。
    • 线程安全:在多线程环境下,可以通过锁或无锁机制确保分配和回收的安全性。
  4. 适用场景

    • 可用于管理基本数据类型(如整数、浮点数)或简单对象的生命周期。
    • 适合需要频繁分配和释放的小对象的场景,如游戏实体管理、临时缓冲区等。
  5. 扩展思路

    • 引入分代回收机制,针对不同生命周期的对象优化内存管理。
    • 提供监控和调试功能,例如记录分配统计信息,帮助分析内存使用情况。

通过以上设计,能够构建一个高效的内存分配池,专注于基本类型的管理,减少内存分配的复杂性,同时提升系统性能。

除了漏掉的按键

  1. 内存分区的结构

    • 内存分区中包含两类数据:
      • 常量数据:例如碰撞定义等,这类数据在游戏启动时初始化后会一直保存在内存中,不会随着游戏进程的变化而被销毁。
      • 世界数据:与游戏世界相关的动态数据,每次开始新游戏时都会被清除并重新加载。
  2. 设计思路

    • 为了优化性能,不需要每次重新创建常量数据。在游戏启动时,将所有常量数据加载到内存中的固定区域,并在整个游戏过程中共享这些数据。
    • 世界数据是动态的,会根据游戏进程更新,在每次开始新游戏时重新加载或生成。
  3. 实现优势

    • 这种分区方法避免了重复加载和创建常量数据,提高了性能。
    • 常量数据与世界数据的分离使得内存管理更加高效,并简化了游戏重置时的处理逻辑。
  4. 额外内容

    • 提到了一些技术背景,例如在 Unix 环境中任务暂停的快捷键,以及 Emacs 和 Windows 的映射差异。
    • 简要讨论了回答问题的方式和时间安排,旨在确保所有问题都能被优先处理。
  5. 未来计划

    • 对上述内存分区和数据管理方法进行详细讨论和解释,确保设计清晰,不遗漏重要细节。

这种内存分区的设计体现了对性能优化的追求,同时也为数据的高效管理奠定了基础。

你能把今天做的事情视觉化地画出来吗?只是更改了碰撞范式吗?

以上内容主要描述了游戏编程的实际过程和相关特点,详细总结如下:

在实际的游戏编程中,流程往往与示范中的操作类似。虽然节奏稍慢,但这是为了更清晰地讲解和说明具体操作。在这种情况下,处理的内容与开发真正的视频游戏时所做的工作基本一致,几乎没有简化。尽管某些部分可能显得复杂,但这些复杂性是为了教育目的而保留的,从而为观看者提供一个深入了解三维编程的窗口。这样的内容可以作为二维引擎的延伸,因为它们在许多方面具有相似性,只是几何和计算更复杂。

在编程风格上,存在一定的“老派”特征。尽管如此,这种风格并不极端现代或完全传统。不同的开发者在优先级和编码方法上各有偏好,一些人倾向于更复杂的C++特性,比如继承和模板,而另一些人则选择更简化的方法。这种差异源于游戏开发中常常不存在绝对正确的解决方案,如何实现特定功能在很大程度上依赖于实际需求和开发者的选择。

对于具体的技术层面,内容还涉及到流媒体相关的处理以及引擎的扩展。尽管某些部分的复杂性可能不是每个项目都需要,但为了全面覆盖教育内容,这些部分被刻意保留。此外,对于三维编程来说,内容展示了它如何作为二维引擎的逻辑延续,并为未来进入三维领域提供了一个合理的过渡。

整体上,这是一种展示真实编程流程的方式,既包含实际开发中的操作,也试图提供一种更广泛的教育性视角,使得内容对不同层次的开发者都有借鉴意义。

指定地面

当前的实现中,地面的处理尚未完善,主要问题并非碰撞检测,而是如何规范地面的定义。现有系统仅在空间中放置了一些实体,例如以网格模式任意排列的树木,这些实体定义了一个平面上的房间。虽然在平面上的碰撞检测能够正常工作,但在涉及堆叠房间的情况下,地面的定义变得模糊,难以准确确定地面的位置。

目前,地面的定义完全依赖于摄像机的指向位置,也就是说,地面的位置与摄像机的Z坐标同步。这种方式显然是不够完善的。当前需要解决的问题并非“如何数学化地处理地面”,而是“如何规范地面的位置定义”。

可能的解决方案包括:

  1. 添加实体定义地面:通过添加实体明确地定义地面的位置。如果某个地方没有地面实体,则代表没有地面。这种方式在三维环境中可能是合理的选择。
  2. 默认存在地面,指定例外:在绝大多数情况下,二维环境下的房间通常有一个平坦的地面,仅少数地方存在例外,例如楼梯或地面缺口。与其逐一定义地面,不如默认地面始终存在,仅在某些地方明确指定“这里没有地面”。

第二种方式可能更适合二维场景,例如《塞尔达传说》或《以撒的结合》这类游戏。在这些游戏中,地面通常是简单的平面,很少存在复杂的地形,因此可以简化地面处理的逻辑。

一个实现方式是:在加载模拟区域时,自动插入一组预定义的地面层,然后通过实体标记哪些地方不存在地面。这种方式可能更符合二维游戏的常见需求,同时避免了每次都需要定义地面的麻烦。

至于地面是作为体积处理还是作为平面处理,当前来看并不是主要问题。两种方式都可以正常工作,可能在后续开发中某种方式会显得更有优势,但现阶段更重要的是找到一个有效的方法来定义地面的位置。目前完全没有地面定义的方式显然是不够的,这也是亟待解决的问题之一。

你使用了内存管理器吗?

当前的代码状态下,处理实体的叠加功能并不复杂。如果只是实现简单的叠加,例如一个实体在另一个实体上方时能够站立行走,这非常容易实现。然而,如果需要更加复杂的物理效果,例如多个实体以物理解谜的方式相互叠加、推动或者产生其他交互,可能会涉及较为棘手的物理处理问题。此外,关于是否使用内存管理器也可以进一步讨论。

你现在是如何布局你的关卡的?会做一个关卡编辑器吗?

目前的关卡布局完全依赖程序生成,因此不会制作关卡编辑器。当前阶段的程序生成仅实现了一些基础的占位功能,用于生成少量房间,以便进行测试。未来将会花费大量时间在关卡生成上,这可能会占据整个游戏开发时间的一半。关卡生成将成为游戏设计中最重要的部分之一,与实体和物体的行为设计共同构成核心内容。预计可能会在这方面投入非常长的时间,例如整个开发周期中的六百天里有四百天可能都用于完善关卡生成系统。这一部分工作将是整个开发过程的重点和难点之一。

在游戏编程行业中,知道大 O 表示法重要吗?

大 O 表示法在游戏编程中非常重要,但它的重要性不仅限于游戏编程,而是适用于任何编程工作。理解代码的扩展性以及代码在处理最大负载时会如何表现是至关重要的。例如,需要了解代码在不同复杂度下的行为,如 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( log ⁡ n ) O(\log n) O(logn) O ( n log ⁡ n ) O(n \log n) O(nlogn) O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n) 等。重点在于理解代码的可扩展性和性能,而不仅仅是记住这些符号表示法。大 O 表示法只是用于描述和交流代码扩展性的一种工具,而核心在于掌握代码在不同规模输入下的实际行为表现。

你提到过进一步分区碰撞,除了将它分割成区域,还有哪些方式?

目前,区域划分仅限于一个大的区域,所有物体都在同一区域内进行碰撞检测。未来的目标是进一步细化区域,将同一个大区域分割成更小的子区域。这些子区域将专门用于识别可能发生碰撞的物体。换句话说,只有位于同一子区域的物体才会进行碰撞检测,而位于不同子区域的物体则不会互相检测。这样做的目的是减少每个子区域中需要检查的物体数量,理想情况下每个子区域中的物体数量应为五到十个,而不是目前的上百个或几百个。这样可以显著提高碰撞检测的效率,避免目前的做法带来的性能问题。

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

;