游戏引擎学习第45天

server/2024/12/15 12:18:44/

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

回顾

我们刚刚开始研究运动方程,展示了如何处理当人物遇到障碍物时的情况。有一种版本是角色会从障碍物上反弹,而另一版本是角色会完全停下来。这种方式感觉不太自然,因为在游戏中,角色应当继续沿着障碍物滑行而不是停止。所以我们决定让角色在接触到障碍时仍然保持运动状态,只是其水平速度会被消减。问题在于我们还没有正确编写游戏循环,这导致角色更新机制不够有效,最终导致角色在接触到障碍物时停滞不前。

上一节遗留了一些问题我没注意看

对角线问题

当前的实现中,对角线方向的碰撞处理并不完美。这是因为碰撞逻辑仅能处理单一方向的反弹,而无法同时处理多方向的交互。例如,在对角线区域的碰撞中,可能仅反弹出一个方向,而非同时考虑水平和垂直方向。
在这里插入图片描述

如何在墙上跑动?

在这里插入图片描述

这里所说的两个版本就是内积前面的系数是1 和2 的两种版本

粘滞问题回顾

我们会遇到一个棘手的问题,当角色接近墙时,可能会出现卡住的情况。我们最初的设计没有充分考虑角色是否可以移动到墙壁上。如果角色被推到墙壁,系统会迅速检测角色是否能够在那里停留。如果角色不能停在原地,系统就会完全拒绝这个动作,从而使角色无法继续移动。这种情况导致角色可能在接触墙时停滞不前,并且如果修正其速度和加速度,角色可能会重新回到墙内。虽然这种情况不常见,但它仍然可能发生,尤其是在浮点数处理上。因此,我们需要重新考虑如何处理角色的移动和碰撞检测,以避免这种不希望的情况出现在最终的游戏代码中。

在代码中,我们通过检测玩家的位置是否与墙壁发生碰撞,决定玩家是否能够移动。具体来说,当计算出玩家的新位置后,代码会测试玩家能否在新位置上站立。如果发现玩家不能在新位置站立,代码会拒绝更新玩家的位置,同时对玩家的速度进行修正。

在这种情况下,虽然玩家的速度被修正,但由于位置未更新,他可能会“粘附”在墙壁上。这种“粘附”现象的原因在于,当玩家的速度被调整后,他在下一帧中可能会再次尝试移动到墙的另一侧,但仍然无法通过检测,导致他持续停留在当前的位置。

每一帧中,玩家都会受到用户输入导致的加速度影响,而这种加速度可能持续朝向墙壁的方向,因此他会不断地尝试靠近墙壁。代码中虽然对速度进行了修正,使其沿着墙壁方向运动,但玩家的加速度仍然可能使他再次尝试进入墙体,因此在某些帧上会表现出“粘在墙上”的情况。

为了解决这个问题,需要修改代码逻辑。当玩家试图向墙壁方向移动时,不应该完全阻止其移动,而是应该调整运动轨迹。例如,可以让玩家的实际移动方向偏离墙壁,使他能够沿着墙壁滑动,而不是停留在原地。这种处理方式确保玩家的运动不会被完全否定,同时避免他反复尝试穿越墙壁而卡在原地的现象。

通过这种方式,玩家的运动逻辑将更加流畅,无论是在靠近墙壁时的表现还是整体运动行为,都可以避免“粘附”问题。

在这里插入图片描述

处理问题的两种方式:搜索在T,搜索在P

处理碰撞检测时,我们通常会采用一种标准的方法。首先,我们会在角色移动到一个新的位置时,不仅仅检查这个新位置是否与墙体相交,还会测试角色从旧位置到新位置整个路径的体积。这种方法允许系统更全面地识别角色是否会撞到墙壁,即使墙很薄。这不仅能够捕捉角色在接触墙时的位置,还会保留角色的实际动量,使其更自然地反弹或停止。

然而,这种方法也存在问题。例如,它可能导致角色在某些情况下“卡住”在墙上。这种情况发生是因为系统丢弃了一些角色实际的运动量,因此角色可能被推回到墙内,导致其不断“卡住”。为了解决这些问题,游戏通常会在碰撞检测后进行速度校正,并在需要时再次移动角色。如果角色遇到墙壁,则会适应其运动以确保角色在碰撞后能正确反弹或继续前进。

这种处理方式虽然有效,但也比较复杂,因为它涉及到一个迭代的搜索过程来调整角色的位置和速度,从而避免卡住现象。这些步骤需要实时计算和多次校正,确保角色的运动与碰撞检测结果一致。这些调整不仅改善了角色的运动体验,还确保了游戏的物理行为符合真实感。

在这里插入图片描述

Search in TimeSearch in Position 是两种不同的算法方法,主要用于计算机科学和工程领域中的问题解决和优化。

Search in Time

  • Search in Time 通常是指算法根据时间的进展来进行操作的情况。这种算法主要关注在时间序列中找到解决方案或做出决策。例如:

    • 时间搜索算法:在模拟或游戏物理系统中,这些算法通常在多个时间步长内检查事件或状态的可能交集。它们用于在时间的推移中找到对象之间的碰撞或相交点。
    • 迭代方法:例如,游戏中的碰撞检测算法可能需要在每个时间步内检查对象的相交情况。这样做的目的是找到对象在时间进程中可能的碰撞。
  • 示例算法:在游戏中,碰撞检测算法可以按时间步骤进行检查,检测物体之间是否发生碰撞。这涉及检查对象在多个时间步内的位置,找出确切的碰撞点。

Search in Position

  • Search in Position 是指基于特定空间位置的搜索算法。这类算法主要关注于对象在空间中的位置配置:

    • 空间搜索算法:这些算法用于在特定的空间域中查找元素。例如在计算机图形学中,空间树(如四叉树或八叉树)、空间散列和空间分区技术都是常见的。
    • 空间数据结构:例如,在游戏中的一种空间分区方法可能使用网格法,将世界划分为单元格,只对位于同一或邻近单元格的物体进行碰撞检测,从而减少了不必要的比较,提高了算法的效率。
  • 示例算法:在游戏中的碰撞检测,可以使用网格法将世界划分为网格,然后仅检查处于相同或邻近网格的物体的碰撞。这种方法显著减少了比较的数量,提升了效率。

关键区别

  • Search in Time 关注于在时间的进程中检查多个可能的未来状态。
  • Search in Position 关注于在空间中对象的位置关系。

这两种方法在开发高效的算法用于模拟、碰撞检测和其他计算问题中都是至关重要的。

搜索在T的问题

在碰撞检测的过程中,这种问题变得相当棘手,尤其是在处理复杂、弯曲的墙体时。假设我们在碰撞检测中需要不断调整角色的位置来避免卡住的问题,这就像是一个无尽的迭代循环。这种情况可能导致系统过度计算,花费大量时间来调整角色的每个小步移动,这样的方式并不高效。比如,当角色在弯曲的墙上移动时,系统需要不断地修正角色的速度来避免卡住墙上的某个点,这不仅增加了计算量,还可能导致角色在墙边不断反复调整,从而影响游戏的流畅性。

我曾经在《目击者》中使用了一种不同的方法,这种方法虽然不一定适用于所有游戏,但在那种情况下效果很好。它减少了这种反复修正的需求,利用了不同的碰撞检测算法。例如,可能是通过一个近似的“惰性碰撞检测”方式,这样角色在接触到墙体时会马上停下来,而不是继续移动,这种做法避免了角色在墙边的反复修正。而且,我发现这种方法能够有效地处理复杂的弯曲墙体,不会导致角色的速度被反复修正到极端。

尽管这种方法在特定场景中表现优越,但它是否适合在动作游戏中使用仍然是一个疑问,因为它可能会影响角色的动作流畅性。如果在一个快速移动和碰撞频繁发生的环境中使用这种方法,可能会显得不合适。因此,我们需要权衡在游戏中使用哪种碰撞检测方法,以确保角色在碰撞时反应真实、流畅。

通过Witness 方式解决问题


在处理游戏碰撞检测时,传统的时间搜索方法(Search in Time)存在显著的局限性。例如,当尝试通过时间维度对碰撞进行逐步校正时,很容易陷入无限循环或无法预测的情况。这种方法在复杂场景中表现不佳,特别是遇到曲面或复杂几何时会导致不准确的修正。

针对这些问题,我们决定摒弃基于时间的碰撞搜索方式,而转向基于位置的搜索(Search in Position)。这一转变的核心思路是,位置搜索相比时间搜索更加有界,易于控制,并且结果更加可靠。


具体实现方式

  1. 定义几何网格
    通过建立一个高分辨率的网格,描述碰撞几何的具体形状。例如,对于一个物体的移动路径,网格会明确标记出哪些位置是允许的,哪些位置是不允许的。

  2. 构造搜索范围
    当物体移动时,会计算出其在当前时间段内可能到达的范围。这一范围被表示为一个矩形区域,网格中对应的单元格会被纳入搜索范围。

  3. 在网格中搜索合法位置
    在上述范围内,算法会检查每个网格单元格,寻找目标点的最近合法位置。这一过程确保物体最终会停留在有效区域内。

  4. 边界平滑
    在计算时,算法会避免物体因碰撞几何中的微小误差或不规则形状而被卡住。例如,在沿墙壁滑动时,即使墙体表面存在肉眼难以察觉的小凸起,算法仍能顺畅地让物体通过。


在这里插入图片描述

优势

  1. 有界性和可预测性
    由于基于位置的搜索范围明确且有限,计算过程始终在可控范围内完成,结果也更容易预测。

  2. 平滑性
    在移动过程中,物体不会因几何边缘的小误差而卡住,整体体验更加流畅。

  3. 适配复杂几何
    无论是直线、曲面,还是其他复杂形状的障碍物,该方法都能提供一致的处理效果。

  4. 减少计算错误的影响
    在某些场景中,由于艺术设计或计算几何上的细微错误,可能会出现细小的碰撞异常。该算法能够智能地忽略这些瞬态误差,保持流畅性。


实际效果

通过这一方法,碰撞检测变得更加高效,特别是在复杂的几何场景下表现突出。例如,当物体在沿着一堵曲面墙移动时,即使墙面存在细微的误差,物体仍然可以顺利通过,而不会被卡住或陷入死循环。


局限性

尽管基于位置的搜索在多个方面优于基于时间的搜索,但其实现难度较高,需要更精细的数学和算法支持。此外,对于一些极端高动态的游戏场景,这一方法可能需要调整,以满足实时性需求。


总结而言,基于位置的碰撞搜索提供了一种高效、精确且流畅的解决方案,大大改善了传统方法在复杂几何环境中的表现。这种方法非常适合需要精确物理模拟的场景,如大型开放世界游戏或注重细节的游戏设计中。

搜索在位置上的问题

在这一部分中,重点讨论了一种基于搜索的方案,用于处理碰撞检测和物体位置计算的问题。这种方法旨在通过搜索空间中的几何点来改进传统方法中的某些限制。

目前提出的问题是,这种方法可能在动态的动作游戏中面临一些困难,尤其是生成“可搜索集”的难度较大。如果场景中有很多动态的物体在移动,如何高效地将这些物体转化为搜索集是一个挑战。

方法的细节与优缺点:

  1. 搜索位置的思路

    • 在此方法中,物体的碰撞位置由一个可搜索的几何集来描述,比如将一个物体的可能位置范围用网格或特定的几何图形表示。
    • 搜索的过程是基于目标点找到几何集中距离最近的点,这种搜索是有界且稳定的。
  2. 静态场景中的优势

    • 在静态场景中,这种方法表现出色,因为所有的几何信息都是已知和确定的,比如一个网格化的地图。
    • 方法可以避免因微小几何误差(如墙面上的细微凸起)导致的卡顿问题。它能够“滑过”这些瞬态误差,从而提供更流畅的体验。
  3. 动态场景中的潜在问题

    • 在动态场景中,问题变得复杂,因为物体位置会随时间变化,而生成“可搜索集”的过程需要实时计算,这可能会显著增加计算复杂性。
    • 例如,对于一个带有圆柱形碰撞边界的动态物体,需要找到其移动后的几何点的最邻近点,这对实时性能可能是一个挑战。
  4. 实现上的取舍

    • 在目前基于网格地图的系统中,这种方法实现起来比较简单,因为地图是静态的,所有几何信息可以预先定义。
    • 但在复杂场景中(如有大量移动的物体),可能需要更复杂的逻辑来动态生成搜索集。

当前的选择与方向:

  • 认为这种方法在静态场景下更加稳定和可靠。因此,当前倾向于尝试这种方法作为基础,随后根据实际效果进行调整。
  • 如果未来遇到性能瓶颈,可以考虑改用另一种方式来优化。
  • 总体来说,这种方法在理论上优越,虽然在复杂动态场景中的实际可行性需要进一步探索。

总结,这是一种基于搜索位置而非时间的方法,强调稳定性和流畅性,但在实现过程中需要平衡静态和动态场景的需求。尽管在复杂情况下可能需要克服挑战,但这种方法值得进一步尝试和改进。

如何在代码中实现特定空间位置的搜索算法

在当前系统中(基于瓦片地图的环境),实现碰撞检测和路径判定的方法是非常简单和直接的。当前的地图结构是完全直线型的,没有复杂的碰撞几何,因此可以使用较为基础的算法来处理。以下是方案的具体步骤和逻辑:


方案实现:

  1. 基本概念

    • 当前的系统是基于瓦片网格的地图。每一个瓦片(tile)可以被判定为“实心”(solid)或者“空的”(empty)。
    • 人物的位置会被表示为当前所在的瓦片,移动时需要检查目标瓦片的属性。
  2. 移动检查流程

    • 计算矩形边界
      对于人物的新位置,计算一个保守的矩形边界,覆盖人物可能触及的所有瓦片。
    • 识别触碰的瓦片
      确定矩形范围内所有被触及的瓦片。例如,遍历所有边界矩形内的瓦片。
    • 过滤可行瓦片
      对每一个触碰的瓦片:
      • 检查它是否是“空的”。如果瓦片是实心的,跳过不处理。
      • 检查是否能够从当前位置通过合法路径到达该瓦片。
    • 路径检测
      如果需要确保有路径连接,使用洪水填充算法(Flood Fill)从当前位置扩展到所有相邻瓦片,直到覆盖所有连接区域。
      • 遇到障碍物停止扩展。
      • 记录所有可到达的空瓦片。
  3. 最近点判定

    • 对于所有可到达的瓦片,检查目标点是否位于瓦片范围内:
      • 如果目标点在某个瓦片中,则选择该瓦片。
      • 如果目标点不在任何瓦片范围内,则在瓦片边缘找到距离目标点最近的点。
      • 最终选择包含最近点的瓦片作为移动的目标瓦片。
  4. 特殊情况处理

    • 如果人物最终处于瓦片边缘,可以根据最近点的逻辑调整到边缘点。
    • 使用简单的矩形边界和最近点算法,解决边缘精度问题。

进一步优化

  • 简化路径检测
    如果地图连接性简单(例如无复杂障碍物),可以省略洪水填充步骤,直接基于矩形范围内的瓦片计算。
  • 动态调整方案
    当前的方案适用于静态地图和较为简单的几何。如果未来需要处理更复杂的碰撞几何或动态物体,可以引入更复杂的逻辑,如动态路径规划或实时更新搜索集。

方法的优点

  • 实现简单:适用于当前环境下的瓦片网格结构。
  • 稳定可靠:保证每次移动仅考虑可到达的瓦片。
  • 灵活扩展:能够适应更复杂的场景需求。

为什么需要洪泛填充

在实现路径判定和碰撞检测时,使用洪水填充(Flood Fill)算法的原因主要是为了处理特殊的路径连通性问题。以下是具体的逻辑和应用场景总结:


问题场景

假设在地图上存在一个壁挂装置和一堵墙,形成了一个狭窄的角落。如果不进行洪水填充,只是简单地检查瓷砖是否“空的”,那么可能会得出错误的结论,认为可以直接移动到某些目标瓷砖。然而,由于这些瓷砖与当前的位置之间实际没有连通路径,这种移动在现实中是不可能的。


洪水填充的必要性

  1. 识别连通路径

    • 洪水填充的核心目标是确保从当前位置到目标瓷砖之间存在实际的连通路径。
    • 即便目标瓷砖是“空的”,如果没有路径连通(例如被墙阻隔),也不能将其判定为可到达。
  2. 避免错误路径

    • 在没有洪水填充的情况下,只是简单地检查目标瓷砖的属性,可能会错误地认为某些瓷砖是可以移动到的。
    • 洪水填充通过模拟路径扩展,排除了所有不可连通的瓷砖,确保结果的准确性。

算法逻辑扩展

  1. 定义起始点

    • 从当前所在的瓷砖开始,以当前位置为洪水填充的起点。
  2. 路径扩展

    • 逐步检查与当前瓷砖相邻的瓷砖:
      • 如果瓷砖是“空的”且未被访问过,则将其标记为可达,并继续向外扩展。
      • 如果瓷砖是“实心”的,则停止扩展。
  3. 连通性判断

    • 只有那些在洪水填充过程中被标记为可达的瓷砖,才被认为是当前点可以到达的目标。
  4. 特殊情况

    • 如果目标瓷砖位于地图的边缘或靠近障碍物,洪水填充可以准确地判断是否有连通路径,而不会因为单纯属性检查而导致错误。

实际意义

  • 准确性提升
    洪水填充确保了路径判定的准确性,尤其是在复杂的地图结构中,避免了错误移动的情况。
  • 适应复杂地图
    这一方法适用于动态障碍或非线性路径的地图结构,有助于解决更多场景中的问题。

总结,洪水填充的引入是为了弥补单纯瓷砖属性检查的不足,它通过模拟路径连通性,确保只有实际可到达的瓷砖才能被选择为目标位置。这种方法为路径规划的准确性和鲁棒性提供了有力支持,在复杂地图或障碍物分布的情况下尤为重要。

玩家有体积的问题

我们现在面临的问题是,在任何碰撞方案中,如何解决角色不是一个单点的问题。当前,我们的角色并不是一个单点,而是具有一定区域的对象,比如一个圆。这导致了碰撞方案需要额外处理角色体积的问题。

问题描述

  1. 碰撞检测的复杂性
    如果角色被当作一个点,那么碰撞检测只需检查一条线段与另一条线段的交点。这种情况计算较为简单。但如果角色是一个圆或椭圆,就需要处理圆与线段的交互,这大大增加了复杂性。例如,圆的体积会导致需要更多计算以避免角色与几何边界的碰撞。

  2. 当前实现的局限性
    当前的碰撞检测让角色变得“无限瘦”在一个方向上,这使得角色在某些情况下可以轻松通过狭窄区域,但也引入了一些其他的问题,例如难以在另一个方向上通过。

  3. 将角色看作圆的挑战
    如果角色的碰撞区域被建模为圆,碰撞检测需要在更复杂的几何空间中找到最接近的点或计算角色与环境的关系。这种方法涉及复杂的数学运算,比如寻找圆形边界与其他几何物体的最近点,这通常比简单的点与线段的交互更困难。

  4. 优化方法
    为了简化问题,可以在角色的碰撞计算中简化几何表示。例如,在考虑一个矩形区域的碰撞时,只需判断矩形边上的区域是否被“填充”,并根据该方向的半径计算碰撞。这种优化方法显著减少了复杂性。

实现策略

  1. 在引擎的设计中,初期可以采用较为简单的碰撞检测方法,比如将角色建模为一个点。这使得在初期实现和调试时,算法相对清晰且易于定义。
  2. 将来,随着引擎复杂性的增加,可以逐步引入更复杂的几何处理方法,比如支持角色体积的更精确的碰撞检测算法。
  3. 确定角色在空间中可能的连接区域,通过定义角色在空间中最近的可达点,建立简单且明确的路径搜索问题。

总结
目前,我们的重点是通过明确定义角色在空间中的状态和碰撞关系,制定清晰的引擎需求。初期的实现重点在于简化复杂性,确保功能性,并为将来的改进和扩展预留空间。这种方法既保证了当前开发的可行性,也为未来的复杂功能打下了坚实的基础。

实现搜索在P

我们正在分析代码的逻辑并改进其结构,以下是我们所处理的内容及目标的详细说明:
目前的代码逻辑围绕判断瓷砖是否为空以及对应实体是否位于同一块瓷砖上展开。我们将重新组织代码以优化其处理方式,并确保其在后续的执行任务中保持正常工作。

  1. 首先,我们识别出当前代码中存在的冗余部分,并决定移除之前的逻辑,这些逻辑已经完成了其功能或不再适用。我们希望采用更清晰、更简洁的方法来实现目标。

  2. 在新的逻辑中:

    • 引入了“玩家之前位置(old player position)”的概念,用于记录玩家在动作开始前的位置。
    • 在进入函数初始时,记录玩家的当前状态并将其存储为“玩家之前位置”,便于后续比较。
    • 将更新玩家位置的代码移至函数外部,以使其更加模块化且易于维护。
  3. 对于状态的判断逻辑:

    • 我们确保能够判断“玩家之前位置”和“新玩家位置”是否在同一块瓷砖上。
    • 通过直接操作游戏状态中的玩家数据,移除了对某些中间变量的依赖,从而简化代码流程。
  4. 为了确保代码仍然正常运行,我们进行了编译和测试:

    • 验证了玩家可以正确地上下楼梯或撞墙,逻辑行为符合预期。
    • 对游戏中其他对象的行为进行初步测试,确认其未受新逻辑调整的影响。
  5. 接下来,我们注意到代码中某些函数或逻辑片段存在相似性,因此只需更新对应的变量即可,不需要重复实现逻辑。

  6. 最终目标是将代码划分为更加明显的模块和部分:

    • 初步将某些片段独立出来,使其更加清晰。
    • 后续可以进一步考虑将功能分离为独立模块,从而提高代码的可读性和可维护性。

通过这些步骤,我们在优化代码的同时,确保了其功能的一致性和行为的正确性。
在这里插入图片描述
在这里插入图片描述

将代码拆分

代码分析表明,当前逻辑中存在两部分独立的功能,分别执行不同的任务。这些逻辑主要包括:

  1. 玩家操作后状态的更新逻辑

    • 这是一个更新函数,基于玩家移动的位置对游戏状态进行调整。
    • 这部分代码还会处理摄像机的相关调整,以反映玩家的最新移动。
    • 由于这是一种通用的逻辑处理方式,我们可以考虑将其移到一个独立的函数中,从而使代码更加清晰和模块化。
  2. 渲染相关逻辑

    • 渲染部分的代码处理的是玩家和游戏场景的视觉表现。
    • 当前的代码结构显示,这部分逻辑可以被进一步拆分以独立于其他功能。

在改进结构的过程中,可以注意以下几点:

  • 模块化改进
    随着我们对代码结构进行梳理,可以更容易地看到哪些部分可以拆分为独立的功能块。例如,更新函数负责根据玩家的最后一次移动调整状态和摄像机位置,这显然可以作为一个独立的单元函数存在。

  • 添加注释和标记
    为了方便后续的整理和拆分,我们可以在代码中添加标注,比如:

    • 更新摄像机和玩家状态,基于最后的移动
    • 明确说明每个功能块的职责,便于日后维护。
  • 识别逻辑分块
    随着进一步整理,可以观察到代码中逐渐显现出清晰的功能模块,这些模块可以独立处理特定的任务,如状态更新、渲染或事件响应。

  • 改进代码的可读性
    有人提到代码显得“太乱”,这是一个常见问题。当功能逻辑混合在一起时,会导致代码难以理解。通过模块化拆分和注释,我们可以显著改善代码的可读性和维护性。

下一步可以考虑逐步拆分这些逻辑,将独立功能整理到单独的函数或模块中,从而使代码结构更加清晰,并便于后续扩展和优化。

简化移动代码

当前代码的主要目标是从根本上优化逻辑,将现有的复杂操作简化并提升效率,同时处理一些关键问题。这一过程分为以下几个步骤和改进方向:

1. 重构逻辑

  • 当前的实现方式即将被废弃,将替换为更简单、更高效的方法。
  • 物理模块仍然保留,例如计算玩家速度的部分。
  • 对原有逻辑进行重置,为新的实现铺垫。

2. 定义新的循环逻辑

  • 为了改进区域搜索和处理,计划实现一个新的循环,覆盖搜索范围中的每个瓦片位置(x 和 y 坐标)。
  • 这种方式允许更加直接和全面地遍历玩家及其目标位置附近的所有瓦片。

3. 碰撞检测优化

  • 当前碰撞检测逻辑将在后续阶段改写,以替换当前复杂实现方式。
  • 新的实现方式会逐步被引入,当前计划暂时保留必要部分。

4. 调整边界问题

  • 在瓦片边界处,存在无符号整数和有符号整数的问题。这可能导致边界处理中的异常情况。
  • 针对这个问题,将通过逻辑调整确保在瓦片边界上仍能正确操作。

5. 代码模块化与可读性提升

  • 通过分离不同功能逻辑,例如物理运算、碰撞检测、区域搜索等,使代码更清晰、易维护。
  • 未来可能将某些常用逻辑移到公共模块中,以减少重复代码并提升效率。

6. 其他问题的评估与优化

  • 在边界处理和循环控制方面,需要对无符号整数和有符号整数的问题进行进一步探讨与解决。
  • 针对每个瓦片的手动处理逻辑,将会进一步观察是否存在优化的可能性。

总结

此次重构的目标是通过简化复杂逻辑并引入新的方法,使得操作更加直观、模块化,并解决瓦片边界处的异常问题。这些改进措施将为后续的优化和功能扩展打下基础。

无符号整数在零边界的问题

在这里,讨论的核心是关于在代码中处理瓦片循环的问题,特别是在处理无符号整数和边界包裹(wrap-around)时的挑战,以及如何确保逻辑的正确性。以下是详细总结:

1. 瓦片循环的边界问题

  • 假设场景中,瓦片编号达到了极大值,例如 4,000,000,000,此时因为无符号整数的性质,瓦片编号会回绕到起点(如编号 0)。
  • 在这种情况下,传统的循环逻辑(例如使用小于或大于比较)会因为回绕而出现逻辑错误,无法正确判断是否达到循环终止条件。

2. 循环条件的调整

  • 为了解决上述问题,必须在循环中使用“是否不等于(not equal to)”的条件,而不是简单的“是否小于(less than)”或“是否大于(greater than)”。
  • 这种方法确保了即使存在回绕,也可以正确处理瓦片编号的范围。

3. 循环逻辑的复杂性

  • 虽然使用“不等于”条件可以解决问题,但它并不是最自然或直观的循环方式。通常,人们习惯用“小于”这样的条件来定义循环范围,但在这里无法适用。
  • 如果要正确实现一个可以处理回绕的逻辑,例如处理一个范围跨越 4,000,000,000 个瓦片的循环,这种调整是必要的。

4. 代码正确性和边界思考

  • 如果希望代码在各种情况下都能正确运行,例如处理非常大的瓦片编号范围或复杂的回绕场景,就必须仔细设计循环逻辑。
  • 这种情况下,循环必须明确处理超出最大瓦片编号后的逻辑,并确保范围计算的准确性。

5. 思考与启发

  • 讨论中还提到,这种循环逻辑调整虽然有其技术必要性,但也体现了在处理类似边界问题时所需要的谨慎。
  • 此外,这种调整方式可能对代码的直观性产生影响,需要开发者额外思考代码的可维护性和可读性。

总结

瓦片循环中的边界问题需要特别关注,尤其是涉及无符号整数回绕时,传统的循环条件可能失效。在这种场景下,通过使用“不等于”条件可以有效解决问题,同时确保逻辑的正确性和鲁棒性。这种方法虽然不够直观,但在需要处理极大瓦片范围的情况下,是一种可靠的解决方案。

为每个循环寻找最近的瓷砖

在处理逻辑时,首先需要找到目标区域的瓦片值,这是首要任务。我们已经有一种可以直接完成这项任务的方法,通过输入所需的参数,可以明确获取该区域对应的瓦片值。接下来可以根据瓦片值的内容决定下一步的处理逻辑。

如果瓦片值为空,则表示该区域是可通过的。另一方面,如果瓦片值显示该区域是阻塞的,那么需要对该区域进行更进一步的逻辑处理,例如分析地形或者障碍物。

为了简化处理流程,考虑将“瓦片值是否为空”作为一个独立的判断功能。通过这一方法,可以快速判断一个瓦片是否为空,而无需每次重新分析逻辑。这一改进有助于减少重复思考,提高代码的清晰度。

当前处理瓦片值只是临时方案,后续会进一步优化。在后期渲染阶段,会更加详细地思考每个游戏瓦片的实际内容,可能会为游戏瓦片添加更复杂的表示,例如具体的地形类型、障碍物等。

目前,为了保证功能的可用性,暂时使用一些简单的数字来代表瓦片内容。尽管这些数字目前没有深刻的意义,但它们为后续的功能扩展提供了灵活性,并能支持一些简单的场景变化。例如,数字值可以用来判断瓦片是否为空,并快速得出对应的结论。

未来,当瓦片内容更加复杂时,可以进一步扩展逻辑,例如判断瓦片是否存在可供玩家站立的位置,或者瓦片是否包含其他特殊内容(如悬崖或危险区域)。如果瓦片为空,那么玩家可以安全地通过;如果瓦片不是空的,则需要更进一步分析具体的瓦片内容和对游戏逻辑的影响。通过这种分步的方法,可以确保功能的渐进式完善,同时保持当前代码的简洁性和易维护性。

检查空瓷砖

接下来,我们需要检查我们周围的瓦片,明确当前矩形空间的边界及布局。具体来说,我们需要定义一个矩形区域,并分析它在空间中的具体形状以及对游戏逻辑的影响。

1. 确定矩形空间

假设我们在某个瓦片上,那么该区域的边界需要基于角色的半径值进行计算。假如角色的碰撞区域是圆形的,那么他的半径决定了他可以站立的范围。这个范围可能包含多个瓦片,而需要避开的是任何阻挡物或者不允许通行的地方。

2. 计算角色的有效范围

我们以角色的半径为基础,从当前瓦片出发,可以得出一个圆形范围。在这个圆形中,角色可以自由移动,但当遇到阻挡物时,角色会保持一定的距离。

  • 例如,当角色靠近某个角落时,圆形范围会与角落的阻挡物产生交集,这就限制了角色能够到达的区域。
  • 在这种情况下,圆形的某些部分会因为阻挡物而被“切掉”,剩下的区域决定了角色的有效移动范围。

3. 分析特殊情况

如果当前瓦片包含对角线阻挡物(如斜墙),角色的有效范围会受到更多限制。这种情况下,我们需要额外考虑角色与这些对角线的距离和接触点。

  • 如果角色以某个角度靠近障碍物,他会在接触到圆形半径边界时停止移动。
  • 角色的有效范围最终会呈现一个包含“缺口”的形状,这些缺口由阻挡物和对角线交集的部分构成。

4. 简化当前逻辑

虽然以上分析非常全面,但在当前阶段,我们可以选择忽略对角线阻挡物和其他复杂的细节。

  • 我们只需要确保矩形范围内,任何阻挡物的基本逻辑处理正确。
  • 对于复杂的情况,可以暂时跳过,后续优化时再进行处理。

5. 下一步定义

接下来,需要在瓦片地图上明确哪些瓦片是空的(可以通行),哪些瓦片是阻挡的。

  • 如果某个瓦片为空,则角色可以自由移动到该瓦片内。
  • 如果某个瓦片被阻挡,则角色需要保持一定的距离,避免进入该瓦片。

总结

在处理角色范围和瓦片的关系时,我们通过角色的半径来确定他可以移动的区域,同时考虑阻挡物对范围的影响。虽然目前可以选择简化处理逻辑,但需要在代码设计中预留出对复杂情况扩展的可能性。这种设计既确保了当前功能的快速实现,又为后续的优化留出了空间。

定义瓷砖内有效的玩家区域

为了确定玩家可以实际停留的有效区域,我们首先需要定义一个矩形范围,明确其最小值和最大值的边界。我们将从瓦片的中心开始,以米为单位计算每个边界的距离。

1. 定义矩形边界

我们会用瓦片中心的坐标,加上或减去瓦片的一半边长,来确定矩形区域的最小角和最大角。

  • 最小角:从瓦片的中心点,减去瓦片的一半边长。
  • 最大角:从瓦片的中心点,加上瓦片的一半边长。
    这样可以确保矩形区域是以瓦片为中心的,同时也相对于瓦片位置保持一致。

2. 处理坐标轴

在计算矩形范围时,我们会分别处理 x 和 y 坐标。每个坐标都会基于瓦片中心进行调整:

  • x 坐标表示瓦片在水平方向的范围。
  • y 坐标表示瓦片在竖直方向的范围。

3. 额外限制条件

为了更好地控制玩家移动,我们还可以为瓦片设置额外的限制条件。例如:

  • 玩家目前不能向“上”移动(z 方向的移动暂未实现)。
  • 如果瓦片具备特定属性(如梯子),可以允许玩家进行向上移动。

4. 初始化玩家位置

玩家的位置被定义为瓦片的中心坐标,同时应用适当的偏移(例如:以米为单位的转换值)。在此基础上,我们可以进一步扩展逻辑,考虑玩家如何从一个瓦片移动到另一个瓦片。

5. 下一步操作:搜索有效范围

为了确定玩家的有效区域,我们需要遍历矩形范围内的所有瓦片,检查哪些瓦片可以通行。这一步的主要操作是:

  1. 遍历从最小角到最大角的所有瓦片。
  2. 对每个瓦片,检查是否为空(可以通行)或者被阻挡(不可通行)。
  3. 根据瓦片属性,决定是否需要进一步处理(例如:特殊属性瓦片,如梯子)。

总结

通过以上步骤,我们可以有效定义玩家的移动范围,并将其限制在合理的瓦片区域内。矩形边界的初始化以及对特殊属性的处理,确保了整个逻辑的灵活性。虽然目前针对“向上移动”的逻辑尚未完善,但这些功能可以在后续阶段添加。搜索过程是确定有效区域的核心,将在后续实现中逐步细化。

如何执行搜索

这段文字描述了一种算法,用于在一个矩形区域内寻找最接近的点。以下是对其中文解释的总结:


首先,我们提出了一个瓦片的位置,并分析了它的可通行性。如果瓦片内没有障碍物,最接近的点就是该瓦片的中心。
如果瓦片内没有点,最接近的点只能在矩形的边缘上。在这种情况下,我们需要检查每条边并找到最接近点。为了实现这一点,我们比较中心点和矩形的边界,找到距离最短的那个点。
算法的核心思想是检查所有可能的边缘,计算每个边缘到中心点的距离,选择最小的那个作为最接近的点。这个方法简化了问题,使得在矩形内找到最接近的点非常直接和高效。
我们用一个简单的公式计算距离,并选择最小的距离作为最终结果,这样就得到了最接近的点。这个过程不需要考虑速度,只需要确保算法工作得足够好。
最后,在程序编译之后,算法可以通过检查每个瓦片的位置来确定最接近的点,并更新当前的最短距离和最接近的点。这样,程序可以不断调整找到的点,直到找到最接近的点为止。


获取矢量长度

我们说如果想要找到向量的长度,可以直接求它的平方根。然而,如果我们仅仅关注它的平方就足够了,而不需要求平方根。这是因为我们只关心是否小于其他数,而不关心实际的距离大小。这使得计算变得更简单,只需要计算向量的平方。这样可以避免多余的计算工作,同时也确保了我们处理的结果始终是正数。即便有负数情况,平方运算也会变正,因此我们不需要考虑负数的情况。总的来说,使用平方来判断距离大小是更高效的方式。

汇总修改的内容
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

为什么不同版本的 GetTileValue()?

当设计游戏中的瓦片系统时,我们通常会面临如何从绝对位置访问瓦片值的问题。在这个问题中,涉及到多个版本的 GetTileValue 函数,它们的主要区别在于它们如何访问和处理瓦片数据。

  1. 基础版本:直接从绝对瓦片位置获取瓦片值。这种方法适合于只需要简单查询的情况。
  2. 捆绑格式:提供了一层额外的功能,将瓦片映射到块中,以便于管理和操作更大的世界。这种方法适用于需要考虑块概念的情况。
  3. 底层访问:最底层的函数用于实际检索瓦片值,它们会进一步传递到更上层函数,直到我们能够直接从瓦片中获取所需的值。

这些函数的设计目的是为了让开发者根据不同的需求选择最合适的函数调用,从而更灵活地处理瓦片数据。对于开发者而言,无论使用哪种方法,都可以直接调用这些函数来获得瓦片值,避免了代码重复和冗长的处理逻辑。

在这里插入图片描述

为什么你开始将变量大写和结构体小写?

在C语言的编码规范中,变量命名通常有两种风格:

  1. 驼峰命名法(CamelCase):变量名的每个单词的第一个字母大写,其他字母小写。例如:TileMapPosition, BestPlayerP. 这种方式在面向对象编程中特别常见,尤其是在需要区分多个相关概念的情况下。

  2. 下划线分隔命名法(Snake_case):变量名中的每个单词之间用下划线分隔,全部字母小写。例如:tile_map_position, best_player_p. 这种命名方式常用于C语言中,尤其是为了保持与C的传统风格一致,并确保变量名的可读性。

这种命名风格的选择通常取决于个人习惯和团队的编程约定。在某些情况下,如在早期的编程工作中所接触的游戏开发环境,像克里斯徒步旅行者那样的风格(即蛇形命名法)可能更为常见。因此,对于某些开发者来说,使用下划线分隔命名法更自然。这并没有绝对的“标准”规定,而是一个习惯问题。

当引入npc时,是否会重用这里展示的代码?

在编写代码时,通常避免过多考虑代码的重复利用。这样的方式有助于保持代码的简洁和专一性,而不是试图让一段代码在多个地方都能够复用。重用代码的想法可以导致复杂性增加,并可能使代码不清晰或者难以维护。

在实际的编程过程中,如果某个问题在不同的场景下都有对应的解决方案,可以考虑提取相同部分的代码,但不要为了复用而去创建不必要的代码模块。通常,最好的策略是判断自己是否正在重复之前做过的事情。如果是,就可以考虑复用之前的解决方案,而不是新写一段代码。如果不是,尽量保持代码的独立性和简洁性。这种“反向思考”方法有助于写出更容易维护和理解的代码。

是否会实现push/pull门?

当我们设计游戏中的物理碰撞和角色运动时,特别是在门口这样的场景中,我们会做一些调整来确保玩家不会被卡住或被挡住。一个可能的方法是利用撞击系统,使得角色在接触到墙壁时能够自动进行适应性调整。例如,如果角色碰到墙壁或门框,它会被稍微推离,这样玩家就能轻松通过,而不是被卡在那个位置。

这种方法可以通过使用一种更具包容性的碰撞模型,比如一个椭圆模型,对于角色在特定区域的移动进行补偿。这样的调整可以使角色自动找到一种能够通过门的路径。这种碰撞处理能够大大改善门口的体验,使角色更自然地从门口进入室内。

此外,如果这感觉更好,我们还可以添加一个漏斗形状的效果,使角色在接近门口时被自动吸引进门。这种调整不仅可以提高游戏的可操作性,也能增强玩家的沉浸感。

看起来可能的移动更像是一个扇形?

在设计游戏中的角色移动和碰撞时,我们常常考虑使用更具包容性的形状来处理角色的周围空间。例如,使用一个类似披萨片的形状可能看起来更自然,但实际操作起来并不一定更有效,因为这可能增加了处理和计算的复杂性。例如,我们必须计算角色周围的“披萨片”区域并处理这些区域的受限行为,这对于当前的计算受限区域来说是不必要的风险。

因此,通常会选择一种更通用的方式来处理这些区域,比如简单的矩形形状,这样可以更容易地进行搜索和碰撞检测。即使这种方法可能看起来不如披萨片那么“自然”,但它可以更好地适应当前的碰撞系统和角色运动模型,确保角色在进入门口时不会被卡住。通过使用这种方法,界限的处理变得更为准确,即便是我们仅仅通过一种标准的矩形形状来进行界限计算。

如何使用这个系统处理当玩家被碰撞杀死的情况?

当设计游戏中角色的碰撞系统时,我们需要考虑如何处理玩家在碰撞中的死亡或困境。例如,如果玩家触碰到一堵墙而被杀死,或者被挤压其中,最简单的处理方法就是直接设定这种碰撞为致命的。如果玩家试图移动到一个“基利”的区域,即带刺的区域,系统会自动处理角色的死亡。

另一方面,若玩家无法找到一个可以停留的安全位置,在这种情况下,系统会让墙壁自动缩小,并对玩家造成威胁,使其处于困境中。玩家的所有动作都会发生在它当前的位置上,并根据当前的游戏规则产生效果。这种处理方式使得碰撞的设计更加直观和简单,即使是在复杂的情境下也能有效地处理角色的碰撞和死亡状态。

因此,我们在决定使用这种系统时,主要是基于它的简便性和直观性,使得处理碰撞和玩家死亡变得更加自然和易于理解。

是否会推动巨石阻挡门?

推大石块以便通过门听起来并不太有趣。这种机制可能会使游戏感觉冗长和乏味,因为它要求玩家在通行中处理不必要的物理障碍。这不仅增加了游戏的复杂性和重复性,也可能打破玩家的游戏体验,使他们觉得在推动这些大石块上花费了不必要的时间。更糟糕的是,这种机制可能会影响游戏节奏,使游戏变得不那么流畅和有趣。

是否会陷入一些在编码教条性的陷阱?

有时,我们会陷入一些教条性的陷阱,这些陷阱使我们忽略了写代码的更好方法。即使意识到这些问题并且知道如何避免,它仍然可能会不经意间发生。这不仅仅是因为编码风格问题,还可能是由于算法本身不够优雅。我们可能会专注于做“最简单的”事情,而不是花时间去思考如何使代码更具扩展性,能够适应未来的需求。

这种情况往往会导致写出的代码质量不高,处理复杂性不够。这种现象会使后期维护变得更加困难,因为新的开发者需要进行大量的修复工作,而不是从头开始构建一个更好的基础。这种局限性可能会导致代码堆积,并阻碍项目的发展。如果我们能更早地识别和解决这些问题,进行更多的清理和扩展工作,那么就能为项目的未来发展做出更好的准备。

这个搜索方法是否能实现圆角效果?

搜索方法本身并不关注它是否产生圆角效果。这取决于如何计算搜索空间。如果我们选择计算基于圆角的搜索空间,那么它将会具备圆角特性,否则就不会。现在实现的最接近点搜索在矩形边界上,并不会自然产生圆角效果。但未来我们可能会升级至圆角搜索,因为实现它并不难。如果选择不这样做,那么结果就会是没有圆角效果。最终,这一切都取决于如何定义和处理搜索空间。

我不太理解这个。为什么不直接用法向量改变玩家的矢量?

使用法向量进行搜索并调整玩家的矢量可能并不总是最有效的方式。首先需要处理碰撞检查,生成能够产生正确点的算法。然后,基于这些正常的法向量进行速度矢量的更新。问题在于,进行这种逐步精确调整会导致许多复杂的计算,尤其是在速度矢量在“角落”区域中遇到阻碍时。若遇到小的缝隙,算法可能无法处理,导致在这类情况中角色会立即停止,无法前进。这些问题使得基于法向量的搜索方法在某些情况下并不如使用其他方法(如某些特定的算法p)更优,因为后者能够避免这些困境,从而避免不确定性和精度问题。

搜索在p时(特定空间位置的搜索算法),会不会有穿越墙壁的问题?

担心系统能处理多少物体的碰撞可能不是当前的首要问题,特别是在高性能的平台上。尽管有可能会遇到系统资源限制的问题,例如在较慢的平台上,更多的对象可能会带来更高的计算负担。然而,在目前的主流硬件环境下,拥有足够的处理能力使得在屏幕上展示数十个或甚至更多的物体并不是问题。玩家管理多物体的能力在设计时需要考虑,但通常并不会成为性能瓶颈,除非设计一种包含大量的投射物和复杂交互的游戏。在碰撞检测方面,洪水填充法可以有效避免玩家穿过墙壁等不合逻辑的行为,这种方法确保了只有在到达某个目标位置时才允许进行穿越,而不会无意穿过不可穿越的障碍。总体上,系统的优先级应该是提供高质量的运动体验,而不是追求极限的计算速度。

搜索代码在遇到会使其反弹的小凹槽时是否会工作?

如果玩家碰到一个稍微有角度的墙,搜索代码会处理这个碰撞并进行反射。这种处理可以让玩家在碰撞时滑翔,而不是反弹。搜索代码的主要目的是找到玩家与墙体最接近的点并反射他的速度向量。虽然这种方法可能不会达到非常高的精度,但它更符合游戏中的自然运动体验,因为大多数情况下,玩家在游戏中会滑翔而不是反弹。对于反弹操作,系统可以选择更靠近碰撞点的方向,并生成一个新的速度向量,以进行反弹效果。这种方式虽然精确度不高,但增加了游戏的趣味性和动态性,使玩家在与墙体接触时更具互动性。

搜索在p (特定空间位置的搜索算法) 是否适用于移动对象?

使用特定空间位置的搜索算法方法进行碰撞时,可以处理移动的物体和非移动的物体之间的相互作用。与其他方法相比,基本上没有区别,只是在处理物体移动时的抽象方式有所不同。通常,开发者倾向于选择有序的运动方法,这种方法能够更好地反映游戏中的物理模拟,因为玩家对这些差异并不太敏感。这种有序的运动方法可能会在游戏中作为默认的碰撞机制,除非有其他重要的理由不采用它。


http://www.ppmy.cn/server/150343.html

相关文章

机器视觉认识OpenCV

一、什么是OpenCV OpenCV 1、绪论 OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。它包含了众多关于图像处理和计算机视觉的通用算法,这些算法可以用于解决各种实际问题,比如人脸识别、物…

【XR】静态初始化与动态初始化(MACKF与VINS的初始化特点)

静态初始化(Static Initialization)和动态初始化(Dynamic Initialization)在多视图几何SLAM系统中各有优缺点,具体到MSCKF与VINS-Mono的实现对比如下: 静态初始化(MSCKF的实现) 特点…

19. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--收支记录

在本篇文章中,我们将一起编写孢子记账的收支记录功能(CURD),同样我们只列出一个具体功能的实现,剩下的功能由读者实现。 一、 需求 需求如下: 编号需求说明1新增记录1.记录内容包括转换前金额、转换后金…

redis 怎么样删除list

在 Redis 中,可以使用以下方法删除列表或列表中的元素: 1. 删除整个列表 使用 DEL 命令删除一个列表键: DEL mylist这个命令会删除键 mylist 及其值(无论 mylist 是一个列表还是其他类型的键)。 2. 删除列表中的部分…

【人工智能-中级】卷积神经网络(CNN)的中阶应用:从图像分类到目标检测

文章目录 卷积神经网络(CNN)的中阶应用:从图像分类到目标检测1. 图像分类:CNN的基础应用CNN结构概述经典网络架构2. 目标检测:从分类到定位基于区域的目标检测方法单阶段目标检测方法边界框回归与NMS(Non-Maximum Suppression)3. 深度学习中的目标检测挑战与解决方案4. …

基于 webRTC Vue 的局域网 文件传输工具

文件传输工具,匿名加密,只需访问网页,即可连接到其他设备,基于 webRTC 和 Vue.js coturn TURN 服务器 docker pull coturn/coturn docker run -d --networkhost \-v $(pwd)/my.conf:/etc/coturn/turnserver.conf \coturn/coturn…

Linux常用指令-----下

Linux常用指令------上 Linux常用指令------中 Linux系列 文章目录 Linux系列前言一、more指令二、less指令三、head指令和tail指令四、grep指令五、zip指令和unzip指令六、tar指令1、打包压缩2. 预览3. 解压解包 前言 在上一篇博客中,我給大家介绍了cat指令&#…

Rust格式化输出用法汇总

文章目录 基本的格式化输出格式化占位符Format 特性总结 Rust的格式化输出是通过std::fmt模块提供的强大功能来实现的。Rust提供了灵活且功能强大的格式化字符串语法,允许开发者根据需求输出不同类型的数据。 基本的格式化输出 Rust中最常用的格式化输出方法是使用…