using System.Collections.Generic; using UnityEngine; using Gley.UrbanSystem.Internal; namespace Gley.TrafficSystem.Internal { /// /// Decides what the vehicle will do next based on received information /// internal class DrivingAI : IDestroyable { private readonly ActiveActions[] _driveActions; private readonly BlinkType[] _blinkTypes; private readonly float[] _waypointSpeed; private readonly DriveActions[] _currentActiveAction; private readonly DriveActions[] _movingActions = new DriveActions[] { DriveActions.AvoidReverse, DriveActions.Reverse, DriveActions.StopInDistance, DriveActions.Follow, DriveActions.Overtake }; private readonly WaypointManager _waypointManager; private readonly TrafficWaypointsDataHandler _trafficWaypointsDataHandler; private readonly AllVehiclesDataHandler _allVehiclesDataHandler; private readonly VehiclePositioningSystem _vehiclePositioningSystem; private readonly PositionValidator _positionValidator; private PlayerInTrigger _playerInTrigger; private DynamicObstacleInTrigger _dynamicObstacleInTrigger; private BuildingInTrigger _buildingInTrigger; private VehicleCrash _vehicleCrash; /// /// Initialize Driving AI /// internal DrivingAI(int nrOfVehicles, WaypointManager waypointManager, TrafficWaypointsDataHandler trafficWaypointsDataHandler, AllVehiclesDataHandler trafficVehicles, VehiclePositioningSystem vehiclePositioningSystem, PositionValidator positionValidator, PlayerInTrigger playerInTrigger, DynamicObstacleInTrigger dynamicObstacleInTrigger, BuildingInTrigger buildingInTrigger, VehicleCrash vehicleCrash) { _waypointManager = waypointManager; _trafficWaypointsDataHandler = trafficWaypointsDataHandler; _allVehiclesDataHandler = trafficVehicles; _vehiclePositioningSystem = vehiclePositioningSystem; _positionValidator = positionValidator; _playerInTrigger = playerInTrigger; _dynamicObstacleInTrigger = dynamicObstacleInTrigger; _buildingInTrigger = buildingInTrigger; _driveActions = new ActiveActions[nrOfVehicles]; _waypointSpeed = new float[nrOfVehicles]; _blinkTypes = new BlinkType[nrOfVehicles]; _currentActiveAction = new DriveActions[nrOfVehicles]; SetVehicleCrashDelegate(vehicleCrash); for (int i = 0; i < nrOfVehicles; i++) { _driveActions[i] = new ActiveActions(new List()); } Assign(); //triggered every time a new object is seen by the front trigger VehicleEvents.onObjectInTrigger += ObjectInTriggerHandler; //triggered every time there are no objects left in trigger VehicleEvents.onTriggerCleared += TriggerClearedHandler; WaypointEvents.onStopStateChanged += StopStateChangedHandler; WaypointEvents.onGiveWayStateChanged += GiveWayStateChangedHandler; } public void Assign() { DestroyableManager.Instance.Register(this); } internal void SetPlayerInTriggerDelegate(PlayerInTrigger newDelegate) { _playerInTrigger = newDelegate; } internal void SetDynamicObstacleInTriggerDelegate(DynamicObstacleInTrigger newDelegate) { _dynamicObstacleInTrigger = newDelegate; } internal void SetBuildingInTriggerDelegate(BuildingInTrigger newDelegate) { _buildingInTrigger = newDelegate; } internal void SetVehicleCrashDelegate(VehicleCrash newDelegate) { Events.onVehicleCrashed -= _vehicleCrash; _vehicleCrash = newDelegate; Events.onVehicleCrashed += _vehicleCrash; } /// /// Reset all pending actions, used when a vehicle is respawned /// /// internal void VehicleActivated(int vehicleIndex) { _waypointSpeed[vehicleIndex] = _trafficWaypointsDataHandler.GetMaxSpeed(_waypointManager.GetTargetWaypointIndex(vehicleIndex)); SetBlinkType(vehicleIndex, BlinkType.Stop, true); AIEvents.TriggetChangeDrivingStateEvent(vehicleIndex, _currentActiveAction[vehicleIndex], GetActionValue(_currentActiveAction[vehicleIndex], vehicleIndex)); } internal void RemoveVehicle(int index) { _driveActions[index] = new ActiveActions(new List()); _currentActiveAction[index] = DriveActions.Forward; } internal void RemoveDriveAction(int index, DriveActions newAction) { //car is out of trigger -> remove current action if (newAction == DriveActions.Continue) { // remove all active actions _driveActions[index].RemoveAll(_movingActions); } else { //remove just current action _driveActions[index].Remove(newAction); } ApplyAction(index); } internal void TimedActionEnded(int index) { switch (_currentActiveAction[index]) { case DriveActions.Follow: AddDriveAction(index, DriveActions.Overtake); break; case DriveActions.Reverse: case DriveActions.StopTemp: case DriveActions.AvoidReverse: RemoveDriveAction(index, _currentActiveAction[index]); _allVehiclesDataHandler.CurrentVehicleActionDone(index); break; default: RemoveDriveAction(index, _currentActiveAction[index]); break; } } internal void WaypointRequested(int vehicleIndex, VehicleTypes vehicleType, bool clearPath) { int freeWaypointIndex; for (int i = 0; i < _driveActions[vehicleIndex].CurrentActiveActions.Count; i++) { DriveActions activeAction = _driveActions[vehicleIndex].CurrentActiveActions[i].ActionType; switch (activeAction) { case DriveActions.StopInPoint: //if current action is stop in point -> no new waypoint is needed if (_waypointManager.HasPath(vehicleIndex)) { if (_waypointManager.GetPath(vehicleIndex).Count == 0) { Events.TriggerDestinationReachedEvent(vehicleIndex); RemoveDriveAction(vehicleIndex, DriveActions.StopInPoint); } } return; case DriveActions.ChangeLane: //if the current vehicle can overtake if (_driveActions[vehicleIndex].CurrentActiveActions[i].Side == RoadSide.Any) { freeWaypointIndex = _waypointManager.GetOtherLaneWaypointIndex(vehicleIndex, vehicleType); } else { freeWaypointIndex = _waypointManager.GetOtherLaneWaypointIndex(vehicleIndex, vehicleType, _driveActions[vehicleIndex].CurrentActiveActions[i].Side, _allVehiclesDataHandler.GetForwardVector(vehicleIndex)); } if (freeWaypointIndex == -1) { //if cannot change lane ContinueStraight(vehicleIndex, vehicleType); if (clearPath) { if (!_trafficWaypointsDataHandler.GetName(_waypointManager.GetTargetWaypointIndex(vehicleIndex)).Contains("Connect")) { _allVehiclesDataHandler.SetMaxSpeed(vehicleIndex, _allVehiclesDataHandler.GetMaxSpeed(vehicleIndex) * 0.7f); } else { _allVehiclesDataHandler.ResetMaxSpeed(vehicleIndex); } } } else { if (clearPath) { _allVehiclesDataHandler.ResetMaxSpeed(vehicleIndex); } Blink(BlinkReasons.Overtake, vehicleIndex, freeWaypointIndex); //can overtake, make sure path is free if (AllClear(vehicleIndex, freeWaypointIndex, clearPath)) { if (!clearPath) { RemoveDriveAction(vehicleIndex, DriveActions.ChangeLane); } SetNextWaypoint(vehicleIndex, freeWaypointIndex); } else { ContinueStraight(vehicleIndex, vehicleType); } } return; case DriveActions.Overtake: //if the current vehicle can overtake freeWaypointIndex = _waypointManager.GetOtherLaneWaypointIndex(vehicleIndex, vehicleType); if (freeWaypointIndex == TrafficSystemConstants.INVALID_WAYPOINT_INDEX) { //if cannot change lane ContinueStraight(vehicleIndex, vehicleType); } else { Blink(BlinkReasons.Overtake, vehicleIndex, freeWaypointIndex); //can overtake, make sure path is free if (AllClear(vehicleIndex, freeWaypointIndex, false)) { //if can change lane -> start blinking SetNextWaypoint(vehicleIndex, freeWaypointIndex); } else { ContinueStraight(vehicleIndex, vehicleType); } } return; case DriveActions.GiveWay: if (_trafficWaypointsDataHandler.IsInIntersection(_waypointManager.GetTargetWaypointIndex(vehicleIndex))) { if (_waypointManager.CanEnterIntersection(vehicleIndex)) { freeWaypointIndex = _waypointManager.GetCurrentLaneWaypointIndex(vehicleIndex, vehicleType); if (freeWaypointIndex != -1) { RemoveDriveAction(vehicleIndex, DriveActions.GiveWay); Blink(BlinkReasons.ChangeLane, vehicleIndex, freeWaypointIndex); SetNextWaypoint(vehicleIndex, freeWaypointIndex); } } } else { int currentWaypointIndex = _waypointManager.GetTargetWaypointIndex(vehicleIndex); if (_trafficWaypointsDataHandler.IsComplexGiveWay(currentWaypointIndex)) { freeWaypointIndex = _waypointManager.GetCurrentLaneWaypointIndex(vehicleIndex, vehicleType); if (freeWaypointIndex != TrafficSystemConstants.INVALID_WAYPOINT_INDEX) { Blink(BlinkReasons.GiveWay, vehicleIndex, freeWaypointIndex); if (!_waypointManager.AreTheseWaypointsATarget(_trafficWaypointsDataHandler.GetGiveWayWaypointList(currentWaypointIndex))) { RemoveDriveAction(vehicleIndex, DriveActions.GiveWay); SetNextWaypoint(vehicleIndex, freeWaypointIndex); } } return; } freeWaypointIndex = _waypointManager.GetOtherLaneWaypointIndex(vehicleIndex, vehicleType); if (freeWaypointIndex == -1) { freeWaypointIndex = _waypointManager.GetCurrentLaneWaypointIndex(vehicleIndex, vehicleType); } if (freeWaypointIndex != -1) { Blink(BlinkReasons.GiveWay, vehicleIndex, freeWaypointIndex); if (_trafficWaypointsDataHandler.IsZipperGiveWay(freeWaypointIndex)) { if (!_waypointManager.IsThisWaypointATarget(freeWaypointIndex)) { RemoveDriveAction(vehicleIndex, DriveActions.GiveWay); SetNextWaypoint(vehicleIndex, freeWaypointIndex); } return; } if (AllClear(vehicleIndex, freeWaypointIndex, clearPath)) { if (_trafficWaypointsDataHandler.HasNeighbors(currentWaypointIndex)) { RemoveDriveAction(vehicleIndex, DriveActions.GiveWay); } SetNextWaypoint(vehicleIndex, freeWaypointIndex); } } else { Blink(BlinkReasons.NoWaypoint, vehicleIndex, freeWaypointIndex); AddDriveAction(vehicleIndex, DriveActions.NoWaypoint); } } //If current vehicle has to give way -> wait until new waypoint is free return; } } //if current vehicle is in no special state -> set next waypoint without any special requirements freeWaypointIndex = _waypointManager.GetCurrentLaneWaypointIndex(vehicleIndex, vehicleType); if (freeWaypointIndex >= 0) { Blink(BlinkReasons.None, vehicleIndex, freeWaypointIndex); SetNextWaypoint(vehicleIndex, freeWaypointIndex); if (!_waypointManager.CanContinueStraight(vehicleIndex, vehicleType)) { AddDriveAction(vehicleIndex, DriveActions.GiveWay); } //remove the no waypoint action if waypoints are found -> used for temporary disable waypoints if (_driveActions[vehicleIndex].CurrentActiveActions.Count > 0) { if (_driveActions[vehicleIndex].CurrentActiveActions[0].ActionType == DriveActions.NoWaypoint) { RemoveDriveAction(vehicleIndex, DriveActions.NoWaypoint); } if (_driveActions[vehicleIndex].CurrentActiveActions[0].ActionType == DriveActions.NoPath) { RemoveDriveAction(vehicleIndex, DriveActions.NoPath); } } } else { NoWaypointsAvailable(vehicleIndex, freeWaypointIndex); } } internal void SetBlinkType(int vehicleIndex, BlinkType value, bool reset = false) { if (reset) { _blinkTypes[vehicleIndex] = BlinkType.Stop; } if (value == BlinkType.Stop && _blinkTypes[vehicleIndex] == BlinkType.Hazard) { return; } if (_blinkTypes[vehicleIndex] != value) { _blinkTypes[vehicleIndex] = value; _allVehiclesDataHandler.SetBlinkLights(vehicleIndex, value); } } internal void SetHazardLights(int vehicleIndex, bool activate) { if (activate) { SetBlinkType(vehicleIndex, BlinkType.Hazard); } else { SetBlinkType(vehicleIndex, BlinkType.Stop, true); } } internal void ChangeLane(bool active, int vehicleIndex, RoadSide side) { if (active) { AddDriveAction(vehicleIndex, DriveActions.ChangeLane, false, side); } else { RemoveDriveAction(vehicleIndex, DriveActions.ChangeLane); } } internal void AddDriveAction(int index, DriveActions newAction, bool force = false, RoadSide side = RoadSide.Any) { if (newAction == DriveActions.ForceForward) { force = true; newAction = DriveActions.Forward; } if (force) { _driveActions[index].CurrentActiveActions.Clear(); } //if the new action is not already in the list-> add it in the required position based on priority if (!_driveActions[index].Contains(newAction)) { bool added = false; for (int i = 0; i < _driveActions[index].CurrentActiveActions.Count; i++) { if (_driveActions[index].CurrentActiveActions[i].ActionType < newAction) { _driveActions[index].CurrentActiveActions.Insert(i, new DriveAction(newAction, side)); added = true; break; } } if (added == false) { _driveActions[index].Add(new DriveAction(newAction, side)); } ApplyAction(index); } } /// /// Compute current maximum available speed in m/s /// /// /// internal float GetMaxSpeedMS(int index) { return ComputeMaxPossibleSpeed(index) / 3.6f; } internal float GetWaypointSpeed(int vehicleIndex) { return _waypointSpeed[vehicleIndex]; } /// /// Based on position, heading, and speed decide what is the next action of the vehicle /// /// /// /// /// private DriveActions GetTriggerAction(int myIndex, Collider other) { VehicleComponent otherVehicle = other.attachedRigidbody.GetComponent(); int otherIndex = otherVehicle.ListIndex; //if it already in other vehicle trigger, stop if (otherVehicle.AlreadyCollidingWith(_allVehiclesDataHandler.GetAllColliders(myIndex))) { if (otherVehicle.CurrentAction != DriveActions.StopTemp && otherVehicle.CurrentAction != DriveActions.StopInDistance && otherVehicle.CurrentAction != DriveActions.GiveWay) { return DriveActions.StopTemp; } else { return DriveActions.ForceForward; } } //if reverse is true, means that other car is reversing so I have to reverse //if (reverse) //{ // return SpecialDriveActionTypes.Reverse; //} bool sameOrientation = _vehiclePositioningSystem.IsSameOrientation(_allVehiclesDataHandler.GetHeading(myIndex), _allVehiclesDataHandler.GetHeading(otherIndex)); //if other car is stationary if (_allVehiclesDataHandler.GetCurrentAction(otherIndex) == DriveActions.StopInDistance || _allVehiclesDataHandler.GetCurrentAction(otherIndex) == DriveActions.StopInPoint || _allVehiclesDataHandler.GetCurrentAction(otherIndex) == DriveActions.GiveWay) { if (sameOrientation) { //if the orientation is the same I stop too return DriveActions.StopInDistance; } } else { bool sameHeading = _vehiclePositioningSystem.IsSameHeading(_allVehiclesDataHandler.GetForwardVector(otherIndex), _allVehiclesDataHandler.GetForwardVector(myIndex)); bool otherIsGoingForward = _vehiclePositioningSystem.IsGoingForward(_allVehiclesDataHandler.GetVelocity(otherIndex), _allVehiclesDataHandler.GetHeading(otherIndex)); if (sameOrientation == false && sameHeading == false) { //not same orientation -> going in opposite direction-> try to avoid it //return DriveActions.AvoidForward; } else { //same orientation but different moving direction if (otherIsGoingForward == false) { // other car is going in reverse so I should also return DriveActions.Reverse; } } if (sameHeading == false) { //going back and hit something -> wait return DriveActions.StopTemp; } else { //follow the car in front if (_allVehiclesDataHandler.GetVelocity(myIndex).sqrMagnitude > 5 && _allVehiclesDataHandler.GetVelocity(otherIndex).sqrMagnitude > 5) { //if the relative angle between the 2 cars is small enough -> follow if (Mathf.Abs(Vector3.SignedAngle(_allVehiclesDataHandler.GetForwardVector(otherIndex), _allVehiclesDataHandler.GetForwardVector(myIndex), Vector3.up)) < 35) { return DriveActions.Follow; } } } //if nothing worked, stop in distance return DriveActions.StopInDistance; } //continue forward return DriveActions.Forward; } private void ObjectInTriggerHandler(int vehicleIndex, ObstacleTypes obstacleType, Collider other) { switch (obstacleType) { case ObstacleTypes.TrafficVehicle: AddDriveAction(vehicleIndex, GetTriggerAction(vehicleIndex, other)); break; case ObstacleTypes.Player: PlayerInTrigger(vehicleIndex, other); break; case ObstacleTypes.DynamicObject: DynamicObjectInTrigger(vehicleIndex, other); break; case ObstacleTypes.StaticObject: BuildingObjectInTrigger(vehicleIndex, other); break; } } private void PlayerInTrigger(int vehicleIndex, Collider other) { _playerInTrigger?.Invoke(vehicleIndex, other); } private void DynamicObjectInTrigger(int vehicleIndex, Collider other) { _dynamicObstacleInTrigger?.Invoke(vehicleIndex, other); } private void BuildingObjectInTrigger(int vehicleIndex, Collider other) { _buildingInTrigger?.Invoke(vehicleIndex, other); } /// /// Apply the first action from list /// /// private void ApplyAction(int index) { //if trigger is true, other vehicles needs to be alerted that the current action changed bool trigger = false; if (_driveActions[index].CurrentActiveActions.Count == 0) { //if list is empty, go forward by default _currentActiveAction[index] = DriveActions.Forward; trigger = true; } else { if (_currentActiveAction[index] != _driveActions[index].CurrentActiveActions[0].ActionType) { trigger = true; _currentActiveAction[index] = _driveActions[index].CurrentActiveActions[0].ActionType; } } //if (currentActiveAction[index] != SpecialDriveActionTypes.Follow && currentActiveAction[index] != SpecialDriveActionTypes.Overtake) //{ // //reset follow speed if no longer follow a vehicle // Debug.Log("RESET FOLLWO SPEED " + index); // vehicleToFollow[index] = -1; AIEvents.TriggerChangeDestinationEvent(index); //} if (trigger) { //trigger corresponding events based on new action switch (_currentActiveAction[index]) { case DriveActions.Reverse: case DriveActions.AvoidReverse: case DriveActions.StopInDistance: case DriveActions.StopInPoint: case DriveActions.GiveWay: AIEvents.TriggerNotifyVehiclesEvent(index, _allVehiclesDataHandler.GetCollider(index)); break; } AIEvents.TriggetChangeDrivingStateEvent(index, _currentActiveAction[index], GetActionValue(_currentActiveAction[index], index)); } } /// /// Returns execution times for each action /// /// /// private float GetActionValue(TrafficSystem.DriveActions action, int index) { switch (action) { case DriveActions.Reverse: return 5; case DriveActions.StopTemp: return Random.Range(3, 5); case DriveActions.Follow: return 1; default: return Mathf.Infinity; } } /// /// Called when a waypoint state changed to update the current vehicle actions /// /// /// /// private void StopStateChangedHandler(int index, bool stopState) { if (stopState == true) { AddDriveAction(index, DriveActions.StopInPoint); } else { RemoveDriveAction(index, DriveActions.StopInPoint); } } private void GiveWayStateChangedHandler(int index, bool giveWayState) { if (giveWayState) { AddDriveAction(index, DriveActions.GiveWay); } else { RemoveDriveAction(index, DriveActions.GiveWay); } } private bool AllClear(int vehicleIndex, int freeWaypointIndex, bool clearPath) { //get the average speed of the car float maxWaypointSpeed = _trafficWaypointsDataHandler.GetMaxSpeed(freeWaypointIndex); float maxCarSpeed = Mathf.Min(maxWaypointSpeed, _allVehiclesDataHandler.GetMaxSpeed(vehicleIndex)); //average between current speed and max speed float averageSpeed = (_allVehiclesDataHandler.GetCurrentSpeed(vehicleIndex) + maxCarSpeed) / 2 / 3.6f; //calculate the distance to the next waypoint float distance = Vector3.Distance(_trafficWaypointsDataHandler.GetPosition(_waypointManager.GetTargetWaypointIndex(vehicleIndex)), _trafficWaypointsDataHandler.GetPosition(freeWaypointIndex)); //time it takes for the car to reach next waypoint float time = distance / averageSpeed; //distance needed to be free on the road float distanceToCheck = maxWaypointSpeed * time; if (clearPath) { distanceToCheck *= 0.1f; } int incomingCarIndex = -1; //if everything is free -> can go if (_waypointManager.AllPreviousWaypointsAreFree(vehicleIndex, distanceToCheck, freeWaypointIndex, ref incomingCarIndex)) { return true; } else { //check speed if (incomingCarIndex != TrafficSystemConstants.INVALID_VEHICLE_INDEX) { if (_allVehiclesDataHandler.GetCurrentSpeed(incomingCarIndex) < 1) { if (!_waypointManager.IsThisWaypointATarget(freeWaypointIndex)) { VehicleComponent vehicle = _allVehiclesDataHandler.GetVehicle(vehicleIndex); if (_positionValidator.IsPositionFree(_trafficWaypointsDataHandler.GetPosition(freeWaypointIndex), vehicle.length, vehicle.coliderHeight, vehicle.ColliderWidth, _waypointManager.GetNextOrientation(freeWaypointIndex))) { return true; } } } } } return false; } private void ContinueStraight(int vehicleIndex, VehicleTypes vehicleType) { //get new waypoint on the same lane int freeWaypointIndex = _waypointManager.GetCurrentLaneWaypointIndex(vehicleIndex, vehicleType); if (freeWaypointIndex >= 0) { SetNextWaypoint(vehicleIndex, freeWaypointIndex); } else { Blink(BlinkReasons.NoWaypoint, vehicleIndex, freeWaypointIndex); NoWaypointsAvailable(vehicleIndex, freeWaypointIndex); } } private void NoWaypointsAvailable(int vehicleIndex, int waypointIndex) { if (waypointIndex == -2) { AddDriveAction(vehicleIndex, DriveActions.NoPath); Events.TriggerDestinationReachedEvent(vehicleIndex); return; } AddDriveAction(vehicleIndex, DriveActions.NoWaypoint); } private void SetNextWaypoint(int vehicleIndex, int freeWaypointIndex) { _waypointManager.SetNextWaypoint(vehicleIndex, freeWaypointIndex); _waypointSpeed[vehicleIndex] = _trafficWaypointsDataHandler.GetMaxSpeed(_waypointManager.GetTargetWaypointIndex(vehicleIndex)); AIEvents.TriggerChangeDestinationEvent(vehicleIndex); } /// /// Determine if blink is required /// private void Blink(BlinkReasons blinkReason, int index, int newWaypointindex) { if (blinkReason == BlinkReasons.NoWaypoint) { SetBlinkType(index, BlinkType.Hazard); return; } int oldWaypointIndex = _waypointManager.GetTargetWaypointIndex(index); Vector3 forward = _allVehiclesDataHandler.GetForwardVector(index); int targetWaypointIndex = newWaypointindex; if (blinkReason == BlinkReasons.None) { if (_trafficWaypointsDataHandler.GetNeighbors(oldWaypointIndex).Length > 1) { blinkReason = BlinkReasons.ChangeLane; } } switch (blinkReason) { case BlinkReasons.Overtake: case BlinkReasons.GiveWay: float angle = Vector3.SignedAngle(forward, _trafficWaypointsDataHandler.GetPosition(newWaypointindex) - _trafficWaypointsDataHandler.GetPosition(oldWaypointIndex), Vector3.up); SetBlinkType(index, DetermineBlinkDirection(angle)); break; case BlinkReasons.ChangeLane: for (int i = 0; i < 5; i++) { if (_trafficWaypointsDataHandler.HasNeighbors(targetWaypointIndex)) { targetWaypointIndex = _trafficWaypointsDataHandler.GetNeighbors(targetWaypointIndex)[0]; } } angle = Vector3.SignedAngle(_trafficWaypointsDataHandler.GetPosition(oldWaypointIndex) - _trafficWaypointsDataHandler.GetPosition(_trafficWaypointsDataHandler.GetPrevs(oldWaypointIndex)[0]), _trafficWaypointsDataHandler.GetPosition(targetWaypointIndex) - _trafficWaypointsDataHandler.GetPosition(oldWaypointIndex), Vector3.up); SetBlinkType(index, DetermineBlinkDirection(angle)); break; case BlinkReasons.None: if (_trafficWaypointsDataHandler.HasNeighbors(newWaypointindex)) { targetWaypointIndex = _trafficWaypointsDataHandler.GetNeighbors(targetWaypointIndex)[0]; angle = Vector3.SignedAngle(_trafficWaypointsDataHandler.GetPosition(oldWaypointIndex) - _trafficWaypointsDataHandler.GetPosition(newWaypointindex), _trafficWaypointsDataHandler.GetPosition(oldWaypointIndex) - _trafficWaypointsDataHandler.GetPosition(targetWaypointIndex), Vector3.up); if (Mathf.Abs(angle) < 1) { SetBlinkType(index, BlinkType.Stop); } } break; case BlinkReasons.NoWaypoint: SetBlinkType(index, BlinkType.Hazard); break; } } /// /// Determine the blink direction /// private BlinkType DetermineBlinkDirection(float angle) { if (angle > 5) { return BlinkType.BlinkRight; } if (angle < -5) { return BlinkType.BlinkLeft; } return BlinkType.Stop; } private float ComputeMaxPossibleSpeed(int index) { float maxSpeed; if (_currentActiveAction[index] == DriveActions.Follow || _currentActiveAction[index] == DriveActions.Overtake) { maxSpeed = Mathf.Min(_allVehiclesDataHandler.GetFollowSpeed(index) * 3.6f, _allVehiclesDataHandler.GetMaxSpeed(index), _waypointSpeed[index]); } else { maxSpeed = Mathf.Min(_allVehiclesDataHandler.GetMaxSpeed(index), _waypointSpeed[index]); } return maxSpeed; } private void TriggerClearedHandler(int vehicleIndex) { RemoveDriveAction(vehicleIndex, DriveActions.Continue); } /// /// Events cleanup /// public void OnDestroy() { Events.onVehicleCrashed -= _vehicleCrash; VehicleEvents.onTriggerCleared -= TriggerClearedHandler; WaypointEvents.onStopStateChanged -= StopStateChangedHandler; WaypointEvents.onGiveWayStateChanged -= GiveWayStateChangedHandler; VehicleEvents.onObjectInTrigger -= ObjectInTriggerHandler; } } }