File size: 4,727 Bytes
40c0886
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using UnityEngine;

namespace UnityAgent
{
    /// <summary>
    /// Arcade car physics: motor torque, steering, braking and a simple
    /// four-wheel raycast suspension. Place this on a root GameObject with
    /// a Rigidbody and four wheel transforms.
    /// </summary>
    [RequireComponent(typeof(Rigidbody))]
    public class VehicleController : MonoBehaviour
    {
        [System.Serializable]
        public class Wheel
        {
            public Transform transform;
            public bool isSteering;
            public bool isPowered;
            public bool isBraking;
            [HideInInspector] public bool isGrounded;
            [HideInInspector] public float suspensionOffset;
        }

        [Header("Wheels")]
        public Wheel[] wheels = new Wheel[4];

        [Header("Engine")]
        public float motorTorque = 1500f;
        public float brakeTorque = 3000f;
        public float maxSteerAngle = 30f;
        public float maxSpeedKph = 180f;

        [Header("Suspension")]
        public float suspensionRestLength = 0.5f;
        public float suspensionSpring = 35000f;
        public float suspensionDamper = 4500f;
        public float wheelRadius = 0.35f;
        public LayerMask wheelMask = ~0;

        [Header("Aerodynamics")]
        public float downforce = 100f;
        public float centerOfMassOffset = -0.5f;

        private Rigidbody _rb;
        private float _throttle;
        private float _steer;
        private float _brake;

        private void Awake()
        {
            _rb = GetComponent<Rigidbody>();
            _rb.centerOfMass += Vector3.up * centerOfMassOffset;
            _rb.interpolation = RigidbodyInterpolation.Interpolate;
        }

        private void Update()
        {
            _throttle = Input.GetAxis("Vertical");
            _steer = Input.GetAxis("Horizontal");
            _brake = Input.GetKey(KeyCode.Space) ? 1f : 0f;
        }

        private void FixedUpdate()
        {
            UpdateWheels();
            ApplySuspension();
            ApplyDrive();
            ApplyDownforce();
        }

        private void UpdateWheels()
        {
            foreach (var w in wheels)
            {
                if (w.transform == null) continue;
                Vector3 origin = w.transform.position;
                bool hit = Physics.Raycast(
                    origin, -transform.up, out RaycastHit r,
                    suspensionRestLength + wheelRadius, wheelMask,
                    QueryTriggerInteraction.Ignore);
                w.isGrounded = hit;
                w.suspensionOffset = hit ? r.distance - wheelRadius : suspensionRestLength;
            }
        }

        private void ApplySuspension()
        {
            foreach (var w in wheels)
            {
                if (!w.isGrounded) continue;
                float force = (suspensionRestLength - w.suspensionOffset) * suspensionSpring;
                Vector3 velocity = _rb.GetPointVelocity(w.transform.position);
                float upwardVel = Vector3.Dot(transform.up, velocity);
                force -= upwardVel * suspensionDamper;
                _rb.AddForceAtPosition(transform.up * Mathf.Max(0f, force), w.transform.position);
            }
        }

        private void ApplyDrive()
        {
            float kph = _rb.velocity.magnitude * 3.6f;
            float speedFactor = Mathf.Clamp01(1f - kph / maxSpeedKph);

            foreach (var w in wheels)
            {
                if (!w.isGrounded) continue;
                if (w.isPowered)
                {
                    Vector3 force = transform.forward * (_throttle * motorTorque * speedFactor);
                    _rb.AddForceAtPosition(force, w.transform.position, ForceMode.Force);
                }
                if (w.isBraking && _brake > 0.01f)
                {
                    Vector3 brakeForce = -transform.forward * (_brake * brakeTorque);
                    _rb.AddForceAtPosition(brakeForce, w.transform.position, ForceMode.Force);
                }
                if (w.isSteering)
                {
                    // Rotate the visual wheel and apply a small yaw torque.
                    w.transform.localRotation = Quaternion.Euler(0f, _steer * maxSteerAngle, 0f);
                }
            }
            // Steering yaw torque.
            if (Mathf.Abs(_steer) > 0.01f)
            {
                float turn = _steer * maxSteerAngle * 0.02f;
                _rb.AddTorque(transform.up * turn * (_rb.velocity.magnitude + 1f), ForceMode.Force);
            }
        }

        private void ApplyDownforce()
        {
            _rb.AddForce(-transform.up * downforce * _rb.velocity.magnitude);
        }
    }
}