仓库: https://gitee.com/mrxiao_com/2d_game
昨天我们花了一点时间来修复一个问题,但基本上是在修复这个问题的过程中,我们决定添加一个功能,那就是在屏幕上控制多个实体。所以如果我有一个手柄,我可以添加另一个角色,现在他就在那儿闲逛。我们开始将英雄角色的子画面控制代码分离出来,独立成一个功能,他在做他的事情。但昨天我们中断了这个过程,因为我们要处理那件事——修复墙壁粘附问题。这是我们曾经讨论过的,我们当时正在编写相应的代码。现在我想谈谈这个问题,但我也想趁着这次机会在这里清理一些东西,因为现在我们实际上有了更多的小英雄,并且支持了模拟摇杆。现在我想要进去处理一些以前在模拟摇杆上的运动方面没有处理的事情。
对斜线移动的回顾
我们最初在处理多方向控制时,并未深入学习矢量。因此,当用户控制英雄角色时,我们做了一个临时解决方法,使得角色在斜向移动时速度不会比直线移动更快。这是因为我们理解了矢量的概念。原来,我们将角色的移动方向定义为向量 (0, 1)
或 (1, 0)
,表示在主要方向上移动。但当同时按下两个方向时,向量会变成 (1, 1)
,这意味着移动速度变得更快,超过了基本的1长度。这显然不合理,因为它比单一方向上的移动速度还要快。因此,我们决定使用毕达哥拉斯定理来计算出合适的向量长度,使得它仍然是1长度的矢量,只是在两个方向上的合成移动。但在那个方向上。
新的模拟杆问题
我们在处理模拟摇杆输入时遇到了一些问题。如果用户使用摇杆进行控制,摇杆能够在单位圆内的任何方向上按比例移动。因此,我们需要确保从摇杆返回的矢量不会超出最大加速度的范围。在这种情况下,我们使用了毕达哥拉斯定理来计算出矢量的长度。如果返回的矢量长度大于1,我们需要将其限制为1。这是为了防止玩家通过使用更广范围的摇杆动作,超出预定的最大加速度范围,从而加速过快。我们通过比较矢量长度的平方与1的最大值来实现这一目标。如果矢量长度的平方超出1,我们就对其进行修剪,以确保加速度始终不超过预定的最大值。
改变向量长度而不改变方向
我们在处理模拟摇杆输入时,遇到了一些问题。假设我们有一个矢量a
,它包含了摇杆在屏幕上移动的方向和距离。在这种情况下,我想要构建一个新的矢量a'
,它的方向与a
相同但其长度限定为1。这意味着我需要将矢量a
的长度缩放到1的比例。
我们使用比例法则来计算这个比例。首先,确定a
的长度,然后将其除以这个长度,得到一个缩放因子。这一因子将应用到原始矢量a
上,使得最终得到的矢量a'
的长度为1,而方向不变。这个过程实际上就是将矢量的长度标准化为1。这样,无论a
的初始长度如何,经过这种处理后,a'
都能表示同样的方向但长度为1,这确保了加速度矢量不会超过预定的最大值。
为什么使用长度=1?
我们要构造一个长度为1的矢量,是因为它可以简化后续的转换过程。如果已知一个矢量的长度是1,那么当我们想要将其转换为其他长度时,只需将其乘以所需的比例即可。这样,可以更方便地调整矢量的长度,而不改变其方向。这种方式特别适用于处理模拟摇杆等输入设备,能够保证比例关系的一致性,使控制更加自然。例如,当模拟摇杆推向极限时,我们希望相应的加速度保持比例一致,从而确保控制的稳定性。最终,我们可以将这个矢量乘以加速度的因子,得到正确的加速度向量。所有这些操作都是为了确保在不同的情况下,矢量能够保持适当的比例和方向。
回到墙壁粘附问题!
在处理碰撞检测时,我考虑了两种方法:Search in Position方法以及确定性方法。经过反复思考后,我倾向于不使用Search in Position方法,因为它们会引入不必要的复杂性,而这在游戏中并不常见。游戏环境中并不常常会发生物体之间的磨擦,因此不需要那么复杂的碰撞检测方式。这种简单的方法更符合游戏的需求,因为它可以减少代码复杂性,节省开发成本。最终,我决定选择传统的方法进行实现,因为它更加高效且更能符合实际需求。
T实现中的搜索的解释
在处理碰撞检测时,我们需要更好的方法来确定实际的碰撞情况。在过去,我们只是检查目标位置是否可达,而现在需要更详细地了解实际碰撞的位置和方向。为此,我们需要解决一系列方程式,以确定玩家何时与某个阻挡者发生碰撞。我们将从最简单的情况开始,像是点与线的碰撞检测,然后逐步扩展到更复杂的场景。在实现过程中,我们需要不断调整玩家的速度矢量,并尝试多次来找到正确的位置。如果无法完全处理,我们会停止,避免过多的计算消耗。通过这种方式,代码复杂性得到了有效控制,同时也能够处理玩家移动中的微小调整。
T中的搜索方程
这个问题涉及到如何表示一个玩家在运动中的位置。我们可以从以下角度进行解释:
-
起始位置和方向:
- 我们可以从一个初始位置 p 0 p0 p0 开始,这里是玩家的起点。
- 玩家沿着一个方向移动,这个方向可以用一个向量
d
表示。 d
是从起始位置到目标位置的距离和方向的差异。
-
时间的影响:
- 我们用
t
来表示一个时间步长,玩家经过这个时间步长会移动一段距离。 - 公式
d = ( B − A ) d = (B - A) d=(B−A)
p ( t ) = p 0 + t ( B − A ) p(t) = p0 + t (B-A) p(t)=p0+t(B−A)
表示了在时间t
后,玩家的位置 p ( t ) p(t) p(t)。 - 其中,
t
是一个从 0 到 1 的比例因子,表示玩家的运动进度。t = 0
表示玩家尚未开始移动,t = 1
表示玩家已经到达目标位置。
- 我们用
-
向量的归一化:
- 如果
d
是一个单位向量,即长度为 1,t
的范围从 0 到实际移动的时间步长,以便能够完全移动到目标位置。 - 否则,
d
的长度可能大于 1,此时t
的范围从 0 到这个实际长度,以便能够按比例移动到目标位置。
- 如果
这种公式化和理解使得我们能够描述一个玩家如何沿着某一方向从一个位置移动到另一个位置,并随时间变化其位置。
最简单的可能情况
对于一个垂直墙壁,我们可以通过方程来解决,当一个人在一个方向上移动时,何时会撞到这堵墙。
我们知道一个垂直墙壁的 x 坐标是一个固定值,而 y 坐标可以是任何值。我们想要找出当一个人从 p 0 p0 p0 开始在 x 方向上的运动时,他会碰到这堵墙的位置。
我们可以用矢量方程 p ( t ) x = p 0 x + t ∗ d x p(t)_x = p0_x + t * d_x p(t)x=p0x+t∗dx 来表示这一点,其中 p ( t ) x p(t)_x p(t)x 是人移动后的位置的 x x x 坐标, p 0 x p0_x p0x 是初始位置的 x x x 坐标, d x d_x dx 是人每次移动的 x x x 向量。
要解决这个问题,我们设定 p ( t ) x p(t)_x p(t)x 等于墙的 x x x 坐标 w a l l x wall_x wallx,这给出了我们要解的方程:
p ( t ) x = p 0 x + t d x = w a l l x p(t)_x = p0_x + td_x = wall_x p(t)x=p0x+tdx=wallx
t d x = w a l l x − p 0 x td_x = wall_x - p0_x tdx=wallx−p0x
通过将 d_x
移到分母位置,可以得出:
t = w a l l x − p 0 x d x t = \frac{{wall_x - p0_x}}{{d_x}} t=dxwallx−p0x
这就是人会在何时撞到这堵墙的时间 t t t。这个公式简单明了,但需要注意的是,如果 d x d_x dx 为零,意味着人不在 x x x 方向上移动,那么时间 t t t 就不存在,无法碰到墙。这是问题的一个关键点。
除以零的问题
在高中的数学课上,老师常常提醒我们不要让 d x d_x dx 等于零,因为除以零是未定义的行为。这一提醒在实际应用中变得很重要。例如,当我们讨论一个人在 x x x 方向上移动时, d x d_x dx 是他每次移动的距离。若 d x d_x dx 为零,表示这个向量仅仅指向垂直或水平的某个方向,而不是横向的运动。这意味着人将无法撞到那堵墙,因为他根本不可能横向移动到那墙上。
当我们解这个方程 t = w a l l x − p 0 x d x t = \frac{{wall_x - p0_x}}{{d_x}} t=dxwallx−p0x 时,若 d x d_x dx 为零,整个方程变成了未定义的,这表明没有合适的解,人无法碰到那堵墙。这就是为何在数学课上,老师一直强调 d x d_x dx 不能为零的原因。
在实际编程中,这一概念也同样重要。我们需要考虑 d x d_x dx 的非零性,确保当我们计算碰撞时不会遇到未定义的情况。若 d x d_x dx 为零,我们可以直接忽略这段检测,因为那不是有效的移动方向。
所以,这个公式不仅告诉我们一个人何时会碰到那堵墙,也确保了计算结果是合理的。
无限的垂直和水平墙壁的方程
我们在解决 x x x 方向上的墙时,使用公式 t = w a l l x − p 0 x d x t = \frac{wall_x - p0_x}{d_x} t=dxwallx−p0x。同样地,当处理 y y y 方向的墙时,公式也类似,只需要将公式中的 x x x 替换为 y y y。通过这些公式,我们可以解决垂直和水平墙壁的碰撞检测问题。
有了这些公式,我们就可以开始实现顶层的地图碰撞系统。在具体实现过程中,我们需要注意以下几点:
-
碰撞检测的基本原理:检测的本质是模拟无限延伸的墙壁。这种墙壁可以延伸到无限高或者无限宽,并覆盖所有可能的碰撞区域。然而,这种方法在某些情况下会出现问题,例如当墙壁上有洞口或者间隙时。
-
穿过墙壁的间隙:在实际场景中,墙壁往往不是完全连续的,而是可能存在门或其他通道。如果直接按无限墙壁的检测逻辑计算,角色将永远无法通过这些间隙,因为这些无限墙壁会被视为完全封闭的。
-
实现动态调整:为了解决上述问题,我们需要对墙壁的碰撞检测进行细化和动态调整。例如,可以将墙壁分割为多个有限部分,而不是单纯模拟无限延伸的墙体。这种方式可以让角色正确地检测到墙体的边界,从而正常通过门或其他通道。
通过这些改进,我们不仅能够准确地模拟角色与墙壁的碰撞,还能为后续更加复杂的碰撞场景(例如任意形状的障碍物或动态障碍物)打下基础。这种实现方法可以同时适应简单和复杂的游戏场景,既满足性能需求,也保留了灵活性。
修正有限墙壁大小的方程
可以通过一个相对简单的方式来确定碰撞点。具体步骤如下:
-
计算实际的碰撞时间:我们可以使用前面提到的
t
值,作为时间参数插入到行走方程中。这种方法能够帮助我们精确找到角色在特定时间的实际位置。具体公式为:
p final = p 0 + t solve ⋅ d p_{\text{final}} = p_0 + t_{\text{solve}} \cdot d pfinal=p0+tsolve⋅d
其中:- p 0 p_0 p0 是角色初始位置。
- t solve t_{\text{solve}} tsolve 是通过碰撞公式计算出的时间。
- d d d 是角色的移动向量。
-
找到碰撞点的具体位置:通过将计算出的 t solve t_{\text{solve}} tsolve 代入方程,可以得出角色在碰撞发生时的具体坐标。
-
验证碰撞点是否有效:在找到具体位置后,可以检查碰撞点的 y y y 坐标(或 x x x 坐标,取决于碰撞方向)。将该坐标与墙壁的实际范围进行对比:
- 如果该坐标落在墙壁的有效范围内,则视为有效碰撞。
- 如果不在范围内,则忽略此碰撞,继续检查其他可能的碰撞。
-
针对每个墙壁进行检测:对于每一个固体墙体的每一侧,可以分别计算和检查可能的碰撞点。每一侧都对应一个范围,只有当碰撞点的坐标落在范围内时,才会被记录为有效碰撞。
-
优化检测逻辑:当发现一个无效的碰撞点时,可以直接跳过当前墙体或块,转而检测下一个可能的碰撞区域。这种方法提高了检测效率,避免了不必要的计算。
通过上述步骤,能够以简单且高效的方式检测碰撞,同时确保结果准确。这种实现方式不仅可以处理基本的方块墙,还可以扩展到更加复杂的障碍物场景,为后续的碰撞处理奠定基础。
实施新的碰撞代码
这段内容讲解了如何实现基础的碰撞检测代码以及其背后的逻辑。以下是详细的总结:
-
逐步实现代码的思路:
由于碰撞检测可能对许多人来说是个全新的概念,因此应按步骤进行代码实现,而不是过于激进地推进。这种方式可以帮助更好地理解逻辑。 -
确定碰撞检测的范围:
我们需要遍历角色周围的所有瓷砖,找出可能发生碰撞的对象。具体包括以下步骤:- 检查这些瓷砖是否是“固体”,即角色无法穿过的类型。
- 如果是固体,则进一步判断是否会因角色的移动方向和位置与瓷砖发生碰撞。
-
相对位置的计算:
为了更高效地进行检测,可以计算玩家与瓷砖之间的相对位置:- 假设玩家的位置是 ( 0 , 0 ) (0,0) (0,0)(或某种基准点),然后通过减去瓷砖的绝对位置来获取相对位置。
- 这一相对位置可以帮助快速确定玩家当前所处的范围以及是否进入某个瓷砖的碰撞范围。
-
设置循环检查瓷砖:
代码需要设置一个循环,针对所有周围的瓷砖进行遍历,以下是关键逻辑:- 遍历玩家当前位置周围的一定范围内的瓷砖。
- 检查每个瓷砖是否符合碰撞条件,比如是否是固体瓷砖。
- 如果是,则继续检查玩家是否会因为移动方向进入该瓷砖的范围。
-
碰撞检测的实现逻辑:
- 确定瓷砖是否是固体类型,这是碰撞检测的基本前提。
- 如果瓷砖为固体,则进一步比较角色当前的相对位置和预期的移动路径,判断是否会与瓷砖发生交集。
- 如果不会交集,则跳过当前瓷砖,继续检查其他瓷砖。
-
初始化和优化循环:
- 在实现碰撞检测前,需要先初始化必要的变量,比如玩家位置、瓷砖地图差异等。
- 确定需要检查的瓷砖范围,避免不必要的计算。
- 通过相对位置优化计算逻辑,使得代码更加高效。
通过以上方法,可以实现一个基础但实用的碰撞检测系统。这种系统能够判断角色在移动过程中是否会撞上某些物体,从而为更复杂的游戏交互提供支持。
如何找到要检查的瓷砖
这段内容进一步探讨了碰撞检测中的复杂逻辑和实现细节,并介绍了一种保守的检测方法。以下是具体的总结:
1. 需要检测哪些瓷砖
- 我们需要明确在角色移动过程中,可能涉及到哪些瓷砖的碰撞检测。
- 假设角色从当前位置移动到新位置,需要检查以下几种瓷砖:
- 当前角色所在的瓷砖。
- 移动后角色所在的目标瓷砖。
- 移动路径中经过的所有瓷砖。
2. 碰撞检测的复杂性
- 碰撞检测并不是简单的任务,因为涉及计算角色移动路径与瓷砖的交集。
- 这里采用了保守检测的方法:
- 检查角色可能涉及的所有瓷砖,即构建一个最小矩形区域(bounding box)。
- 该矩形覆盖角色移动路径的起点和终点之间的所有瓷砖。
- 这种方法虽然可能会检测到一些不必要的瓷砖,但确保不会遗漏任何潜在的碰撞。
3. 优化碰撞检测的可能性
- 理想情况下,可以优化为只检测角色路径实际穿过的瓷砖:
- 比如,如果路径是对角线,可以只检测沿路径经过的瓷砖。
- 这种优化能减少不必要的计算,但实现起来更复杂,适合在性能优化阶段再考虑。
4. 实现保守检测的逻辑
- 确定检测范围:
- 根据角色当前位置和目标位置,计算出最小和最大的瓷砖坐标范围(min/max tile index)。
- 包括 X 轴和 Y 轴的范围。
- 遍历瓷砖:
- 在上述范围内,遍历所有的瓷砖,逐个检查是否有碰撞。
- 通过添加 1 来确保包含目标瓷砖的位置范围。
5. 计算最小值和最大值的实现
- 定义一个最小值和最大值的计算函数,用于比较两点的坐标:
- 如果坐标 A 小于坐标 B,则最小值为 A,否则为 B。
- 同理,最大值为较大的坐标值。
- 通过计算角色当前位置和目标位置的最小值和最大值,确定检测的瓷砖范围。
6. 支持坐标循环包裹
- 为了处理特殊地图(如连续环绕的地图),需要支持坐标的“循环包裹”:
- 比如,当坐标溢出边界时,可以回到另一侧继续计算。
- 因此,使用“不等于”判断条件,而不是“少于”,以支持循环包裹逻辑。
7. 总结核心逻辑
- 基本步骤:
- 计算角色移动路径上的最小和最大瓷砖坐标。
- 遍历这些瓷砖范围内的所有瓷砖。
- 检查每个瓷砖是否存在碰撞条件。
- 优化注意事项:
- 保守检测虽然简单,但可能会浪费一些计算资源。
- 针对优化需求,可以缩小检测范围,只计算必要的瓷砖。
通过这种保守检测的方法,可以有效实现基础的碰撞检测,同时为后续优化提供了良好的基础框架。
多个墙壁碰撞的处理
在处理多个墙壁的碰撞检测时,我们需要解决一个问题:如何判断玩家与哪一个墙壁发生了实际碰撞。解决的关键是找到最小的t值,也就是最早发生碰撞的时间点。
我们的方法是:
- 首先,计算玩家在时间步内的移动矢量
PlayerDelta
,表示玩家从当前位置到目标位置的位移矢量。通过将t值乘以这个矢量,就可以计算出玩家在任何时刻的位置。 - 设定初始的
tMin
值为1(表示完成整个时间步的移动),所有发生在这个t值之后的碰撞都不需要考虑。 - 遍历所有可能的墙壁,计算玩家与这些墙壁的碰撞点:
- 对于每个墙壁,根据其坐标范围和玩家的当前位置,逐个测试墙壁的四个边界。
- 使用公式 T s = W x − P 0 x PlayerDelta x T_s = \frac{W_x - P_{0x}}{\text{PlayerDelta}_x} Ts=PlayerDeltaxWx−P0x(其中 W x W_x Wx 是墙壁的x坐标, P 0 x P_{0x} P0x 是玩家当前的x坐标)来计算玩家与墙壁的碰撞时间。
- 每次计算后,更新
TMin
值为最小的T值,即最早的碰撞时间。 - 最终,使用最小的T值
TMin
来确定玩家实际停止的位置。
为了高效地完成这一过程,我们将创建一个函数TestWall
,该函数用于处理单个墙壁的检测。该函数将接收墙壁的位置信息以及玩家的移动矢量,并返回对应的碰撞时间。随后,我们会循环调用这个函数来测试所有墙壁。
为了优化代码:
- 不再需要存储所有可能的碰撞点,而是直接比较并更新最小T值。
- 在更新t值时,可以避免额外的复杂计算。
- 通过直接处理
PlayerDelta
和墙壁信息,可以将逻辑简化为多个独立的墙壁检测。
当前代码完成了主要逻辑框架,但尚未完全实现用于测试所有墙壁的循环和最终的碰撞位置更新。这部分将在后续继续完善。
为什么从 p 搜索改为 t 搜索?
我们讨论了两种算法之间的权衡:一种是搜索在 ( P ) 空间中的算法,另一种是搜索在 ( T ) 空间中的算法。这两者各有优缺点,我们需要基于实际需求选择合适的算法。
搜索在 ( P ) 空间中的特点:
- 优点:
- 具有迭代次数的边界限制,可以提供确定性的结果。
- 能够平滑地忽略几何上的一些小错误。
- 缺点:
- 需要构造和了解搜索空间(即 ( P ) 空间)。对于复杂的碰撞几何结构,可能会显得非常低效。
- 对弹性效果的处理较差,因为无法准确判断物体与墙壁多次碰撞的行为。
- 在效率上可能不如另一种算法,特别是当搜索空间复杂时。
搜索在 ( T ) 空间中的特点:
- 优点:
- 无需构建搜索空间。它直接沿直线驱动,一旦检测到碰撞便改变方向。这种方式更高效。
- 对弹性行为的处理较好,特别是当需要模拟反弹较多的情况时。
- 算法更灵活,因为不需要明确了解空间的结构。
- 缺点:
- 由于迭代次数可能不受限制,可能需要对结果进行人为的边界限制,这可能导致非最优解。
算法选择的理由:
经过权衡后,选择了搜索在 ( T ) 空间中的算法。虽然它可能在某些情况下不如搜索在 ( P ) 空间中的算法准确,但由于它的效率更高,更适合我们处理的场景。尤其是在无需频繁构建复杂搜索空间的情况下,这种算法可以更快地产生结果。
此外,通过在迭代次数上设置一定的限制,即使牺牲了一部分准确性,算法仍然可以在大多数情况下提供满意的性能和结果。
总结来说,搜索在 ( T ) 空间中的算法因为其效率和灵活性成为了优选,尤其是在对几何弹性要求较高的应用中。这种选择减少了算法的复杂性,同时满足了性能的需求。
它如何处理两个不同楼层的玩家状态?
在处理两个玩家处于不同楼层时的状态时,主要关注的是碰撞检测和渲染机制。
碰撞检测:
碰撞检测系统能够正常工作,因为它依赖于实体所在位置的瓦片 ( Z ) 值进行检测。无论两个玩家位于不同的楼层还是同一楼层,碰撞检测的逻辑都不受影响。这是因为系统会根据每个实体的当前楼层位置来独立计算碰撞结果,因此状态的分离并不会对检测机制造成干扰。
渲染问题:
需要特别关注的是渲染问题。当两个玩家位于不同楼层并且可能出现在不同屏幕区域时,如何合理地呈现场景成为关键。如果其中一个玩家移出当前屏幕范围,我们可能会引入以下规则来简化渲染逻辑:
- 由主玩家的所在位置决定屏幕的中心,次要玩家的活动范围将受到限制,不能离开主玩家所在的屏幕区域。
- 通过这种规则,避免了实现分屏渲染的复杂性,也简化了对多个屏幕状态的管理。
总结:
整体来说,碰撞检测逻辑能够自然适应两个玩家在不同楼层的状态,而渲染问题则可以通过限制屏幕范围来解决,从而避免额外的技术难题。这个方案既简化了实现过程,也保证了游戏体验的一致性。
为什么 p 搜索的代码复杂性比 t 搜索大得多?
在实现搜索算法时,复杂性显著增加主要是由于需要处理搜索的多样性和几何体的细节。
复杂性原因:
-
搜索算法复杂性高:T搜索算法简单,主要是沿着路径直线行进,遇到障碍物则改变方向。这是一个较为简单的算法,因为它不需要了解周围的空间形状,只需要碰撞检测和路径跟踪即可。然而,寻找最接近点问题(如在P中搜索)则要复杂得多,因为它需要计算出每个可能碰撞的几何体的最接近点。这种任务涉及到处理多边形或其他复杂形状的“挤压点”,即计算某个点距离某一形状的最短距离,这非常复杂。
-
迭代过程:在搜索过程中,需要多次迭代来寻找最接近的点。这需要处理各种不同形状的几何数据,包括外形和碰撞形状。每一次碰撞都可能涉及到不同的几何体形态,需要耗费大量的计算资源去处理,因此代码复杂度增加。
-
空间的处理:在复杂几何的情况下,像Minkowski加法这样的数学运算可能被用来计算最接近的点。这种计算方式更加复杂,通常需要特别设计的算法和数据结构来支持。例如,在处理椭圆、圆形或任意形状时,需要处理“可能到达点”的问题,这要求开发者预先知道哪些形状可以到达,这样系统才能有效地检测是否可以穿越障碍物。
-
系统效率:在遇到这一问题时,选择性地采用搜索算法(例如T搜索)可以显著提高效率。如果每个玩家必须能够准确穿越屏幕上的所有障碍物,那么实现起来就变得非常复杂和昂贵。然而,在某些情况下(如证据检测系统),这些复杂的运算反而是免费使用的。
总结:
总体而言,T搜索算法的复杂性主要是由它的简单性和直接路径计算而带来的,而搜索算法则涉及到处理各种复杂几何和空间关系的细节,导致复杂性和计算开销显著增加。这种差异性决定了在不同场景中选择合适的算法和数据结构。
为什么不将地图视作矩阵,并通过坐标验证物体和碰撞?
使用矩阵来处理地图可能会带来一些问题:
-
局限性:如果地图本身是一个矩阵,那么与当前使用的瓦片地图相比,实际上的差别不大。瓦片地图本质上也是一个二维数组,只是它更加简化了和缩小了处理复杂度。矩阵视角可能无法很好地处理复杂的几何形状和细节,比如曲线或非正交的边界,这些都是瓦片地图无法轻易表示的。
-
碰撞检测:在矩阵视角下,碰撞检测变得更加复杂。为了在两个点之间确定是否发生碰撞,仍然需要编写碰撞代码来处理可能出现的交集,这并没有实际减少代码量或简化问题。而瓦片地图则能够通过简单的位操作来确定两个格子的相对位置,从而有效地检测碰撞。
-
灵活性和扩展性:如果采用矩阵视角处理碰撞检测,所需的复杂度和计算资源将会增加。我们实际需要做的是,允许各种复杂的几何形状用于碰撞检测,如圆形、椭圆形或其他任意形状。矩阵视角无法轻易适应这些形状的检测,而瓦片地图更易于扩展,可以轻松支持复杂的碰撞处理。
-
世界表示:瓦片地图只是当前使用的世界表示之一,它并不反映实际世界中的所有可能形态。而使用更高级的几何表示(如矩阵中的多边形或曲线)能够更好地贴合实际的物理模型和碰撞检测需求。因此,矩阵视角未必是最合适的选择,特别是在碰撞检测和物理模拟方面。
因此,虽然将地图视为一个矩阵可能会简化一些初步的逻辑,但从实际需求和可扩展性的角度来看,瓦片地图或其他复杂的几何表示更为合适。
可以更详细地讲解坐标系统与碰撞检测的工作原理吗?
储存世界时,我们的瓦片地图可以被视为一个巨大的4亿 x 4亿的二维数组。这些瓦片实际上是一个巨大的立方体,包含了4亿个小立方体,每个小立方体代表一个瓦片的位置。我们在这个立方体中只存储实际世界中存在的瓦片数据,使用稀疏存储机制来优化内存使用。这样做是因为我们无法实际存储一个4亿 x 4亿 x 4亿的全立方体,而是只存储其中包含世界数据的部分。
当玩家从一个瓦片移动到另一个瓦片时,碰撞检测器会处理这些瓦片。它不在乎这些数据是从哪里来的,只关注这些瓦片的位置。碰撞检测器只需查看玩家当前所在的瓦片,以及他即将要进入的瓦片,检查周围的所有瓦片,判断这些瓦片是否为固体。如果有,检测器会确定玩家可能会碰撞到这些瓦片的位置,并停止玩家的动作。
这种方式允许我们只处理需要的瓦片数据,并且在迭代时快速地进行碰撞检测,而不需要对整个世界进行遍历。这种机制简化了碰撞检测的实现,同时优化了内存使用。
能检查玩家的位置,查看它是否在墙的内侧吗?
当进行3D碰撞检测时,我们可以通过检查玩家的当前位置来判断他是否在墙体的内部。这种方法利用了墙体的方向性特性,即它们通常指向外部。因此,我们只考虑那些指向玩家的墙体,而不考虑其他方向的墙体。这种优化可以通过计算内积来实现,使用向量的转置和余弦计算来确定墙体是否朝向玩家。如果角度小于90度,余弦值为正,说明墙体朝向玩家。
这种方法的优点在于,它能够减少不必要的碰撞检测工作,只关注那些可能发生碰撞的墙体,从而提升性能。这种优化可以有效减少计算量,使得碰撞检测更加高效。我们将在后续的碰撞检测过程中进一步探讨这些优化策略。
零长度向量标准化的问题
当讨论零长度矢量时,需要注意它的特殊情况。在规范化过程中,如果一个矢量的长度为零,则其方向就不存在,因此不能对其进行任何长度上的变更。这意味着,如果一个矢量的长度为零,我们不能进行常规的矢量操作,因为它不具有方向性。如果需要对矢量进行规范化,必须首先确保矢量的长度不为零。这是因为如果长度为零,规范化等于除以零,是不可行的操作。
在实现规范化算法时,如果遇到零长度矢量,就需要特殊处理。通常,这种情况会被忽略或返回一个默认值,避免进行除以零的错误操作。这种处理方法取决于具体的应用场景和算法设计,从而确保算法的健壮性和正确性。
为什么有时玩家会穿墙或角落?是艺术还是编程?
在处理这种情况下,如果玩家能够穿越墙壁或坠入其中,这是一个可能由几种原因引起的问题。首先,碰撞几何本身可能不正确。例如,玩家的尺寸与墙的几何形状不匹配,使得他能够穿过墙体,这种情况属于碰撞几何错误。而另一种可能是编程错误,在检测和处理碰撞时,程序可能不够保守,未能妥善处理复杂的边界和玩家位置,这种情况属于编程缺陷。
这些问题可能源于数值精度问题或算法设计的不当。例如,某些特定情况下的碰撞检查可能未能考虑到所有可能的情况,从而导致玩家能够通过看似坚固的物体。因此,解决这些问题可能需要更仔细地检查和调整碰撞检测算法,使之更健壮并能正确处理各种特殊情况。
需要进行固体墙和物体的数学计算吗,还是基本的程序算术?
在处理坚固的墙壁和物体时,无论是做基本的碰撞检测还是更复杂的物理计算,都需要进行数学运算。即使是简单的几何体碰撞检测,也需要进行数学计算来确定物体之间的相交或碰撞情况。这种运算涉及到坐标、距离和相交点等方面的计算,因此它属于基本的程序算术的一部分,无论所处理的物体是墙壁还是其他物件,都需要进行这些运算。
聊天补充:我认为他是指图形上。墙可以是空心的。
在进行碰撞检测时,无论对象是实心的还是空心的,对其内部分并不关心,因为碰撞检测只关注对象的外边界。即使对象内部分为空,碰撞检测仍会考虑到其外围的碰撞物体。如果一个物体与玩家相交,它就会停止,因此不管物体是实心的还是空心的,处理方法是相同的。这个边界的概念是确保碰撞检测准确的一部分,不需要关注到物体的内部结构。