从两个角度确定是顺时针还是逆时针旋转。

4
我正在使用XNA制作游戏。 我有敌人和玩家。 敌人应该逐渐转向玩家。他们应该计算出需要顺时针或逆时针旋转的方向,以最短的距离为准。
通过使用Atan2,我得到了敌人当前面对的角度和它应该面对的角度(敌人和玩家之间连线的角度)。
然而,我得到了一些奇怪的行为。像下面这种情况,敌人可能会朝着错误的方向转弯。
我的代码(如下所示)不断变长,但问题仍然存在。在游戏中克服这个问题必须很常见。有没有解决这个问题的方法?
            //this bit is just in case enemy has rotated more than 360 degrees (gets set back to 0)
            if (Math.Abs(_blocklist[0]._floor.Revolutions) >= 2)
            {
                _blocklist[0]._floor.Rotation = 0.0f;
            }

            //enemy rotation in radians          
            float blockroat = _blocklist[0]._floor.Rotation;
            // vector to player - vector to enemy
            _vectToPlayer = playerpos - _blocklist[0].Centre
            angletoplayer = (float)(Math.Atan2(_vectToPlayer.Y, _vectToPlayer.X));
            diff = blockroat - angletoplayer;

            if (diff < -Math.PI)
            {
                diff += (float) Math.PI;
                diff = -diff;
            }
            else if (diff > Math.PI)
            {
                diff -= (float)Math.PI;
                diff = -diff;
            }

            // if enemy angle if off by a certain amount
            if (Math.Abs(diff) >_maxturn)
            {
                if (diff < 0)
                {
                    //turn clockwise
                    _blocklist[0]._floor.Rotation += _maxturn;
                }
                else
                {
                     //turn anti clockwise
                    _blocklist[0]._floor.Rotation -= _maxturn;
                }
            }

更新

最终我采用了第二种方法,像这样... 完美地工作。而且比我之前的代码整洁得多。

            //enemy rotation in radians from farseer (red line)
            float brot = _blocklist[0]._floor.Rotation + ((float)Math.PI/2);
            //vector from enemy to player (blue line)
            Vector2 _vectToPlayer = playerpos - _blocklist[0].Centre;
            //cross product of 2d vectors
            cross = (_vectToPlayer.X * (float)Math.Sin(brot)) - ((float)Math.Cos(brot) * _vectToPlayer.Y);

            //tolerance for how closely enemy must point towards player
            if (Math.Abs(cross) > 5)
            {
                if (cross > 0)
                {
                    //turn anticlockwise
                    _blocklist[0]._floor.Rotation -= _npcstats.maxturnspeed;
                }
                else
                {
                    //turn clockwise
                    _blocklist[0]._floor.Rotation += _npcstats.maxturnspeed;
                } 
            }

我认为我的以前的代码基本上是在按照建议的方法1来进行操作。但是我无法使其正常工作...我认为这是因为Farseer坐标系统和它与我的坐标系统之间的交互存在问题。


在我看来,你总是旋转敌人的_maxturn数量而不是diff的数量。我看不到_maxturn的定义,但我猜它是一种常量? - Sopuli
是的,那是因为敌人的转向速度受到最大速度限制。他每帧只能转动那么多。 - Guye Incognito
4个回答

11

技巧 #1:

你使用的约定我不熟悉。按照你的约定,东方是0,北方是-π/2,西方是π和-π,南方是π/2。所有角度都在-π和π之间。

通常情况下,人物面向东方的角度为零,北方是π/2,西方是π,正南方是3π/2。所有角度都在0到2π之间。

我们假设采用通常的约定而不是你的约定。首先要正确计算在通常约定下红色和蓝色向量的角度;如何计算由您决定。

从两个角度中减去红色向量的角度。现在我们有了站在原点面向正东的角度。

现在归一化新的蓝色角度;如果小于0,则加上2π。如果大于2π,则减去2π。重复此过程,直到它在0和2π之间。

现在我们有两个角度;新红色向量的角度为零,新蓝色向量的角度在0和2π之间。

如果新的蓝色向量的角度小于π,则人物必须向左转。如果大于π,则向右转。

技巧 #2:

在蓝色和红色向量上取一个非零点,例如(bx, by)(rx, ry)。现在计算bx*ry-by*rx。如果它是正数,则右转;如果是负数,则左转。如果为零,则要通过其他方式确定它们是直接面对或背对的情况。(这本质上是Jacek的回答更直接地陈述)。


我正在使用XNA的Farseer物理库。身体的旋转方式包含在其中。我猜这是因为一件事物可以从其起始点以正面或负面的方式旋转。此外,事物可以保持旋转并处于像20弧度这样的状态。(不过我正在防止这种情况发生) - Guye Incognito
1
@GuyeIncognito:这并不是一种不合理的惯例,只是我习惯于思考的方式不同。你可以轻松地将我的技巧适应到你的惯例中。本质上,我的建议是将整个系统旋转到一个更容易理解正确答案的方向,然后在该系统中获取答案。当然,这样做是有效的,因为刚性旋转整个系统并不会改变答案。 - Eric Lippert
2
@GuyeIncognito:所以按照你的约定,从两个角度中减去红色角度,将新的蓝色角度归一化为-π到π之间,现在如果新的蓝色角度为负数,则向左转,如果为正数,则向右转。 - Eric Lippert

5
如果您有蓝色和红色的Vector3向量,可以这样做:
Vector3 crossProduct = Vector3.Cross(red, blue)
if (crossProduct.z > 0)
    // Turn Right
else
    // Turn Left

2
非常简单。假设您有两个角度alpha和beta,并且您想告诉最短的从alpha到beta的运动是顺时针还是逆时针。您需要执行以下操作: 将alpha设置为0,并将您给定的相同偏移量添加到beta上。 现在,如果beta大于180度,则运动为逆时针,否则运动为顺时针。 例如:alpha为10度,beta为350度。因此,对两个角度进行-10度偏移会将alpha设置为0,将beta设置为340度,并且运动是逆时针的。
def direction_by_2_angles(alpha, beta)
  # true clockwize, false not clockwize
  delta = 360 - alpha
  beta = beta + delta
  beta = beta % 360
  beta < 180 ? true : false
end

1

我搜索了一下是否有超短的答案,但没有找到。看起来技巧1很受欢迎。不过我已经实现了它,这里是代码:

//ccw = 1, cw = -1
//refineAngle = normallize angle to range [-PI, PI]

currentAngle = refineAngle(currentAngle);
targetAngle =  refineAngle(targetAngle);

if(targetAngle < 0)
    targetAngle += (PI *2);
if(currentAngle < 0)
    currentAngle += (PI *2);
if(targetAngle < currentAngle)
    targetAngle += (PI *2);

if(targetAngle - currentAngle <= PI)
    return 1;
else
    return -1;

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