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

Ver*_*roq 19 algorithm

我正在制作一个游戏,它是一个计算机控制的炮塔.炮塔可以旋转360度.

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

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

if (objdeg > gundeg)
{
    gundeg++;
}
if (objdeg < gundeg)
{
    gundeg--;
}
Run Code Online (Sandbox Code Playgroud)

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

如何修复我的代码,使其不会愚蠢?

Jus*_*eff 23

如果你用"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位,角度为&= 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;
}
Run Code Online (Sandbox Code Playgroud)

  • @finnw - 我不同意过早优化.是的,削减分区可能会优化,但BAMS的重点不在于优化,因为它是利用整数的自然模2 ^ N属性. (3认同)
  • +1因为这是一个令人耳目一新的方法! (2认同)
  • 过早的优化是万恶之源。 (2认同)

Kir*_*ein 20

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

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

if (objdeg != gundeg)
{
    if ((gundeg - objdeg) > 180)
       gundeg++;
    else
       gundeg--;
}
Run Code Online (Sandbox Code Playgroud)

编辑:新的解决方案

我根据评论中的反馈改进了我的解决方案.这决定了目标是否位于炮塔的"左侧"或"右侧",并决定转向的方向.如果目标距离超过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;
 }
Run Code Online (Sandbox Code Playgroud)


Ric*_*ard 12

归一化为[0,360]:

(即半开放范围)

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

361 % 360
Run Code Online (Sandbox Code Playgroud)

将是1.

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

gundeg %= 360
Run Code Online (Sandbox Code Playgroud)

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

double FMod(double a, double b) {
  return a - Math.floor(a / b) * b;
}
Run Code Online (Sandbox Code Playgroud)

哪个方向转?

哪种方式更短(如果转弯是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;
  }
}
Run Code Online (Sandbox Code Playgroud)

NB.这需要测试.

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

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

  • 这说明了StackOverflow在西方最快的问题中最糟糕的一个问题,即正确的答案得到的回答高于正确答案.最初的问题是关于确定转向的方式(所以它将涉及检查x是否更小或360-x,其中x是角度mod 360的差异),但这个答案是11票并且尽管没有回答问题. (24认同)
  • 但是你如何使用它来确定转向哪种方式? (7认同)
  • 所以你先用一些看似不错的东西回答,快速回击,然后阅读正确的答案,将它们合并到你的帖子中,免费代表.辉煌! (7认同)

eri*_*hui 11

我倾向于赞成一个解决方案

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

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

正如讨论为什么不同编程语言中的模数不同?这是指优秀的维基百科文章,有很多方法可以执行模运算.常见的实现将商围绕零或负无穷大.

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

double ModNearestInt(double a, double b) {
    return a - b * round(a / b);
}
Run Code Online (Sandbox Code Playgroud)

具有剩余返回的良好属性

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

所以,

double angleToTarget = ModNearestInt(objdeg - gundeg, 360.0);
Run Code Online (Sandbox Code Playgroud)

将是objdeg和gundeg之间的最小角度,标志将指示方向.


J-1*_*DiZ 6

只需比较以下内容:

gundeg - objdeg
objdeg - gundeg 
gundeg - objdeg + 360
objdeg - gundeg + 360
Run Code Online (Sandbox Code Playgroud)

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


Jos*_*rke 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");


    }
Run Code Online (Sandbox Code Playgroud)

感谢John Pirie的灵感

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