protected override void UpdateTargetDirection(Driver.Sensors sensors, Driver.Actuators actuators) { // Read player input. float turningInput = Input.GetAxis("Turning"); if (turningInput == 0) { // If the input was just released, square to 90 degrees. if (lastTurningInput != 0) { CardinalDirection cardinalDirection = DirectionHelpers.GetCardinalDirectionForVector(sensors.transform.forward); actuators.targetDirection = DirectionHelpers.cardinalDirectionVectors[cardinalDirection]; // Reset turning input time for next time. turningInputNonZeroTime = 0; } } else { // If the input was just changed, change lane. if (turningInput < 0 && lastTurningInput >= 0) { // We want to go left, but if our current target was to go right, just return to current lane. if (actuators.targetLane > sensors.carTracker.currentLane) { actuators.targetLane = sensors.carTracker.currentLane; } else { actuators.targetLane = Math.Max(sensors.carTracker.currentLane - 1, 0); } } else if (turningInput > 0 && lastTurningInput <= 0) { // We want to go right, but if our current target was to go left, just return to current lane. if (actuators.targetLane < sensors.carTracker.currentLane) { actuators.targetLane = sensors.carTracker.currentLane; } else { actuators.targetLane = Math.Min(sensors.carTracker.currentLane + 1, sensors.carTracker.representativeStreet.lanesCount + 1); } } // If turning has been active enough time, change target direction. turningInputNonZeroTime += Time.deltaTime; if (turningInputNonZeroTime > laneChangeOnlyDuration) { float rotationAngleDegrees = turningInput * maxAngleChangeDifferenceDegrees * Mathf.Sign(sensors.car.speed); Quaternion rotation = Quaternion.Euler(0, rotationAngleDegrees, 0); actuators.targetDirection = rotation * sensors.transform.forward; } } lastTurningInput = turningInput; }
private void UpdateTargetSpeed(Driver.Sensors sensors, Driver.Actuators actuators) { // Read player input. float speedChangeInput = Input.GetAxis("Speed Change"); // When the car has stopped and we're not pressing anything, reset speed sign. if (sensors.car.speed == 0 && speedChangeInput == 0) { speedSign = 0; } // Nothing to do if we're keeping the current speed and no change is requested. if (keepTargetSpeed && speedChangeInput == 0) { return; } if (speedChangeInput != 0) { // If sign is not set when we're stopped, determine the direction we want to go in. if (speedSign == 0 && sensors.car.speed == 0) { speedSign = Math.Sign(speedChangeInput); } // We want to change speed so calculate new target speed. float absoluteSpeedChangeInput = speedChangeInput * speedSign; float absoluteSpeedChange = absoluteSpeedChangeInput * speedChangeRate * Time.deltaTime; float newAbsoluteTargetSpeed = Mathf.Abs(actuators.targetSpeed) + absoluteSpeedChange; // Ensure minimum speed difference for faster reaction. float absoluteCarSpeed = Mathf.Abs(sensors.car.speed); if (absoluteSpeedChangeInput < 0) { newAbsoluteTargetSpeed = Mathf.Clamp(newAbsoluteTargetSpeed, 0, Mathf.Max(0, absoluteCarSpeed - minSpeedChangeDifference)); } else if (absoluteSpeedChangeInput > 0 && newAbsoluteTargetSpeed < absoluteCarSpeed + minSpeedChangeDifference) { newAbsoluteTargetSpeed = Mathf.Max(newAbsoluteTargetSpeed, absoluteCarSpeed + minSpeedChangeDifference); } // Set new target speed. actuators.targetSpeed = newAbsoluteTargetSpeed * speedSign; // Don't keep the speed until we stop changing it. keepTargetSpeed = false; } else { // Round the current car speed as the new target. actuators.targetSpeed = Mathf.Round(sensors.car.speed / roundingSpeedStep) * roundingSpeedStep; keepTargetSpeed = true; } }
public override void Act(Driver.Sensors sensors, Driver.Actuators actuators) { // By default we're intending to go straight. actuators.turningIntent = Driver.TurningIntent.Straight; UpdateNextIntersectionDirection(sensors); UpdateTargetSpeed(sensors, actuators); UpdateTargetLaneAndDirection(sensors, actuators); DrawDebugNextIntersectionDirection(sensors); }
private Intersection GetTargetIntersection(Driver.Sensors sensors) { if (sensors.carTracker.intersection != null) { return(sensors.carTracker.intersection); } Street currentStreet = sensors.carTracker.street; CardinalDirection currentDirection = sensors.carTracker.streetCardinalDirection; return(currentDirection == CardinalDirection.West || currentDirection == CardinalDirection.South ? currentStreet.startIntersection : currentStreet.endIntersection); }
private void UpdateNextIntersectionDirection(Driver.Sensors sensors) { // We need to be on a street and not having been on a street previously (as we would have already decided where to go). Street currentStreet = sensors.carTracker.street; if (currentStreet != null && previousStreet == null) { // Determine where to go at the end of this street. CardinalDirection currentDirection = sensors.carTracker.streetCardinalDirection; Intersection targetIntersection = GetTargetIntersection(sensors); // See where we might want to go. var potentialDirections = new List <CardinalDirection>(); CardinalDirection oppositeDirection = DirectionHelpers.GetOppositeDirection(currentDirection); foreach (CardinalDirection direction in DirectionHelpers.cardinalDirections) { // Don't try to go backwards by default. if (direction == oppositeDirection) { continue; } // Direction is possible if the intersection has this street. Street street = targetIntersection.GetStreetInDirection(direction); if (street != null) { // Make sure we're not entering into a one-way street from the wrong direction. if (street.isOneWay == false || !targetIntersection.HasStopLineForStreet(street)) { potentialDirections.Add(direction); } } } // Only if no options were there, go back. if (potentialDirections.Count == 0) { potentialDirections.Add(oppositeDirection); } // Choose a random option. int randomIndex = UnityEngine.Random.Range(0, potentialDirections.Count); // Determine exiting values. nextIntersectionExitingCardinalDirection = potentialDirections[randomIndex]; nextIntersectionExitingDirection = DirectionHelpers.cardinalDirectionVectors[nextIntersectionExitingCardinalDirection]; nextIntersectionExitingStreet = targetIntersection.GetStreetInDirection(nextIntersectionExitingCardinalDirection); // Figure out which lane to go into. float angleToTarget = Vector3.SignedAngle(sensors.carTracker.streetDirection, DirectionHelpers.cardinalDirectionVectors[nextIntersectionExitingCardinalDirection], Vector3.up); Street nextStreet = targetIntersection.GetStreetInDirection(nextIntersectionExitingCardinalDirection); if (nextIntersectionExitingCardinalDirection == currentDirection) { // Be in the middle (or right) lane when going straight. nextIntersectionExitingLane = nextStreet.validLanesCount / 2 + 1; nextIntersectionTurningIntent = Driver.TurningIntent.Straight; } else if (nextIntersectionExitingCardinalDirection == oppositeDirection) { // Turn into leftmost lane for U-turns. nextIntersectionExitingLane = 1; nextIntersectionTurningIntent = Driver.TurningIntent.UTurn; } else if (angleToTarget < 0) { // Be in leftmost lane after upcoming left turns. nextIntersectionExitingLane = 1; nextIntersectionTurningIntent = Driver.TurningIntent.Left; } else { // Be in the rightmost lane after upcoming right turns. nextIntersectionExitingLane = nextStreet.validLanesCount; nextIntersectionTurningIntent = Driver.TurningIntent.Right; } nextIntersectionExitingPoint = nextIntersectionExitingStreet.GetCenterOfLanePosition(nextIntersectionExitingLane, 0, nextIntersectionExitingCardinalDirection); // Store entering values. nextIntersectionEnteringCardinalDirection = currentDirection; nextIntersectionEnteringDirection = DirectionHelpers.cardinalDirectionVectors[currentDirection]; nextIntersectionEnteringStreet = currentStreet; // Figure out which lane to enter the intersection from. switch (nextIntersectionTurningIntent) { case Driver.TurningIntent.Straight: // Be in the middle (or right) lane when going straight. nextIntersectionEnteringLane = currentStreet.validLanesCount / 2 + 1; break; case Driver.TurningIntent.Left: // Be in leftmost lane for upcoming left turns. nextIntersectionEnteringLane = 1; break; case Driver.TurningIntent.Right: // Be in the rightmost lane for upcoming right turns. nextIntersectionEnteringLane = currentStreet.validLanesCount; break; case Driver.TurningIntent.UTurn: // Turn from the sidewalk for U-turns. nextIntersectionEnteringLane = 0; break; } nextIntersectionEnteringPoint = nextIntersectionEnteringStreet.GetCenterOfLanePosition(nextIntersectionEnteringLane, nextIntersectionEnteringStreet.length, nextIntersectionEnteringCardinalDirection); } // Update street. previousStreet = currentStreet; }
private void DrawDebugNextIntersectionDirection(Driver.Sensors sensors) { Debug.DrawRay(sensors.car.transform.position + Vector3.up * 2, DirectionHelpers.cardinalDirectionVectors[nextIntersectionExitingCardinalDirection] * 5, Color.magenta); }
private float CalculateSafeDistance(Driver.Sensors sensors) { return(Mathf.Max(minimumSafeDistance, safeDistanceTime * sensors.car.speed)); }
private float GetDistanceToTargetIntersection(Driver.Sensors sensors) { Intersection targetIntersection = GetTargetIntersection(sensors); return(Mathf.Sqrt(targetIntersection.bounds.SqrDistance(sensors.car.transform.position))); }
private void UpdateTargetLaneAndDirection(Driver.Sensors sensors, Driver.Actuators actuators) { Street currentStreet = sensors.carTracker.street; Intersection currentIntersection = sensors.carTracker.intersection; float distanceToIntersection = GetDistanceToTargetIntersection(sensors); float turningTargetDistance = Math.Max(turningTargetMinimumDistance, turningTargetTime * sensors.car.speed); // See if we should start performing a turn. bool performingTurn = false; switch (nextIntersectionTurningIntent) { case Driver.TurningIntent.Left: case Driver.TurningIntent.Right: // When turning left and right, start turning when close enough to the intersection. performingTurn = currentIntersection != null || distanceToIntersection < turningTargetDistance; break; case Driver.TurningIntent.UTurn: // When performing a U-turn, start in the intersection. performingTurn = currentIntersection != null; break; } if (performingTurn) { DrawDebugIntersectionArc(); switch (nextIntersectionTurningIntent) { case Driver.TurningIntent.Left: case Driver.TurningIntent.Right: // For right turns, follow an arc. float xEnter = Vector3.Dot(nextIntersectionExitingDirection, nextIntersectionEnteringPoint); float xExit = Vector3.Dot(nextIntersectionExitingDirection, nextIntersectionExitingPoint); float xDelta = xExit - xEnter; float zEnter = Vector3.Dot(nextIntersectionEnteringDirection, nextIntersectionEnteringPoint); float zExit = Vector3.Dot(nextIntersectionEnteringDirection, nextIntersectionExitingPoint); float zDelta = zExit - zEnter; // Place a target in front of the car. Vector3 carPosition = sensors.car.transform.position; Vector3 target = carPosition + sensors.car.transform.forward * turningTargetDistance; Debug.DrawLine(carPosition + Vector3.up * 2, target + Vector3.up * 2, Color.magenta); float xTarget = Vector3.Dot(nextIntersectionExitingDirection, target) - xEnter; float zTarget = Vector3.Dot(nextIntersectionEnteringDirection, target) - zEnter; // Move target onto the arc. float zTargetFraction = zTarget / zDelta; float xTargetFraction = xTarget / xDelta; float angle = Mathf.Atan2(zTargetFraction, 1 - xTargetFraction); xTargetFraction = 1 - Mathf.Cos(angle); zTargetFraction = xTargetFraction > 1 ? 1 : Mathf.Sin(angle); xTarget = xTargetFraction * xDelta; zTarget = zTargetFraction * zDelta; target = nextIntersectionEnteringPoint + nextIntersectionExitingDirection * xTarget + nextIntersectionEnteringDirection * zTarget; // Direct the car towards the adjusted target. actuators.targetDirection = (target - carPosition).normalized; Debug.DrawLine(carPosition + Vector3.up * 2, target + Vector3.up * 2, Color.blue); break; case Driver.TurningIntent.UTurn: // For U-turns, start by turning right as tight as possible. if (Vector3.Angle(nextIntersectionEnteringDirection, sensors.car.transform.forward) < 45) { actuators.targetDirection = Quaternion.AngleAxis(90, Vector3.up) * nextIntersectionEnteringDirection; } else { actuators.targetDirection = nextIntersectionExitingDirection; } break; } actuators.targetLane = nextIntersectionExitingLane; } else if (currentStreet != null) { // If we're on a street, go to correct turning lane. float angleToTarget = Vector3.SignedAngle(sensors.carTracker.streetDirection, DirectionHelpers.cardinalDirectionVectors[nextIntersectionExitingCardinalDirection], Vector3.up); CardinalDirection currentDirection = sensors.carTracker.streetCardinalDirection; CardinalDirection oppositeDirection = DirectionHelpers.GetOppositeDirection(currentDirection); int currentLane = sensors.carTracker.currentLane; int desiredLane = nextIntersectionEnteringLane; if (desiredLane != currentLane) { // Start moving to the desired lane when close enough to the intersection. int laneDifference = desiredLane - currentLane; float minimumDistanceToIntersection = sensors.driverProfile.distanceForChangingLane * Mathf.Abs(laneDifference); // For U-turns from the sidewalk, only drive onto the sidewalk in the last meters. if (nextIntersectionTurningIntent == Driver.TurningIntent.UTurn && currentLane == 1) { minimumDistanceToIntersection = uTurnDriveToSidewalkDistance; } if (distanceToIntersection < minimumDistanceToIntersection) { actuators.turningIntent = nextIntersectionTurningIntent; // Make sure the neighbor lane is free in the safe distance region before and after the car. int neighborLane = currentLane + Math.Sign(laneDifference); float safeDistance = CalculateSafeDistance(sensors); Vector3 streetDirection = sensors.carTracker.streetDirection; float carLength = sensors.car.bounds.size.z; Vector3 origin = sensors.carTracker.GetCenterOfLanePosition(neighborLane) + (safeDistance + carLength / 2) * streetDirection + Vector3.up * sensors.car.bounds.extents.y; float checkDistance = 2 * safeDistance + carLength; if (!Physics.Raycast(origin, -streetDirection, checkDistance)) { actuators.targetLane = desiredLane; Debug.DrawRay(origin, -streetDirection * checkDistance, Color.green, 1); } else { Debug.DrawRay(origin, -streetDirection * checkDistance, Color.red); } } } actuators.targetDirection = sensors.carTracker.streetDirection; } }
private void UpdateTargetSpeed(Driver.Sensors sensors, Driver.Actuators actuators) { Transform carTransform = sensors.car.transform; RaycastHit hitInfo; Vector3 origin; Color debugColor; // Calculate desired parameters. float safeDistance = CalculateSafeDistance(sensors); float matchingDistance = safeDistance * speedMatchingSafeDistanceFactor; int currentValidLane = Mathf.Clamp(sensors.carTracker.currentLane, 0, 3); float desiredLaneSpeedMph = sensors.driverProfile.respectsSpeedLimits ? Traffic.speedPerLaneMph[currentValidLane] : 150; float desiredTurningLaneSpeedMph = turningSpeedPerLaneMph[currentValidLane]; // By default, we're not waiting on anyone. waitingForCar = null; // See if we'll be turning in the upcoming intersection. if (sensors.carTracker.streetCardinalDirection != nextIntersectionExitingCardinalDirection) { // If we're close enough to the interception, indicate turning intent. float distanceToIntersection = GetDistanceToTargetIntersection(sensors); if (sensors.carTracker.intersection != null || distanceToIntersection < sensors.driverProfile.distanceForTurning) { actuators.turningIntent = nextIntersectionTurningIntent; // For right turns, start checking for oncoming traffic. if (nextIntersectionTurningIntent == Driver.TurningIntent.Right) { // Check each of the lanes we'll have to cross. int currentLane = sensors.carTracker.currentLane; int lastLane = nextIntersectionEnteringStreet.lanesCount; for (int lane = currentLane + 1; lane <= lastLane; lane++) { origin = sensors.carTracker.GetCenterOfLanePosition(lane, nextIntersectionEnteringCardinalDirection) + Vector3.up * sensors.car.bounds.extents.y; float distanceForTurning = sensors.driverProfile.distanceForTurning; debugColor = Color.green; if (Physics.Raycast(origin, nextIntersectionEnteringDirection, out hitInfo, distanceForTurning)) { // Make sure the obstacle is a car. GameObject obstacle = hitInfo.collider.gameObject; if (obstacle.CompareTag(Tags.car)) { // Car was detected, see if we need to wait for it. bool shouldWaitForCar = true; // Don't wait if the car is already waiting for us. Driver driver = obstacle.GetComponentInParent <Driver>(); TrafficDriver trafficDriver = driver?.controller as TrafficDriver; if (trafficDriver?.waitingForCar == sensors.car) { shouldWaitForCar = false; } // When turning right, see if other driver's intent can help us not wait. if (actuators.turningIntent == Driver.TurningIntent.Right && driver != null) { // Don't wait if the other car is also turning right. if (driver.turningIntent == Driver.TurningIntent.Right) { shouldWaitForCar = false; } // Don't wait if the other car is turning left and we're going right into a different lane. if (driver.turningIntent == Driver.TurningIntent.Left && nextIntersectionExitingLane > 1) { shouldWaitForCar = false; } } if (shouldWaitForCar) { // stop and wait before turning! desiredTurningLaneSpeedMph = 0; debugColor = Color.red; // Communicate that we're waiting for this car. waitingForCar = obstacle.GetComponentInParent <Car>(); Debug.DrawLine(carTransform.position + Vector3.up, obstacle.transform.position + Vector3.up, Color.black); } } } Debug.DrawRay(origin, nextIntersectionEnteringDirection * distanceForTurning, debugColor); } } } // See if we're already in the intersection. if (sensors.carTracker.intersection != null) { // Just keep the turning speed. desiredLaneSpeedMph = desiredTurningLaneSpeedMph; } else { // Slow down towards desired turning speed when approaching an intersection. desiredLaneSpeedMph = Mathf.Lerp(desiredTurningLaneSpeedMph, desiredLaneSpeedMph, (distanceToIntersection - matchingDistance) / matchingDistance); } } float desiredLaneSpeed = desiredLaneSpeedMph * PhysicsHelper.milesPerHourToMetersPerSecond; // Shoot a ray out to see how far the distance is to the obstacle in the front. origin = sensors.car.frontCenterPointWorld; Vector3 forward = carTransform.forward; forward.y = 0; forward.Normalize(); float newFrontDistance; if (Physics.Raycast(origin, forward, out hitInfo, maximumRaycastDistance)) { // Because the raycasts fluctuate, smooth the measurement for more stable behavior. newFrontDistance = Mathf.SmoothDamp(frontDistance, hitInfo.distance, ref frontDistanceSmoothVelocity, distanceSmoothTime); } else { newFrontDistance = maximumRaycastDistance; } // Only make any considerations if we can figure out the relative speed. if (frontDistance < maximumRaycastDistance && newFrontDistance < maximumRaycastDistance) { // Determine obstacle speed, based on what it is. if (hitInfo.collider.gameObject.CompareTag(Tags.car)) { // We hit a car. Because the raycasts fluctuate, smooth the measurement for more stable behavior. float obstacleRelativeSpeed = (newFrontDistance - frontDistance) / Time.deltaTime; float newObstacleSpeed = sensors.car.speed + obstacleRelativeSpeed; frontObstacleSpeed = Mathf.SmoothDamp(frontObstacleSpeed, newObstacleSpeed, ref frontObstacleSpeedSmoothVelocity, obstacleSpeedSmoothTime); // If speed is low enough, consider it to be stopped. if (newObstacleSpeed < minimumRecognizableObstacleSpeed && frontObstacleSpeed < minimumRecognizableObstacleSpeed) { frontObstacleSpeed = 0; } } else { // We didn't hit a car, assume it's stationary. frontObstacleSpeed = 0; } // Recalculate safe distance for stationary obstacles. if (frontObstacleSpeed == 0) { safeDistance = minimumSafeDistanceForStationary; matchingDistance = safeDistance * speedMatchingSafeDistanceFactor; } // If we're closer than the safe distance to the obstacle, stop! float distanceToSafeDistance = newFrontDistance - safeDistance; if (distanceToSafeDistance < 0) { actuators.targetSpeed = 0; } else { // Nothing to do if the obstacle is moving away. float obstacleRelativeSpeed = frontObstacleSpeed - sensors.car.speed; if (obstacleRelativeSpeed > minimumMovingAwayRelativeSpeed) { actuators.targetSpeed = desiredLaneSpeed; } else { // Match obstacle's speed the closer you are to the safe distance (at which point you should exactly match it). float fractionFromSafeDistance = (newFrontDistance - matchingDistance) / (maximumRaycastDistance - matchingDistance); actuators.targetSpeed = Mathf.Lerp(frontObstacleSpeed, desiredLaneSpeed, fractionFromSafeDistance); } } } else { // There is no obstacle, simply drive at the lane speed. actuators.targetSpeed = desiredLaneSpeed; frontObstacleSpeed = 0; } // Store front distance for next frame. frontDistance = newFrontDistance; // Draw debug information. /* * debugColor = Color.yellow; * if (actuators.targetSpeed == desiredLaneSpeed) debugColor = Color.green; * if (actuators.targetSpeed == 0) debugColor = Color.red; * * Vector3 endOfRay = origin + forward * newFrontDistance; * Debug.DrawLine(origin, endOfRay, debugColor); * * Vector3 safeDistanceFromOrigin = origin + forward * safeDistance; * Debug.DrawLine(safeDistanceFromOrigin - Vector3.up, safeDistanceFromOrigin + Vector3.up, debugColor); * * Vector3 safeDistanceFromTarget = origin + forward * (newFrontDistance - safeDistance); * Debug.DrawLine(safeDistanceFromTarget - Vector3.up, safeDistanceFromTarget + Vector3.up, debugColor); * * Debug.DrawLine(endOfRay + Vector3.up * 3, endOfRay + forward * frontObstacleSpeed + Vector3.up * 3, Color.red); */ }
protected virtual void UpdateTargetDirection(Driver.Sensors sensors, Driver.Actuators actuators) { float turningInput = Input.GetAxis("Turning"); bool performTurn = false; if (Input.GetButtonDown("Turning")) { // See if the turn button is down. if (Input.GetButton("Turn")) { // We want to make a turn. performTurn = true; } else { // Turn button is not down, so do a lane switch. if (turningInput < 0) { // We want to go left, but if our current target was to go right, just return to current lane. if (actuators.targetLane > sensors.carTracker.currentLane) { actuators.targetLane = sensors.carTracker.currentLane; } else { actuators.targetLane = Math.Max(sensors.carTracker.currentLane - 1, 0); } } else { // We want to go right, but if our current target was to go left, just return to current lane. if (actuators.targetLane < sensors.carTracker.currentLane) { actuators.targetLane = sensors.carTracker.currentLane; } else { actuators.targetLane = Math.Min(sensors.carTracker.currentLane + 1, sensors.carTracker.representativeStreet.lanesCount + 1); } } } } // If turn button is pressed and we have a direction, we should turn. if (Input.GetButtonDown("Turn") && turningInput != 0) { performTurn = true; } // Perform the turn if controls dictated it. if (performTurn) { // When going backwards, turning needs to be reversed. if (sensors.car.speed < 0) { turningInput *= -1; } // Determine to which side we're currently turning. Vector3 forward = sensors.car.transform.forward; Quaternion currentTargetDelta = Quaternion.FromToRotation(forward, actuators.targetDirection); // Use delta angle to wrap the angle to -180..180 range. float currentTargetDeltaAngle = Mathf.DeltaAngle(0, currentTargetDelta.eulerAngles.y); Vector3 newDirection; if (turningInput < 0) { // We want to go left, but if our current target was to go right, just return to current direction. if (currentTargetDeltaAngle > 45) { newDirection = forward; } else { newDirection = Quaternion.Euler(0, -90, 0) * forward; } } else { // We want to go right, but if our current target was to go left, just return to current direction. if (currentTargetDeltaAngle < -45) { newDirection = forward; } else { newDirection = Quaternion.Euler(0, 90, 0) * forward; } } // Square rotation to 90 degrees. CardinalDirection cardinalDirection = DirectionHelpers.GetCardinalDirectionForVector(newDirection); actuators.targetDirection = DirectionHelpers.cardinalDirectionVectors[cardinalDirection]; } }
public override void Act(Driver.Sensors sensors, Driver.Actuators actuators) { UpdateTargetSpeed(sensors, actuators); UpdateTargetDirection(sensors, actuators); }