我正在制作一个游戏,它是一个计算机控制的炮塔.炮塔可以旋转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)
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
(即半开放范围)
使用模运算符执行"获得除法余数":
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].
eri*_*hui 11
我倾向于赞成一个解决方案
Krypes提出的交叉积解决方案符合此标准,但有必要首先从角度生成向量.我相信JustJeff的BAMS技术也符合这个标准.我会提供另一个......
正如讨论为什么不同编程语言中的模数不同?这是指优秀的维基百科文章,有很多方法可以执行模运算.常见的实现将商围绕零或负无穷大.
但是,如果舍入到最接近的整数:
double ModNearestInt(double a, double b) {
return a - b * round(a / b);
}
Run Code Online (Sandbox Code Playgroud)
具有剩余返回的良好属性
所以,
double angleToTarget = ModNearestInt(objdeg - gundeg, 360.0);
Run Code Online (Sandbox Code Playgroud)
将是objdeg和gundeg之间的最小角度,标志将指示方向.
只需比较以下内容:
gundeg - objdeg
objdeg - gundeg
gundeg - objdeg + 360
objdeg - gundeg + 360
Run Code Online (Sandbox Code Playgroud)
并选择绝对值最小的那个.
这是一个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的灵感
编辑:我对我的位置设置器不满意,所以我把它清理干净了