Keine Ergebnisse gefunden
Ask any non-programmer what it takes to write computer code and the tentative response might be something like, “You have to be good at math?”
As developers, of course we know this isn't true, but it's a common misconception that works in our favor. In a survey conducted for Change the Equation, 30 percent of Americans say they'd rather clean the bathroom than solve a math problem. As long as that's true, our reputation as lovable math geniuses is safe.
I talk to loads of developers just itching to try their hand at games, and the question of math comes up a lot. The bad news is that game development usually does involve math (at least more than some other kinds of programming). The good news is that game math isn't actually that hard (Mostly. Some of it is very, very hard). The rise of frameworks like Unity has had an overall calming effect on math anxiety among first-time game programmers, but the truth is that understanding the math at the heart of these frameworks makes for better games.
So where do we begin our quest for math literacy in pursuit of superior game creation? Let's bounce a ball! It's a good example because just moving something around the screen can be instructive, but it's also a problem of surprising depth. There are lots of interesting ratholes and tangents for us to explore as we tackle this simple exercise.
Let's start with the basics. We have a circle on the screen. We want it to move in straight lines at a constant speed. We want the screen edges to act as “walls,” reflecting the circle's motion.
To make all this happen, there are a few things we need to keep track of. The circle (our low-tech ball) has a position and a velocity. The screen has a width and height. We'll ignore complications like friction, elasticity, and ball diameter for now (but reserve the right to add them later, if we feel ambitious).
For simplicity, we treat the lower-left corner of the screen as the origin (0, 0) of our coordinate system, with x increasing to the right and y increasing as we move toward the top of the screen. This is the arrangement commonly used in mathematics—and has been adopted by many popular game development frameworks—so it feels natural.
Now we can represent the ball's position by the coordinate pair (x, y). Similarly, we represent the ball's velocity by a pair of deltas, vx and vy, that get added to x and y (respectively) every time we update the screen. Technically, the deltas vx and vy describe a vector, but we often write them as a point—such as (vx,vy)—because points and vectors are so intimately connected, and are frequently manipulated together.
Vectors are quantities with direction and magnitude. We draw them as arrows in order to convey both of these characteristics simultaneously, since an arrow (obviously) has a direction, and its length can indicate magnitude. We label them using bold or a little arrow hat, as in the diagram above.
Vectors have no inherent location, but they can be used to modify the location of objects that do, like our ball. We can add and subtract vectors to produce new vectors, and combine them with points to move those points around. They're also useful for figuring out angles and distances, and are critical to 96.57 percent of computer game graphics (approximately). Bare vectors are sometimes shown starting at the origin.
As you can see above, adding vectors together amounts to arranging them head-to-tail. Adding one to a point moves the point in the vector's direction by a distance equal to the vector's length. You can also multiply a vector by a scalar, which, as the name implies, is just a scaling factor. To multiply v by the scalar 6/5, multiply each component by the scalar value to yield (6/5 vx, 6/5 vy). Subtracting a vector is the same as flipping its direction with a scalar of -1, then adding.
Armed with nothing more than points and vectors, we can start to think about how our ball will actually bounce around a screen. Given an initial position (x, y) and velocity (vx, vy), we can always figure out where it will end up after the next update.
Of course, the new position may be off the screen, which is where the bounce comes in. If ynew is out of bounds, for example, we reposition the ball by a distance equal to the amount it overshot the boundary, but in the opposite direction. That is, if the new position would be n vertical pixels off the screen, we substract 2n from the new y component to move the ball back onscreen by the same amount. We also flip the sign of the vertical velocity so the ball will move in the opposite vertical direction from now on, away from the wall it just “hit.”
The next time the ball needs updating, we again add its current position and velocity—now (vx,−vy), since the last bounce changed its vertical direction of motion. This time perhaps xnew will be out of bounds, passing m pixels beyond the edge of the screen. We compensate in the same way we did for the vertical component: we adjust the horizontal position by −2m and flip the sign of the horizontal velocity.
Because all of the boundaries in this simplified scenario are perfectly horizontal or perfectly vertical, these straightforward bounce calculations are pretty easy to implement in code. For example:
// Basic bouncing ball
public class Ball {
private int x, y;
private int vx, vy;
public Ball( int x, int y, int vx, int vy ) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
// Called every frame with the current screen width and height.
public void move( int width, int height ) {
// Add the velocity to position.
x += vx;
y += vy;
// Bounce off vertical walls, if necessary.
if( 0 > x || x >= width ) {
// Flip the horizontal velocity.
vx = -vx;
// If x is negative (the ball is off the screen to the left),
// then simply flipping its sign is enough to move it to where
// we want it to be. Otherwise (the ball is off the screen to
// the right), we need to take the excess x - width and subtract
// it from the screen width, yielding x = width - (x - width) =
// width - x + width = 2*width - x. Either way, we negate x. In
// the first case, we’re done; in the second, we just need to
// add 2*width.
x = -x;
if( 0 > x ) // if now negative, must be 2nd case
x += width << 1;
}
// Bounce off horizontal walls, if necessary.
if( 0 > y || y >= height ) {
// Follow the same logic as above.
vy = -vy;
y = -y;
if( 0 > y )
y += height << 1;
}
}
}
Note that in most cases, we probably won't see the actual bounce itself. This is because the ball will appear to have jumped from its starting position to its final “bounced” position between frames. (We'll see it actually touch the wall only if adding its velocity and position places it in direct contact with the boundary.) It turns out this isn't a problem, though, because our brain can “fill in” the missing action. It combines the old and new locations, plus what it knows of the ball's previous speed and trajectory, to infer that a bounce happened—even though we didn't actually witness it.
There's a wrinkle here that we haven't addressed, though. What happens when the ball's velocity is so great that it hits multiple walls during a single update? This isn't as pathological as it sounds—just picture a fast ball headed into a corner, or one constrained by some on-screen container.
We have to adjust our code a little bit to account for all bounces that may apply; we have to process the ball's position until the result is a valid screen location (see highlighted loop, below). Again, the bounces themselves won't necessarily be visible, since the ball simply moves from its starting position to its ending position.
public void move( int width, int height ) {
// Add the velocity to position.
x += vx;
y += vy;
while( 0 > x || x >= width || y > 0 || y >= height ) {
// Same vertical bounce code as before.
if( 0 > x || x >= width ) {
vx = -vx;
x = -x;
if( 0 > x )
x += width << 1;
}
// Similarly for the horizontal bounce code.
if( 0 > y || y >= height ) {
vy = -vy;
y = -y;
if( 0 > y )
y += height << 1;
}
}
}
Now that we have a basic understanding of what a vector is (direction and magnitude), how we represent one (as a point whose x and y coordinates each measure a delta along the corresponding axis), and why they're useful in games (they can model velocity and movement), we can explore common ways to manipulate them to achieve interesting effects. We've already seen how we can use vectors to reflect motion—at least, as long as the reflective surface is straight up and down or perfectly level—but what if we want to bounce our ball on a surface that's slanted? What if we don't necessarily want to move in a straight line? What if we want our velocity to change over time?
It turns out that vectors can help in all of these situations, which is good, because they are all common occurrences in a typical game. Over the course of the next few installments in this series, we'll cover these and many more useful applications of our new best friend, the vector. That's just the beginning, though.
Join us for future posts on other mathematical tidbits indispensable to game developers, like interpolation, bit manipulation, randomization, path finding, 3D, and much more.
Stay tuned!
-peter (@peterdotgames)