仓库: https://gitee.com/mrxiao_com/2d_game
向量数学的重要性
-
矢量数学非常重要,因为 它在某种程度上类似于将C和C++视为高于汇编语言的语言,从而使得我们能够以略高的层次思考问题,同时保留大部分性能好处和直接访问的类型。这种思维方式就像是标量数学的一种强化,使得我们能够处理更高效的代码。虽然我们偶尔仍然需要直接处理这些矢量中的个别值,但大部分时间里,我们可以从概念上去思考它们。这种方法使得我们能够进行更强大的操作,而不仅仅是简单的x和y坐标的处理。因此,矢量数学赋予了我们一种更具语义性的方法来定义和操作这些向量,允许我们在更高的语义层次上进行处理。
-
这使得我们能够以更高效的方式思考和实现游戏逻辑, 并在开发过程中不断提升我们的编程技巧。
上一集回顾
-
我们刚刚实现了小角色的简单运动方程。到目前为止,我们制作了一个小瓷砖地图,让角色可以在其中走动。尽管这里没有动态的背景,但我们已经能够画出这些墙壁、门和其他元素,使角色能够沿虚拟楼梯移动、穿越屏幕边缘等。
-
我们刚刚实现了角色的运动方程,使得角色在移动时能够感受到惯性。即使是简单的运动变化,也使得角色在移动时更加吸引人。这种感觉的不同主要来自于角色在停止和加速过程中的滑行行为,这让角色的运动更加自然和流畅。
-
现在我们不仅仅是关注角色的移动代码,还在尝试了解矢量。为了进一步探索矢量代码的作用,我想更多地谈谈我们可以如何利用这些代码来理解和实现一些更高级的操作。这不仅是为了优化角色运动代码,也是为了更好地理解和利用矢量的概念。
标量和向量数学符号的一致性
-
我们在之前的工作中使用了矢量数学,但这并不明显。我在最后一次直播中提到过,但我想再提一次,因为它非常重要。在讨论运动方程时,我只是简化地假设我们是在处理标量,而不是矢量。我们进行了标量积分,并得到了运动方程,它们在第一次输入时就工作得非常好。这种方法让我能够在不处理向量的情况下,依然高效地使用运动方程。实际上,我们在输入这些方程式时,根本没有考虑到它们是矢量。
-
这只是一个小小的暗示,说明为什么我们应该关心更强大的数学对象——因为它们允许我们简化思考。我们可以在不必担心 X、Y 和 Z 等具体坐标的情况下,直接考虑抽象的量,这样可以使我们的代码更加简洁和易于维护。虽然有时候我们需要明确考虑它们是向量,但大多数情况下,我们可以将标量和矢量互换使用,这种方式非常强大。
-
我们可能会进一步探讨矢量数学,看看它如何帮助我们简化问题和实现更强大的功能。
今天的目标:反弹墙面
-
有人提到了这一点,我觉得这挺有趣的。思考一下如何在游戏中实现一个角色反弹的行为可能是一个很好的学习内容。例如,当角色碰到墙壁时,速度应该反向反射,这种方式就像一个弹跳球一样。这种情况让我开始思考,如何在游戏中实现角色的跳跃,即使不预期角色在命令下跳跃,它也应该能够跳跃起来。
-
这些操作涉及到矢量数学,因为它们与运动、速度和方向变化相关。像是反弹或跳跃这样的功能,实际上可以用矢量操作来实现。例如,当角色碰到墙壁时,我们可以计算出新的速度矢量方向,使角色反弹。类似地,当角色跳跃时,我们可以增加一个垂直的位移量,从地面高度跳跃起来。
-
虽然这些功能可能在实际游戏开发中并不常见,但它们可以帮助我们学习和理解矢量数学的应用。在实现角色反弹和跳跃的代码时,我们需要掌握如何使用矢量进行碰撞检测和速度反转。这种能力不仅仅是为了游戏中的特效,也是游戏开发者在处理物理模拟时必不可少的技能。
黑板图示反弹行为
-
好的,所以如果我过去构建这个案例,我们可以开始讨论。那么让我们想一想我们现在的情况吧。假设有一面墙,它就是我的墙。我会设想,当这个角色碰到它时,反弹的起始点是世界的原点,也就是坐标系的零零点。这只是为了简单起见,因为这个位置可以自由地进行位移调整。为了计算方便,我可以将整个世界移动到一个合适的位置,如果需要的话,再把它移回来。因此,我只是简化了事情。
-
这样做的目的是使得后续的计算更加直观和简便。当角色碰到墙后,反弹的方向和速度会怎样变化呢?这个计算可以通过矢量数学来实现。我们可以假设角色的速度矢量在反弹时会被反转,或是以某个角度重新计算出新的速度矢量。这种方式,不管是平行还是垂直的反弹,矢量数学都能很好地处理这种物理行为。这种操作不仅限于碰撞反弹,还可以应用于角色的跳跃和其他物理模拟中,帮助我们更好地理解和利用矢量数学的功能。
轴对齐向量反射
当我们讨论入射向量的反射时,我们在思考物体在与墙碰撞后如何改变其运动。入射向量表示物体在碰撞之前的速度,而法向量则代表表面的法线方向。反射公式用于计算物体碰撞后的反射速度矢量 V reflected V_{\text{reflected}} Vreflected。
首先,我们假设入射向量
V
in
V_{\text{in}}
Vin 和法向量
N
N
N。法向量
N
N
N 是单位矢量,方向与墙的法线方向一致。反射向量
V
reflected
V_{\text{reflected}}
Vreflected 可以通过公式:
V
reflected
=
V
in
−
2
×
(
V
in
⋅
N
)
×
N
V_{\text{reflected}} = V_{\text{in}} - 2 \times (V_{\text{in}} \cdot N) \times N
Vreflected=Vin−2×(Vin⋅N)×N
来计算,其中
V
in
⋅
N
V_{\text{in}} \cdot N
Vin⋅N 是入射矢量在法向量方向上的投影。
在实际应用中,当物体撞到墙时,其速度沿法向反向反射。这个反射过程导致物体的Y分量发生负方向的变化,而X分量保持不变,因为墙只作用于垂直方向,而水平方向没有物理障碍。通过这种方式,我们模拟了碰撞时的物理能量损失和运动方向的改变,使得反弹更加真实。
这种反射操作实质上是一个标量计算,它涉及矢量的两个分量。虽然从表面上看,这些操作涉及的是标量而不是矢量运算,但它们仍然是基于物理学的矢量计算,因为入射速度的改变影响了反射后的运动路径。这样一来,即使从直观上看我们在处理标量,其实是在应用矢量的物理性质。
一般情况下的向量反射
在处理反射时,我们考虑了沿一个主轴的情况,例如墙壁作为主轴。当我们有一个倾斜的墙壁时,情况就不同了。反射不再只是简单地反向,而是涉及到矢量的实际结构。矢量不是标量,它们有各自的组件,例如X分量和Y分量。要处理这种情况,我们需要了解Hadamard乘积,即将两个矢量的对应组件相乘,从而生成新的矢量。这种方式使得我们可以在操作中不必退回到标量。然而,这种Hadamard乘积在处理所有问题时并不是非常实用。
Hadamard乘积(或元素乘积)是两个同维向量(通常为矩阵的行向量或列向量)之间的元素-wise乘法。给定两个同维向量 A A A 和 B B B,其Hadamard乘积 C C C 定义为:
C = A ∘ B C = A \circ B C=A∘B
其中 C C C 是结果向量,其每一个元素 c i c_i ci 是 A i × B i A_i \times B_i Ai×Bi,其中 A i A_i Ai 和 B i B_i Bi 分别是向量 A A A 和 B B B 的第 i i i 个元素。Hadamard乘积的主要作用是保留每个向量的结构信息,并在两向量的对应元素之间进行计算,而不是像点积那样计算它们的整体。
举个例子,如果有两个向量:
A
=
[
a
1
,
a
2
,
a
3
]
B
=
[
b
1
,
b
2
,
b
3
]
A = [a_1, a_2, a_3] \\ B = [b_1, b_2, b_3]
A=[a1,a2,a3]B=[b1,b2,b3]
其Hadamard乘积
C
C
C 是:
C
=
[
a
1
×
b
1
,
a
2
×
b
2
,
a
3
×
b
3
]
C = [a_1 \times b_1, a_2 \times b_2, a_3 \times b_3]
C=[a1×b1,a2×b2,a3×b3]
这种乘法在应用中尤其重要,例如在处理图像数据时,它可以用于像素级计算。
内积/标量/点积
我们现在要认识一种对我们非常有用的东西,那就是内积(或称为点积)。在游戏开发中,它通常被称为界点积。内积允许我们解决各种问题,尽管看起来可能不是那么有用。它把两个向量压缩成一个标量值,而不是一个新的向量。这种标量值实际上等于这两个向量之间的角度的余弦值乘以它们的长度,这使得内积在计算几何和矢量运算中非常实用。
通过点积,我们可以把矢量缩减到一个单一的值,这个值代表了两个矢量的相似性和它们之间的关系。这种运算不仅限于二维向量,任何维度的向量都可以进行点积计算。使用点积,我们可以很容易地计算出向量之间的相似性,或者简化的计算在游戏物理和图形处理中非常有用。
这就是内积的强大之处,尽管它看起来只是简单的数学运算,但它在实际应用中却是非常实用的工具。
以下是内积、标量和点积的几个示例,帮助理解这些概念的应用:
内积(Dot Product)
定义为两个向量的乘积,其结果是一个标量。例如,对于两个二维向量:
-
向量 a \mathbf{a} a 和 b \mathbf{b} b 的内积:
a = ( 3 , 4 ) , b = ( 1 , 2 ) \mathbf{a} = (3, 4), \quad \mathbf{b} = (1, 2) a=(3,4),b=(1,2)
内积 a ⋅ b \mathbf{a} \cdot \mathbf{b} a⋅b 计算如下:
a ⋅ b = 3 × 1 + 4 × 2 \mathbf{a} \cdot \mathbf{b} = 3 \times 1 + 4 \times 2 a⋅b=3×1+4×2
= 3 + 8 = 3 + 8 =3+8
= 11 = 11 =11
结果为标量值 11 11 11。 -
向量 a \mathbf{a} a 和 b \mathbf{b} b 的内积:
a = ( 2 , − 1 , 3 ) , b = ( 4 , 0 , 2 ) \mathbf{a} = (2, -1, 3), \quad \mathbf{b} = (4, 0, 2) a=(2,−1,3),b=(4,0,2)
内积计算:
a ⋅ b = 2 × 4 + ( − 1 ) × 0 + 3 × 2 \mathbf{a} \cdot \mathbf{b} = 2 \times 4 + (-1) \times 0 + 3 \times 2 a⋅b=2×4+(−1)×0+3×2
= 8 + 0 + 6 = 8 + 0 + 6 =8+0+6
= 14 = 14 =14
结果为标量值 14 14 14。
标量(Scalar)
标量是一个实数值。例如,内积的结果作为标量可以表示向量的大小和方向。对于标量 s s s 和向量 v \mathbf{v} v:
- 标量倍向量:如果标量
s
=
3
s = 3
s=3 和向量
v
=
(
2
,
4
)
\mathbf{v} = (2, 4)
v=(2,4),则标量倍向量为:
s × v = 3 × ( 2 , 4 ) = ( 6 , 12 ) s \times \mathbf{v} = 3 \times (2, 4) = (6, 12) s×v=3×(2,4)=(6,12)
新向量的每个分量都是原向量的分量乘以标量 3 3 3。
内积的应用:向量长度
- 勾股定理方法:向量
A
A
A 的长度可以使用勾股定理来计算。对于一个向量
A
=
(
a
x
,
a
y
)
A = (ax, ay)
A=(ax,ay),其长度
∣
A
∣
|A|
∣A∣ 由下式给出:
∣ A ∣ = a x 2 + a y 2 |A| = \sqrt{ax^2 + ay^2} ∣A∣=ax2+ay2
这种方法利用了欧几里得距离公式来找出向量的模。
-
点积方法:向量 A A A 与自身的点积给出了 A A A 的平方长度。公式为:
A T A = a x 2 + a y 2 A^T A = ax^2 + ay^2 ATA=ax2+ay2
取这个值的平方根得到 A A A 的长度。 -
几何解释:向量之间的夹角的余弦可以帮助理解一个向量投影到另一个向量上的情况。对于两个向量 A A A 和 B B B,
A ⋅ B = ∣ A ∣ × ∣ B ∣ × cos ( θ ) A \cdot B = |A| \times |B| \times \cos(\theta) A⋅B=∣A∣×∣B∣×cos(θ)
这个投影公式对于将一个向量投影到另一个单位向量上非常有用,使得夹角 θ \theta θ 变得直接。 -
单位向量概念:长度为 1 的向量称为单位向量。单位向量的概念简化了这些计算,因为在点积时,夹角 cos ( θ ) \cos(\theta) cos(θ) 直接变成 1。
-
实际应用:这些概念在计算机图形学、物理学(处理力和速度)、以及数学中许多涉及向量运算的领域中都是至关重要的。
理解这些基础概念,使得人们可以在向量投影、向量之间的角度计算,以及更广泛地应用向量代数的各种应用中进行操作。
使用内积解决一般反弹
这个部分讨论了如何使用内积来解决反弹问题。反弹问题涉及到对某一物体的速度进行反射,使其在碰撞后返回或改变方向。我们需要知道表面的法向量作为反射的单位向量,称之为R
。
首先,我们需要理解反射的方向。给定一个进来的速度向量V
和表面法向量R
,我们可以通过内积将V
投影到R
上,从而得出一个标量,这个标量反映了V
在R
方向上的投影长度。这一标量被称为反射量。
V
⋅
R
V \cdot R
V⋅R
然后,我们可以通过将这个反射量乘以法向量R
,得到一个新的向量
V
⋅
R
⋅
R
V \cdot R \cdot R
V⋅R⋅R。为了实际实现反射,只需将V
减去两倍的这个反射量,从而使向量在反射方向上翻转。这种方法使得我们可以不受限于仅反射于对齐的轴系统,而是可以处理任何形状的墙面。
V
′
=
V
+
(
−
2
V
⋅
R
⋅
R
)
=
V
−
2
V
⋅
R
⋅
R
V' = V + (-2V \cdot R \cdot R) = V - 2V \cdot R \cdot R
V′=V+(−2V⋅R⋅R)=V−2V⋅R⋅R
总结来说,我们通过内积和反射计算,可以灵活地处理各种形状的碰撞问题,使得反弹变得更加广泛和实用。
A ⋅ B = ∣ A ∣ ∣ B ∣ cos ( θ ) A \cdot B = |A| |B| \cos(\theta) A⋅B=∣A∣∣B∣cos(θ)
其中 ∣ A ∣ |A| ∣A∣ 和 ∣ B ∣ |B| ∣B∣ 是两个向量的模(长度),而 θ \theta θ 是它们之间的夹角。
反射计算步骤
-
定义反射的单位向量(法向量):
- 首先,确定反射的方向单位向量 n ^ \hat{n} n^。这是一个指向反射表面的单位向量。例如,如果反射的表面法向量为 n ^ = ( n x , n y ) \hat{n} = (n_x, n_y) n^=(nx,ny),则它的长度是1。
-
反射计算:
- 假设进入的速度向量为 V = ( V x , V y ) V = (V_x, V_y) V=(Vx,Vy)。
- 计算反射后的速度向量
V
′
V'
V′ 的公式为:
V ′ = V − 2 ( V ⋅ n ^ ) n ^ V' = V - 2 (V \cdot \hat{n}) \hat{n} V′=V−2(V⋅n^)n^ - 这里, V ⋅ n ^ V \cdot \hat{n} V⋅n^ 是向量 V V V 在法向量 n ^ \hat{n} n^ 方向上的投影,它的值是一个标量。
- 将这个标量乘以法向量 n ^ \hat{n} n^,并将所得向量从 V V V 中减去,得到反射后的向量 V ′ V' V′。
举例
- 示例:
- 进入向量 V = ( 3 , 4 ) V = (3, 4) V=(3,4),法向量 n ^ = ( 0 , 1 ) \hat{n} = (0, 1) n^=(0,1) (即垂直于墙的法向量)。
- 计算投影
V
⋅
n
^
V \cdot \hat{n}
V⋅n^:
V ⋅ n ^ = 3 × 0 + 4 × 1 = 4 V \cdot \hat{n} = 3 \times 0 + 4 \times 1 = 4 V⋅n^=3×0+4×1=4 - 反射计算:
V ′ = V − 2 × 4 × n ^ V' = V - 2 \times 4 \times \hat{n} V′=V−2×4×n^
V ′ = ( 3 , 4 ) − 2 × 4 × ( 0 , 1 ) V' = (3, 4) - 2 \times 4 \times (0, 1) V′=(3,4)−2×4×(0,1)
V ′ = ( 3 , 4 ) − ( 0 , 8 ) V' = (3, 4) - (0, 8) V′=(3,4)−(0,8)
V ′ = ( 3 , − 4 ) V' = (3, -4) V′=(3,−4)
这就是如何通过内积来计算一个反射向量。内积的应用简化了反射的计算,不论反射方向如何。
实现反弹的解决方案
为了实现弹跳效果,我们首先需要计算内积。在我们的数学例程中,内积(dot product)是将两个向量的对应分量相乘并相加得到一个标量。为了实现反弹,我们需要计算出对象的速度和与之相交的墙的法向量之间的内积。
-
定义内积函数:它计算两个向量之间的内积。
inline real32 InnerProduct(v2 A, v2 B) { real32 Result = A.X * B.X + A.Y * B.Y; return Result; }
-
计算反弹:
- 我们首先获取当前对象的速度。
- 然后获取与碰撞墙的法向量(通常是墙的垂直方向)。
- 计算内积,得到与墙的相交点的法向量的投影。
- 使用这个投影向量反射当前对象的速度。
-
实际应用:
- 硬编码一个简单的案例来验证反弹效果。
- 假设当前对象速度向下反弹碰到底部墙壁。
- 根据计算的内积和反射公式,调整对象的速度。
总结来说,通过内积和反射公式,我们可以非常有效地实现物体与墙体之间的碰撞反弹。这个过程确保了物理合理的反射方向和强度。
用于检测碰撞墙方向的占位代码
我们需要解决的问题是,如果直接处理当前的反射逻辑,可能会出现错误的行为。这是因为我们没有正确计算墙壁的方向。因此,我们的目标是找到一种方法来准确确定玩家碰撞的墙壁方向。当前的代码中尚未完善碰撞检测机制,我们需要实现这一功能。
分析碰撞行为
-
检测碰撞点
我们需要检测玩家位置(PlayerP
)的左侧、右侧以及新的位置,来确定是否与墙壁发生碰撞。通过检查这些点,识别出哪些是无效的(即互相穿透的)。 -
记录碰撞信息
一旦检测到碰撞,我们需要记录导致玩家无法移动的墙壁位置。这可以通过 TileMap 的数据结构来获取。 -
计算墙壁方向
根据玩家的移动方向和 TileMap 的数据,可以确定墙壁的方向。- 如果 X 轴坐标发生变化,我们可以判断是水平方向的墙壁(例如,向左移动撞到右侧的墙)。
- 如果 Y 轴坐标发生变化,则可以判断是垂直方向的墙壁(例如,向下移动撞到顶部的墙)。
代码逻辑优化
在实现碰撞检测时,我们通过对玩家移动方向的分析来更新墙壁的方向。这需要对每个移动方向进行判断,并根据当前状态调整玩家的反射方向。
-
碰撞方向判断
如果TileMap
表明当前位置无效(碰撞),我们通过比较当前位置和目标位置,判断是水平碰撞还是垂直碰撞,并得出墙壁的法线方向。 -
更新玩家位置
根据墙壁法线方向,反射玩家的速度向量。这确保了玩家在碰撞后会以正确的反射角度反弹。 -
动态行为调整
随着速度增加,玩家的反弹强度也会增强。这种动态行为符合预期的物理效果。
结果
经过优化后,玩家的移动逻辑具备了完善的碰撞检测和反射功能。玩家能够在碰到墙壁时准确反弹,并根据速度变化调整反弹强度。这种机制提供了更为真实和稳定的物理交互效果,适合进一步扩展。
简短的能量损失和恢复系数评论
我们可以通过引入一个恢复系数(Coefficient of Restitution)来让玩家在碰撞时的反弹更加柔和。恢复系数是一个小于 1 的数值,用于缩减反弹矢量的幅度,从而减少反弹的距离。这一调整可以让玩家的反弹行为更加平滑和自然。
调整碰撞反弹
-
恢复系数的作用
恢复系数会乘以反弹矢量,用于控制反弹的强度。通过减小恢复系数的值,可以让反弹更加柔和,模拟出更为逼真的物理效果。 -
优化对角线碰撞处理
当前的实现中,对角线方向的碰撞处理并不完美。这是因为碰撞逻辑仅能处理单一方向的反弹,而无法同时处理多方向的交互。例如,在对角线区域的碰撞中,可能仅反弹出一个方向,而非同时考虑水平和垂直方向。 -
改进碰撞检测逻辑
如果希望更准确地处理对角线碰撞,可以对边界区域进行圆角处理,模拟更平滑的边缘碰撞效果。这样不仅能够改善对角线反弹的逻辑,还可以提升整体的物理模拟体验。 -
当前实现的效果
尽管当前实现对对角线处理不够完美,但整体效果仍然可以接受,且在功能上是正确的。玩家在碰撞时可以正确地弹回,且反弹强度与速度成正比。虽然视觉上略显粗糙,但逻辑已经能够支持基础的反弹行为。
未来改进方向
- 可以进一步优化碰撞区域的形状,使边缘更平滑,以改进复杂碰撞的表现。
- 为对角线反弹添加更精确的逻辑,从而支持更复杂的物理交互。
- 动态调整恢复系数,以便适应不同的游戏场景需求。
通过以上优化,反弹逻辑不仅可以实现基础的物理功能,还能提升视觉效果和交互体验,使系统更具沉浸感和趣味性。
挑战:如何在墙上跑动?
我们现在探讨一个新的问题:如何调整碰撞响应,使角色在碰撞到墙壁时,不是直接反弹,而是沿着墙壁平滑移动,类似“地震式移动”的效果。在之前的讨论中,我们已经了解了点积的原理,现在可以运用这个知识来实现上述目标。
目标
-
改变碰撞行为
当角色与墙壁发生碰撞时,不再发生弹开,而是顺着墙壁平滑移动。 -
利用点积实现运动方向调整
通过点积的计算,可以确定角色速度矢量与墙壁法线的关系,从而调整角色的移动方向。
实现步骤
-
计算点积
使用角色的速度矢量和墙壁的法线矢量计算点积。这可以帮助我们分解角色的速度矢量,提取垂直和平行于墙壁的分量。 -
分解速度矢量
- 垂直分量:利用点积公式,将速度矢量分解到法线方向上,用于检测和调整角色的碰撞深度。
- 平行分量:通过去除垂直分量,得到角色沿墙壁的移动方向。
-
调整速度方向
- 移除垂直分量,保留平行分量。
- 角色在碰撞后,速度矢量被修改为沿墙壁方向平滑移动,而不是弹开。
-
平滑过渡
为了实现更加自然的效果,可以引入微调算法,使角色在碰撞到墙壁时缓慢过渡到平行方向的移动状态。
关键数学部分
-
点积公式:
点积 = v ⃗ ⋅ n ⃗ = ∣ v ⃗ ∣ ∣ n ⃗ ∣ cos θ \text{点积} = \vec{v} \cdot \vec{n} = |\vec{v}| |\vec{n}| \cos \theta 点积=v⋅n=∣v∣∣n∣cosθ
通过点积可以计算出矢量在法线方向上的投影。 -
矢量分解:
垂直分量 = ( v ⃗ ⋅ n ⃗ ) ⋅ n ⃗ \text{垂直分量} = (\vec{v} \cdot \vec{n}) \cdot \vec{n} 垂直分量=(v⋅n)⋅n
平行分量 = v ⃗ − 垂直分量 \text{平行分量} = \vec{v} - \text{垂直分量} 平行分量=v−垂直分量
补充说明
这种实现方式有两个部分:
- 数学部分可以直接通过矢量运算完成。
- 编程实现部分需要在物理引擎或碰撞逻辑中具体定义,例如墙壁的法线如何计算,以及如何动态调整角色的速度矢量。
这一逻辑的核心在于利用点积来分解和调整速度矢量。通过这种方法,我们可以轻松实现更自然的碰撞效果,使角色在遇到障碍时表现出平滑的运动。
关于粘滞行为的讨论
上面内容讨论了在物理模拟和碰撞检测中,如何改进对象与墙体等障碍物的碰撞响应,使其更符合预期的游戏体验。
主要内容与问题分析:
-
实现目标:
- 希望在对象碰撞墙体时,不是简单地反弹,而是沿着墙体平滑滑动,模拟类似经典游戏中的墙体摩擦行为。
- 需要通过数学手段校正速度矢量,使对象在碰撞时改变运动方向,而不是直接停止。
-
数学分析与计算:
- 当前碰撞校正只基于简单的反射矢量校正,未考虑深层次的细节。例如,速度矢量校正后,运动仍可能存在不平滑问题。
- 使用反射矢量调整速度后,仍需重新尝试以校正新的移动方向。这需要在每帧中动态更新。
-
边界问题与局限:
- 当前算法会出现"粘滞"现象:当玩家推动对象贴近墙体时,速度会因碰撞而削减,但位置校正失败,导致对象看似停滞。
- 这是因为算法仅简单地禁止进入墙体,而未实时调整对象的最终位置和速度矢量。
-
改进方法:
- 在检测到碰撞时,回溯到碰撞发生的精确点,然后重新调整速度矢量以避免重叠。
- 增加多个尝试步骤:每次修正后再次检测碰撞并调整,直到最终达到无重叠的稳定状态。
- 尝试应用"渐近逼近法",即逐步靠近障碍边界,在允许范围内逼近最佳位置。
-
实践中的挑战:
- 游戏模拟是离散的时间步长,而现实世界的碰撞是连续的弹性形变。这种离散性不可避免地带来精度与计算开销的权衡。
- 针对当前问题的潜在解决方案有多种,但都需在效率与效果之间找到平衡。
-
计划与进一步探索:
- 在后续工作中,将展示更准确的碰撞响应方法,包括对玩家移动矢量的细粒度调整。
- 考虑使用一种新的方法,将碰撞检测与速度调整分离,模拟更真实的碰撞行为。
总结:
以上内容系统性地探讨了碰撞检测中的速度校正问题,以及游戏中碰撞响应的实现与优化方法。这些方法将逐步展开测试和改进,目标是实现更符合真实物理的模拟效果,同时保留良好的游戏体验。
再解释一下为什么角色会粘在墙上?
在代码中,我们通过检测玩家的位置是否与墙壁发生碰撞,决定玩家是否能够移动。具体来说,当计算出玩家的新位置后,代码会测试玩家能否在新位置上站立。如果发现玩家不能在新位置站立,代码会拒绝更新玩家的位置,同时对玩家的速度进行修正。
在这种情况下,虽然玩家的速度被修正,但由于位置未更新,他可能会“粘附”在墙壁上。这种“粘附”现象的原因在于,当玩家的速度被调整后,他在下一帧中可能会再次尝试移动到墙的另一侧,但仍然无法通过检测,导致他持续停留在当前的位置。
每一帧中,玩家都会受到用户输入导致的加速度影响,而这种加速度可能持续朝向墙壁的方向,因此他会不断地尝试靠近墙壁。代码中虽然对速度进行了修正,使其沿着墙壁方向运动,但玩家的加速度仍然可能使他再次尝试进入墙体,因此在某些帧上会表现出“粘在墙上”的情况。
为了解决这个问题,需要修改代码逻辑。当玩家试图向墙壁方向移动时,不应该完全阻止其移动,而是应该调整运动轨迹。例如,可以让玩家的实际移动方向偏离墙壁,使他能够沿着墙壁滑动,而不是停留在原地。这种处理方式确保玩家的运动不会被完全否定,同时避免他反复尝试穿越墙壁而卡在原地的现象。
通过这种方式,玩家的运动逻辑将更加流畅,无论是在靠近墙壁时的表现还是整体运动行为,都可以避免“粘附”问题。
在什么时候会缩小角色?
在开发过程中,当我们计划缩小主角的尺寸时,目前还不需要特别在意这些调整,因为现阶段的开发并未涉及到对尺寸敏感的操作。然而,当我们开始编写跳跃相关的代码时,可能需要对主角的缩放进行更精确的调整,以确保主角的表现符合预期。
因此,为了未来的功能开发,可能会在接下来的工作中对主角的缩放进行适当的优化。与此同时,还可能对美术资源文件进行更新,加入一些新的素材以配合这些调整。这样可以更好地确保在后续实现跳跃动作等功能时,整体效果更加流畅和准确。
是否考虑使用 sin(0) 到 sin(90) 作为非线性惯性曲线?
关于使用正弦函数从0到90度作为非线性惯性曲线的建议,目前还未涉及任何曲线插值的讨论,因此这种实现可以作为未来深入开发时再考虑的选项。目前尚不确定正弦曲线是否是最合适的选择。
一个主要原因是,计算正弦函数通常比其他实现方法更加耗费资源。在需要实现惯性曲线的情况下,可能会倾向于选择其他方法,例如使用贝塞尔曲线。这是因为贝塞尔曲线不仅计算成本更低,而且能够提供灵活的控制点来精确调整惯性效果,从而满足需求。
你是否会处理玩家和怪物的某种物理引擎?
关于是否会统一处理玩家和怪物的运动逻辑,未来的确计划实现一套能够同时处理玩家和怪物行为的运动代码。不过,"物理引擎"这一术语并不完全准确,因为目标并不是完全模拟真实的物理现象。
核心目标是基于一组自定义的规则来实现玩家和怪物的行为,其中一些规则会参考物理学中的基本运动方程。例如,部分规则可能涉及基于物理的加速度和速度计算,但整体上并不试图构建一个全面的物理引擎。
与物理引擎的区别在于,物理引擎通常用于高精度的物理模拟,而这里更多是为了满足游戏设计需求而对特定行为进行定制化处理。这种方式能够平衡真实物理效果与游戏性之间的关系,同时也更适合解决特定场景中的问题。
你是否需要为敌人编写不同的碰撞检测代码?
对于敌人是否需要不同的碰撞检测代码,通常不需要。整体上碰撞检测逻辑会是一个相对统一的代码集合,能够适用于玩家和敌人。然而,在具体实现中可能会根据实际需求加入一些特殊处理。
特殊处理的原因是,敌人和玩家的碰撞检测可能需要微调,以实现不同的游戏效果。例如,玩家的碰撞检测可能更加细致,用于处理跳跃、攀爬等复杂行为,而敌人的碰撞逻辑可能更偏向于简单高效,以便处理多个敌人的场景。
根据需求,有可能为玩家和敌人分别设计一些特定的规则,但总体来说,基础的碰撞检测逻辑会尽可能统一,这样可以避免冗余代码并提高开发效率。特殊规则主要是为了满足游戏性和玩法设计的差异化需求,而不是因为技术实现上的限制。
你是否计划在地图中加入非轴对齐的墙面?
对于地图中的墙壁,我们的计划是避免传统的非轴对齐墙壁。然而,我们确实打算在地图中放置一些结构,这些结构会影响游戏的碰撞检测。
例如,如果地图是森林,我们会放置树木来填充墙壁,而不是直接设置物理墙。这些树木将会作为碰撞检测的对象,这样当玩家沿着它们行走时,它们会更具自然的感觉,而不是感觉像传统的瓷砖地图。这种方式不仅增加了游戏的真实性,还提供了更多的可能性。
我们可能会使用一些锯齿状的表面来代替直线墙壁,也可以在游戏中添加一些表现为墙壁的实体,这些实体可能会以不同的角度出现。这些元素将需要进行碰撞检测计算,以应对任意表面的反弹情况。这比简单地处理直线墙壁要复杂得多,但这种方式能够增加游戏的可玩性和视觉吸引力。
对位置基动态的看法?
基于位置的动态确实是一个有趣的设计选择。它主要依赖于当前和之前的位置来推算物理行为,而不是直接使用速度或加速度。这种方法适用于我们目前的游戏开发进程,因为我们并不直接使用速度或加速度进行物理计算,而是依赖于位置的改变来模拟运动。这种方式使得物理计算更加简化,因为不需要频繁地处理速度和加速度变化,而是基于时间步长来一步步更新物体的位置。
在这个过程中,我们仍然需要掌握运动的基本方程式,只不过是在特定的位置上解决这些方程式,而不是依赖于速度或加速度的积分。这种方法使得物理引擎的实现更加直观和易于管理。
明天的直播将会涉及编码和质量保证,这两个方面都会进行。
是否会进行连续碰撞检测?
没有真正的连续碰撞检测。我的意思是,我们在计算机上都是离散的,而如果我们谈论的是检查沿着一个矢量的家伙是否已经相交,那只不过是一个离散的检测过程。我们会对角色沿着其运动方向进行碰撞检测,看是否与其他物体发生了交叉。而这一检测过程是离散的,即我们只在特定的时间点进行检查,而不是每个小时间间隔都进行碰撞计算。
因此,这种碰撞检测的设计更多依赖于一个周期性的检查,基本上只是一个物体在移动过程中可能与环境中的其他物体相交的检查。
函数的计算成本是否只通过经验来学习?
事物的计算成本并没有什么具体的关系,它实际上是由CPU指令生成的。你需要通过经验来了解这些函数的计算成本及它们如何影响性能。这种了解并不是来自于C++本身,而是取决于CPU的指令集和处理方式。
当需要优化时,你需要花时间分析和计时这些不同功能的效果,了解它们的实际表现。对于大多数情况来说,编写和优化代码的实际体验是最有效的方式。
对于其他角度是否比四元数更有用的问题,这是一个未来可能会涉及的讨论,但至少在目前的项目中,我们不会涉及到三维旋转,因此四元数的实际应用和必要性似乎不会成为主要的问题。
是否会有最小墙面距离?
可能取决于我们如何实施它。如果我们选择继续使用瓷砖地图碰撞,我可能会选择做验证检查。但我不知道我们最终会如何处理这个问题,所以我还在权衡。
计算角色停在墙之前的向量是否可以解决粘滞?
计算矢量的问题不在于计算本身,而是在于仅在知道发生碰撞之后才计算这个矢量。当我们知道发生碰撞时,我们不接受新的位置。这意味着我们需要进一步探讨如何在检测到碰撞时,就能够接受位置进行校正,而不是等到处理完成后再计算新的位置。这样做可以避免额外的计算和不必要的延迟。