2

背景:我的鸟瞰 JavaScript 游戏中的每个精灵都有 8 张图像,分别代表顶部、右上角、右侧、右下等,具体取决于玩家的太空船速度。

问题:给定 sprite.speed.x 和 sprite.speed.y 的值(例如可能是 4 和 -2.5,或 2 和 0),我如何获得正确的角度(度数)?给定这个角度,我可以查找哪个度数值代表哪个精灵图像。或者也许有更简单的方法。(目前我只是使用类似“如果 x 低于零使用左图像”等,这将导致几乎所有时间都使用对角线图像。)

环顾四周,我发现...

angle = Math.atan2(speed.y, speed.x);

...但不知何故我仍然错过了一些东西。

PS:零速可以忽略,这些精灵只会使用最后一个有效的方向图像。

非常感谢您的帮助!

4

3 回答 3

10

好问题!我喜欢 tom10 的回答(在标记上,+1),但想知道是否可以在没有太多三角函数的情况下完成。这是一个简短的解决方案,然后是解释。

// slope is a constant, 0.414...; calculate it just once
var slope = Math.tan(Math.PI/8);

// do this for each x,y point
var s1 = x * slope + y > 0 ? 0 : 1;
var s2 = y * slope + x > 0 ? 0 : 1;
var s3 = y * slope - x < 0 ? 0 : 1;
var s4 = x * slope - y > 0 ? 0 : 1;

var segment = 4 * s4 + 2 * (s2 ^ s4) + (s1 ^ s2 ^ s3 ^ s4);

这将值设置segment在 0 到 7 之间。这是一个包含 2000 个随机点的示例(答案末尾的完整源代码)。使用精灵速度的 x,y 值,您可以使用分段值来获取适当的精灵图像。

替代文字

多多!

那么这是如何工作的呢? 我们的表达式看起来确实有点神秘。

观察一:我们想将围绕该点的圆分成 8 个角度尺寸相等的段。360/8 = 每段 45 度。8 段中的 4 段以 x 轴和 y 轴的两侧之一为中心,分别以 45/2 = 22.5 度切割。

替代文字

观察二:平面上的直线方程a*x + b*y + c = 0,当化为不等式时,a*x + b*y + c > 0可用于检验点位于直线的哪一侧。我们所有的四条线都穿过原点(x=0,y=0),因此强制 c=0。此外,它们都与 x 或 y 轴成 22.5 度角。这让我们得到了四线方程:

y = x * tan(22.5); y = -x * tan(22.5); x = y * tan(22.5); x = -y * tan(22.5)

变成不等式我们得到:

x * tan(22.5) - y > 0; x * tan(22.5) + y > 0; y * tan(22.5) - x > 0; y * tan(22.5) + x > 0

测试给定点的不等式让我们知道它所在的每条线的每一侧: 替代文字 替代文字

替代文字 替代文字

观察三:我们可以结合测试结果得到我们想要的段数模式。这是一个视觉细分:

按顺序:4 * s42 * (s2 ^ s4)总和4 * s4 + 2 * (s2 ^ s4) 替代文字 替代文字 替代文字

(^ 符号是 Javascript XOR 运算符。)

这是s1 ^ s2 ^ s3 ^ s4,首先是单独的,然后添加到4 * s4 + 2 * (s2 ^ s4) 替代文字 替代文字

额外的功劳:我们可以调整计算以仅使用整数算术吗?是的——如果已知xy是整数,我们可以将不等式的两边乘以某个常数(并四舍五入),从而得到完全整数数学。(然而,这在 Javascript 上会丢失,它的数字总是双精度浮点数。):

var s1 = x * 414 + y * 1000 > 0 ? 0 : 1;
var s2 = y * 414 + x * 1000 > 0 ? 0 : 1;
var s3 = y * 414 - x * 1000 < 0 ? 0 : 1;
var s4 = x * 414 - y * 1000 > 0 ? 0 : 1;

上面示例的完整源代码:(只需将其放入新的 html 文件中,然后在任何浏览器中打开)

(参见 jsbin 上的现场演示)

<html>
    <head>
        <style type="text/css">
            .dot { position: absolute; font: 10px Arial }
            .d0 { color: #FF0000; }
            .d1 { color: #FFBF00; }
            .d2 { color: #7fcc00; }
            .d3 { color: #00FF7F; }
            .d4 { color: #00FFFF; }
            .d5 { color: #5555FF; }
            .d6 { color: #aF00FF; }
            .d7 { color: #FF00BF; }
        </style>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script type="text/javascript">
            $(function() {
                var $canvas = $("#canvas");
                var canvasSize = 300;
                var count = 2000;
                var slope = Math.tan(Math.PI/8);

                $canvas.css({ width: canvasSize, height: canvasSize });
                for (var i = 0; i < count; ++i) {

                    // generate a random point
                    var x = Math.random() - 0.5;
                    var y = Math.random() - 0.5;

                    // draw our point
                    var $point = $("<div class='dot'></div>")
                        .css({
                            left: Math.floor((x + 0.5) * canvasSize) - 3,
                            top:  Math.floor((y + 0.5) * canvasSize) - 6 })
                        .appendTo($canvas);

                    // figure out in what segment our point lies
                    var s1 = x * slope + y > 0 ? 0 : 1;
                    var s2 = y * slope + x > 0 ? 0 : 1;
                    var s3 = y * slope - x < 0 ? 0 : 1;
                    var s4 = x * slope - y > 0 ? 0 : 1;
                    var segment = 4 * s4 + 2 * (s2 ^ s4) + (s1 ^ s2 ^ s3 ^ s4);

                    // modify the point's html content and color
                    // (via its CSS class) to indicate its segment
                    $point
                        .text(segment)
                        .addClass("d" + segment);
                }
            });
        </script>
    </head>
    <body>
        <div id="canvas" style="position: absolute; border: 1px solid blue">
        </div>
    </body>
</html>
于 2010-09-08T08:31:04.413 回答
3

你的建议完全正确!请注意,Math.atan2 的结果是以弧度表示的,您可能更熟悉度数;您可以使用angle_degrees = angle*(180./pi).

(另请注意,您不需要按照 RCIX 的建议进行标准化,尽管您可以根据需要进行标准化。您所拥有的angle = Math.atan2(speed.y, speed.x);, 应该可以正常工作。)

于 2010-09-08T04:44:22.223 回答
0

你走在正确的轨道上。标准化您的速度向量(首先检查两个分量是否为 0),对其调用 atan2,然后将获得的弧度值转换为某种友好的方向枚举或可用于选择正确精灵的东西。

于 2010-09-08T02:21:55.627 回答