Problems with custom controller

I’m having a few issues with my character controller.
While I do have movement and jumping working, as well as aiming the weapon, the problems are with character states.

So what’s going on is I added a force in Update that just adds gravity so the character will fall down when they walk off the edge of a platform. I also have the CharacterState set up with Idle, Walk, Jump, and Fall. The problem with this is, with the gravity being added in Update, if I jump and hold down the jump key long enough then my character will be stuck in Fall, unable to do anything but look up and down. Another thing that happens is if I let go of Jump at the right moment, I will hit the ceiling in Fall and switch to Idle, and I’ll slowly float down.

Additionally, I’m using velocity to move the character and it isn’t really working as I’d want. First off, even with gravity being added at the beginning, I can’t walk off of platforms and I instead just fly over the gaps. Also, since I’m moving the player along forward, I don’t know how to rotate the character so forward is in the opposite direction when Left is being pressed.

Here is my code so far:

using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour {

	#region Animations
	public AnimationClip[] IdleAnimations;
	public AnimationClip[] WalkAnimations;
	public AnimationClip[] JumpAnimations;
	public AnimationClip[] FallAnimations;
	#endregion

	#region Variables
	public float WalkSpeed;
	public float Gravity;
	public float JumpForce;
	bool IsGrounded;
	bool HasJumped;
	bool HasHitCeiling;
	Vector3 Forward;
	CharacterController controller;
	//Object LevelMesh;
	//Object Player;
	RaycastHit hit;
	#endregion

	#region Enums
	enum PlayerDirection
	{
		Forward = 0,
		Up = 1,
		Down = 2
	}
	enum PlayerState
	{
		Idle = 0,
		Walking = 1,
		Jumping = 2,
		Falling = 3,
		Dead = 4
	}
	PlayerDirection playerDirection;
	PlayerState playerState;
	#endregion

	// Use this for initialization
	void Start () {
		Forward = transform.forward;
		playerState = PlayerState.Idle;
		IsGrounded = true;
		//LevelMesh = GameObject.FindGameObjectWithTag ("Level");
		//Player = gameObject;
	}
	
	// Update is called once per frame
	void Update () {
		rigidbody.AddForce (Vector3.down * Gravity);
		ChangeAnimation ();
		AimGun ();
		print ("State: " + playerState + "; Direction: " + playerDirection);
		//MOVE THE PLAYER IN FORWARD AT WALKSPEED

		float h = Input.GetAxis ("Horizontal");
		if (h != 0)
		{
			if (IsGrounded)
			{
				playerState = PlayerState.Walking;
			}
			rigidbody.velocity = Forward * WalkSpeed;
			//rigidbody.AddForce(Forward * WalkSpeed);
		}
		else if (h == 0)
		{
			if (IsGrounded)
			{
				playerState = PlayerState.Idle;
				rigidbody.AddForce (new Vector3(0,0,0));
			}
		}

		//JUMP
		if (Input.GetButtonDown ("Jump") && playerState != PlayerState.Jumping && playerState != PlayerState.Falling)
		{
			rigidbody.AddForce (new Vector3(0, JumpForce, 0));
			//print ("You are jumping.");
			playerState = PlayerState.Jumping;
			IsGrounded = false;
		}
		if (Input.GetButtonUp ("Jump") && playerState == PlayerState.Jumping)
		{
			playerState = PlayerState.Falling;
		}
		switch (playerState)
		{
		case PlayerState.Falling:
			//rigidbody.AddForce (new Vector3(0,-Gravity,0));
			IsGrounded = false;
			break;
		}
		//FALL WHEN CEILING IS HIT
		//ALSO BE ABLE TO LOOK UP WHILE GROUNDED
		//AND LOOK UP & DOWN WHILE IN AIR
	}
	void OnCollisionEnter (Collision col)
	{
		if (col.gameObject.tag == "LevelMesh")
		{
			if (playerState == PlayerState.Falling)
			{
				IsGrounded = true;
				playerState = PlayerState.Idle;
			}
			if (playerState == PlayerState.Jumping && !IsGrounded || playerState == PlayerState.Falling && !IsGrounded)
			{
				playerState = PlayerState.Falling;
			}
		}
	}
	void ChangeAnimation()
	{
		switch (playerState)
		{
			case PlayerState.Idle:
				if (playerDirection == PlayerDirection.Forward)
				{
					animation.Play (IdleAnimations[0].name);
				}
				if (playerDirection == PlayerDirection.Up)
				{
					animation.Play (IdleAnimations[1].name);
				}
			break;
		case PlayerState.Walking:
			if (playerDirection == PlayerDirection.Forward)
			{
				animation.Play (WalkAnimations[0].name);
			}
			if (playerDirection == PlayerDirection.Up)
			{
				animation.Play (WalkAnimations[1].name);
			}
			break;
		case PlayerState.Jumping:
			if (playerDirection == PlayerDirection.Forward)
			{
				animation.Play (JumpAnimations[0].name);
			}
			if (playerDirection == PlayerDirection.Up)
			{
				animation.Play (JumpAnimations[1].name);
			}
			if (playerDirection == PlayerDirection.Down)
			{
				animation.Play (JumpAnimations[2].name);
			}
			break;
		case PlayerState.Falling:
			if (playerDirection == PlayerDirection.Forward)
			{
				animation.Play (FallAnimations[0].name);
			}
			if (playerDirection == PlayerDirection.Up)
			{
				animation.Play (FallAnimations[1].name);
			}
			if (playerDirection == PlayerDirection.Down)
			{
				animation.Play (FallAnimations[2].name);
			}
			break;
		}
	}
	void AimGun()
	{
		if (Input.GetButton ("AimUp"))
		{
			playerDirection = PlayerDirection.Up;
		}
		if (Input.GetButton ("AimDown") && playerState == PlayerState.Jumping || Input.GetButton ("AimDown") && playerState == PlayerState.Falling)
		{
			playerDirection = PlayerDirection.Down;
		}
		if (!Input.GetButton ("AimUp") && !Input.GetButton ("AimDown"))
		{
			playerDirection = PlayerDirection.Forward;
		}
	}
}

First you would like to call AddForce in FixedUpdate() and handling Input in Update(). Because FixedUpdate() uses the fixedtimestep used for physic behaivour.

Second I would cast a Raycast from the player towards the ground to check if there is ground beneth us. This would kill the problem with touching the ceiling and go into idle/walk state. Also Lerp your player position to the impact to prevent that the player flys away and sticks to the ground:

    if( Physics.Raycast( myTransform.position, -Vector3.up, 0.5, hit ) ) {
     isGrounded = true;
     myTransform.position = Vector3.Lerp( myTransform.position, hit.point, Time.deltaTime * frictionAmount );
    } else {
     isGrounded = false;
    }

Third, if you want that the player rotates into the direction you want to move like in an action adventure (Uncharted etc.) there are already solutions: moving player relative to camera. - Unity Answers