在鸟瞰游戏中计算正确的精灵方向图像? (这里的数学可能是速度矢量到角度?)

发布于 2024-09-18 08:04:14 字数 423 浏览 5 评论 0 原文

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

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

四处搜索,我发现......

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

但不知何故我是还是缺少一些东西。

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

非常感谢您的帮助!

Background: I have 8 images for every sprite in my bird's view JavaScript game, representing top, top-right, right, right-bottom etc., depending on the player's space ship speed.

Question: Given the values sprite.speed.x and sprite.speed.y (which could be something like 4 and -2.5, or 2 and 0 for instance), how do I get the correct angle in degrees? Given that angle, I could then have a lookup for which degrees value represents which sprite image. Or perhaps there's an even easier way. (Currently I'm just using something like "if x below zero use left image" etc. which will result in diagonal images used almost all of the time.)

Searching around, I found ...

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

... but somehow I'm still missing something.

PS: Zero speed can be ignored, these sprites will just use whatever was the last valid direction image.

Thanks so much for any help!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

つ低調成傷 2024-09-25 08:04:15

好问题!我喜欢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 值,您可以使用分段值来拾取适当的精灵图像。

alt text

啊啊!

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

观察一:我们希望将点周围的圆分成 8 个角度尺寸相等的线段。 360/8 = 每段 45 度。 8 个片段中的 4 个以 x 轴和 y 轴两侧之一为中心,每个片段以 45/2 = 22.5 度进行切片。

alt text

观察二:平面上的直线方程,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

测试给定点的不等式可以让我们知道它位于每条线的每一侧:
替代文本
替代文本

替代文本” ><br />
<img src=

观察三:我们可以结合测试结果得到我们想要的段号模式。下面是一个直观的细分:

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

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

这里是 s1 ^ s2 ^ s3 ^ s4 ,首先单独使用,然后添加到 4 * s4 + 2 * (s2 ^ s4)
替代文本
alt text

额外功劳:我们可以调整计算以仅使用整数算术吗?是的——如果已知xy是整数,我们可以将不等式两边乘以某个常数(并四舍五入),从而得到完全整数的数学结果。 (但是,在 Javascript 上,这会丢失,因为 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>

Good question! I liked tom10's answer (on the mark, +1), but wondered if it can be done without much trigonometry. Here's a solution in short, followed by an explanation.

// 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);

This sets the value of segment between 0 and 7. Here's an example with 2000 random points (full source code at the end of the answer). Using the x,y values of the sprite's speed, you can use the segment value to pick up the appropriate sprite image.

alt text

Tadaa!

So how does this work? Our segment expression does look a bit cryptic.

Observation one: we want to split the circle around the point into 8 segments of equal angular dimension. 360/8 = 45 degrees per segment. Four of the 8 segments are centered on one of the two sides of the x and y axes, sliced at 45/2 = 22.5 degrees each.

alt text

Observation two: The equation of a line on a plane, a*x + b*y + c = 0, when turned into an inequality, a*x + b*y + c > 0 can be used to test on which side of the line a point is located. All our four lines cross the origin (x=0, y=0), and hence force c=0. Further, they are all at a 22.5 degrees angle from either the x or the y axis. This gets us the four line equations:

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

Turned into inequalities we get:

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

Testing the inequalities for a given point lets us know on each side of each line it lies:
alt text
alt text

alt text
alt text

Observation three: we can combine the test results to obtain the segment number pattern we want. Here's a visual breakdown:

In sequence: 4 * s4, 2 * (s2 ^ s4) and the sum 4 * s4 + 2 * (s2 ^ s4)
alt text
alt text
alt text

(The ^ symbol is the Javascript XOR operator.)

And here is s1 ^ s2 ^ s3 ^ s4, first on its own, and then added to 4 * s4 + 2 * (s2 ^ s4)
alt text
alt text

Extra credit: can we tweak the calculation to use only integer arithmetic? Yes -- if x and y are known to be integers, we could multiply both sides of the inequalities by some constant (and round off), resulting in completely integer math. (This would be lost, however, on Javascript, whose numbers are always double precision floating point.):

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;

Full source code for our sample above: (just drop it in a new html file, and open in any browser)

(see as a live demo on 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>
少钕鈤記 2024-09-25 08:04:15

你的建议完全正确!请注意,Math.atan2 的结果以弧度为单位,您可能更熟悉度数;您可以使用 angle_ Degrees = angle*(180./pi) 进行转换。

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

What you suggest is exactly right! Note that the result of Math.atan2 is in radians, and you're probably more familiar with degrees; you can convert using angle_degrees = angle*(180./pi).

(Note also that you don't need to normalize as RCIX suggested, though you can if you want to. What you have, angle = Math.atan2(speed.y, speed.x);, should work just fine.)

梦途 2024-09-25 08:04:15

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

You were on the right track. Normalize your speed vector (check for both components being 0 first) , call atan2 on it, and then convert the radians value you get to some sort of friendly direction enum or something that you can use to pick the right sprite.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文