Calculate correct sprite direction image in bird's view game? (Math here might be speed vector to degrees angle?)
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 somethin开发者_开发知识库g.
PS: Zero speed can be ignored, these sprites will just use whatever was the last valid direction image.
Thanks so much for any help!
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.
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.
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:
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)
(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)
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>
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.)
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.
精彩评论