Hover ship controller with raycast from Unity EDU

//PlayerInput.cs:
//This script handles reading inputs from the player and passing it on to the vehicle. We 
//separate the input code from the behaviour code so that we can easily swap controls 
//schemes or even implement and AI "controller". Works together with the VehicleMovement script

using UnityEngine;

public class PlayerInput : MonoBehaviour
{
	public string verticalAxisName = "Vertical";        //The name of the thruster axis
	public string horizontalAxisName = "Horizontal";    //The name of the rudder axis
	public string brakingKey = "Brake";                 //The name of the brake button

	//We hide these in the inspector because we want 
	//them public but we don't want people trying to change them
	[HideInInspector] public float thruster;			//The current thruster value
	[HideInInspector] public float rudder;				//The current rudder value
	[HideInInspector] public bool isBraking;			//The current brake value

	void Update()
	{
		//If the player presses the Escape key and this is a build (not the editor), exit the game
		if (Input.GetButtonDown("Cancel") && !Application.isEditor)
			Application.Quit();

		//If a GameManager exists and the game is not active...
		if (GameManager.instance != null && !GameManager.instance.IsActiveGame())
		{
			//...set all inputs to neutral values and exit this method
			thruster = rudder = 0f;
			isBraking = false;
			return;
		}

		//Get the values of the thruster, rudder, and brake from the input class
		thruster = Input.GetAxis(verticalAxisName);
		rudder = Input.GetAxis(horizontalAxisName);
		isBraking = Input.GetButton(brakingKey);
	}
}

//VehicleMovement.cs:
//This script handles all of the physics behaviors for the player's ship. The primary functions
//are handling the hovering and thrust calculations. 

using UnityEngine;

public class VehicleMovement : MonoBehaviour
{
	public float speed;						//The current forward speed of the ship

	[Header("Drive Settings")]
	public float driveForce = 17f;			//The force that the engine generates
	public float slowingVelFactor = .99f;   //The percentage of velocity the ship maintains when not thrusting (e.g., a value of .99 means the ship loses 1% velocity when not thrusting)
	public float brakingVelFactor = .95f;   //The percentage of velocty the ship maintains when braking
	public float angleOfRoll = 30f;			//The angle that the ship "banks" into a turn

	[Header("Hover Settings")]
	public float hoverHeight = 1.5f;        //The height the ship maintains when hovering
	public float maxGroundDist = 5f;        //The distance the ship can be above the ground before it is "falling"
	public float hoverForce = 300f;			//The force of the ship's hovering
	public LayerMask whatIsGround;			//A layer mask to determine what layer the ground is on
	public PIDController hoverPID;			//A PID controller to smooth the ship's hovering

	[Header("Physics Settings")]
	public Transform shipBody;				//A reference to the ship's body, this is for cosmetics
	public float terminalVelocity = 100f;   //The max speed the ship can go
	public float hoverGravity = 20f;        //The gravity applied to the ship while it is on the ground
	public float fallGravity = 80f;			//The gravity applied to the ship while it is falling

	[Header("Music Manager")]
	public GameObject musicManager;			//The music manager game object to trigger the collision music
	public AudioSource staticSnd;			//A reference to the static sound Game Object for wall collision sparks

	Rigidbody rigidBody;					//A reference to the ship's rigidbody
	PlayerInput input;						//A reference to the player's input					
	float drag;								//The air resistance the ship recieves in the forward direction
	bool isOnGround;						//A flag determining if the ship is currently on the ground


	void Start()
	{
		//Get references to the Rigidbody and PlayerInput components
		rigidBody = GetComponent<Rigidbody>();
		input = GetComponent<PlayerInput>();

		//Calculate the ship's drag value
		drag = driveForce / terminalVelocity;
	}

	void FixedUpdate()
	{
		//Calculate the current speed by using the dot product. This tells us
		//how much of the ship's velocity is in the "forward" direction 
		speed = Vector3.Dot(rigidBody.velocity, transform.forward);

		//Calculate the forces to be applied to the ship
		CalculatHover();
		CalculatePropulsion();
	}

	void CalculatHover()
	{
		//This variable will hold the "normal" of the ground. Think of it as a line
		//the points "up" from the surface of the ground
		Vector3 groundNormal;

		//Calculate a ray that points straight down from the ship
		Ray ray = new Ray(transform.position, -transform.up);

		//Declare a variable that will hold the result of a raycast
		RaycastHit hitInfo;

		//Determine if the ship is on the ground by Raycasting down and seeing if it hits 
		//any collider on the whatIsGround layer
		isOnGround = Physics.Raycast(ray, out hitInfo, maxGroundDist, whatIsGround);

		//If the ship is on the ground...
		if (isOnGround)
		{
			//...determine how high off the ground it is...
			float height = hitInfo.distance;
			//...save the normal of the ground...
			groundNormal = hitInfo.normal.normalized;
			//...use the PID controller to determine the amount of hover force needed...
			float forcePercent = hoverPID.Seek(hoverHeight, height);
			
			//...calulcate the total amount of hover force based on normal (or "up") of the ground...
			Vector3 force = groundNormal * hoverForce * forcePercent;
			//...calculate the force and direction of gravity to adhere the ship to the 
			//track (which is not always straight down in the world)...
			Vector3 gravity = -groundNormal * hoverGravity * height;

			//...and finally apply the hover and gravity forces
			rigidBody.AddForce(force, ForceMode.Acceleration);
			rigidBody.AddForce(gravity, ForceMode.Acceleration);
		}
		//...Otherwise...
		else
		{
			//...use Up to represent the "ground normal". This will cause our ship to
			//self-right itself in a case where it flips over
			groundNormal = Vector3.up;

			//Calculate and apply the stronger falling gravity straight down on the ship
			Vector3 gravity = -groundNormal * fallGravity;
			rigidBody.AddForce(gravity, ForceMode.Acceleration);
		}

		//Calculate the amount of pitch and roll the ship needs to match its orientation
		//with that of the ground. This is done by creating a projection and then calculating
		//the rotation needed to face that projection
		Vector3 projection = Vector3.ProjectOnPlane(transform.forward, groundNormal);
		Quaternion rotation = Quaternion.LookRotation(projection, groundNormal);

		//Move the ship over time to match the desired rotation to match the ground. This is 
		//done smoothly (using Lerp) to make it feel more realistic
		rigidBody.MoveRotation(Quaternion.Lerp(rigidBody.rotation, rotation, Time.deltaTime * 10f));

		//Calculate the angle we want the ship's body to bank into a turn based on the current rudder.
		//It is worth noting that these next few steps are completetly optional and are cosmetic.
		//It just feels so darn cool
		float angle = angleOfRoll * -input.rudder;

		//Calculate the rotation needed for this new angle
		Quaternion bodyRotation = transform.rotation * Quaternion.Euler(0f, 0f, angle);
		//Finally, apply this angle to the ship's body
		shipBody.rotation = Quaternion.Lerp(shipBody.rotation, bodyRotation, Time.deltaTime * 10f);
	}

	void CalculatePropulsion()
	{
		//Calculate the yaw torque based on the rudder and current angular velocity
		float rotationTorque = input.rudder - rigidBody.angularVelocity.y;
		//Apply the torque to the ship's Y axis
		rigidBody.AddRelativeTorque(0f, rotationTorque, 0f, ForceMode.VelocityChange);

		//Calculate the current sideways speed by using the dot product. This tells us
		//how much of the ship's velocity is in the "right" or "left" direction
		float sidewaysSpeed = Vector3.Dot(rigidBody.velocity, transform.right);

		//Calculate the desired amount of friction to apply to the side of the vehicle. This
		//is what keeps the ship from drifting into the walls during turns. If you want to add
		//drifting to the game, divide Time.fixedDeltaTime by some amount
		Vector3 sideFriction = -transform.right * (sidewaysSpeed / Time.fixedDeltaTime); 

		//Finally, apply the sideways friction
		rigidBody.AddForce(sideFriction, ForceMode.Acceleration);

		//If not propelling the ship, slow the ships velocity
		if (input.thruster <= 0f)
			rigidBody.velocity *= slowingVelFactor;

		//Braking or driving requires being on the ground, so if the ship
		//isn't on the ground, exit this method
		if (!isOnGround)
			return;

		//If the ship is braking, apply the braking velocty reduction
		if (input.isBraking)
			rigidBody.velocity *= brakingVelFactor;

		//Calculate and apply the amount of propulsion force by multiplying the drive force
		//by the amount of applied thruster and subtracting the drag amount
		float propulsion = driveForce * input.thruster - drag * Mathf.Clamp(speed, 0f, terminalVelocity);
		rigidBody.AddForce(transform.forward * propulsion, ForceMode.Acceleration);
	}

	void OnCollisionEnter(Collision collision)
	{
		//As the ship enters a collision with an object on the Wall layer...
		if (collision.gameObject.layer == LayerMask.NameToLayer ("Wall")) 
		{
			//call the function in the music mixer script to play the collsion music sound
			musicManager.GetComponent<MusicMixer> ().PlayWallCollisionMusic ();
		}
	}

	void OnCollisionStay(Collision collision)
	{
		//If the ship has collided with an object on the Wall layer...
		if (collision.gameObject.layer == LayerMask.NameToLayer("Wall"))
		{
			//...calculate how much upward impulse is generated and then push the vehicle down by that amount 
			//to keep it stuck on the track (instead up popping up over the wall)
			Vector3 upwardForceFromCollision = Vector3.Dot(collision.impulse, transform.up) * transform.up;
			rigidBody.AddForce(-upwardForceFromCollision, ForceMode.Impulse);

			//Check if static sound is not playing during the collision...
			if (!staticSnd.isPlaying) 
			{
				//...play the collision static sound while we are colliding
				staticSnd.Play ();
			}
		}
	}

	// As we exit from the collision with the wall
	void OnCollisionExit(Collision collision)
	{
		if (collision.gameObject.layer == LayerMask.NameToLayer ("Wall")) 
		{
			//...check if the static sound is playing still...
			if (staticSnd.isPlaying) 
			{
				//...if it is playing then stop playing the static sound
				staticSnd.Stop ();
			}
		}
	}

	public float GetSpeedPercentage()
	{
		//Returns the total percentage of speed the ship is traveling
		return rigidBody.velocity.magnitude / terminalVelocity;
	}
}