using UnityEngine; using System.Collections; using System.Collections.Generic; using GameKitController.Audio; public class hoverCraftController : vehicleController { [Header ("Custom Settings")] [Space] public float maxSteeringTorque = 1400; public float rollOnTurns = 10; public float rollOnTurnsTorque = 20; public float steerBrakingTorque = 1400; public float brakingTorque; public float pitchCompensationTorque; public float rollCompensationTorque = 10; public float timeToFlip = 2; public float audioEngineSpeed; public float engineMinVolume = 0.5f; public float engineMaxVolume = 1; public float minAudioPitch = 0.5f; public float maxAudioPitch = 1.2f; public float maxForwardAcceleration = 20; public float maxReverseAcceleration = 15; public float maxBrakingDeceleration = 30; public float autoBrakingDeceleration; public float maxSpeed = 30; public AnimationCurve accelerationCurve; public float verticalReduction = 10; public float maxPitchAngle = 45; public Vector3 extraRigidbodyForce; public float minSteerInputIdle = 0.4f; public float minSteerInputMoving = 0.4f; [Space] [Header ("Hovercraft Settings")] [Space] public List hoverEngineList = new List (); public OtherCarParts otherCarParts; public hoverCraftSettings settings; [Space] [Header ("Debug")] [Space] public bool anyOnGround; float audioPower = 0; float maxEnginePower; float resetTimer; float steerInput; float forwardInput; float originalJumpPower; int i; int collisionForceLimit = 5; bool rotating; float leftInput = 0; float rightInput = 0; float currentMinSteerInput; hoverEngineSettings currentEngine; int hoverEngineListCount; ParticleSystem currentParticleSystem; protected override void InitializeAudioElements () { otherCarParts.InitializeAudioElements (); } public override void Awake () { base.Awake (); } public override void Start () { base.Start (); hoverEngineListCount = hoverEngineList.Count; //get the boost particles inside the vehicle for (i = 0; i < otherCarParts.boostingParticles.Count; i++) { if (otherCarParts.boostingParticles [i].gameObject.activeSelf) { otherCarParts.boostingParticles [i].gameObject.SetActive (false); } } for (i = 0; i < hoverEngineList.Count; i++) { currentEngine = hoverEngineList [i]; currentEngine.hasTurbine = currentEngine.turbine != null; currentEngine.hasParticles = currentEngine.ParticleSystem != null; } setAudioState (otherCarParts.engineAudioElement, 5, 0, true, false, false); setAudioState (otherCarParts.engineStartAudioElement, 5, 0.7f, false, false, false); originalJumpPower = vehicleControllerSettings.jumpPower; } public override void vehicleUpdate () { base.vehicleUpdate (); maxEnginePower = 0; for (i = 0; i < hoverEngineListCount; i++) { currentEngine = hoverEngineList [i]; if (currentEngine.maxEnginePower > maxEnginePower) { maxEnginePower = currentEngine.maxEnginePower; } //configure every particle system according to the engine state float rpm = Mathf.Lerp (currentEngine.minRPM, currentEngine.maxRPM, currentEngine.maxEnginePower); if (currentEngine.hasTurbine) { currentEngine.turbine.transform.Rotate (0, rpm * Time.deltaTime * 6, 0); } if (currentEngine.hasParticles) { var hoverEngineParticleEmission = currentEngine.ParticleSystem.emission; hoverEngineParticleEmission.rateOverTime = currentEngine.maxEmission * currentEngine.maxEnginePower; currentEngine.ParticleSystem.transform.position = currentEngine.hit.point + currentEngine.dustHeight * currentEngine.hit.normal; currentEngine.ParticleSystem.transform.LookAt (currentEngine.hit.point + 10 * currentEngine.hit.normal); } } audioPower = Mathf.Lerp (maxEnginePower, motorInput, audioEngineSpeed); otherCarParts.engineAudio.volume = Mathf.Lerp (engineMinVolume, engineMaxVolume, audioPower); otherCarParts.engineAudio.pitch = Mathf.Lerp (minAudioPitch, maxAudioPitch, audioPower); if (Mathf.Abs (horizontalAxis) > 0.05f) { steerInput = Mathf.Lerp (steerInput, horizontalAxis, Time.deltaTime * 10); } else { steerInput = Mathf.Lerp (steerInput, horizontalAxis, Time.deltaTime * 10); } if (Mathf.Abs (motorInput) > 0.05f) { forwardInput = Mathf.Lerp (forwardInput, motorInput, Time.deltaTime * 10); } else { forwardInput = Mathf.Lerp (forwardInput, motorInput, Time.deltaTime * 10); } float left = 0; float right = 0; if ((forwardInput > 0.05f || forwardInput < -0.05f) && (steerInput < 0.05f && steerInput > -0.05f)) { left = right = forwardInput; //print("moving forward or backward"); } else if (forwardInput > 0.05f && steerInput > 0) { left = forwardInput; right = -steerInput; //print("moving forward and to the right"); } else if (forwardInput > 0.05f && steerInput < 0) { left = steerInput; right = forwardInput; //print("moving forward and to the left"); } else if ((forwardInput < 0.05f && forwardInput > -0.05f) && steerInput > 0) { left = 0; right = steerInput; //print("moving to the right"); } else if ((forwardInput < 0.05f && forwardInput > -0.05f) && steerInput < 0) { left = -steerInput; right = 0; //print("moving to the left"); } else if (forwardInput < -0.05f && steerInput > 0) { left = 0; right = -steerInput; //print("moving backward and to the right"); } else if (forwardInput < -0.05f && steerInput < 0) { left = steerInput; right = 0; //print("moving backward and to the left"); } leftInput = Mathf.Lerp (leftInput, left, Time.deltaTime * 10); rightInput = Mathf.Lerp (rightInput, right, Time.deltaTime * 10); Vector3 rightHandLebarEuler = otherCarParts.rightHandLebar.transform.localEulerAngles; Vector3 lefttHandLebarEuler = otherCarParts.leftHandLebar.transform.localEulerAngles; otherCarParts.rightHandLebar.transform.localRotation = Quaternion.Euler (settings.steerAngleLimit * rightInput * 2, rightHandLebarEuler.y, rightHandLebarEuler.z); otherCarParts.leftHandLebar.transform.localRotation = Quaternion.Euler (settings.steerAngleLimit * leftInput * 2, lefttHandLebarEuler.y, lefttHandLebarEuler.z); //reset the vehicle rotation if it is upside down if (currentSpeed < 5) { //check the current rotation of the vehicle with respect to the normal of the gravity normal component, which always point the up direction float angle = Vector3.Angle (currentNormal, transform.up); if (angle > 60 && !rotating) { resetTimer += Time.deltaTime; if (resetTimer > timeToFlip) { resetTimer = 0; StartCoroutine (rotateVehicle ()); } } } } void FixedUpdate () { currentSpeed = mainRigidbody.linearVelocity.magnitude; if (usingHoverBoardWaypoint) { return; } //apply turn if (Mathf.Approximately (horizontalAxis, 0)) { float localR = Vector3.Dot (mainRigidbody.angularVelocity, transform.up); mainRigidbody.AddRelativeTorque (0, -localR * steerBrakingTorque, 0); } else { float targetRoll = -rollOnTurns * horizontalAxis; float roll = Mathf.Asin (transform.right.y) * Mathf.Rad2Deg; if (Mathf.Abs (roll) > Mathf.Abs (targetRoll)) { roll = 0; } else { roll = Mathf.DeltaAngle (roll, targetRoll); } mainRigidbody.AddRelativeTorque (0, horizontalAxis * maxSteeringTorque, roll * rollOnTurnsTorque); } if (!usingGravityControl && !jumpInputPressed) { Vector3 localVelocity = transform.InverseTransformDirection (mainRigidbody.linearVelocity); Vector3 extraForce = Vector3.Scale (localVelocity, extraRigidbodyForce); mainRigidbody.AddRelativeForce (mainRigidbody.mass * (-extraForce)); Vector3 gravityForce = (9.8f * currentNormal).normalized; //use every engine to keep the vehicle in the air for (i = 0; i < hoverEngineListCount; i++) { currentEngine = hoverEngineList [i]; //if the current engine is the main engine if (!currentEngine.mainEngine) { //find force direction by rotating local up vector towards world up Vector3 engineUp = currentEngine.engineTransform.up; Vector3 enginePosition = currentEngine.engineTransform.position; engineUp = Vector3.RotateTowards (engineUp, gravityForce, currentEngine.maxEngineAngle * Mathf.Deg2Rad, 1); //check if the vehicle is on ground currentEngine.maxEnginePower = 0; if (Physics.Raycast (enginePosition, -engineUp, out currentEngine.hit, currentEngine.maxHeight, settings.layer)) { //calculate down force currentEngine.maxEnginePower = Mathf.Pow ((currentEngine.maxHeight - currentEngine.hit.distance) / currentEngine.maxHeight, currentEngine.Exponent); float force = currentEngine.maxEnginePower * currentEngine.engineForce; float velocityUp = Vector3.Dot (mainRigidbody.GetPointVelocity (enginePosition), engineUp); float drag = -velocityUp * Mathf.Abs (velocityUp) * currentEngine.damping; mainRigidbody.AddForceAtPosition ((force + drag) * engineUp, enginePosition); } } else { //find current local pitch and roll float pitch = Mathf.Asin (Vector3.Dot (transform.forward, gravityForce)) * Mathf.Rad2Deg; float roll = Mathf.Asin (Vector3.Dot (transform.right, gravityForce)) * Mathf.Rad2Deg; pitch = Mathf.DeltaAngle (pitch, 0); roll = Mathf.DeltaAngle (roll, 0); //apply compensation torque float auxPitch = -pitch * pitchCompensationTorque; float auxRoll = roll * rollCompensationTorque; if (!anyOnGround) { auxPitch *= 0.5f; auxRoll *= 0.5f; } mainRigidbody.AddRelativeTorque (pitch, 0, auxRoll); } } bool brakeActive = (braking || isBrakeActive ()); if (brakeActive) { float localR = Vector3.Dot (mainRigidbody.angularVelocity, transform.up); mainRigidbody.AddRelativeTorque (0, -localR * brakingTorque, 0); for (i = 0; i < hoverEngineListCount; i++) { currentEngine = hoverEngineList [i]; if (currentEngine.mainEngine) { mainRigidbody.linearVelocity = Vector3.Lerp (mainRigidbody.linearVelocity, Vector3.zero, Time.deltaTime); } } } else { for (i = 0; i < hoverEngineListCount; i++) { currentEngine = hoverEngineList [i]; if (currentEngine.mainEngine) { float movementMultiplier = settings.inAirMovementMultiplier; if (Physics.Raycast (currentEngine.engineTransform.position, -transform.up, out currentEngine.hit, currentEngine.maxHeight, settings.layer)) { movementMultiplier = 1; } //current speed along forward axis float speed = Vector3.Dot (mainRigidbody.linearVelocity, transform.forward); //if the vehicle doesn't move by input, apply automatic brake bool isAutoBraking = Mathf.Approximately (motorInput, 0) && autoBrakingDeceleration > 0; float thrust = motorInput; if (isAutoBraking) { thrust = -Mathf.Sign (speed) * autoBrakingDeceleration / maxBrakingDeceleration; } //check if it is braking, for example speed and thrust have opposing signs bool isBraking = speed * motorInput < 0; //don't apply force if speed is max already if (Mathf.Abs (speed) < maxSpeed || isBraking) { //position on speed curve float normSpeed = Mathf.Sign (motorInput) * speed / maxSpeed; //apply acceleration curve and select proper maximum value float acc = accelerationCurve.Evaluate (normSpeed) * (isBraking ? maxBrakingDeceleration : thrust > 0 ? maxForwardAcceleration : maxReverseAcceleration); //drag should be added to the acceleration float sdd = speed * extraRigidbodyForce.z; float dragForce = sdd + mainRigidbody.linearDamping * speed; float force = acc * thrust + dragForce; //reduce acceleration if the vehicle is close to vertical orientation and is trrying to go higher float y = Vector3.Dot (transform.forward, gravityForce); if (maxPitchAngle < 90 && y * thrust > 0) { if (!isAutoBraking) { float pitch2 = Mathf.Asin (Mathf.Abs (y)) * Mathf.Rad2Deg; if (pitch2 > maxPitchAngle) { float reduction = (pitch2 - maxPitchAngle) / (90 - maxPitchAngle) * verticalReduction; force /= 1 + reduction; } } } mainRigidbody.AddForce ((boostInput * movementMultiplier * force) * transform.forward, ForceMode.Acceleration); } } } } } //set the exhaust particles state for (i = 0; i < otherCarParts.normalExhaust.Count; i++) { if (isTurnedOn && currentSpeed < 20) { if (!otherCarParts.normalExhaust [i].isPlaying) { otherCarParts.normalExhaust [i].Play (); } } else { if (otherCarParts.normalExhaust [i].isPlaying) { otherCarParts.normalExhaust [i].Stop (); } } } for (i = 0; i < otherCarParts.heavyExhaust.Count; i++) { if (isTurnedOn && currentSpeed > 10 && motorInput > 0.1f) { if (!otherCarParts.heavyExhaust [i].isPlaying) { otherCarParts.heavyExhaust [i].Play (); } } else { if (otherCarParts.heavyExhaust [i].isPlaying) { otherCarParts.heavyExhaust [i].Stop (); } } } anyOnGround = true; int totalWheelsOnAir = 0; for (i = 0; i < hoverEngineListCount; i++) { currentEngine = hoverEngineList [i]; if (!Physics.Raycast (currentEngine.engineTransform.position, -currentEngine.engineTransform.up, out currentEngine.hit, currentEngine.maxHeight, settings.layer)) { totalWheelsOnAir++; } } //if the total amount of wheels in the air is equal to the number of wheel sin the vehicle, anyOnGround is false if (totalWheelsOnAir == hoverEngineListCount && anyOnGround) { anyOnGround = false; } } IEnumerator jumpCoroutine () { jumpInputPressed = true; yield return new WaitForSeconds (0.5f); jumpInputPressed = false; } public override void updateCameraSteerState () { if (localLook.z < 0f) { localLook.x = Mathf.Sign (localLook.x); } steering = localLook.x; steering = Mathf.Clamp (steering, -1f, 1f); if (axisValues.y != 0) { currentMinSteerInput = minSteerInputMoving; } else { currentMinSteerInput = minSteerInputIdle; } if (Mathf.Abs (steering) > currentMinSteerInput) { horizontalAxis = steering; } else { horizontalAxis = 0; } } //if the vehicle is using the gravity control, set the state in this component public override void changeGravityControlUse (bool state) { base.changeGravityControlUse (state); } //the player is getting on or off from the vehicle, so public override void changeVehicleState () { base.changeVehicleState (); } public override void setTurnOnState () { setAudioState (otherCarParts.engineAudioElement, 5, 0, true, true, false); setAudioState (otherCarParts.engineStartAudioElement, 5, 0.7f, false, true, false); } public override void setTurnOffState (bool previouslyTurnedOn) { base.setTurnOffState (previouslyTurnedOn); if (previouslyTurnedOn) { setAudioState (otherCarParts.engineAudioElement, 5, 0, false, false, true); setAudioState (otherCarParts.engineEndAudioElement, 5, 1, false, true, false); } steerInput = 0; } public override bool isDrivingActive () { return driving; } public override void setEngineOnOrOffState () { base.setEngineOnOrOffState (); } public override void turnOnOrOff (bool state, bool previouslyTurnedOn) { base.turnOnOrOff (state, previouslyTurnedOn); } //the vehicle has been destroyed, so disabled every component in it public override void disableVehicle () { //stop the audio sources setAudioState (otherCarParts.engineAudioElement, 5, 0, false, false, false); setAudioState (otherCarParts.engineStartAudioElement, 5, 0.7f, false, false, false); setTurnOffState (false); //disable the exhausts particles for (i = 0; i < otherCarParts.normalExhaust.Count; i++) { otherCarParts.normalExhaust [i].Stop (); } for (i = 0; i < otherCarParts.heavyExhaust.Count; i++) { otherCarParts.heavyExhaust [i].Stop (); } //disable the controller this.enabled = false; } //reset the vehicle rotation if it is upside down IEnumerator rotateVehicle () { rotating = true; Quaternion currentRotation = transform.rotation; //rotate in the forward direction of the vehicle Quaternion dstRotPlayer = Quaternion.LookRotation (transform.forward, currentNormal); for (float t = 0; t < 1;) { t += Time.deltaTime * 3; transform.rotation = Quaternion.Slerp (currentRotation, dstRotPlayer, t); mainRigidbody.linearVelocity = Vector3.zero; yield return null; } rotating = false; } //if the vehicle is using the boost, set the boost particles public override void usingBoosting () { base.usingBoosting (); for (int i = 0; i < otherCarParts.boostingParticles.Count; i++) { currentParticleSystem = otherCarParts.boostingParticles [i]; if (usingBoost) { if (!currentParticleSystem.isPlaying) { if (!currentParticleSystem.gameObject.activeSelf) { currentParticleSystem.gameObject.SetActive (true); } currentParticleSystem.Play (); var boostingParticlesMain = currentParticleSystem.main; boostingParticlesMain.loop = true; } } else { if (currentParticleSystem.isPlaying) { var boostingParticlesMain = currentParticleSystem.main; boostingParticlesMain.loop = false; } } } } //use a jump platform public void useVehicleJumpPlatform (Vector3 direction) { StartCoroutine (jumpCoroutine ()); mainRigidbody.AddForce (mainRigidbody.mass * direction, ForceMode.Impulse); } public void useJumpPlatformParable (Vector3 direction) { Vector3 jumpForce = direction; mainRigidbody.AddForce (jumpForce, ForceMode.VelocityChange); } public void setNewJumpPower (float newJumpPower) { vehicleControllerSettings.jumpPower = newJumpPower * 100; } public void setOriginalJumpPower () { vehicleControllerSettings.jumpPower = originalJumpPower; } public void setCanJumpState (bool state) { vehicleControllerSettings.canJump = state; } //OVERRIDE FUNCTIONS FOR VEHICLE CONTROLLER //if any collider in the vehicle collides, then public override void setCollisionDetected (Collision currentCollision) { //check that the collision is not with the player if (!currentCollision.gameObject.CompareTag ("Player")) { //if the velocity of the collision is higher that the limit if (currentCollision.relativeVelocity.magnitude > collisionForceLimit) { //set the collision audio with a random collision clip if (otherCarParts.crashAudioElements.Length > 0) { setAudioState (otherCarParts.crashAudioElements [UnityEngine.Random.Range (0, otherCarParts.crashAudioElements.Length)], 5, 1, false, true, false); } } } } public override void startBrakeVehicleToStopCompletely () { braking = true; } public override void endBrakeVehicleToStopCompletely () { braking = false; } public override float getCurrentSpeed () { return currentSpeed; } //CALL INPUT FUNCTIONS public override void inputJump () { if (driving && !usingGravityControl && isTurnedOn && vehicleControllerSettings.canJump) { if (anyOnGround && !jumpInputPressed) { StartCoroutine (jumpCoroutine ()); mainRigidbody.AddForce (mainRigidbody.mass * vehicleControllerSettings.jumpPower * currentNormal, ForceMode.Impulse); } if (usingHoverBoardWaypoint) { StartCoroutine (jumpCoroutine ()); pickOrReleaseHoverboardVehicle (false, false); Vector3 totalJumpForce = mainRigidbody.mass * hoverboardJumpForce * (currentNormal + transform.forward); if (useHoverboardJumpClamp) { totalJumpForce = Vector3.ClampMagnitude (totalJumpForce, hoverboardJumpClamp); } mainRigidbody.AddForce (totalJumpForce, ForceMode.Impulse); } } } public override void inputHoldOrReleaseTurbo (bool holdingButton) { if (driving && !usingGravityControl && isTurnedOn && !usingHoverBoardWaypoint) { //boost input if (holdingButton) { usingBoost = true; //set the camera move away action mainVehicleCameraController.usingBoost (true, vehicleControllerSettings.boostCameraShakeStateName, vehicleControllerSettings.useBoostCameraShake, vehicleControllerSettings.moveCameraAwayOnBoost); } else { //stop boost usingBoost = false; //disable the camera move away action mainVehicleCameraController.usingBoost (false, vehicleControllerSettings.boostCameraShakeStateName, vehicleControllerSettings.useBoostCameraShake, vehicleControllerSettings.moveCameraAwayOnBoost); //disable the boost particles usingBoosting (); boostInput = 1; } } } public override void inputSetTurnOnState () { if (driving && !usingGravityControl) { if (mainVehicleHUDManager.canSetTurnOnState) { setEngineOnOrOffState (); } } } public override void inputHorn () { if (driving) { setAudioState (otherCarParts.hornAudioElement, 5, 1, false, true, false); } } public override void inputHoldOrReleaseBrake (bool holdingButton) { if (driving && !usingGravityControl) { braking = holdingButton; } } [System.Serializable] public class hoverEngineSettings { public string Name; public Transform engineTransform; public ParticleSystem ParticleSystem; public float maxEmission = 250; public float dustHeight = 0.1f; public float maxHeight = 2; public float engineForce = 4000; public float damping = 150; public float Exponent = 2; public float maxEngineAngle = 10; public bool mainEngine; public float minRPM = 100; public float maxRPM = 150; public Transform turbine; [HideInInspector] public RaycastHit hit; [HideInInspector] public float maxEnginePower; [HideInInspector] public bool hasTurbine; [HideInInspector] public bool hasParticles; } [System.Serializable] public class OtherCarParts { public Transform rightHandLebar; public Transform leftHandLebar; public Transform COM; public GameObject chassis; public AudioClip engineStartClip; public AudioElement engineStartAudioElement; public AudioClip engineClip; public AudioElement engineAudioElement; public AudioClip engineEndClip; public AudioElement engineEndAudioElement; public AudioClip[] crashClips; public AudioElement[] crashAudioElements; public AudioClip hornClip; public AudioElement hornAudioElement; public List normalExhaust = new List (); public List heavyExhaust = new List (); public AudioSource engineStartAudio; public AudioSource engineAudio; public AudioSource crashAudio; public AudioSource hornAudio; public List boostingParticles = new List (); public void InitializeAudioElements () { if (engineStartClip != null) { engineStartAudioElement.clip = engineStartClip; } if (engineClip != null) { engineAudioElement.clip = engineClip; } if (engineEndClip != null) { engineEndAudioElement.clip = engineEndClip; } if (crashClips != null && crashClips.Length > 0) { crashAudioElements = new AudioElement[crashClips.Length]; for (var i = 0; i < crashClips.Length; i++) { crashAudioElements [i] = new AudioElement { clip = crashClips [i] }; } } if (hornClip != null) { hornAudioElement.clip = hornClip; } if (engineStartAudio != null) { engineStartAudioElement.audioSource = engineStartAudio; } if (engineAudio != null) { engineAudioElement.audioSource = engineAudio; engineEndAudioElement.audioSource = engineAudio; } if (crashAudio != null) { foreach (var audioElement in crashAudioElements) { audioElement.audioSource = crashAudio; } } if (hornAudio != null) { hornAudioElement.audioSource = hornAudio; } } } [System.Serializable] public class hoverCraftSettings { public LayerMask layer; public float steerAngleLimit; [Range (0, 1)] public float inAirMovementMultiplier; } }