代码之家  ›  专栏  ›  技术社区  ›  HKVariant

当玩家旋转到圆周率时,旋转速度为360

  •  15
  • HKVariant  · 技术社区  · 7 年前

    使用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
        }
    }
    

    鼠标线是转向面向鼠标的弧度,我只是加上转向率[在本例中为2]。

    当鼠标到达左侧并穿过中心y轴时,弧度角从-π变为π,反之亦然。这会使玩家完成360度的全方位练习。

    解决这个问题的正确方法是什么?我试着将角度设为绝对值,它只会出现在pi和0[左 位于中心y轴的正方形右侧]。

    我附上了问题的gif,以便更好地可视化。 gif

    基本总结:

    玩家缓慢地旋转以跟随鼠标,但当角度达到pi时,极性会发生变化,这会导致玩家进行360度[将所有背部计数到相反的极性角度]。

    编辑: dt是delta时间,只是为了适当的帧解耦运动变化明显

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

    Github回购暂时: here

    你需要 this library 建造它![去拿它]

    2 回复  |  直到 7 年前
        1
  •  7
  •   icza    7 年前

    事先注意: 我下载了您的示例repo并将我的更改应用到它上,它运行得非常完美。这里有一段录音:

    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 规格化() 将使用这两个输入角度生成正确的结果,而前者的循环将有50次迭代,后者的循环将有0次迭代。

    还要注意的是 规格化() 可以通过使用类似于整数除法和余数的浮点运算来优化“大”角度,但如果坚持使用标准化或“小”角度,此版本实际上更快。

        2
  •  2
  •   EvilTak    7 年前

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

    您的问题源于旋转角度的使用。由于逆三角函数的不连续性,对于相对接近的输入,很难消除函数值中的“跳跃”(如果不是完全不可能的话)。具体而言,当 x < 0 ,则, atan2(+0, x) = +pi (其中 +0 是一个非常接近零的正数),但是 atan2(-0, x) = -pi 这就是为什么你会体验到 2 * pi 这导致了你的问题。

    因此,直接使用向量、旋转矩阵和/或四元数通常更好。他们使用角度作为三角函数的参数,三角函数是连续的,可以消除任何不连续性。就我们而言, spherical linear interpolation (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是如何在这里发挥作用的?考虑到上述声明,我们必须 slerp geometrically 从…起 (cos p.rotateRad,sin p.rotateRad) (代表人 current )至 (win.MousePosition()。Y-p.pos.Y,赢。鼠标位置()。X-p.pos.X)。标准化 (代表人 target )通过适当的参数,该参数将由转速确定。

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

    slerp(p0, p1; t) = p0 * sin(A * (1-t)) / sin A + p1 * sin (A * t) / sin A
    

    哪里 A 是单位向量之间的角度 p0 p1 cos A = dot(p0, p1)

    就我们而言, p0 == current p1 == target .剩下的就是参数的计算 t ,也可以将其视为slerp通过的角度的分数。因为我们知道我们要旋转一个角度 p.turnSpeed * dt 在每个时间步, t = p.turnSpeed * dt / A .在替换 T ,我们的slerp公式变为

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

    避免计算 A. 使用 acos ,我们可以使用复合角度公式 sin 进一步简化。请注意,slerp操作的结果存储在 result

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

    我们现在有了需要计算的一切 后果 .如前所述, cos A=点(p0,p1) .同样, sin A = abs(cross(p0, p1)) 哪里 cross(a, b) = a.X * b.Y - a.Y * b.X

    现在的问题是从 后果 .请注意 result = (cos newRotation, sin newRotation) .有两种可能性:

    1. 直接计算 rotateRad 通过 p.rotateRad = atan2(result.Y, result.X)
    2. 如果可以访问二维旋转矩阵,只需将旋转矩阵替换为矩阵

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