Physics, and in particular player physics, can make or break a game; the "feel" of the game is obviously directly tied to it. So I guess it's not unreasonable for one to spend weeks trying to get it to work "just right". Still... ugh.
There are two big considerations to make with physics: collision detection and collision response. Both gave me a lot of headaches, but here I'll just talk about the response. When dealing with player physics, the most common type of collision response is the sliding response. Boot up your favorite FPS and head straight for the nearest wall. You'll notice that as you hit the wall, you start to move along it. The more of an angle you approach the wall at, the faster you slide. To visualize this, imagine this picture is a top-down view of some game (velocities are not to scale).
You can see that the player approaches the wall on a diagonal, and once the wall is hit, the attempted velocity is adjusted to point right.
The awesome thing about it is that it's a very one-size-fits-almost-all response method. You can collide at any wall at any angle at any velocity and it will properly slide you without any special case code. It also works for hitting corners and ramps - again, without any special case code.
To explain this from a technical point of view, the slide is achieved by projecting the player's velocity from its original direction into one perpendicular with the wall. This is done using a collision normal, which your collision system should return as part of the collision information. A normal is a vector that essentially describes an orientation. In this case, a collision normal will describe which way the wall is facing. In this example, the wall is facing down (from our top-down view). Thus, the normal also faces down. To project the velocity, we do a dot product between the attempted velocity and the collision normal. This will tell us how far into the wall we would have penetrated. You then simply "push" the velocity out of the wall, along the collision normal, by this amount. You'll notice that if you take the attempted velocity in the above picture and push it out of the wall, the resulting direction is that of "Adjusted Velocity". The code would look like this:
// Project the velocity onto the wall's plane
distanceToPlane = D3DXVec3Dot(&velocity, &collisionNormal);
velocity -= (distanceToPlane * collisionNormal);
It's really that simple. If you work with the math you'll notice that as the angle you hit the wall changes (and thus, the direction of your velocity), the amount of velocity lost as a result of the collision also changes. If you hit a wall straight on, there'll be practically nothing left. If you're constantly applying gravity, this response type has the added bonus of making sure the player can't climb up slopes that are too steep. They'll go as far as their velocity takes them, then slide back down to the bottom.
You may have noticed that I said "one-size-fits-almost-all". That is because there are some unwanted side effects of this response type, almost all related to the handling of ground. As I said, applying gravity will make the player slide down steep slopes. It will also, however, make the player slide down shallow ones too, ones that they should normally be able to stand on. Unless your players are on rollerskates or something, this is probably not what you want. A second side effect is that you will go up shallow slopes slower than if you were on flat ground. This may be desirable, but in my particular case, it was not.
A third problem isn't particularly related to slide response, but is still critical to note. If you were standing on a ground slope and started moving down it, you probably want the player to smoothly glide down the slope. Instead, what happens is that you move forward into the air, and no collisions are detected until gravity catches up and makes you hit the slope. The next frame, you're back in the air again, as the force of gravity was reset upon collision. The result is that the player more bounces down shallow slopes than glides down them. Of course, this will feel awkward to the player, but it will also cause your player to be considered off the ground, and render him unable to jump.
Solving these problems is more complicated, and requires special-case code. Thus, the result is that you have one type of collision response for the ground, and slide response for everything else. The former is what was giving me the headaches. In a future post, I'll talk about some of the ways you can detect and handle ground collisions.

No comments:
Post a Comment