我正在制作一款游戏,其中有一个电脑控制的炮塔。 炮塔可以旋转360度。
它使用trig来找出瞄准枪所需的角度(objdeg)并且枪的当前角度存储在(gundeg)
以下代码以设定的速度旋转喷枪
if (objdeg > gundeg)
{
gundeg++;
}
if (objdeg < gundeg)
{
gundeg--;
}
问题是如果有一个10度的物体,枪会旋转,射击并摧毁它,如果另一个目标出现在320度,枪将逆时针旋转310度,而不是顺时针旋转60度以击中它
如何修复我的代码,以免愚蠢行为?
答案 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)
(即半开放范围)
使用模数运算符执行“获得除法余数”:
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)
我倾向于支持
的解决方案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);
}
具有其余返回的漂亮属性
所以,
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中有类似的问题。 我的当前旋转角度为度,目标旋转角度为度。 这两个轮换可以任意大,所以我的功能有三个目标:
我想出了以下几点:
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 ++中完美运行,并进行一些语法更改。 然后,可以将该通用解决方案的返回值轻松用于解决任何特定问题,例如使用减法来获得相对旋转。
我知道这个问题很老,可以提供足够的具体答案,但我希望有一个类似问题的人在互联网上绊倒,可以从我的一般解决方案中汲取灵感。