在一个循环平面上计算两点间的连线角度

3

好的,我有一个大小为512x512的游戏场地,它是循环的,-32会变成xy512

现在我需要计算两个实体之间的角度,我有以下代码作为一种解决方法,它大部分时间都有效,但有时仍然失败:

Shooter.getAngle = function(a, b) {
    var ax = a.x;
    var bx = b.x;
    if (a.x < this.width / 4 && b.x > this.width - this.width / 4) {
        ax += this.width;

    } else if (a.x > this.width - this.width / 4 && b.x < this.width / 4) {
        bx += this.width;
    }

    var ay = a.y;
    var by = b.y;
    if (a.y < this.height / 4 && b.x > this.height - this.height / 4) {
        ay += this.height;

    } else if (a.y > this.height - this.height / 4 && b.y < this.height / 4) {
        by += this.height;
    }
    return this.wrapAngle(Math.atan2(ax - bx, ay - by) + Math.PI);
};

我只是想不出如何正确地将a投影到b上,以便正确考虑包装。
如果有人能帮忙,那就太好了,因为远离目标的寻踪导弹并不好玩。
澄清一下,如果玩家向右移动,则导弹应跟随他越过场地边缘到达左侧,就像另一个玩家一样。
编辑:
这是一个测试版本,示例表明它正确地包装,但在不必包装的情况下会出现错误:
function getAngle(a, b) {
    var tx = a.x - b.x;
    var ty = a.y - b.y;

    // actual area goes from -16 to 512 due to the border space that's out of screen
    tx = ((tx + 16) % (480 + 32)) - 16;
    ty = ((ty + 16) % (480 + 32)) - 16;
    var r = Math.atan2(ty, tx) * (180 / Math.PI);

    // example
    // |> b                    a >|
    // a.x = 460
    // b.x = 60

    // missile should go over the right border
    // tx = 400 
    // r = 0
    // missile goes right, OK

    // example2
    // |< a                    b <|
    // a.x = 60
    // b.x = 460

    // missile should go over the left border
    // tx = -400 
    // r = 180
    // missile goes left, OK


    // example3
    // |     a  >>>>  b                  |
    // a.x = 60
    // b.x = 280

    // missile should go right
    // tx = -220
    // r = 180
    // missile goes left, WRONG

    // example4
    // |     b  <<<<  a                  |
    // a.x = 280
    // b.x = 60

    // missile should go left
    // tx = 220
    // r = 0
    // missile goes right, WRONG

    console.log(ty, tx);
    console.log(r);
}

function Point(x, y) {
    this.x = x;
    this.y = y;
}

getAngle(new Point(460, 240), new Point(60, 240));
getAngle(new Point(60, 240), new Point(460, 240));
getAngle(new Point(60, 240), new Point(280, 240));
getAngle(new Point(280, 240), new Point(60, 240));

看起来唯一让它工作的方法是检查 a.x < width * 0.25b.x > width * 0.75 等情况,但这有缺陷,至少在我上面发布的版本中是如此:/


目标喜欢当自导导弹飞走时的感觉 ;) - jball
4
如果您的游戏范围只有480x480,那么-32怎么会变成512呢?我希望它应该是减去32后等于448,或者可能是正数32。此外,导弹是否也会绕过屏幕边缘,或者您希望它们在玩家绕过屏幕时转身追踪玩家返回到屏幕的另一侧? - Michael Dorgan
如果我靠近左边缘并向右边缘附近的目标发射导弹,导弹应该尝试越过场地的边缘吗?还是导弹只应该跟随玩家越过场地的边缘? - Peter Milley
请注意我的代码更改。我将我的包装代码更改为将坐标包装到-240..240,而不是0..480,这样可以纠正错误的测试用例。对此我很抱歉。还要检查和纠正您自己的包装代码,因为仅使用模运算并不正确,因为它只调整正值,并不能正确地包装负值。 - schnaader
我转换到模块,因为你的旧while()代码在某些时候无法正常工作,尽管取模部分在之后加上了一些附加检查,但仍然存在问题。不管怎样,你的新包装代码运行得非常好。 - Ivo Wetzel
显示剩余2条评论
2个回答

5
我会采用以下方法:以目标为坐标系的中心(并适当地包装导弹的坐标),然后计算角度。因此,伪代码如下(EDIT2:修正了代码,类似的问题也导致了您测试中的问题):
// missile.x/y = missile's coordinates
// target.x/y = target's coordinates
temp.x = missile.x - target.x;
temp.y = missile.y - target.y;

// The wrapping code - feel free to adjust,
// from the comments it seems you handle this
// a bit differently
while (temp.x < -240)
  temp.x += 480
while (temp.y < -240)
  temp.y += 480
while (temp.x > 240)
  temp.x -= 480
while (temp.y > 240)
  temp.y -= 480

// Now you can calculate the angle your missile must go,
// it has to fly across the line from temp.x/y to (0,0)

编辑:我看了视频,认为你的代码存在一些错误,但我不确定具体在哪里。使用数学函数和角度可能有点棘手,因为通常数学坐标系的Y轴向上,而屏幕坐标系的Y轴向下。

现在最好的方法可能是开始一些单元测试,类似于这样:

// Create a function that returns the angle a missile must
// fly and cares about all the dirty details with wrapping etc.
MissileAngle(missile.x, missile.y, target.x, target.y)
// Now create some tests for it, f.e.:
MissileAngle(missile top left corner, target bottom right corner)
  Expected Result: Northwest
MissileAngle(missile bottom right corner, target top left corner)
  Expected Result: Southeast

请注意,此处未使用坐标,因为它严重依赖于您使用的坐标系统,这可能有助于您更好地了解问题的整体情况(当然,希望能帮助您解决错误,至少可以快速检查是否正确工作,而无需玩实际游戏,这会更耗时)。


这是它目前的外观视频:http://www.youtube.com/watch?v=Nl6gwMKrZxg(边框有点难以看清...) - Ivo Wetzel
在这种情况下,我猜测您的角度计算可能有问题。然而,您问题中的代码似乎很好,使用atan2(dx, dy)并添加Pi(180°)来将导弹旋转到目标。所以可能是wrapAngle出了问题,它是做什么的? - schnaader
嗯...看起来没问题。顺便说一下,你可以通过交换目标和导弹来消除额外的wrapAngle和加上180°的计算:atan2(bx - ax, by - ay)应该与wrapAngle(atan2(ax - bx, ay - by) + Math.Pi)完全相同,只是更快,而且对于那些讨厌的错误有更少的空间 :) - schnaader
我再次观看视频时注意到的另一件有趣的事情是:在开头,红色飞船位于左上角,并发射了两枚导弹,这些导弹沿着X轴正确地飞行(向右),但沿Y轴方向错误(向上),之后,飞船稍微旋转并再次开火,但这次Y轴方向正确(向下),而X轴方向错误(向左)。因此,似乎存在某些额外因素(例如飞船的初始角度)来影响导弹的路径(missile.x/y < target.x/y)。 - schnaader
1
更新后的代码完美运行,也许我应该在实际纸上画出整个流程,而不是只在脑海中想象。无论如何,非常感谢 :) - Ivo Wetzel
显示剩余2条评论

0
只是提出一个显而易见的建议:应该是Math.atan2(ay-by,ax-bx),这不是你帖子中的笔误吗?如果是的话,请原谅,但我肯定会看到它会导致你的导弹行为异常。
编辑后添加:没关系,我刚刚看到了你的评论。

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