AAA game control in an indie’s evening
Yeah.. ok.. that title may be overstating my case a bit.
So, something that got fashionable in AAA games, specifically third person AAA games, is smart input optimisation. Load up the modern third person game of your choice, and run at a wall at 45 degrees. If it’s a decent game with reviews that say the controls are smooth, £10 says the character doesn’t blink, and starts running along the wall in the direction you were pointing at the analogue speed you specified. Feels good, right? A bit cheaty, but it turns out, you suck at movement input (we all do), and that little bit of code means your character doesn’t share your ineptitude.. he or she will keep moving smoothly until your brain resets and you start controlling the game properly again.
So. I wanted this in Volume, because it’s a game of tight corridors, and getting snagged on corners is never fun.. I don’t want my player to even be aware of the existence of their collision volume.. so steering them around makes a lot of sense (my cover system uses the trigger, so keeping the player away from walls is fine).
I listened to an awesome talk from a coder on the original Infamous, a game which popularised this approach. It’s called “Reading the player’s mind through his thumbs”.. you can buy it, as I did, as a $4 download from the gdc vault.
Honestly, their solutions don’t really apply to the simplicity of my little game, and sound like they needed an army to set up all the splines ;D.. but one sentiment was important.. you can either catch problems when they occur, or you can catch input, check if it’s optimal, and if it isn’t, clean it up before you even move the character, and that is much smoother.. so that’s what I did. And I did it pretty cheaply, and pretty fast.
Disclaimer: I am a shit coder, but fuck it, my games work. This can certainly be done better. I’m not posting code, just my solution.. but there’s nothing in here an intermediate Unity dev can’t repeat.
So.. it’s simple. Let me walk you through it.
Step 1: I do my input code, which outputs a rotation for the player, and a velocity I want to apply to them. I use custom stuff, rigidbody based, but it doesn’t matter really.. all that matters, I have myself a rotation, and a velocity that I want the player character to use at the end of the frame.
Step 2: I cast three rays from the player’s location. One from each extent of the player collision capsule, and one from the centre of the player collision capsule. They all cast out in the direction of movement.. I then store the distance they travel before they hit a wall collider.
This is where it gets surprisingly straightforward. By comparing the distance these rays travelled, I can tell which ‘side’ of the player character any obstruction less than their maximum avoidance distance is. So.. if I see that the left ray has an obstruction that’s nearer than an obstruction at the centre, I can tell that there’s something (possibly a diagonal) to the players left.. ditto if the center is closer than the right, and steering to the right should avoid. The centre point makes corners easier.. I’m not convinced I need it, but.. fuck it.. try it without.. I’m sure I did it for a reason. If all rays have the same or similar obstructions, I’m probably trying to run straight into a wall, in which case, the player character stops.
Easy. Once I know which way to go to avoid, I rotate the rotation the player character should be pointing 5 degrees away from obstruction.. I check to see if the new movement is similarly blocked, if it isn’t, I leave the new value as the input, if it is, I repeat the rotation, again and again, until a clear path is found (I do stop checking once I’ve rotated the character through 90 degrees, if I’ve done that, chances are I’m in a corner that it’d look weird to run out of.. so I just stop movement ala running headfirst at a wall).
The original values, or the updated rotation and the original velocity if we noticed an obstruction, are passed to the player rigidbody, and the character moves.
Here’s how it looks, hope that’s handy to ya (those three prongs are the raycasts from my input, the cyan line is the generated path):