1 using System;
2 using UnityEngine;
3
4 namespace UnityStandardAssets.Vehicles.Aeroplane
5 {
6 [RequireComponent(typeof (Rigidbody))]
7 public class AeroplaneController : MonoBehaviour
8 {
9 [SerializeField] private float m_MaxEnginePower = 40f; // The maximum output of the engine.
10 [SerializeField] private float m_Lift = 0.002f; // The amount of lift generated by the aeroplane moving forwards.
11 [SerializeField] private float m_ZeroLiftSpeed = 300; // The speed at which lift is no longer applied.
12 [SerializeField] private float m_RollEffect = 1f; // The strength of effect for roll input.
13 [SerializeField] private float m_PitchEffect = 1f; // The strength of effect for pitch input.
14 [SerializeField] private float m_YawEffect = 0.2f; // The strength of effect for yaw input.
15 [SerializeField] private float m_BankedTurnEffect = 0.5f; // The amount of turn from doing a banked turn.
16 [SerializeField] private float m_AerodynamicEffect = 0.02f; // How much aerodynamics affect the speed of the aeroplane.
17 [SerializeField] private float m_AutoTurnPitch = 0.5f; // How much the aeroplane automatically pitches when in a banked turn.
18 [SerializeField] private float m_AutoRollLevel = 0.2f; // How much the aeroplane tries to level when not rolling.
19 [SerializeField] private float m_AutoPitchLevel = 0.2f; // How much the aeroplane tries to level when not pitching.
20 [SerializeField] private float m_AirBrakesEffect = 3f; // How much the air brakes effect the drag.
21 [SerializeField] private float m_ThrottleChangeSpeed = 0.3f; // The speed with which the throttle changes.
22 [SerializeField] private float m_DragIncreaseFactor = 0.001f; // how much drag should increase with speed.
23
24 public float Altitude { get; private set; } // The aeroplane's height above the ground.
25 public float Throttle { get; private set; } // The amount of throttle being used.
26 public bool AirBrakes { get; private set; } // Whether or not the air brakes are being applied.
27 public float ForwardSpeed { get; private set; } // How fast the aeroplane is traveling in it's forward direction.
28 public float EnginePower { get; private set; } // How much power the engine is being given.
29 public float MaxEnginePower{ get { return m_MaxEnginePower; }} // The maximum output of the engine.
30 public float RollAngle { get; private set; }
31 public float PitchAngle { get; private set; }
32 public float RollInput { get; private set; }
33 public float PitchInput { get; private set; }
34 public float YawInput { get; private set; }
35 public float ThrottleInput { get; private set; }
36
37 private float m_OriginalDrag; // The drag when the scene starts.
38 private float m_OriginalAngularDrag; // The angular drag when the scene starts.
39 private float m_AeroFactor;
40 private bool m_Immobilized = false; // used for making the plane uncontrollable, i.e. if it has been hit or crashed.
41 private float m_BankedTurnAmount;
42 private Rigidbody m_Rigidbody;
43 WheelCollider[] m_WheelColliders;
44
45
46 private void Start()
47 {
48 m_Rigidbody = GetComponent<Rigidbody>();
49 // Store original drag settings, these are modified during flight.
50 m_OriginalDrag = m_Rigidbody.drag;
51 m_OriginalAngularDrag = m_Rigidbody.angularDrag;
52
53 for (int i = 0; i < transform.childCount; i++ )
54 {
55 foreach (var componentsInChild in transform.GetChild(i).GetComponentsInChildren<WheelCollider>())
56 {
57 componentsInChild.motorTorque = 0.18f;
58 }
59 }
60 }
61
62
63 public void Move(float rollInput, float pitchInput, float yawInput, float throttleInput, bool airBrakes)
64 {
65 // transfer input parameters into properties.s
66 RollInput = rollInput;
67 PitchInput = pitchInput;
68 YawInput = yawInput;
69 ThrottleInput = throttleInput;
70 AirBrakes = airBrakes;
71
72 ClampInputs();
73
74 CalculateRollAndPitchAngles();
75
76 AutoLevel();
77
78 CalculateForwardSpeed();
79
80 ControlThrottle();
81
82 CalculateDrag();
83
84 CaluclateAerodynamicEffect();
85
86 CalculateLinearForces();
87
88 CalculateTorque();
89
90 CalculateAltitude();
91 }
92
93
94 private void ClampInputs()
95 {
96 // clamp the inputs to -1 to 1 range
97 RollInput = Mathf.Clamp(RollInput, -1, 1);
98 PitchInput = Mathf.Clamp(PitchInput, -1, 1);
99 YawInput = Mathf.Clamp(YawInput, -1, 1);
100 ThrottleInput = Mathf.Clamp(ThrottleInput, -1, 1);
101 }
102
103
104 private void CalculateRollAndPitchAngles()
105 {
106 // Calculate roll & pitch angles
107 // Calculate the flat forward direction (with no y component).
108 var flatForward = transform.forward;
109 flatForward.y = 0;
110 // If the flat forward vector is non-zero (which would only happen if the plane was pointing exactly straight upwards)
111 if (flatForward.sqrMagnitude > 0)
112 {
113 flatForward.Normalize();
114 // calculate current pitch angle
115 var localFlatForward = transform.InverseTransformDirection(flatForward);
116 PitchAngle = Mathf.Atan2(localFlatForward.y, localFlatForward.z);
117 // calculate current roll angle
118 var flatRight = Vector3.Cross(Vector3.up, flatForward);
119 var localFlatRight = transform.InverseTransformDirection(flatRight);
120 RollAngle = Mathf.Atan2(localFlatRight.y, localFlatRight.x);
121 }
122 }
123
124
125 private void AutoLevel()
126 {
127 // The banked turn amount (between -1 and 1) is the sine of the roll angle.
128 // this is an amount applied to elevator input if the user is only using the banking controls,
129 // because that's what people expect to happen in games!
130 m_BankedTurnAmount = Mathf.Sin(RollAngle);
131 // auto level roll, if there's no roll input:
132 if (RollInput == 0f)
133 {
134 RollInput = -RollAngle*m_AutoRollLevel;
135 }
136 // auto correct pitch, if no pitch input (but also apply the banked turn amount)
137 if (PitchInput == 0f)
138 {
139 PitchInput = -PitchAngle*m_AutoPitchLevel;
140 PitchInput -= Mathf.Abs(m_BankedTurnAmount*m_BankedTurnAmount*m_AutoTurnPitch);
141 }
142 }
143
144
145 private void CalculateForwardSpeed()
146 {
147 // Forward speed is the speed in the planes's forward direction (not the same as its velocity, eg if falling in a stall)
148 var localVelocity = transform.InverseTransformDirection(m_Rigidbody.velocity);
149 ForwardSpeed = Mathf.Max(0, localVelocity.z);
150 }
151
152
153 private void ControlThrottle()
154 {
155 // override throttle if immobilized
156 if (m_Immobilized)
157 {
158 ThrottleInput = -0.5f;
159 }
160
161 // Adjust throttle based on throttle input (or immobilized state)
162 Throttle = Mathf.Clamp01(Throttle + ThrottleInput*Time.deltaTime*m_ThrottleChangeSpeed);
163
164 // current engine power is just:
165 EnginePower = Throttle*m_MaxEnginePower;
166 }
167
168
169 private void CalculateDrag()
170 {
171 // increase the drag based on speed, since a constant drag doesn't seem "Real" (tm) enough
172 float extraDrag = m_Rigidbody.velocity.magnitude*m_DragIncreaseFactor;
173 // Air brakes work by directly modifying drag. This part is actually pretty realistic!
174 m_Rigidbody.drag = (AirBrakes ? (m_OriginalDrag + extraDrag)*m_AirBrakesEffect : m_OriginalDrag + extraDrag);
175 // Forward speed affects angular drag - at high forward speed, it's much harder for the plane to spin
176 m_Rigidbody.angularDrag = m_OriginalAngularDrag*ForwardSpeed;
177 }
178
179
180 private void CaluclateAerodynamicEffect()
181 {
182 // "Aerodynamic" calculations. This is a very simple approximation of the effect that a plane
183 // will naturally try to align itself in the direction that it's facing when moving at speed.
184 // Without this, the plane would behave a bit like the asteroids spaceship!
185 if (m_Rigidbody.velocity.magnitude > 0)
186 {
187 // compare the direction we're pointing with the direction we're moving:
188 m_AeroFactor = Vector3.Dot(transform.forward, m_Rigidbody.velocity.normalized);
189 // multipled by itself results in a desirable rolloff curve of the effect
190 m_AeroFactor *= m_AeroFactor;
191 // Finally we calculate a new velocity by bending the current velocity direction towards
192 // the the direction the plane is facing, by an amount based on this aeroFactor
193 var newVelocity = Vector3.Lerp(m_Rigidbody.velocity, transform.forward*ForwardSpeed,
194 m_AeroFactor*ForwardSpeed*m_AerodynamicEffect*Time.deltaTime);
195 m_Rigidbody.velocity = newVelocity;
196
197 // also rotate the plane towards the direction of movement - this should be a very small effect, but means the plane ends up
198 // pointing downwards in a stall
199 m_Rigidbody.rotation = Quaternion.Slerp(m_Rigidbody.rotation,
200 Quaternion.LookRotation(m_Rigidbody.velocity, transform.up),
201 m_AerodynamicEffect*Time.deltaTime);
202 }
203 }
204
205
206 private void CalculateLinearForces()
207 {
208 // Now calculate forces acting on the aeroplane:
209 // we accumulate forces into this variable:
210 var forces = Vector3.zero;
211 // Add the engine power in the forward direction
212 forces += EnginePower*transform.forward;
213 // The direction that the lift force is applied is at right angles to the plane's velocity (usually, this is 'up'!)
214 var liftDirection = Vector3.Cross(m_Rigidbody.velocity, transform.right).normalized;
215 // The amount of lift drops off as the plane increases speed - in reality this occurs as the pilot retracts the flaps
216 // shortly after takeoff, giving the plane less drag, but less lift. Because we don't simulate flaps, this is
217 // a simple way of doing it automatically:
218 var zeroLiftFactor = Mathf.InverseLerp(m_ZeroLiftSpeed, 0, ForwardSpeed);
219 // Calculate and add the lift power
220 var liftPower = ForwardSpeed*ForwardSpeed*m_Lift*zeroLiftFactor*m_AeroFactor;
221 forces += liftPower*liftDirection;
222 // Apply the calculated forces to the the Rigidbody
223 m_Rigidbody.AddForce(forces);
224 }
225
226
227 private void CalculateTorque()
228 {
229 // We accumulate torque forces into this variable:
230 var torque = Vector3.zero;
231 // Add torque for the pitch based on the pitch input.
232 torque += PitchInput*m_PitchEffect*transform.right;
233 // Add torque for the yaw based on the yaw input.
234 torque += YawInput*m_YawEffect*transform.up;
235 // Add torque for the roll based on the roll input.
236 torque += -RollInput*m_RollEffect*transform.forward;
237 // Add torque for banked turning.
238 torque += m_BankedTurnAmount*m_BankedTurnEffect*transform.up;
239 // The total torque is multiplied by the forward speed, so the controls have more effect at high speed,
240 // and little effect at low speed, or when not moving in the direction of the nose of the plane
241 // (i.e. falling while stalled)
242 m_Rigidbody.AddTorque(torque*ForwardSpeed*m_AeroFactor);
243 }
244
245
246 private void CalculateAltitude()
247 {
248 // Altitude calculations - we raycast downwards from the aeroplane
249 // starting a safe distance below the plane to avoid colliding with any of the plane's own colliders
250 var ray = new Ray(transform.position - Vector3.up*10, -Vector3.up);
251 RaycastHit hit;
252 Altitude = Physics.Raycast(ray, out hit) ? hit.distance + 10 : transform.position.y;
253 }
254
255
256 // Immobilize can be called from other objects, for example if this plane is hit by a weapon and should become uncontrollable
257 public void Immobilize()
258 {
259 m_Immobilized = true;
260 }
261
262
263 // Reset is called via the ObjectResetter script, if present.
264 public void Reset()
265 {
266 m_Immobilized = false;
267 }
268 }
269 }
2 using UnityEngine;
3
4 namespace UnityStandardAssets.Vehicles.Aeroplane
5 {
6 [RequireComponent(typeof (Rigidbody))]
7 public class AeroplaneController : MonoBehaviour
8 {
9 [SerializeField] private float m_MaxEnginePower = 40f; // The maximum output of the engine.
10 [SerializeField] private float m_Lift = 0.002f; // The amount of lift generated by the aeroplane moving forwards.
11 [SerializeField] private float m_ZeroLiftSpeed = 300; // The speed at which lift is no longer applied.
12 [SerializeField] private float m_RollEffect = 1f; // The strength of effect for roll input.
13 [SerializeField] private float m_PitchEffect = 1f; // The strength of effect for pitch input.
14 [SerializeField] private float m_YawEffect = 0.2f; // The strength of effect for yaw input.
15 [SerializeField] private float m_BankedTurnEffect = 0.5f; // The amount of turn from doing a banked turn.
16 [SerializeField] private float m_AerodynamicEffect = 0.02f; // How much aerodynamics affect the speed of the aeroplane.
17 [SerializeField] private float m_AutoTurnPitch = 0.5f; // How much the aeroplane automatically pitches when in a banked turn.
18 [SerializeField] private float m_AutoRollLevel = 0.2f; // How much the aeroplane tries to level when not rolling.
19 [SerializeField] private float m_AutoPitchLevel = 0.2f; // How much the aeroplane tries to level when not pitching.
20 [SerializeField] private float m_AirBrakesEffect = 3f; // How much the air brakes effect the drag.
21 [SerializeField] private float m_ThrottleChangeSpeed = 0.3f; // The speed with which the throttle changes.
22 [SerializeField] private float m_DragIncreaseFactor = 0.001f; // how much drag should increase with speed.
23
24 public float Altitude { get; private set; } // The aeroplane's height above the ground.
25 public float Throttle { get; private set; } // The amount of throttle being used.
26 public bool AirBrakes { get; private set; } // Whether or not the air brakes are being applied.
27 public float ForwardSpeed { get; private set; } // How fast the aeroplane is traveling in it's forward direction.
28 public float EnginePower { get; private set; } // How much power the engine is being given.
29 public float MaxEnginePower{ get { return m_MaxEnginePower; }} // The maximum output of the engine.
30 public float RollAngle { get; private set; }
31 public float PitchAngle { get; private set; }
32 public float RollInput { get; private set; }
33 public float PitchInput { get; private set; }
34 public float YawInput { get; private set; }
35 public float ThrottleInput { get; private set; }
36
37 private float m_OriginalDrag; // The drag when the scene starts.
38 private float m_OriginalAngularDrag; // The angular drag when the scene starts.
39 private float m_AeroFactor;
40 private bool m_Immobilized = false; // used for making the plane uncontrollable, i.e. if it has been hit or crashed.
41 private float m_BankedTurnAmount;
42 private Rigidbody m_Rigidbody;
43 WheelCollider[] m_WheelColliders;
44
45
46 private void Start()
47 {
48 m_Rigidbody = GetComponent<Rigidbody>();
49 // Store original drag settings, these are modified during flight.
50 m_OriginalDrag = m_Rigidbody.drag;
51 m_OriginalAngularDrag = m_Rigidbody.angularDrag;
52
53 for (int i = 0; i < transform.childCount; i++ )
54 {
55 foreach (var componentsInChild in transform.GetChild(i).GetComponentsInChildren<WheelCollider>())
56 {
57 componentsInChild.motorTorque = 0.18f;
58 }
59 }
60 }
61
62
63 public void Move(float rollInput, float pitchInput, float yawInput, float throttleInput, bool airBrakes)
64 {
65 // transfer input parameters into properties.s
66 RollInput = rollInput;
67 PitchInput = pitchInput;
68 YawInput = yawInput;
69 ThrottleInput = throttleInput;
70 AirBrakes = airBrakes;
71
72 ClampInputs();
73
74 CalculateRollAndPitchAngles();
75
76 AutoLevel();
77
78 CalculateForwardSpeed();
79
80 ControlThrottle();
81
82 CalculateDrag();
83
84 CaluclateAerodynamicEffect();
85
86 CalculateLinearForces();
87
88 CalculateTorque();
89
90 CalculateAltitude();
91 }
92
93
94 private void ClampInputs()
95 {
96 // clamp the inputs to -1 to 1 range
97 RollInput = Mathf.Clamp(RollInput, -1, 1);
98 PitchInput = Mathf.Clamp(PitchInput, -1, 1);
99 YawInput = Mathf.Clamp(YawInput, -1, 1);
100 ThrottleInput = Mathf.Clamp(ThrottleInput, -1, 1);
101 }
102
103
104 private void CalculateRollAndPitchAngles()
105 {
106 // Calculate roll & pitch angles
107 // Calculate the flat forward direction (with no y component).
108 var flatForward = transform.forward;
109 flatForward.y = 0;
110 // If the flat forward vector is non-zero (which would only happen if the plane was pointing exactly straight upwards)
111 if (flatForward.sqrMagnitude > 0)
112 {
113 flatForward.Normalize();
114 // calculate current pitch angle
115 var localFlatForward = transform.InverseTransformDirection(flatForward);
116 PitchAngle = Mathf.Atan2(localFlatForward.y, localFlatForward.z);
117 // calculate current roll angle
118 var flatRight = Vector3.Cross(Vector3.up, flatForward);
119 var localFlatRight = transform.InverseTransformDirection(flatRight);
120 RollAngle = Mathf.Atan2(localFlatRight.y, localFlatRight.x);
121 }
122 }
123
124
125 private void AutoLevel()
126 {
127 // The banked turn amount (between -1 and 1) is the sine of the roll angle.
128 // this is an amount applied to elevator input if the user is only using the banking controls,
129 // because that's what people expect to happen in games!
130 m_BankedTurnAmount = Mathf.Sin(RollAngle);
131 // auto level roll, if there's no roll input:
132 if (RollInput == 0f)
133 {
134 RollInput = -RollAngle*m_AutoRollLevel;
135 }
136 // auto correct pitch, if no pitch input (but also apply the banked turn amount)
137 if (PitchInput == 0f)
138 {
139 PitchInput = -PitchAngle*m_AutoPitchLevel;
140 PitchInput -= Mathf.Abs(m_BankedTurnAmount*m_BankedTurnAmount*m_AutoTurnPitch);
141 }
142 }
143
144
145 private void CalculateForwardSpeed()
146 {
147 // Forward speed is the speed in the planes's forward direction (not the same as its velocity, eg if falling in a stall)
148 var localVelocity = transform.InverseTransformDirection(m_Rigidbody.velocity);
149 ForwardSpeed = Mathf.Max(0, localVelocity.z);
150 }
151
152
153 private void ControlThrottle()
154 {
155 // override throttle if immobilized
156 if (m_Immobilized)
157 {
158 ThrottleInput = -0.5f;
159 }
160
161 // Adjust throttle based on throttle input (or immobilized state)
162 Throttle = Mathf.Clamp01(Throttle + ThrottleInput*Time.deltaTime*m_ThrottleChangeSpeed);
163
164 // current engine power is just:
165 EnginePower = Throttle*m_MaxEnginePower;
166 }
167
168
169 private void CalculateDrag()
170 {
171 // increase the drag based on speed, since a constant drag doesn't seem "Real" (tm) enough
172 float extraDrag = m_Rigidbody.velocity.magnitude*m_DragIncreaseFactor;
173 // Air brakes work by directly modifying drag. This part is actually pretty realistic!
174 m_Rigidbody.drag = (AirBrakes ? (m_OriginalDrag + extraDrag)*m_AirBrakesEffect : m_OriginalDrag + extraDrag);
175 // Forward speed affects angular drag - at high forward speed, it's much harder for the plane to spin
176 m_Rigidbody.angularDrag = m_OriginalAngularDrag*ForwardSpeed;
177 }
178
179
180 private void CaluclateAerodynamicEffect()
181 {
182 // "Aerodynamic" calculations. This is a very simple approximation of the effect that a plane
183 // will naturally try to align itself in the direction that it's facing when moving at speed.
184 // Without this, the plane would behave a bit like the asteroids spaceship!
185 if (m_Rigidbody.velocity.magnitude > 0)
186 {
187 // compare the direction we're pointing with the direction we're moving:
188 m_AeroFactor = Vector3.Dot(transform.forward, m_Rigidbody.velocity.normalized);
189 // multipled by itself results in a desirable rolloff curve of the effect
190 m_AeroFactor *= m_AeroFactor;
191 // Finally we calculate a new velocity by bending the current velocity direction towards
192 // the the direction the plane is facing, by an amount based on this aeroFactor
193 var newVelocity = Vector3.Lerp(m_Rigidbody.velocity, transform.forward*ForwardSpeed,
194 m_AeroFactor*ForwardSpeed*m_AerodynamicEffect*Time.deltaTime);
195 m_Rigidbody.velocity = newVelocity;
196
197 // also rotate the plane towards the direction of movement - this should be a very small effect, but means the plane ends up
198 // pointing downwards in a stall
199 m_Rigidbody.rotation = Quaternion.Slerp(m_Rigidbody.rotation,
200 Quaternion.LookRotation(m_Rigidbody.velocity, transform.up),
201 m_AerodynamicEffect*Time.deltaTime);
202 }
203 }
204
205
206 private void CalculateLinearForces()
207 {
208 // Now calculate forces acting on the aeroplane:
209 // we accumulate forces into this variable:
210 var forces = Vector3.zero;
211 // Add the engine power in the forward direction
212 forces += EnginePower*transform.forward;
213 // The direction that the lift force is applied is at right angles to the plane's velocity (usually, this is 'up'!)
214 var liftDirection = Vector3.Cross(m_Rigidbody.velocity, transform.right).normalized;
215 // The amount of lift drops off as the plane increases speed - in reality this occurs as the pilot retracts the flaps
216 // shortly after takeoff, giving the plane less drag, but less lift. Because we don't simulate flaps, this is
217 // a simple way of doing it automatically:
218 var zeroLiftFactor = Mathf.InverseLerp(m_ZeroLiftSpeed, 0, ForwardSpeed);
219 // Calculate and add the lift power
220 var liftPower = ForwardSpeed*ForwardSpeed*m_Lift*zeroLiftFactor*m_AeroFactor;
221 forces += liftPower*liftDirection;
222 // Apply the calculated forces to the the Rigidbody
223 m_Rigidbody.AddForce(forces);
224 }
225
226
227 private void CalculateTorque()
228 {
229 // We accumulate torque forces into this variable:
230 var torque = Vector3.zero;
231 // Add torque for the pitch based on the pitch input.
232 torque += PitchInput*m_PitchEffect*transform.right;
233 // Add torque for the yaw based on the yaw input.
234 torque += YawInput*m_YawEffect*transform.up;
235 // Add torque for the roll based on the roll input.
236 torque += -RollInput*m_RollEffect*transform.forward;
237 // Add torque for banked turning.
238 torque += m_BankedTurnAmount*m_BankedTurnEffect*transform.up;
239 // The total torque is multiplied by the forward speed, so the controls have more effect at high speed,
240 // and little effect at low speed, or when not moving in the direction of the nose of the plane
241 // (i.e. falling while stalled)
242 m_Rigidbody.AddTorque(torque*ForwardSpeed*m_AeroFactor);
243 }
244
245
246 private void CalculateAltitude()
247 {
248 // Altitude calculations - we raycast downwards from the aeroplane
249 // starting a safe distance below the plane to avoid colliding with any of the plane's own colliders
250 var ray = new Ray(transform.position - Vector3.up*10, -Vector3.up);
251 RaycastHit hit;
252 Altitude = Physics.Raycast(ray, out hit) ? hit.distance + 10 : transform.position.y;
253 }
254
255
256 // Immobilize can be called from other objects, for example if this plane is hit by a weapon and should become uncontrollable
257 public void Immobilize()
258 {
259 m_Immobilized = true;
260 }
261
262
263 // Reset is called via the ObjectResetter script, if present.
264 public void Reset()
265 {
266 m_Immobilized = false;
267 }
268 }
269 }