一旦玩家击中pi,玩家轮换的转动速度为

时间:2018-04-08 05:45:12

标签: math go triggers mouse game-physics

使用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是增量时间,只是为了适当的帧移动变化明显

p.rotateRad从0开始,是一个float64。

临时Github回购:here

您需要this library来构建它! [去吧]

2 个答案:

答案 0 :(得分:7)

事先注意:我下载了您的示例回购并在其上应用了我的更改,并且它完美无缺。这是它的录音:

fixed cursor following

(供参考,GIF记录为byzanz

一个简单而简单的解决方案是不比较角度mouseRad和改变的p.rotateRad),而是计算和“规范化”差异所以它在-Pi..Pi范围内。然后你可以根据差异的标志(负面或正面)来决定转向的方式。

“正常化”角度可以通过添加/减去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次迭代,在后者的情况下将有0次迭代。

另请注意,normalize()可以通过使用模拟整数除法和余数的浮点运算来优化“大”角度,但如果你坚持标准化或“小”角度,这个版本实际上更快。

答案 1 :(得分:2)

前言:这个答案假设了一些关于线性代数,三角学和旋转/变换的知识。

您的问题源于旋转角度的使用。由于反三角函数的不连续性,消除&#34;跳跃&#34;是非常困难的(如果不是完全不可能的话)。在相对接近的输入的函数的值。具体来说,当x < 0时,atan2(+0, x) = +pi(其中+0是一个非常接近于零的正数),但atan2(-0, x) = -pi。这正是您遇到导致问题的2 * pi差异的原因。

因此,直接使用矢量,旋转矩阵和/或四元数通常更好。他们使用角度作为三角函数的参数,这些函数是连续的并消除任何不连续性。在我们的例子中,spherical linear interpolation (slerp)应该可以解决问题。

由于您的代码测量了鼠标相对位置与对象绝对旋转形成的角度,因此我们的目标归结为旋转对象,使 local (1, 0) world 空间中的= (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(p0, p1; t) = p0 * sin(A * (1-t)) / sin A + p1 * sin (A * t) / sin A 表示)通过适当的参数{}由转速决定。

现在我们已经奠定了基础,我们可以继续实际计算新的轮换。根据slerp公式,

A

p0是单位向量p1cos A = dot(p0, p1)p0 == current之间的角度。

在我们的案例中,p1 == targett。唯一剩下的就是计算参数p.turnSpeed * dt,它也可以被认为是slerp通过的角度的一部分。因为我们知道我们将在每个时间步t = p.turnSpeed * dt / A以角度t旋转。在替换p0 * sin(A - p.turnSpeed * dt) / sin A + p1 * sin (p.turnSpeed * dt) / sin A 的值后,我们的slerp公式变为

A

为避免使用acos计算sin,我们可以使用result的复合角度公式来进一步简化此操作。请注意,slerp操作的结果存储在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)实际找到轮换的问题。请注意rotateRad。有两种可能性:

  1. p.rotateRad = atan2(result.Y, result.X)
  2. 直接计算|result.X -result.Y| |result.Y result.X|
  3. 如果您可以访问2D旋转矩阵,只需将旋转矩阵替换为矩阵

    即可
    signal.signal(signal.SIGWINCH, signal.SIG_IGN)