Hey everyone, Evan Hemsley here!
We've been working hard on revamping the animation system. Since Failsafe is a game that is grounded in physical interaction with the environment, we have long felt that we would like our animations to be more reactive.
Most first-person games use completely different animations for first-person and third-person. Imagine that you are playing a first-person shooter, where the main character is constantly carrying a gun. A major issue is that the gun points out from your character, so if you walk up to a wall, the gun will appear to be going through, or "clipping", through the wall. Most first-person shooters solve this problem by drawing the first-person model and animations after the rest of the world has been drawn. We have chosen not to go this route because we feel that it creates a disconnect when interacting with the world. We also don't need our character to carry large weapons or anything like that. Of course, having the player's extremities clip through walls or the ground is also very jarring. How do we prevent this from happening?
Here is our solution:
Every time the position of a hand is going to be updated, we perform a spherecast* from the corresponding shoulder to the location where the hand is going to be placed by the animation. If the spherecast detects a collision with the environment, we take note of the location of the collision, as well as the normal of the surface. Now we can place the hand at the point of collision, instead of at its animated position. The same procedure applies for the feet as well, except we use the corresponding thigh instead of shoulder.
The problem of rotation remains: we wish to align the character's hand relative to the surface of collision. We can think of orientation in 3-dimensional space as being composed of two vectors* - a forward-facing vector and an upward-facing vector. Imagine that there are two vectors emerging from your right hand - one pointing our from your fingers (the forward-facing vector) and another pointing out from the back of your hand (the upward-facing vector). These vectors describe the orientation of your hand. If you take your hand and place it on a wall, you will notice that the upward-facing vector will be the same as the normal vector of the wall. Our forward-facing vector points up towards the ceiling. Now we can use these vectors to describe a rotation that positions the hand neatly against the wall.
There's a problem though - what if the surface against which we press our hand is slanted? What if we press our hand against the floor? Now our forward-facing vector should not be pointing upwards at all. To solve this issue, we take the vertical component of the surface normal vector, and use it to interpolate between the player's up vector and the player's forward vector, and then project that interpolated vector onto the collision surface. In simpler terms, the more horizontal the surface normal vector* is (i.e. with a vertical wall), the more vertical our forward-facing vector in the rotation needs to be. Now we have a rotation that properly aligns the player's extremities to any surface that they collide with.
Here's what the calculation looks like in Unity C# code
transform.position = environmentHit.point + (environmentHit.normal * (collisionCastRadius + 0.002f));
Vector3 forwardDirection = Vector3.ProjectOnPlane(Vector3.Lerp(player.transform.up, player.transform.forward, environmentHit.normal.y), environmentHit.normal);
transform.rotation = Quaternion.LookRotation(forwardDirection, environmentHit.normal) * Quaternion.Euler(collisionNormalRotationOffset);
The result is quite satisfactory.
*Spherecast: A procedure that projects a sphere of given radius along a ray and returns collision information if that sphere collides with an object.
*Vector: A mathematical structure used to represent data such as points or directions in n-dimensional space.
*Normal Vector: The vector perpendicular to a surface at a given point.