一旦玩家旋转360度达到圆周率,转速如何?

15

因为Golang在游戏开发上表现良好,所以我选择使用它来制作游戏。我让玩家始终面向鼠标,但希望通过转弯率使某些角色的转向速度更慢。以下是它计算转弯圆周的方法:

func (p *player) handleTurn(win pixelgl.Window, dt float64) {
    mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y, win.MousePosition().X-p.pos.X) // the angle the player needs to turn to face the mouse
    if mouseRad > p.rotateRad-(p.turnSpeed*dt) {
        p.rotateRad += p.turnSpeed * dt
    } else if mouseRad < p.rotateRad+(p.turnSpeed*dt) {
        p.rotateRad -= p.turnSpeed * dt
    }
}

mouseRad是为了面向鼠标而转动的弧度,我只是在增加旋转速率 [在这种情况下,是2]。

问题在于当鼠标到达左侧并越过中心y轴时,弧度角从-pi变为pi或反之。这会导致玩家做出完整的360度旋转。

有什么适当的方法来解决这个问题吗?我试过将角度变成绝对值,但它只在pi和0 [中央y轴的左边右边]发生。

我附上一个gif以更好地说明问题。gif

基本总结:

玩家缓慢旋转以跟随鼠标,但当角度达到pi时,它会改变极性,这会导致玩家做出360度旋转[计算回相反极性角度]。

编辑: dt是delta时间,仅用于正确的帧解耦运动变化

p.rotateRad从0开始,并且是float64。

Github repo暂时在这里

您需要此库来构建它![获取它]


你能把这个推到代码库里吗?这样我就可以记录一些东西来进行调试和测试了。 - Ron
@Ron 好的,等我一下。它显然使用Go语言,并且需要库https://github.com/faiface/pixel -- 你只需获取它即可。 - HKVariant
1
@Ron添加到主贴中。 - HKVariant
谢谢,我知道已经有人回答了,但我还是要下载并查看一下。我以前没有使用过 Go 的任何可视化库,想要尝试一下。 - Ron
2个回答

7

注意:我下载了你的示例代码并进行了修改,结果运行完美无缺。这是一个录屏:

fixed cursor following

(供参考,GIF 是使用 byzanz 录制的)


一种简单的解决方案是计算和“标准化”差异,使其在-Pi..Pi范围内,而不是比较角度(mouseRad和更改后的p.rotateRad)。然后,您可以根据差异的符号(负或正)决定向哪个方向转动。

通过添加/减去2 * Pi,可以将角度“标准化”,使其落在-Pi..Pi范围内。添加/减去2 * Pi不会改变角度,因为2 * Pi恰好是一个完整的圆。

这是一个简单的标准化函数:

func normalize(x float64) float64 {
    for ; x < -math.Pi; x += 2 * math.Pi {
    }
    for ; x > math.Pi; x -= 2 * math.Pi {
    }
    return x
}

并在您的handleTurn()中像这样使用:

func (p *player) handleTurn(win pixelglWindow, dt float64) {
    // the angle the player needs to turn to face the mouse:
    mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y,
        win.MousePosition().X-p.pos.X)

    if normalize(mouseRad-p.rotateRad-(p.turnSpeed*dt)) > 0 {
        p.rotateRad += p.turnSpeed * dt
    } else if normalize(mouseRad-p.rotateRad+(p.turnSpeed*dt)) < 0 {
        p.rotateRad -= p.turnSpeed * dt
    }
}

您可以在这个Go Playground演示中进行操作。

请注意,如果您将角度归一化(处于-Pi..Pi范围内),那么normalize()函数中的循环最多只会执行1次,因此速度非常快。显然,您不希望像100*Pi + 0.1这样存储角度,因为它与0.1相同。对于这两个输入角度,normalize()都会产生正确的结果,但前者的循环将有50次迭代,而后者的循环将没有迭代。

另请注意,normalize()可以通过使用类似于整数除法和余数的浮点运算来优化“大”角度,但是如果您坚持使用归一化或“小”角度,则此版本实际上更快。


感谢这个答案。由于目前我只需要使用“小”角度,所以这个答案非常完美。解释得很好,它非常有效! - HKVariant
@HKVariant 不要误解,我的解决方案适用于所有角度(不仅限于“小”角度),你可能甚至不会注意到性能差异。我只是说对于“大”角度,归一化可能更有效。有关详细信息,请参见 此链接此链接 - icza

2
前言:本答案假定读者具有一定的线性代数、三角学和旋转/变换知识。
你的问题源于旋转角度的使用。由于反三角函数的不连续性质,相对接近的输入值很难(甚至根本不可能)消除函数值的“跳跃”。具体而言,当 x < 0 时,atan2(+0, x) = +pi(其中 +0 是非常接近零的正数),但是 atan2(-0, x) = -pi。这正是你遇到的导致问题的 2 * pi 差异的原因。
因此,通常最好直接使用向量、旋转矩阵和/或四元数。它们使用角度作为三角函数的参数,这些函数是连续的并消除了任何不连续性。在我们的情况下,球面线性插值(slerp) 应该能解决问题。
由于您的代码测量了鼠标相对位置与物体绝对旋转形成的角度,我们的目标是将物体旋转,使得 本地(1, 0) (= (cos rotateRad, sin rotateRad)世界 空间中) 指向鼠标。实际上,我们必须旋转物体,以便 (cos p.rotateRad, sin p.rotateRad) 等于 (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized
在这里 slerp 是如何发挥作用的?考虑到上述语句,我们只需通过适当的参数从 (cos p.rotateRad, sin p.rotateRad)(由current表示)到 (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized(由target表示) 几何上执行 slerp 即可,该参数将由旋转速度确定。
既然我们已经打好了基础,我们可以继续计算新的旋转角度。根据slerp公式,
slerp(p0, p1; t) = p0 * sin(A * (1-t)) / sin A + p1 * sin (A * t) / sin A

其中A是单位向量p0p1之间的夹角,即cos A = dot(p0, p1)

在我们的情况下,p0 == currentp1 == target。唯一剩下的就是计算参数t,它也可以被视为通过slerp旋转角度的分数。由于我们知道每个时间步长都会旋转一个角度p.turnSpeed * dtt = p.turnSpeed * dt / A。替换t的值后,我们的slerp公式变为

p0 * sin(A - p.turnSpeed * dt) / sin A + p1 * sin (p.turnSpeed * dt) / sin A

为了避免使用acos计算A,我们可以使用sin的复合角公式进一步简化。请注意,slerp操作的结果存储在result中。
result = p0 * (cos(p.turnSpeed * dt) - sin(p.turnSpeed * dt) * cos A / sin A) + p1 * sin(p.turnSpeed * dt) / sin A

我们现在有计算result所需的一切。如前所述,cos A = dot(p0, p1)。同样,sin A = abs(cross(p0, p1)),其中cross(a, b) = a.X * b.Y - a.Y * b.X
现在问题实际上是找到从result旋转的方法。请注意,result = (cos newRotation, sin newRotation)。有两种可能性:
  1. Directly calculate rotateRad by p.rotateRad = atan2(result.Y, result.X), or
  2. If you have access to the 2D rotation matrix, simply replace the rotation matrix with the matrix

    |result.X -result.Y|
    |result.Y  result.X|
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接