使计算机实现360度= 0度,旋转炮塔

时间:2009-06-26 12:47:18

标签: algorithm

我正在制作一款游戏,其中有一个电脑控制的炮塔。 炮塔可以旋转360度。

它使用trig来找出瞄准枪所需的角度(objdeg)并且枪的当前角度存储在(gundeg)

以下代码以设定的速度旋转喷枪

if (objdeg > gundeg)
{
    gundeg++;
}
if (objdeg < gundeg)
{
    gundeg--;
}

问题是如果有一个10度的物体,枪会旋转,射击并摧毁它,如果另一个目标出现在320度,枪将逆时针旋转310度,而不是顺时针旋转60度以击中它

如何修复我的代码,以免愚蠢行为?

15 个答案:

答案 0 :(得分:22)

如果用“BAMS”代表你的角度,你可以完全避免除法(和mod),它代表二进制角度测量系统。我们的想法是,如果将角度存储在N位整数中,则使用该整数的整个范围来表示角度。这样,就不必担心溢出过360了,因为你的表示的自然模2 ^ N属性会为你处理它。

例如,假设您使用8位。这会将您的圆圈切割成256个可能的方向。 (你可以选择更多的位,但为了这个例子,8很方便)。设0x00代表0度,0x40代表90度,0x80代表180度,0xC0代表270度。再也不用担心“标志”了,BAMS对于角度来说是很自然的。如果将0xC0解释为“无符号”并按比例缩放到360/256度,则角度为(+192)(360/256)= +270;但如果将0xC0解释为'signed',则角度为(-64)(360/256)= -90。请注意,-90和+270在角度方面的含义相同。

如果要将三角函数应用于BAMS角度,可以预先计算表格。对表来说有一些技巧,但是你可以看到这些表并不是那么大。为8位BAMS存储双精度值的整个正弦和余弦表不会超过4K的内存,在当今的环境中饲喂鸡肉。

由于你提到在游戏中使用它,你可能会逃脱8位或10位表示。无论何时加上或减去角度,都可以使用逻辑AND运算将结果强制为N位,例如8位的角度&amp; = 0x00FF。

忘记最好的部分(编辑)

在BAMS系统中可以轻松解决右转与左转问题。只需区分,并确保只保留N个有意义的位。将MSB解释为符号位表示应该转向哪种方式。如果差值为负,则通过差值的abs()转向相反的方向。

这个丑陋的小C程序证明了这一点。首先尝试输入20 10和20 30。然后试着绕过零点来欺骗它。给它20 -10,它会向左转。给它20 350,它仍然向左转。请注意,因为它是以8位完成的,所以181与180无法区分,所以如果你将它提供20 201并且它向右而不是向左 - 在由8位提供的分辨率中向右转,向右转,请不要感到惊讶这种情况是一样的。投入20 205,它会走得更短。

#include <stdio.h>
#include <math.h>

#define TOBAMS(x)    (((x)/360.0) * 256)
#define TODEGS(b)    (((b)/256.0) * 360)

int main(void)
{
    double a1, a2;     // "real" angles
    int b1, b2, b3;    // BAMS angles


    // get some input
    printf("Start Angle ? ");
    scanf("%lf", &a1);

    printf("Goal Angle ? ");
    scanf("%lf", &a2);

    b1 = TOBAMS(a1);
    b2 = TOBAMS(a2);

    // difference increases with increasing goal angle
    // difference decreases with increasing start angle
    b3 = b2 - b1;
    b3 &= 0xff;

    printf("Start at %7.2lf deg and go to %7.2lf deg\n", a1, a2);
    printf("BAMS   are 0x%02X and 0x%02X\n", b1, b2);
    printf("BAMS diff is 0x%02X\n", b3);

    // check what would be the 'sign bit' of the difference
    // negative (msb set) means turn one way, positive the other
    if( b3 & 0x80 )
    {
        // difference is negative; negate to recover the
        // DISTANCE to move, since the negative-ness just
        // indicates direction.

        // cheap 2's complement on an N-bit value:
        // invert, increment, trim
        b3 ^= -1;       // XOR -1 inverts all the bits
        b3 += 1;        // "add 1 to x" :P
        b3 &= 0xFF;     // retain only N bits

        // difference is already positive, can just use it
        printf("Turn left %lf degrees\n", TODEGS(b3));
        printf("Turn left %d counts\n", b3);
    }
    else
    {
        printf("Turn right %lf degrees\n", TODEGS(b3));
        printf("Turn right %d counts\n", b3);
    }

    return 0;
}

答案 1 :(得分:20)

如果你需要在一个方向上旋转180度以瞄准炮塔,那么旋转另一个方向会更快。

我只是检查一下,然后按适当的方向旋转

if (objdeg != gundeg)
{
    if ((gundeg - objdeg) > 180)
       gundeg++;
    else
       gundeg--;
}

编辑:新解决方案

我根据评论中的反馈改进了我的解决方案。这决定了目标是否位于炮塔的“左侧”或“右侧”,并决定转向的方向。如果目标距离超过180度,它会反转这个方向。

if (objdeg != gundeg)
{
  int change = 0;
  int diff = (gundeg - objdeg)%360;
  if (diff < 0)
     change = 1;
  else
     change = -1;

  if (Math.Abs(diff) > 180)
     change = 0 - change;

  gundeg += change;
 }

答案 2 :(得分:12)

归一化为[0,360]:

(即半开放范围)

使用模数运算符执行“获得除法余数”:

361 % 360

将是1。

在C / C ++ / ...样式语言中,这将是

gundeg %= 360

注意(感谢评论):如果gundeg是一个浮点类型,你需要在C / C ++:fmod中使用库函数,或者自己动手(.NET):

double FMod(double a, double b) {
  return a - Math.floor(a / b) * b;
}

转向哪个方向?

哪种方式更短(如果转弯是180°,那么答案是任意的),在C#中,假设方向是逆时针测量的

TurnDirection WhichWayToTurn(double currentDirection, double targetDirection) {
  Debug.Assert(currentDirection >= 0.0 && currentDirection < 360.0
               && targetDirection >= 0.0 && targetDirection < 360.0);

  var diff = targetDirection - currentDirection ;
  if (Math.Abs(diff) <= FloatEpsilon) {
    return TurnDirection.None;
  } else if (diff > 0.0) {
    return TurnDirection.AntiClockwise;
  } else {
    return TurnDirection.Clockwise;
  }
}

NB。这需要进行测试。

注意使用assert来确认标准化角度的前置条件,并且我使用断言,因为这是一个内部函数,不应该接收未验证的数据。如果这是一个通常可重用的函数,则参数检查应抛出异常或返回错误(取决于语言)。

另请注意。要弄清楚这样的事情没有比铅笔和纸更好的了(我的初始版本错了,因为我正在使用(-180,180)和[0,360]混合。

答案 3 :(得分:10)

我倾向于支持

的解决方案
  • 没有很多嵌套的 if 语句
  • 不假设两个角度中的任何一个都在特定范围内,例如[0,360]或[-180,180]
  • 具有恒定的执行时间

Krypes 提出的交叉积解决方案符合此标准,但有必要首先从角度生成向量。我相信 JustJeff 的BAMS技术也满足这个标准。我会提供另一个......

正如Why is modulus different in different programming languages?所讨论的那样,它引用了优秀的Wikipedia Article,有许多方法可以执行模运算。常见的实现将商围绕零或负无穷大。

但是,如果你四舍五入到最接近的整数:

double ModNearestInt(double a, double b) {
    return a - b * round(a / b);
}

具有其余返回的漂亮属性

  • 始终在区间[-b / 2,+ b / 2]
  • 总是最短距离为零

所以,

double angleToTarget = ModNearestInt(objdeg - gundeg, 360.0);

将是objdeg和gundeg之间的最小角度,符号将指示方向。

答案 4 :(得分:6)

只需比较以下内容:

gundeg - objdeg
objdeg - gundeg 
gundeg - objdeg + 360
objdeg - gundeg + 360

并选择绝对值最小的那个。

答案 5 :(得分:5)

这是一个workign C#示例,这将是正确的方式。 :

public class Rotater
{
    int _position;
    public Rotater()
    {

    }
    public int Position
    {
        get
        {
            return _position;
        }
        set            
        {
            if (value < 0)
            {
                _position = 360 + value;
            }
            else
            {
                _position = value;
            }
            _position %= 360;
        }
    }
    public bool RotateTowardsEx(int item)
    {
        if (item > Position)
        {
            if (item - Position < 180)
            {
                Position++;
            }
            else
            {
                Position--;
            }
            return false;
        }
        else if (Position > item)
        {
            if (Position - item < 180)
            {
                Position--;
            }
            else
            {
                Position++;
            }
            return false;
        }
        else
        {
            return true;
        }
    }
}

    static void Main(string[] args)
    {


        do
        {
            Rotater rot = new Rotater();
            Console.Write("Enter Starting Point: ");
            var startingPoint = int.Parse(Console.ReadLine());
            rot.Position = startingPoint;
            int turns = 0;

            Console.Write("Enter Item Point: ");
            var item = int.Parse(Console.ReadLine());
            while (!rot.RotateTowardsEx(item))
            {
                turns++;
            }
            Console.WriteLine(string.Format("{0} turns to go from {1} to {2}", turns, startingPoint, item));
        } while (Console.ReadLine() != "q");


    }

感谢John Pirie的灵感

编辑:我对我的职位定位器不满意,所以我把它清理干净了

答案 6 :(得分:3)

您需要根据哪个是较短的距离来决定是向左还是向右旋转。然后你需要采取模数:

if (objdeg > gundeg)
{
    if (objdeg - gundeg < 180)
    {
        gundeg++;
    }
    else
    {
        gundeg--;
    }
}
if (objdeg < gundeg)
{
    if (gundeg - objdeg < 180)
    {
        gundeg--;
    }
    else
    {
        gundeg++;
    }
}
if (gundeg < 0)
{
    gundeg += 360;
}
gundeg = gundeg % 360;

答案 7 :(得分:3)

实际上,这是解决这个问题的一种更简单的方法。两个向量的交叉乘积为您提供表示法线(例如垂直)的向量。作为这个的假象,给定两个矢量a,b,它们位于xy平面上,x b = c意味着c =(0,0,+ -1)。

c的z分量的符号(例如,它是从xy平面出来还是进入xy平面)取决于它是围绕z轴左转还是右转,使得a等于b。

Vector3d turret
Vector3d enemy

if turret.equals(enemy) return;
Vector3d normal = turret.Cross(enemy);
gundeg += normal.z > 0 ? 1 : -1; // counter clockwise = +ve

答案 8 :(得分:1)

尝试使用整数除法除以180并基于偶数/奇数结果转动?

749/180 = 4顺便顺时针旋转29度(749%180)

719/180 = 3所以你逆时针旋转1度(180 - 719%180)

答案 9 :(得分:0)

问题在于找到能够提供最短距离的方向。

然而,减法可能导致负数,需要考虑 如果你在每次检查时移动枪一步,我不知道你什么时候会做模数 而且,如果您想一步移动枪,您只需正确地添加/减去增量。

为此, Kirschstein 似乎在想我离她最近 我正在使用这个简单的psudo代码中的整数。

if (objdeg != gundeg)
{
    // we still need to move the gun
    delta = gundeg - objdeg
    if (delta > 0)
        if (unsigned(delta) > 180)
           gundeg++;
        else
           gundeg--;
    else // delta < 0
        if (unsigned(delta) > 180)
           gundeg--;        
        else
           gundeg++;

    if (gundeg == 360)
        gundeg = 0;
    else if (gundeg == -1)
        gundeg = 359;
}

尝试使用gundeg = 10和objdeg = 350逐步执行此操作,以查看gundeg将如何从10向下移动到0然后再从359向下移动到350.

答案 10 :(得分:0)

以下是我最近在游戏中实现类似内容的方式:

double gundeg;

// ...

double normalizeAngle(double angle)
{
    while (angle >= 180.0)
    {
        angle -= 360.0;
    }
    while (angle < -180.0)
    {
       angle += 360.0;
    }
    return angle;
}

double aimAt(double objAngle)
{
    double difference = normalizeAngle(objdeg - gundeg);
    gundeg = normalizeAngle(gundeg + signum(difference));
}

所有角度变量都限制在-180 .. + 180,这使得这种计算更容易。

答案 11 :(得分:0)

冒着自行车撞击的风险,将度数存储为整数而不是自己的等级可能是“原始迷恋”的情况。如果我没记错的话,“实用程序员”一书建议创建一个用于存储学位并对其进行操作的课程。

答案 12 :(得分:0)

这是我能想到的可以解决问题的短测试伪代码示例。它适用于正角度0..359的域,它在处理“正常”角度之前首先处理边缘条件。

if (objdeg >= 180 and gundeg < 180)
    gundeg = (gundeg + 359) % 360;
else if (objdeg < 180 and gundeg >= 180)
    gundeg = (gundeg + 1) % 360;
else if (objdeg > gundeg)
    gundeg = (gundeg + 1) % 360;
else if (objdeg < gundeg)
    gundeg = (gundeg + 359) % 360;
else
    shootitnow();

答案 13 :(得分:0)

这可能有点晚了......可能很晚......但我最近遇到了类似的问题,发现这在GML中运行得很好。

var diff = angle_difference(gundeg, objdeg)

if (sign(diff)>0){
   gundeg --;
}else{
   gundeg ++;
}

答案 14 :(得分:0)

我在python中有类似的问题。 我的当前旋转角度为度,目标旋转角度为度。 这两个轮换可以任意大,所以我的功能有三个目标:

  1. 保持两个角度都小
  2. 保持角度之间的差异<= 180°
  3. 返回的角度必须等于输入的角度

我想出了以下几点:

def rotation_improver(c,t):
    """
    c is current rotation, t is target rotation. \n
    returns two values that are equivalent to c and t but have values between -360 and 360
    """
    ci = c%360
    if ci > 180:
        ci -= 360
    ti = t%360
    if not abs(ci-ti) <= 180:
        ti -= 360
    return ci,ti

它应该在c ++中完美运行,并进行一些语法更改。 然后,可以将该通用解决方案的返回值轻松用于解决任何特定问题,例如使用减法来获得相对旋转。

我知道这个问题很老,可以提供足够的具体答案,但我希望有一个类似问题的人在互联网上绊倒,可以从我的一般解决方案中汲取灵感。