Math 在玩家身上打开速率';当它达到圆周率时,旋转360度

Math 在玩家身上打开速率';当它达到圆周率时,旋转360度,math,go,triggers,mouse,game-physics,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 pla

使用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
    }
}
func(p*player)handleTurn(赢像素窗口,dt float64){
mouseRad:=math.Atan2(p.pos.Y-win.MousePosition().Y,win.MousePosition().X-p.pos.X)//玩家需要转向面对鼠标的角度
如果mouseRad>p.rotateRad-(p.turnSpeed*dt){
p、 旋转半径+=p.旋转速度*dt
}否则,如果mouseRad
mouseRad是朝向鼠标旋转的弧度,我只是添加旋转率[在本例中,2]

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

解决这个问题的正确方法是什么?我尝试过将角度设置为绝对值,但它只使其出现在pi和0处[中心y轴上正方形的左侧和右侧]

我附加了一个问题的gif,以提供更好的可视化效果

基本概述:

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

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

p、 rotateRad从0开始,是一个浮点64

Github回购协议暂时:


你需要建造它![go get it]

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

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

因此,直接使用向量、旋转矩阵和/或四元数通常更好。他们使用角度作为三角函数的参数,三角函数是连续的,可以消除任何不连续性。在我们的情况下,我们应该这样做

由于您的代码测量鼠标相对位置与对象绝对旋转形成的角度,因此我们的目标归结为旋转对象,使局部轴
(1,0)
=(cos rotateRad,sin rotateRad)
指向鼠标。实际上,我们必须旋转对象,使
(cos p.rotateRad,sin p.rotateRad)
等于
(win.MousePosition().Y-p.pos.Y,win.MousePosition().X-p.pos.X)。标准化

slerp是如何在这里发挥作用的?考虑到上面的陈述,我们只需从
(cos p.rotateRad,sin p.rotateRad)
(由
current
表示)到
(win.MousePosition().Y-p.pos.Y,win.MousePosition().X-p.pos.X)通过适当的参数,该参数将由转速决定

现在我们已经奠定了基础,我们可以继续实际计算新的旋转。根据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
为了避免使用
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
我们现在有了计算
结果所需的一切。如前所述,
cosa=dot(p0,p1)
。类似地,
sina=abs(cross(p0,p1))
,其中
cross(A,b)=A.X*b.Y-A.Y*b.X

现在是从
结果
实际查找旋转的问题。请注意,
result=(cos-newRotation,sin-newRotation)
。有两种可能性:

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

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

  • 事先注意:我下载了您的示例回购协议,并对其应用了我的更改,它运行得非常完美。这里有一段录音:

    (为了便于参考,使用
    byzanz
    录制GIF)


    一个简单易行的解决方案是不比较角度(
    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
    }
    
    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
        }
    }