680 lines
21 KiB
C#
680 lines
21 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using Cinemachine;
|
|
using System;
|
|
|
|
namespace HeneGames.Airplane
|
|
{
|
|
[RequireComponent(typeof(Rigidbody))]
|
|
public class SimpleAirPlaneController : MonoBehaviour
|
|
{
|
|
public enum AirplaneState
|
|
{
|
|
Flying,
|
|
Landing,
|
|
Takeoff,
|
|
}
|
|
|
|
public Action crashAction;
|
|
|
|
#region Private variables
|
|
|
|
private List<SimpleAirPlaneCollider> airPlaneColliders = new List<SimpleAirPlaneCollider>();
|
|
|
|
private float maxSpeed = 0.6f;
|
|
private float speedMultiplier;
|
|
private float currentYawSpeed;
|
|
private float currentPitchSpeed;
|
|
private float currentRollSpeed;
|
|
private float currentSpeed;
|
|
private float currentEngineLightIntensity;
|
|
private float currentEngineSoundPitch;
|
|
private float lastEngineSpeed;
|
|
|
|
private bool planeIsDead;
|
|
|
|
private Rigidbody rb;
|
|
private Runway currentRunway;
|
|
|
|
//Input variables
|
|
private float inputH;
|
|
private float inputV;
|
|
private bool inputTurbo;
|
|
private bool inputYawLeft;
|
|
private bool inputYawRight;
|
|
|
|
#endregion
|
|
|
|
public AirplaneState airplaneState;
|
|
|
|
[Header("Wing trail effects")]
|
|
[Range(0.01f, 1f)]
|
|
[SerializeField] private float trailThickness = 0.045f;
|
|
[SerializeField] private TrailRenderer[] wingTrailEffects;
|
|
|
|
[Header("Rotating speeds")]
|
|
[Range(5f, 500f)]
|
|
[SerializeField] private float yawSpeed = 50f;
|
|
|
|
[Range(5f, 500f)]
|
|
[SerializeField] private float pitchSpeed = 100f;
|
|
|
|
[Range(5f, 500f)]
|
|
[SerializeField] private float rollSpeed = 200f;
|
|
|
|
[Header("Rotating speeds multiplers when turbo is used")]
|
|
[Range(0.1f, 5f)]
|
|
[SerializeField] private float yawTurboMultiplier = 0.3f;
|
|
|
|
[Range(0.1f, 5f)]
|
|
[SerializeField] private float pitchTurboMultiplier = 0.5f;
|
|
|
|
[Range(0.1f, 5f)]
|
|
[SerializeField] private float rollTurboMultiplier = 1f;
|
|
|
|
[Header("Moving speed")]
|
|
[Range(5f, 100f)]
|
|
[SerializeField] private float defaultSpeed = 10f;
|
|
|
|
[Range(10f, 200f)]
|
|
[SerializeField] private float turboSpeed = 20f;
|
|
|
|
[Range(0.1f, 50f)]
|
|
[SerializeField] private float accelerating = 10f;
|
|
|
|
[Range(0.1f, 50f)]
|
|
[SerializeField] private float deaccelerating = 5f;
|
|
|
|
[Header("Turbo settings")]
|
|
[Range(0f, 100f)]
|
|
[SerializeField] private float turboHeatingSpeed;
|
|
|
|
[Range(0f, 100f)]
|
|
[SerializeField] private float turboCooldownSpeed;
|
|
|
|
[Header("Turbo heat values")]
|
|
[Tooltip("Real-time information about the turbo's current temperature (do not change in the editor)")]
|
|
[Range(0f, 100f)]
|
|
[SerializeField] private float turboHeat;
|
|
|
|
[Tooltip("You can set this to determine when the turbo should cease overheating and become operational again")]
|
|
[Range(0f, 100f)]
|
|
[SerializeField] private float turboOverheatOver;
|
|
|
|
[SerializeField] private bool turboOverheat;
|
|
|
|
[Header("Sideway force")]
|
|
[Range(0.1f, 15f)]
|
|
[SerializeField] private float sidewaysMovement = 15f;
|
|
|
|
[Range(0.001f, 0.05f)]
|
|
[SerializeField] private float sidewaysMovementXRot = 0.012f;
|
|
|
|
[Range(0.1f, 5f)]
|
|
[SerializeField] private float sidewaysMovementYRot = 1.5f;
|
|
|
|
[Range(-1, 1f)]
|
|
[SerializeField] private float sidewaysMovementYPos = 0.1f;
|
|
|
|
[Header("Engine sound settings")]
|
|
[SerializeField] private AudioSource engineSoundSource;
|
|
|
|
[SerializeField] private float maxEngineSound = 1f;
|
|
|
|
[SerializeField] private float defaultSoundPitch = 1f;
|
|
|
|
[SerializeField] private float turboSoundPitch = 1.5f;
|
|
|
|
[Header("Engine propellers settings")]
|
|
[Range(10f, 10000f)]
|
|
[SerializeField] private float propelSpeedMultiplier = 100f;
|
|
|
|
[SerializeField] private GameObject[] propellers;
|
|
|
|
[Header("Turbine light settings")]
|
|
[Range(0.1f, 20f)]
|
|
[SerializeField] private float turbineLightDefault = 1f;
|
|
|
|
[Range(0.1f, 20f)]
|
|
[SerializeField] private float turbineLightTurbo = 5f;
|
|
|
|
[SerializeField] private Light[] turbineLights;
|
|
|
|
[Header("Colliders")]
|
|
[SerializeField] private Transform crashCollidersRoot;
|
|
|
|
[Header("Takeoff settings")]
|
|
[Tooltip("How far must the plane be from the runway before it can be controlled again")]
|
|
[SerializeField] private float takeoffLenght = 30f;
|
|
|
|
private void Start()
|
|
{
|
|
//Setup speeds
|
|
maxSpeed = defaultSpeed;
|
|
currentSpeed = defaultSpeed;
|
|
ChangeSpeedMultiplier(1f);
|
|
|
|
//Get and set rigidbody
|
|
rb = GetComponent<Rigidbody>();
|
|
rb.isKinematic = true;
|
|
rb.useGravity = false;
|
|
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
|
|
|
SetupColliders(crashCollidersRoot);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
AudioSystem();
|
|
HandleInputs();
|
|
|
|
switch (airplaneState)
|
|
{
|
|
case AirplaneState.Flying:
|
|
FlyingUpdate();
|
|
break;
|
|
|
|
case AirplaneState.Landing:
|
|
LandingUpdate();
|
|
break;
|
|
|
|
case AirplaneState.Takeoff:
|
|
TakeoffUpdate();
|
|
break;
|
|
}
|
|
}
|
|
|
|
#region Flying State
|
|
|
|
private void FlyingUpdate()
|
|
{
|
|
UpdatePropellersAndLights();
|
|
|
|
//Airplane move only if not dead
|
|
if (!planeIsDead)
|
|
{
|
|
Movement();
|
|
SidewaysForceCalculation();
|
|
}
|
|
else
|
|
{
|
|
ChangeWingTrailEffectThickness(0f);
|
|
}
|
|
|
|
//Crash
|
|
if (!planeIsDead && HitSometing())
|
|
{
|
|
Crash();
|
|
}
|
|
}
|
|
|
|
private void SidewaysForceCalculation()
|
|
{
|
|
float _mutiplierXRot = sidewaysMovement * sidewaysMovementXRot;
|
|
float _mutiplierYRot = sidewaysMovement * sidewaysMovementYRot;
|
|
|
|
float _mutiplierYPos = sidewaysMovement * sidewaysMovementYPos;
|
|
|
|
//Right side
|
|
if (transform.localEulerAngles.z > 270f && transform.localEulerAngles.z < 360f)
|
|
{
|
|
float _angle = (transform.localEulerAngles.z - 270f) / (360f - 270f);
|
|
float _invert = 1f - _angle;
|
|
|
|
transform.Rotate(Vector3.up * (_invert * _mutiplierYRot) * Time.deltaTime);
|
|
transform.Rotate(Vector3.right * (-_invert * _mutiplierXRot) * currentPitchSpeed * Time.deltaTime);
|
|
|
|
transform.Translate(transform.up * (_invert * _mutiplierYPos) * Time.deltaTime);
|
|
}
|
|
|
|
//Left side
|
|
if (transform.localEulerAngles.z > 0f && transform.localEulerAngles.z < 90f)
|
|
{
|
|
float _angle = transform.localEulerAngles.z / 90f;
|
|
|
|
transform.Rotate(-Vector3.up * (_angle * _mutiplierYRot) * Time.deltaTime);
|
|
transform.Rotate(Vector3.right * (-_angle * _mutiplierXRot) * currentPitchSpeed * Time.deltaTime);
|
|
|
|
transform.Translate(transform.up * (_angle * _mutiplierYPos) * Time.deltaTime);
|
|
}
|
|
|
|
//Right side down
|
|
if (transform.localEulerAngles.z > 90f && transform.localEulerAngles.z < 180f)
|
|
{
|
|
float _angle = (transform.localEulerAngles.z - 90f) / (180f - 90f);
|
|
float _invert = 1f - _angle;
|
|
|
|
transform.Translate(transform.up * (_invert * _mutiplierYPos) * Time.deltaTime);
|
|
transform.Rotate(Vector3.right * (-_invert * _mutiplierXRot) * currentPitchSpeed * Time.deltaTime);
|
|
}
|
|
|
|
//Left side down
|
|
if (transform.localEulerAngles.z > 180f && transform.localEulerAngles.z < 270f)
|
|
{
|
|
float _angle = (transform.localEulerAngles.z - 180f) / (270f - 180f);
|
|
|
|
transform.Translate(transform.up * (_angle * _mutiplierYPos) * Time.deltaTime);
|
|
transform.Rotate(Vector3.right * (-_angle * _mutiplierXRot) * currentPitchSpeed * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
private void Movement()
|
|
{
|
|
//Move forward
|
|
transform.Translate(Vector3.forward * currentSpeed * Time.deltaTime);
|
|
|
|
//Store last speed
|
|
lastEngineSpeed = currentSpeed;
|
|
|
|
//Rotate airplane by inputs
|
|
transform.Rotate(Vector3.forward * -inputH * currentRollSpeed * Time.deltaTime);
|
|
transform.Rotate(Vector3.right * inputV * currentPitchSpeed * Time.deltaTime);
|
|
|
|
//Rotate yaw
|
|
if (inputYawRight)
|
|
{
|
|
transform.Rotate(Vector3.up * currentYawSpeed * Time.deltaTime);
|
|
}
|
|
else if (inputYawLeft)
|
|
{
|
|
transform.Rotate(-Vector3.up * currentYawSpeed * Time.deltaTime);
|
|
}
|
|
|
|
//Accelerate and deacclerate
|
|
if (currentSpeed < maxSpeed)
|
|
{
|
|
currentSpeed += accelerating * Time.deltaTime;
|
|
}
|
|
else
|
|
{
|
|
currentSpeed -= deaccelerating * Time.deltaTime;
|
|
}
|
|
|
|
//Turbo
|
|
if (inputTurbo && !turboOverheat)
|
|
{
|
|
//Turbo overheating
|
|
if(turboHeat > 100f)
|
|
{
|
|
turboHeat = 100f;
|
|
turboOverheat = true;
|
|
}
|
|
else
|
|
{
|
|
//Add turbo heat
|
|
turboHeat += Time.deltaTime * turboHeatingSpeed;
|
|
}
|
|
|
|
//Set speed to turbo speed and rotation to turbo values
|
|
maxSpeed = turboSpeed;
|
|
|
|
currentYawSpeed = yawSpeed * yawTurboMultiplier;
|
|
currentPitchSpeed = pitchSpeed * pitchTurboMultiplier;
|
|
currentRollSpeed = rollSpeed * rollTurboMultiplier;
|
|
|
|
//Engine lights
|
|
currentEngineLightIntensity = turbineLightTurbo;
|
|
|
|
//Effects
|
|
ChangeWingTrailEffectThickness(trailThickness);
|
|
|
|
//Audio
|
|
currentEngineSoundPitch = turboSoundPitch;
|
|
}
|
|
else
|
|
{
|
|
//Turbo cooling down
|
|
if(turboHeat > 0f)
|
|
{
|
|
turboHeat -= Time.deltaTime * turboCooldownSpeed;
|
|
}
|
|
else
|
|
{
|
|
turboHeat = 0f;
|
|
}
|
|
|
|
//Turbo cooldown
|
|
if (turboOverheat)
|
|
{
|
|
if(turboHeat <= turboOverheatOver)
|
|
{
|
|
turboOverheat = false;
|
|
}
|
|
}
|
|
|
|
//Speed and rotation normal
|
|
maxSpeed = defaultSpeed * speedMultiplier;
|
|
|
|
currentYawSpeed = yawSpeed;
|
|
currentPitchSpeed = pitchSpeed;
|
|
currentRollSpeed = rollSpeed;
|
|
|
|
//Engine lights
|
|
currentEngineLightIntensity = turbineLightDefault;
|
|
|
|
//Effects
|
|
ChangeWingTrailEffectThickness(0f);
|
|
|
|
//Audio
|
|
currentEngineSoundPitch = defaultSoundPitch;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Landing State
|
|
|
|
public void AddLandingRunway(Runway _landingThisRunway)
|
|
{
|
|
currentRunway = _landingThisRunway;
|
|
}
|
|
|
|
//My trasform is runway landing adjuster child
|
|
private void LandingUpdate()
|
|
{
|
|
UpdatePropellersAndLights();
|
|
|
|
ChangeWingTrailEffectThickness(0f);
|
|
|
|
//Stop speed
|
|
currentSpeed = Mathf.Lerp(currentSpeed, 0f, Time.deltaTime);
|
|
|
|
//Set local rotation to zero
|
|
transform.localRotation = Quaternion.Lerp(transform.localRotation, Quaternion.Euler(0f,0f,0f), 2f * Time.deltaTime);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Takeoff State
|
|
|
|
private void TakeoffUpdate()
|
|
{
|
|
UpdatePropellersAndLights();
|
|
|
|
//Reset colliders
|
|
foreach (SimpleAirPlaneCollider _airPlaneCollider in airPlaneColliders)
|
|
{
|
|
_airPlaneCollider.collideSometing = false;
|
|
}
|
|
|
|
//Accelerate
|
|
if (currentSpeed < turboSpeed)
|
|
{
|
|
currentSpeed += (accelerating * 2f) * Time.deltaTime;
|
|
}
|
|
|
|
//Move forward
|
|
transform.Translate(Vector3.forward * currentSpeed * Time.deltaTime);
|
|
|
|
//Far enough from the runaway go back to flying state
|
|
float _distanceToRunway = Vector3.Distance(transform.position, currentRunway.transform.position);
|
|
if(_distanceToRunway > takeoffLenght)
|
|
{
|
|
currentRunway = null;
|
|
airplaneState = AirplaneState.Flying;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Audio
|
|
private void AudioSystem()
|
|
{
|
|
if (engineSoundSource == null)
|
|
return;
|
|
|
|
if (airplaneState == AirplaneState.Flying)
|
|
{
|
|
engineSoundSource.pitch = Mathf.Lerp(engineSoundSource.pitch, currentEngineSoundPitch, 10f * Time.deltaTime);
|
|
|
|
if (planeIsDead)
|
|
{
|
|
engineSoundSource.volume = Mathf.Lerp(engineSoundSource.volume, 0f, 10f * Time.deltaTime);
|
|
}
|
|
else
|
|
{
|
|
engineSoundSource.volume = Mathf.Lerp(engineSoundSource.volume, maxEngineSound, 1f * Time.deltaTime);
|
|
}
|
|
}
|
|
else if (airplaneState == AirplaneState.Landing)
|
|
{
|
|
engineSoundSource.pitch = Mathf.Lerp(engineSoundSource.pitch, defaultSoundPitch, 1f * Time.deltaTime);
|
|
engineSoundSource.volume = Mathf.Lerp(engineSoundSource.volume, 0f, 1f * Time.deltaTime);
|
|
}
|
|
else if (airplaneState == AirplaneState.Takeoff)
|
|
{
|
|
engineSoundSource.pitch = Mathf.Lerp(engineSoundSource.pitch, turboSoundPitch, 1f * Time.deltaTime);
|
|
engineSoundSource.volume = Mathf.Lerp(engineSoundSource.volume, maxEngineSound, 1f * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private methods
|
|
|
|
private void UpdatePropellersAndLights()
|
|
{
|
|
if(!planeIsDead)
|
|
{
|
|
//Rotate propellers if any
|
|
if (propellers.Length > 0)
|
|
{
|
|
RotatePropellers(propellers, currentSpeed * propelSpeedMultiplier);
|
|
}
|
|
|
|
//Control lights if any
|
|
if (turbineLights.Length > 0)
|
|
{
|
|
ControlEngineLights(turbineLights, currentEngineLightIntensity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Rotate propellers if any
|
|
if (propellers.Length > 0)
|
|
{
|
|
RotatePropellers(propellers, 0f);
|
|
}
|
|
|
|
//Control lights if any
|
|
if (turbineLights.Length > 0)
|
|
{
|
|
ControlEngineLights(turbineLights, 0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetupColliders(Transform _root)
|
|
{
|
|
if (_root == null)
|
|
return;
|
|
|
|
//Get colliders from root transform
|
|
Collider[] colliders = _root.GetComponentsInChildren<Collider>();
|
|
|
|
//If there are colliders put components in them
|
|
for (int i = 0; i < colliders.Length; i++)
|
|
{
|
|
//Change collider to trigger
|
|
colliders[i].isTrigger = true;
|
|
|
|
GameObject _currentObject = colliders[i].gameObject;
|
|
|
|
//Add airplane collider to it and put it on the list
|
|
SimpleAirPlaneCollider _airplaneCollider = _currentObject.AddComponent<SimpleAirPlaneCollider>();
|
|
airPlaneColliders.Add(_airplaneCollider);
|
|
|
|
//Add airplane conroller reference to collider
|
|
_airplaneCollider.controller = this;
|
|
|
|
//Add rigid body to it
|
|
Rigidbody _rb = _currentObject.AddComponent<Rigidbody>();
|
|
_rb.useGravity = false;
|
|
_rb.isKinematic = true;
|
|
_rb.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
|
}
|
|
}
|
|
|
|
private void RotatePropellers(GameObject[] _rotateThese, float _speed)
|
|
{
|
|
for (int i = 0; i < _rotateThese.Length; i++)
|
|
{
|
|
_rotateThese[i].transform.Rotate(Vector3.forward * -_speed * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
private void ControlEngineLights(Light[] _lights, float _intensity)
|
|
{
|
|
for (int i = 0; i < _lights.Length; i++)
|
|
{
|
|
if(!planeIsDead)
|
|
{
|
|
_lights[i].intensity = Mathf.Lerp(_lights[i].intensity, _intensity, 10f * Time.deltaTime);
|
|
}
|
|
else
|
|
{
|
|
_lights[i].intensity = Mathf.Lerp(_lights[i].intensity, 0f, 10f * Time.deltaTime);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private void ChangeWingTrailEffectThickness(float _thickness)
|
|
{
|
|
for (int i = 0; i < wingTrailEffects.Length; i++)
|
|
{
|
|
wingTrailEffects[i].startWidth = Mathf.Lerp(wingTrailEffects[i].startWidth, _thickness, Time.deltaTime * 10f);
|
|
}
|
|
}
|
|
|
|
private bool HitSometing()
|
|
{
|
|
for (int i = 0; i < airPlaneColliders.Count; i++)
|
|
{
|
|
if (airPlaneColliders[i].collideSometing)
|
|
{
|
|
//Reset colliders
|
|
foreach(SimpleAirPlaneCollider _airPlaneCollider in airPlaneColliders)
|
|
{
|
|
_airPlaneCollider.collideSometing = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public methods
|
|
|
|
public virtual void Crash()
|
|
{
|
|
//Invoke action
|
|
crashAction?.Invoke();
|
|
|
|
//Set rigidbody to non cinematic
|
|
rb.isKinematic = false;
|
|
rb.useGravity = true;
|
|
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
|
|
|
|
//Add last speed to rb
|
|
rb.AddForce(transform.forward * lastEngineSpeed, ForceMode.VelocityChange);
|
|
|
|
//Change every collider trigger state and remove rigidbodys
|
|
for (int i = 0; i < airPlaneColliders.Count; i++)
|
|
{
|
|
airPlaneColliders[i].GetComponent<Collider>().isTrigger = false;
|
|
Destroy(airPlaneColliders[i].GetComponent<Rigidbody>());
|
|
}
|
|
|
|
//Kill player
|
|
planeIsDead = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Variables
|
|
|
|
/// <summary>
|
|
/// Returns a percentage of how fast the current speed is from the maximum speed between 0 and 1
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public float PercentToMaxSpeed()
|
|
{
|
|
float _percentToMax = (currentSpeed * speedMultiplier) / turboSpeed;
|
|
|
|
return _percentToMax;
|
|
}
|
|
|
|
public bool PlaneIsDead()
|
|
{
|
|
return planeIsDead;
|
|
}
|
|
|
|
public bool UsingTurbo()
|
|
{
|
|
if(maxSpeed == turboSpeed)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public float CurrentSpeed()
|
|
{
|
|
return currentSpeed * speedMultiplier;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a turbo heat between 0 and 100
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public float TurboHeatValue()
|
|
{
|
|
return turboHeat;
|
|
}
|
|
|
|
public bool TurboOverheating()
|
|
{
|
|
return turboOverheat;
|
|
}
|
|
|
|
/// <summary>
|
|
/// With this you can adjust the default speed between one and zero
|
|
/// </summary>
|
|
/// <param name="_speedMultiplier"></param>
|
|
public void ChangeSpeedMultiplier(float _speedMultiplier)
|
|
{
|
|
if(_speedMultiplier < 0f)
|
|
{
|
|
_speedMultiplier = 0f;
|
|
}
|
|
|
|
if(_speedMultiplier > 1f)
|
|
{
|
|
_speedMultiplier = 1f;
|
|
}
|
|
|
|
speedMultiplier = _speedMultiplier;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Inputs
|
|
|
|
private void HandleInputs()
|
|
{
|
|
inputH = SimpleAirplaneInput.Horizontal;
|
|
inputV = SimpleAirplaneInput.Vertical;
|
|
inputYawLeft = SimpleAirplaneInput.YawLeft;
|
|
inputYawRight = SimpleAirplaneInput.YawRight;
|
|
inputTurbo = SimpleAirplaneInput.Turbo;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |