使用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轴]。
基本摘要:
播放器慢慢旋转以跟随鼠标,但是当角度达到pi时,它会改变极性,这会导致玩家进行360 [将所有背部计数到相反的极性角度]。
编辑: dt是增量时间,只是为了适当的帧移动变化明显
p.rotateRad从0开始,是一个float64。
临时Github回购:here
您需要this library来构建它! [去吧]
答案 0 :(得分:7)
事先注意:我下载了您的示例回购并在其上应用了我的更改,并且它完美无缺。这是它的录音:
(供参考,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
是单位向量p1
和cos A = dot(p0, p1)
或p0 == current
之间的角度。
在我们的案例中,p1 == target
和t
。唯一剩下的就是计算参数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
。有两种可能性:
p.rotateRad = atan2(result.Y, result.X)
或|result.X -result.Y|
|result.Y result.X|
如果您可以访问2D旋转矩阵,只需将旋转矩阵替换为矩阵
即可signal.signal(signal.SIGWINCH, signal.SIG_IGN)