using System.Collections.Generic;
using System.Collections;
using UnityEngine;
using UnityEngine.Splines;
using Unity.Mathematics;
using Random = UnityEngine.Random;
public class TrafficCarV2 : MonoBehaviour
{
[SerializeField] private float maxSpeed = 45.0f;
[SerializeField] private float acceleration = 1.0f;
[SerializeField] private float aggressive = 15;
[SerializeField] private float speed = 35.0f;
[SerializeField] private float changeLaneSpeed = 1.0f;
[Header("Detection")]
[SerializeField] private float detectionDistance = 20.0f;
[SerializeField] private float detectionAngle = 30.0f;
[SerializeField] private float sideDetectionDistance = 10.0f;
[Header("Layers")]
[SerializeField] private LayerMask detectionLayerMask;
[SerializeField] private LayerMask floorLayerMask;
private bool alive = true;
private int currentLane = 0;
private TrafficManager trafficManager;
private Rigidbody body;
private SplineContainer currentSplineContainer;
private RoadInfo currentRoad;
private float progress = 0.0f;
private float currentLateralOffset = 0.0f;
private List<Vector3> joinSpline = null;
private int joinSplineIndex = 0;
private float previousHeight;
private Coroutine moveCoroutine;
private Coroutine obstacleCheckCoroutine;
private Coroutine accelerationCoroutine;
private System.Random random;
public void SetTrafficManager(TrafficManager trafficManager)
{
this.trafficManager = trafficManager;
}
public bool GetAlive()
{
return this.alive;
}
public float GetCurrentSpeed()
{
return this.speed;
}
public int GetCurrentLane()
{
return this.currentLane;
}
float GetTotalOffsetByLane()
{
return this.currentLane * 3.75f;
}
void Awake()
{
this.body = GetComponent<Rigidbody>();
this.random = new System.Random();
}
//Corrutines
public void StartCoroutine()
{
this.StopCoroutines();
this.moveCoroutine = StartCoroutine(MoveCoroutine());
this.obstacleCheckCoroutine = StartCoroutine(ObstacleCheckCoroutine());
this.accelerationCoroutine = StartCoroutine(AccelerationCoroutine());
}
public void StopCoroutines()
{
if (this.moveCoroutine != null)
{
StopCoroutine(this.moveCoroutine);
this.moveCoroutine = null;
}
if (this.obstacleCheckCoroutine != null)
{
StopCoroutine(this.obstacleCheckCoroutine);
this.obstacleCheckCoroutine = null;
}
if (this.accelerationCoroutine != null)
{
StopCoroutine(this.accelerationCoroutine);
this.accelerationCoroutine = null;
}
}
//Movement
IEnumerator MoveCoroutine()
{
while (true)
{
if (this.joinSpline != null)
{
FollowJoinSpline();
}
else
{
this.FollowCurrentSpline();
if(this.progress > 0.995f)
{
SplineContainer newContainer = FindNearbySplineContainer();
if(newContainer == null)
{
this.HideThisCar(0.0f, false, false);
}
else
{
this.currentSplineContainer = newContainer;
this.currentRoad = this.currentSplineContainer.GetComponent<RoadInfo>();
this.currentLane = this.GetCorrectLaneNumber(this.currentLane);
this.CreateJoinPath(newContainer);
this.currentLateralOffset = this.GetTotalOffsetByLane(); //We asume we will be in the correct lane offset by the end of this
this.progress = 0;
}
}
}
yield return null;
}
}
//Real Spline Container
private void FollowCurrentSpline()
{
progress += (speed * Time.deltaTime) / this.currentSplineContainer.Splines[0].GetLength();
(Vector3 position, Vector3 tangent, Vector3 groundNormal, Vector3 totalOffset) = GetAllInfoFromPosition(this.currentSplineContainer, progress);
this.currentLateralOffset = GetLateralMovement();
position += Vector3.Cross(groundNormal, tangent).normalized * this.currentLateralOffset;
position = AdjustHeightToGround(position);
Quaternion targetRotation = Quaternion.LookRotation(tangent, groundNormal);
this.transform.rotation = Quaternion.Slerp(body.rotation, targetRotation, Time.deltaTime * 10.0f);
this.transform.position = position;
}
private float GetLateralMovement()
{
this.currentLateralOffset = Mathf.MoveTowards(this.currentLateralOffset, this.GetTotalOffsetByLane(), this.changeLaneSpeed * Time.deltaTime);
return this.currentLateralOffset;
}
//Follow Join Spline
private void FollowJoinSpline()
{
if (this.joinSplineIndex >= this.joinSpline.Count)
{
this.joinSpline = null;
this.joinSplineIndex = 0;
return;
}
//Position
Vector3 targetPosition = this.joinSpline[this.joinSplineIndex];
targetPosition = AdjustHeightToGround(targetPosition);
this.transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * 2.0f * Time.deltaTime);;
//Rotation
Vector3 directionToTarget = (targetPosition - transform.position).normalized;
if(directionToTarget != Vector3.zero){
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
this.transform.rotation = Quaternion.Slerp(body.rotation, targetRotation, Time.deltaTime * 10.0f);
}
if (Vector3.Distance(transform.position, targetPosition) < 0.1f)
{
this.joinSplineIndex++;
}
}
//General
private (Vector3, Vector3, Vector3, Vector3) GetAllInfoFromPosition(SplineContainer splineContainer, float progress, int splineToFollow = 0)
{
Vector3 groundNormal = Vector3.up;
Vector3 position, tangent, totalOffset;
RaycastHit hit;
float3 position3, tangent3, up3;
splineContainer.Evaluate(splineContainer.Splines[splineToFollow], progress, out position3, out tangent3, out up3);
position = Utils.Float3ToVector3(position3);
tangent = Utils.Float3ToVector3(tangent3);
if (Physics.Raycast(position + Vector3.up * 10, Vector3.down, out hit, 20f, this.floorLayerMask))
{
groundNormal = hit.normal;
}
totalOffset = Vector3.Cross(groundNormal, tangent).normalized * this.GetTotalOffsetByLane();
return (position, tangent, groundNormal, totalOffset);
}
private Vector3 AdjustHeightToGround(Vector3 position)
{
if (float.IsNaN(this.previousHeight))
{
this.previousHeight = position.y;
}
RaycastHit hit;
float hoverOffset = 1.5f;
float baseInterpolationSpeed = 10f;
float maxInterpolationSpeed = 100f;
if (Physics.Raycast(position + Vector3.up * 5, Vector3.down, out hit, 40f, this.floorLayerMask))
{
float desiredHeight = hit.point.y + hoverOffset;
float heightDifference = Mathf.Abs(this.previousHeight - desiredHeight);
float interpolationSpeed = Mathf.Clamp(heightDifference * 10f, baseInterpolationSpeed, maxInterpolationSpeed);
position.y = Mathf.Lerp(this.previousHeight, desiredHeight, Time.fixedDeltaTime * interpolationSpeed);
this.previousHeight = position.y;
}
return position;
}
//SplineManagement
private SplineContainer FindNearbySplineContainer()
{
SplineContainer closestSpline = null;
float closestDistance = 100.0f;
SplineContainer[] allSplines = FindObjectsByType<SplineContainer>(FindObjectsSortMode.None);
foreach (SplineContainer splineContainer in allSplines)
{
if (splineContainer == this.currentSplineContainer)
{
continue;
}
Vector3 splineStartPoint, splineStartTangent;
(Vector3 newStartPoint, Vector3 newTangent, Vector3 _, Vector3 _) = this.GetAllInfoFromPosition(splineContainer, 0);
splineStartPoint = Utils.Float3ToVector3(newStartPoint);
splineStartTangent = Utils.Float3ToVector3(newTangent);
float distance = Vector3.Distance(this.transform.position, splineStartPoint);
if (distance < closestDistance)
{
closestDistance = distance;
closestSpline = splineContainer;
}
}
return closestSpline;
}
private void CreateJoinPath(SplineContainer newContainer)
{
Vector3 currentPosition = transform.position;
Vector3 currentForward = transform.forward;
Vector3 offsetPosition = currentPosition;
(Vector3 newStartPoint, Vector3 newTangent, Vector3 _, Vector3 totalOffset) = GetAllInfoFromPosition(newContainer, 0.0f);
Vector3 targetPositionWithOffset = newStartPoint + totalOffset;
this.joinSpline = CalculateTransitionPath(offsetPosition, targetPositionWithOffset, currentForward, newTangent);
}
private List<Vector3> CalculateTransitionPath(Vector3 startPoint, Vector3 endPoint, Vector3 startTangent, Vector3 endTangent, int resolution = 5)
{
List<Vector3> path = new List<Vector3>();
float distance = Vector3.Distance(new Vector3(startPoint.x, 0, startPoint.z), new Vector3(endPoint.x, 0, endPoint.z));
startTangent = startTangent.normalized * (distance / 3);
endTangent = endTangent.normalized * (distance / 3);
for (int i = 0; i <= resolution; i++)
{
float t = (float)i / resolution;
Vector3 bezierPoint = CalculateBezierPoint(t, startPoint, startPoint + startTangent, endPoint - endTangent, endPoint);
path.Add(bezierPoint);
Debug.DrawLine(bezierPoint, bezierPoint + Vector3.up * 0.5f, Color.red, 5.0f);
}
return path;
}
private Vector3 CalculateBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3 p = uuu * new Vector3(p0.x, 0, p0.z);
p += 3 * uu * t * new Vector3(p1.x, 0, p1.z);
p += 3 * u * tt * new Vector3(p2.x, 0, p2.z);
p += ttt * new Vector3(p3.x, 0, p3.z);
return p;
}
//Aceleration
IEnumerator AccelerationCoroutine()
{
while (true)
{
if (this.speed < this.maxSpeed && Random.Range(0, 100) <= this.aggressive)
{
this.speed += this.acceleration;
}
yield return new WaitForSeconds(Random.Range(2f, 3f));
}
}
//Obstacles
private IEnumerator ObstacleCheckCoroutine()
{
while (true)
{
CheckForObstacles();
yield return new WaitForSeconds(1f);
}
}
private void CheckForObstacles()
{
Collider[] carsInRadius = Physics.OverlapSphere(this.transform.position, this.detectionDistance, this.detectionLayerMask);
foreach (Collider car in carsInRadius)
{
if (car.gameObject.GetInstanceID() == this.gameObject.GetInstanceID())
{
continue;
}
Transform target = car.transform;
Vector3 dirToTarget = (target.position - this.transform.position).normalized;
if (Vector3.Angle(this.transform.forward, dirToTarget) < this.detectionAngle / 2)
{
TrafficCarV2 targetLogic = car.GetComponent<TrafficCarV2>();
if (targetLogic.GetCurrentSpeed() < this.speed && targetLogic.GetCurrentLane() == this.currentLane)
{
this.AvoidObstacle(targetLogic);
return;
}
}
}
}
private void AvoidObstacle(TrafficCarV2 avoidCar)
{
int leftRoad = this.GetCorrectLaneNumber(this.currentLane - 1);
int rightRoad = this.GetCorrectLaneNumber(this.currentLane + 1);
Collider[] carsInRadius = Physics.OverlapSphere(this.transform.position, this.sideDetectionDistance, this.detectionLayerMask);
foreach (Collider car in carsInRadius)
{
TrafficCarV2 targetLogic = car.GetComponent<TrafficCarV2>();
if (targetLogic.GetCurrentLane() == leftRoad)
{
leftRoad = this.currentLane;
}
if (targetLogic.GetCurrentLane() == rightRoad)
{
rightRoad = this.currentLane;
}
}
//If we have both
if (leftRoad != this.currentLane && rightRoad != this.currentLane)
{
this.currentLane = (Random.Range(0, 1) == 0) ? rightRoad : leftRoad;
}
//If we have left
else if (leftRoad != this.currentLane)
{
this.currentLane = leftRoad;
}
//If we have right
else if (rightRoad != this.currentLane)
{
this.currentLane = rightRoad;
}
//If we are fucked
else
{
this.speed = avoidCar.speed - 1 == 0 ? avoidCar.speed : avoidCar.speed - 1;
}
}
//Events
private void OnCollisionEnter(Collision collision)
{
if (collision.transform.tag == "Player" && collision.relativeVelocity.magnitude > 15.0f && this.alive)
{
this.alive = false;
collision.gameObject.GetComponentInParent<CarParameters>().CarCrassInvoke(this);
this.HideThisCar(10.0f, true, true);
}
}
private void OnTriggerEnter(Collider collider)
{
if (collider.transform.tag == "DeathWall")
{
Vector3 carForward = transform.forward;
Vector3 colliderForward = collider.transform.forward;
float angle = Vector3.Angle(carForward, colliderForward);
if(angle < 45f){
collider.gameObject.GetComponentInParent<CarParameters>().CarPassInvoke(this);
this.HideThisCar(1.5f, false, false);
}
}
}
//Utils
private void HideThisCar(float time = 2.5f, bool physics = false, bool reset = false)
{
if (physics)
{
this.body.useGravity = true;
this.body.isKinematic = false;
}
this.StopCoroutines();
if (reset)
{
CancelInvoke("DisableCar");
}
Invoke("DisableCar", time);
}
private IEnumerator DisableCar()
{
this.body.useGravity = false;
this.body.isKinematic = true;
this.gameObject.SetActive(false);
this.trafficManager.AddAvailableCar(this.gameObject);
return null;
}
public int GetCorrectLaneNumber(int lane)
{
int halfLane = this.currentRoad.GetLanesNumber() / 2;
int clampedLane = Mathf.Clamp(lane, -halfLane, halfLane);
if (this.currentRoad.GetLanesNumber() % 2 == 0 && clampedLane == 0)
{
clampedLane = lane > 0 ? 1 : -1;
}
return clampedLane;
}
//Used for the TrafficCarManager
public void MoveToSpline(SplineContainer container, float progress)
{
if (container == null)
{
return;
}
this.alive = true;
this.currentSplineContainer = container;
this.progress = progress;
this.currentRoad = this.currentSplineContainer.GetComponent<RoadInfo>();
int divisor = Mathf.FloorToInt(this.currentRoad.GetLanesNumber() / 2);
this.currentLane = this.GetCorrectLaneNumber(this.random.Next(divisor * -1, divisor));
(Vector3 position, Vector3 tangent, Vector3 groundNormal, Vector3 totalOffset) = this.GetAllInfoFromPosition(this.currentSplineContainer, this.progress);
Quaternion targetRotation = Quaternion.LookRotation(tangent, groundNormal);
this.transform.position = position;
this.transform.rotation = targetRotation;
}
}