Wednesday, May 2, 2012

Jump start jumps

I once heard that elegant code is in some respect contradictory to useful software, because it's often the sum of all exceptions that make a program useful, rather than it being strictly logical. Do you remember the frustration back when the javascript of slow-loading web pages was allowed to switch input focus to a login input box while you were typing a URL? The software was just following it's strict logic, switching input when it was told, while the better way of handling it would be a not-so-elegant check if the user have manually focused something else in between the start loading and finished loading, or a timer checking if the input focus change is still relevant.

I wouldn't say that this contradiction applies to all aspects of software development, but certainly to most parts that interface with people. In a game, the most common part of the code that interface with people, apart from the GUI, is caracter controllers.

One would think that modeling a character with physics would automatically yield natural and intuitive behavior. In reality it's almost the complete opposite - the more real physics you use to model your character, the harder they are to control. The most common way to write a character controller is probably to not use physics at all, but to just use one or several raycasts or shapecasts. But what if you want the character to interact physically with the environment?

I recently spent some time writing and tweaking the character controller of our next game. Without revealing too much, I can say it's not a sequel to Sprinkle and that it includes a character which can interact with the environment. I decided early on that a pure raycast/shapecast based method wouldn't suffice, because then the environment wouldn't respond. One can cheat to some extent by applying downward forces etc, but there will always be situations where it doesn't fully work.

Instead of representing the character with detailed geometry I use two circles stacked on top of each other, slightly overlapping. It's nice to have a round shape for the character, since you want it to slide easily over obstacles. These circles only represent the torso, so in addition I also do a couple of downward raycasts for the legs. The raycasts control a special joint that keeps the character floating above ground. You don't want to rely on regular collision detection and friction for character motion, mostly because you need more control than that, so the spheres are really only for contacts on the sides or above the character.

Since the character can stand on any object, the special joint is reinserted every frame, attaching to whatever the character happens to be standing on. The joint itself constrains the motion of the character relative the other object using the regular constraint solver. It has one target relative velocities and one maximum force to reach that target for each degree of freedom (x, y and rotation in 2D). To me, this is a very intuitive interface to a character controller, instead of using forces. For example, if we want the character to walk left we set the X target relative velocity to -2 m/s and the maximum force to however strong we want the character to be. If we were using explicit forces we would need to adapt the force to the resistance (which of course at the time we don't know). The Y component gets a relative velocity based on the current distance to the ground. If we're below our rest-distance then it should be slightly positive, otherwise slightly negative. The maximum force is however strong the legs should be.

These very useful, one-dimensional velocity constraints are something I have always missed from off-the-shelf physics engines. All physics engines use them internally, but for some reason they are never exposed through the public API (well, Meqon was the exception here of course :)

For jumping, I also use the character joint, but with a higher positive Y relative velocity. If the character is standing on a dynamic object it will automatically get a downward push through the constraint solver. While play testing I noticed that some players consistently press the jump button a few frames too late, falling off a cliff instead of jumping to the next. I'm not sure if this is due to input lag or something else, but I added a few frames of grace period, allowing mid-air jumping if ground contact was recently lost. I'm also recording the ground normal continuously in a small ring buffer, so when a jump occurs I can go back and look for the best (most upward) jump direction. I also implemented an early abort mode, so that if the player releases the jump button quickly the jump terminates, and the character returns to the ground more quickly. All these special cases are supposed to feel intuitive to the extent where they are not even recognized as features. They just make the game easier to play, but has no logical explanation and certainly do not yield more elegant code.