I’m not an expert, but I’ll mention some of the things I’ve learned.
When I make my controllers, I use rigidbodies primarily and use the input from the keyboard, to create a direction vector which is then applied directly to move the character. One of the nice things about this, is that it makes it very easy to set a speed. This may be extremely common, I don’t really know.
One of the biggest things that gets on my nerves, is when I see non-normalized inputs. Here’s what I mean:
Vector2 dirVector = new Vector2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical"));
Vector2 dirVector = new Vector2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical")).normalized;
If input is not normalized, the character will move faster when moving diagonally. You can solve for the direction vector by adding the magnitudes of the horizontal and vertical vectors and applying the pythagorean theorem. For example, if you are moving diagonally without a normalized input, the magnitude of the direction vector will equal this:
magnitude = sqrt (1^2 + 1^2),
sqrt (2) = 1.41421356237...
Whenever you are applying input, you must always ensure that the input magnitude is equal to 1. Also, as far as smoothing player input, use pre-filtered input modes, in other words using GetAxis over GetAxisRaw. GetAxisRaw returns either -1, 0, or 1, while GetAxis is pre-filtered and will give a smooth transition.
By storing the input as a vector, you can use the input directly as your direction vector for movement. I find this is cleaner, and more direct than checking input directions using conditional statements.
Moving the Player
This is very dependent on the style of controller that you are making, but in my opinion (for rigidbody controllers) Rigidbody.MovePosition is the best method for moving the character. When it comes to using vectors to move characters, like I mentioned above, I personally find using this method the most robust. I’ve created controllers that uses Rigidbody.AddForce, but I always find that they tend to get messy and require more code than a controller using Rigidbody.MovePosition. In addition, I find that they tend to be less controllable and less responsive.
Using the above section for reference, you can setup very basic player movement in 2 lines of code:
Vector3 dirVector = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical")).normalized;
GetComponent <Rigidbody> ().MovePosition (transform.position + dirVector * Time.deltaTime);
One of the things I have seen many times when applying gravity to a rigidbody player, is this:
Rigidbody.AddForce (-transform.up * gravity);
This is physically flawed because this is not how gravity works. Here’s what isn’t included in the above example: FORCE MODE
By default, AddForce applies a force using ForceMode.Force. This just applies a certain number of newtons to the player. Gravity, however, doesn’t work quite in the same way. To think about it, gravity applies a force per kilogram. For example, take the equation F = ma, you know that really famous one from physics class, the force on the object is equal to gravitational acceleration, MULTIPLIED by the mass of the object. So, to properly apply gravity to a character controller when using rigidbodies, ForceMode.Acceleration needs to be included to properly add force to the player:
Rigidbody.AddForce (-transform.up * gravity, ForceMode.Acceleration);
The full list of ForceModes can be found here.
A recent thing I’ve come across in my own code is jumping. I’ve always had trouble getting the player to jump a specific height every time. In addition, if I wanted to change the player mass, it has always been slightly inconvenient to change the jump force. So instead, I looked back into ForceModes. One of the modes is ForceMode.VelocityChange. This will: “Add an instant velocity change to the rigidbody, ignoring its mass.” Using physics energy equations, potential energy and kinetic energy, it is very easy to solve for the exact velocity needed to reach a certain jump height:
mgh = 1/2mv^2,
2gh = v^2,
v = sqrt (2gh).
So with relative ease, I can set the exact velocity needed to reach my jump height:
Rigidbody.AddForce (transform.up * sqrt (2gh), ForceMode.VelocityChange);
Grounding the Player
One of the things that took me a while to learn is, the importance of skin width when detecting when the player is grounded with a raycast. All this means is starting the origin of your raycast (or linecast) slightly inside the collider bounds of your player. This vastly improves the consistency of determining if your character is grounded (especially on slopes).
These are the 5 biggest things I’ve learned when creating controllers. Everybody has their own opinions, but I feel that these methods produce the most consistent results, while also being very customization and friendly to nearly any situation.