CarAIControl.cs
13.3 KB
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
using System;
using UnityEngine;
using Random = UnityEngine.Random;
#pragma warning disable 649
namespace UnityStandardAssets.Vehicles.Car
{
[RequireComponent(typeof (CarController))]
public class CarAIControl : MonoBehaviour
{
public enum BrakeCondition
{
NeverBrake, // the car simply accelerates at full throttle all the time.
TargetDirectionDifference, // the car will brake according to the upcoming change in direction of the target. Useful for route-based AI, slowing for corners.
TargetDistance, // the car will brake as it approaches its target, regardless of the target's direction. Useful if you want the car to
// head for a stationary target and come to rest when it arrives there.
}
// This script provides input to the car controller in the same way that the user control script does.
// As such, it is really 'driving' the car, with no special physics or animation tricks to make the car behave properly.
// "wandering" is used to give the cars a more human, less robotic feel. They can waver slightly
// in speed and direction while driving towards their target.
[SerializeField] [Range(0, 1)] private float m_CautiousSpeedFactor = 0.05f; // percentage of max speed to use when being maximally cautious
[SerializeField] [Range(0, 180)] private float m_CautiousMaxAngle = 50f; // angle of approaching corner to treat as warranting maximum caution
[SerializeField] private float m_CautiousMaxDistance = 100f; // distance at which distance-based cautiousness begins
[SerializeField] private float m_CautiousAngularVelocityFactor = 30f; // how cautious the AI should be when considering its own current angular velocity (i.e. easing off acceleration if spinning!)
[SerializeField] private float m_SteerSensitivity = 0.05f; // how sensitively the AI uses steering input to turn to the desired direction
[SerializeField] private float m_AccelSensitivity = 0.04f; // How sensitively the AI uses the accelerator to reach the current desired speed
[SerializeField] private float m_BrakeSensitivity = 1f; // How sensitively the AI uses the brake to reach the current desired speed
[SerializeField] private float m_LateralWanderDistance = 3f; // how far the car will wander laterally towards its target
[SerializeField] private float m_LateralWanderSpeed = 0.1f; // how fast the lateral wandering will fluctuate
[SerializeField] [Range(0, 1)] private float m_AccelWanderAmount = 0.1f; // how much the cars acceleration will wander
[SerializeField] private float m_AccelWanderSpeed = 0.1f; // how fast the cars acceleration wandering will fluctuate
[SerializeField] private BrakeCondition m_BrakeCondition = BrakeCondition.TargetDistance; // what should the AI consider when accelerating/braking?
[SerializeField] private bool m_Driving; // whether the AI is currently actively driving or stopped.
[SerializeField] private Transform m_Target; // 'target' the target object to aim for.
[SerializeField] private bool m_StopWhenTargetReached; // should we stop driving when we reach the target?
[SerializeField] private float m_ReachTargetThreshold = 2; // proximity to target to consider we 'reached' it, and stop driving.
private float m_RandomPerlin; // A random value for the car to base its wander on (so that AI cars don't all wander in the same pattern)
private CarController m_CarController; // Reference to actual car controller we are controlling
private float m_AvoidOtherCarTime; // time until which to avoid the car we recently collided with
private float m_AvoidOtherCarSlowdown; // how much to slow down due to colliding with another car, whilst avoiding
private float m_AvoidPathOffset; // direction (-1 or 1) in which to offset path to avoid other car, whilst avoiding
private Rigidbody m_Rigidbody;
private void Awake()
{
// get the car controller reference
m_CarController = GetComponent<CarController>();
// give the random perlin a random value
m_RandomPerlin = Random.value*100;
m_Rigidbody = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
if (m_Target == null || !m_Driving)
{
// Car should not be moving,
// use handbrake to stop
m_CarController.Move(0, 0, -1f, 1f);
}
else
{
Vector3 fwd = transform.forward;
if (m_Rigidbody.velocity.magnitude > m_CarController.MaxSpeed*0.1f)
{
fwd = m_Rigidbody.velocity;
}
float desiredSpeed = m_CarController.MaxSpeed;
// now it's time to decide if we should be slowing down...
switch (m_BrakeCondition)
{
case BrakeCondition.TargetDirectionDifference:
{
// the car will brake according to the upcoming change in direction of the target. Useful for route-based AI, slowing for corners.
// check out the angle of our target compared to the current direction of the car
float approachingCornerAngle = Vector3.Angle(m_Target.forward, fwd);
// also consider the current amount we're turning, multiplied up and then compared in the same way as an upcoming corner angle
float spinningAngle = m_Rigidbody.angularVelocity.magnitude*m_CautiousAngularVelocityFactor;
// if it's different to our current angle, we need to be cautious (i.e. slow down) a certain amount
float cautiousnessRequired = Mathf.InverseLerp(0, m_CautiousMaxAngle,
Mathf.Max(spinningAngle,
approachingCornerAngle));
desiredSpeed = Mathf.Lerp(m_CarController.MaxSpeed, m_CarController.MaxSpeed*m_CautiousSpeedFactor,
cautiousnessRequired);
break;
}
case BrakeCondition.TargetDistance:
{
// the car will brake as it approaches its target, regardless of the target's direction. Useful if you want the car to
// head for a stationary target and come to rest when it arrives there.
// check out the distance to target
Vector3 delta = m_Target.position - transform.position;
float distanceCautiousFactor = Mathf.InverseLerp(m_CautiousMaxDistance, 0, delta.magnitude);
// also consider the current amount we're turning, multiplied up and then compared in the same way as an upcoming corner angle
float spinningAngle = m_Rigidbody.angularVelocity.magnitude*m_CautiousAngularVelocityFactor;
// if it's different to our current angle, we need to be cautious (i.e. slow down) a certain amount
float cautiousnessRequired = Mathf.Max(
Mathf.InverseLerp(0, m_CautiousMaxAngle, spinningAngle), distanceCautiousFactor);
desiredSpeed = Mathf.Lerp(m_CarController.MaxSpeed, m_CarController.MaxSpeed*m_CautiousSpeedFactor,
cautiousnessRequired);
break;
}
case BrakeCondition.NeverBrake:
break;
}
// Evasive action due to collision with other cars:
// our target position starts off as the 'real' target position
Vector3 offsetTargetPos = m_Target.position;
// if are we currently taking evasive action to prevent being stuck against another car:
if (Time.time < m_AvoidOtherCarTime)
{
// slow down if necessary (if we were behind the other car when collision occured)
desiredSpeed *= m_AvoidOtherCarSlowdown;
// and veer towards the side of our path-to-target that is away from the other car
offsetTargetPos += m_Target.right*m_AvoidPathOffset;
}
else
{
// no need for evasive action, we can just wander across the path-to-target in a random way,
// which can help prevent AI from seeming too uniform and robotic in their driving
offsetTargetPos += m_Target.right*
(Mathf.PerlinNoise(Time.time*m_LateralWanderSpeed, m_RandomPerlin)*2 - 1)*
m_LateralWanderDistance;
}
// use different sensitivity depending on whether accelerating or braking:
float accelBrakeSensitivity = (desiredSpeed < m_CarController.CurrentSpeed)
? m_BrakeSensitivity
: m_AccelSensitivity;
// decide the actual amount of accel/brake input to achieve desired speed.
float accel = Mathf.Clamp((desiredSpeed - m_CarController.CurrentSpeed)*accelBrakeSensitivity, -1, 1);
// add acceleration 'wander', which also prevents AI from seeming too uniform and robotic in their driving
// i.e. increasing the accel wander amount can introduce jostling and bumps between AI cars in a race
accel *= (1 - m_AccelWanderAmount) +
(Mathf.PerlinNoise(Time.time*m_AccelWanderSpeed, m_RandomPerlin)*m_AccelWanderAmount);
// calculate the local-relative position of the target, to steer towards
Vector3 localTarget = transform.InverseTransformPoint(offsetTargetPos);
// work out the local angle towards the target
float targetAngle = Mathf.Atan2(localTarget.x, localTarget.z)*Mathf.Rad2Deg;
// get the amount of steering needed to aim the car towards the target
float steer = Mathf.Clamp(targetAngle*m_SteerSensitivity, -1, 1)*Mathf.Sign(m_CarController.CurrentSpeed);
// feed input to the car controller.
m_CarController.Move(steer, accel, accel, 0f);
// if appropriate, stop driving when we're close enough to the target.
if (m_StopWhenTargetReached && localTarget.magnitude < m_ReachTargetThreshold)
{
m_Driving = false;
}
}
}
private void OnCollisionStay(Collision col)
{
// detect collision against other cars, so that we can take evasive action
if (col.rigidbody != null)
{
var otherAI = col.rigidbody.GetComponent<CarAIControl>();
if (otherAI != null)
{
// we'll take evasive action for 1 second
m_AvoidOtherCarTime = Time.time + 1;
// but who's in front?...
if (Vector3.Angle(transform.forward, otherAI.transform.position - transform.position) < 90)
{
// the other ai is in front, so it is only good manners that we ought to brake...
m_AvoidOtherCarSlowdown = 0.5f;
}
else
{
// we're in front! ain't slowing down for anybody...
m_AvoidOtherCarSlowdown = 1;
}
// both cars should take evasive action by driving along an offset from the path centre,
// away from the other car
var otherCarLocalDelta = transform.InverseTransformPoint(otherAI.transform.position);
float otherCarAngle = Mathf.Atan2(otherCarLocalDelta.x, otherCarLocalDelta.z);
m_AvoidPathOffset = m_LateralWanderDistance*-Mathf.Sign(otherCarAngle);
}
}
}
public void SetTarget(Transform target)
{
m_Target = target;
m_Driving = true;
}
}
}