Skip to content

Instantly share code, notes, and snippets.

@niuage
Last active May 31, 2020 13:57
Show Gist options
  • Save niuage/170c5a0d8bf69a3980cf7eec358c3962 to your computer and use it in GitHub Desktop.
Save niuage/170c5a0d8bf69a3980cf7eec358c3962 to your computer and use it in GitHub Desktop.
// Some small parts of the code are specific to my setup, as I'm sometimes getting game objects by name (for the wheel parts),
// but most of it could be easily adapted to your project.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering.Universal.Internal;
using UnityEngine.UIElements;
public class Hoverjet : MonoBehaviour
{
[SerializeField]
private Wheel[] wheels;
private Wheel[] rightWheels;
private Wheel[] leftWheels;
private int wheelsInContactCount = 0;
public float height;
public float thrust;
public float forwardSpeed;
public float rotationSpeed;
public Color thrusterColor;
public float minForceMultiplier;
public float flightRotationDelay;
private float startedFlighingAt;
[HideInInspector]
public Rigidbody rb;
[HideInInspector]
public float mass;
[HideInInspector]
public float verticalAxis, horizontalAxis;
private Vector3 lastPosition;
[SerializeField]
private float _speed = 0;
public float Speed {
get { return _speed; }
set { _speed = value; }
}
private float zRotationSpeed;
public float zRotationAngle;
public float zRotationSmoothTime;
private void Start()
{
rb = GetComponent<Rigidbody>();
mass = rb.mass;
rb.centerOfMass += transform.up * -3 + transform.forward * -0.3f;
for (int i = 0; i < wheels.Length; i++) wheels[i].Init(this);
rightWheels = new Wheel[2] { wheels[0], wheels[2] };
leftWheels = new Wheel[2] { wheels[1], wheels[3] };
lastPosition = transform.position;
}
private void Update()
{
verticalAxis = Input.GetAxis("Vertical");
horizontalAxis = Input.GetAxis("Horizontal");
}
void FixedUpdate()
{
ComputeSpeed();
for (int i = 0; i < wheels.Length; i++) wheels[i].IsBoosted = false;
bool anyWheelsWereInContact = wheelsInContactCount > 0;
wheelsInContactCount = 0;
if (Mathf.Abs(verticalAxis) > 0.1f) BoostWheels(wheels);
if (horizontalAxis > 0.1f) BoostWheels(leftWheels);
if (horizontalAxis < -0.1f) BoostWheels(rightWheels);
for (int i = 0; i < wheels.Length; i++) {
Wheel wheel = wheels[i];
if (Physics.Raycast(wheel.thrusterTransform.position + transform.up, Vector3.down, out RaycastHit hit, height))
wheel.Hit = hit;
else
wheel.isTouchingGround = false;
wheel.Thrust();
wheel.UpdateState();
if (wheel.isTouchingGround) wheelsInContactCount += 1;
}
// Apply force and torque
if (wheelsInContactCount > 1) {
rb.AddForce(transform.forward * forwardSpeed * mass * verticalAxis, ForceMode.Force);
rb.AddTorque(transform.up * rotationSpeed * mass * horizontalAxis, ForceMode.Force);
Vector3 newRotation = transform.eulerAngles;
newRotation.z = Mathf.SmoothDampAngle(newRotation.z, horizontalAxis * -zRotationAngle, ref zRotationSpeed, zRotationSmoothTime);
transform.eulerAngles = newRotation;
}
// Allow driver to move while airborne after a certain amount of time
if (wheelsInContactCount <= 1 && startedFlighingAt + flightRotationDelay < Time.time) {
transform.Rotate(verticalAxis, 0, 0);
transform.Rotate(0, 0, -horizontalAxis * 2);
}
// Records the time at which the car starts flying
if (anyWheelsWereInContact && wheelsInContactCount == 0) startedFlighingAt = Time.time;
// Allow the car to conserve its momentum when airborne
rb.drag = wheelsInContactCount * 0.4f;
}
private void BoostWheels(Wheel[] _wheels)
{
for (int i = 0; i < _wheels.Length; i++) { _wheels[i].IsBoosted = true; }
}
private void ComputeSpeed()
{
Speed = (transform.position - lastPosition).sqrMagnitude / Time.fixedDeltaTime;
lastPosition = transform.position;
}
private void OnDrawGizmos()
{
for (int i = 0; i < wheels.Length; i++) {
Wheel wheel = wheels[i];
if (wheel.isTouchingGround) {
if (wheel.IsBoosted) {
Gizmos.color = Color.red;
} else {
Gizmos.color = Color.grey;
}
Gizmos.DrawSphere(wheel.Hit.point, 0.2f);
}
}
}
}
[Serializable]
class Wheel
{
public GameObject visual;
private Transform visualTransform;
public float emissionRateMultiplier;
private Transform rim;
[HideInInspector]
public Transform thrusterTransform;
private ParticleSystem dustParticles;
private Hoverjet jet;
[HideInInspector]
public bool isTouchingGround;
private bool _isBoosted;
public bool IsBoosted { get { return _isBoosted; } set { wasBoosted = _isBoosted; _isBoosted = value; } }
private bool wasBoosted;
private MaterialPropertyBlock block;
Renderer boosterRenderer;
private RaycastHit _hit;
public RaycastHit Hit {
get { return _hit; }
set { _hit = value; isTouchingGround = true; }
}
public float thrustMultiplier;
private int groundLayer;
public void Init(Hoverjet _jet)
{
jet = _jet;
visualTransform = visual.transform;
rim = visualTransform.Find("Rim");
thrusterTransform = visualTransform.Find("thruster");
Transform dust = visualTransform.Find("dust");
if (dust != null) {
dustParticles = dust.gameObject.GetComponent<ParticleSystem>();
}
block = new MaterialPropertyBlock();
boosterRenderer = thrusterTransform.Find("Plane").GetComponent<Renderer>();
groundLayer = LayerMask.NameToLayer("Plane");
}
public void Thrust()
{
if (!isTouchingGround) return;
jet.rb.AddForceAtPosition(
Vector3.up * jet.thrust * (IsBoosted ? thrustMultiplier : 1) * Mathf.InverseLerp(jet.height, jet.minForceMultiplier, Hit.distance), // force
thrusterTransform.position - Vector3.up // at position
);
EmitSandParticles();
}
public void UpdateState()
{
RotateRimToTerrain();
RotateBoosterToMovingDirection();
UpdateColor();
}
/// /////////////////////////////////////////////////////////////////////////////////////////////////
private void EmitSandParticles()
{
if (dustParticles == null) return;
var emission = dustParticles.emission;
if (isTouchingGround && Hit.collider.gameObject.layer == groundLayer)
emission.rateOverTime = emissionRateMultiplier * Mathf.InverseLerp(2, 5, jet.Speed);
else
emission.rateOverTime = 0f;
}
private void UpdateColor()
{
if (IsBoosted) {
SetThrusterColor(jet.thrusterColor * Mathf.Lerp(1, 3, jet.verticalAxis + jet.horizontalAxis));
} else if (wasBoosted) {
SetThrusterColor(jet.thrusterColor);
}
}
private void SetThrusterColor(Color color)
{
block.SetColor("_BaseColor", color);
boosterRenderer.SetPropertyBlock(block);
}
private void RotateRimToTerrain()
{
Vector3 direction = isTouchingGround ? Hit.normal : thrusterTransform.up;
rim.rotation = Quaternion.RotateTowards(
rim.rotation,
Quaternion.FromToRotation(rim.up, direction) * rim.rotation,
90 * Time.deltaTime
);
// Restrict the rotation on X axis.
// there's probably a more efficient way to do this than doing the full rotation then cancelling out the other axis.
Quaternion q = rim.localRotation;
q.eulerAngles = new Vector3(q.eulerAngles.x, 0, 0);
rim.localRotation = q;
}
private void RotateBoosterToMovingDirection()
{
thrusterTransform.rotation = Quaternion.RotateTowards(
thrusterTransform.rotation,
Quaternion.FromToRotation(
thrusterTransform.up,
Vector3.up + (jet.verticalAxis * jet.transform.forward + jet.horizontalAxis * jet.transform.right).Map(comp => comp * 0.6f)
) * thrusterTransform.rotation,
360 * Time.deltaTime
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment