├── .gitattributes ├── 01_starter_project.unitypackage ├── 04_car_prefab_1.unitypackage ├── 05_car_prefab_2.unitypackage ├── 06_car_prefab_AI.unitypackage ├── 07_Testing_car_ai.unitypackage ├── 09_Car_markers_1.unitypackage ├── 10_Car_markers_2.unitypackage ├── 11_Car_markers_3.unitypackage ├── 12_Testing_Car_graph.unitypackage ├── 13_Finding_Start_and_End_Marker_P1.unitypackage ├── 14_Finding_Start_and_End_Marker_P2.unitypackage ├── 15_Finding_Start_and_End_Marker_P3.unitypackage ├── 16_Smart intersections.unitypackage ├── 17_Integrating_Pedestrians_1.unitypackage ├── 18_Integrating_Pedestrians_2.unitypackage ├── 19_Fix_4_way_street_marker_connection.unitypackage ├── README.md └── Scripts ├── AI ├── CarAI.cs ├── CarAI.cs.meta ├── CarController.cs ├── CarController.cs.meta ├── CarSpawner.cs ├── CarSpawner.cs.meta ├── SmartCrosswalks.cs └── SmartRoad.cs ├── AiDirector.cs ├── Marker.cs ├── RoadHelper.cs └── RoadHelperStraight.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /01_starter_project.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/01_starter_project.unitypackage -------------------------------------------------------------------------------- /04_car_prefab_1.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/04_car_prefab_1.unitypackage -------------------------------------------------------------------------------- /05_car_prefab_2.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/05_car_prefab_2.unitypackage -------------------------------------------------------------------------------- /06_car_prefab_AI.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/06_car_prefab_AI.unitypackage -------------------------------------------------------------------------------- /07_Testing_car_ai.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/07_Testing_car_ai.unitypackage -------------------------------------------------------------------------------- /09_Car_markers_1.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/09_Car_markers_1.unitypackage -------------------------------------------------------------------------------- /10_Car_markers_2.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/10_Car_markers_2.unitypackage -------------------------------------------------------------------------------- /11_Car_markers_3.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/11_Car_markers_3.unitypackage -------------------------------------------------------------------------------- /12_Testing_Car_graph.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/12_Testing_Car_graph.unitypackage -------------------------------------------------------------------------------- /13_Finding_Start_and_End_Marker_P1.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/13_Finding_Start_and_End_Marker_P1.unitypackage -------------------------------------------------------------------------------- /14_Finding_Start_and_End_Marker_P2.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/14_Finding_Start_and_End_Marker_P2.unitypackage -------------------------------------------------------------------------------- /15_Finding_Start_and_End_Marker_P3.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/15_Finding_Start_and_End_Marker_P3.unitypackage -------------------------------------------------------------------------------- /16_Smart intersections.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/16_Smart intersections.unitypackage -------------------------------------------------------------------------------- /17_Integrating_Pedestrians_1.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/17_Integrating_Pedestrians_1.unitypackage -------------------------------------------------------------------------------- /18_Integrating_Pedestrians_2.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/18_Integrating_Pedestrians_2.unitypackage -------------------------------------------------------------------------------- /19_Fix_4_way_street_marker_connection.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunnyValleyStudio/Simple-Traffic-System-Unity-2020/9242eadcd16881f51f48fd9309ff159f263fc600/19_Fix_4_way_street_marker_connection.unitypackage -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple-Traffic-System-Unity-2020 2 | [![Tutorial](https://img.youtube.com/vi/mu7f3Z1lRsE/0.jpg)](https://youtu.be/mu7f3Z1lRsE) 3 | 4 |

How to create a simple traffic system for a city builder type of game in Unity 2020. This is not suited for Unity 2019 since I use attribute [field: SerializeField] to show UnityEvents in the inspector. It only works reliably on Unity version 2020 since they have modified the Serialization process. 5 | 6 | 7 |

Attribution: 8 | Made by Sunny Valley Studio 9 | -------------------------------------------------------------------------------- /Scripts/AI/CarAI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.Events; 6 | 7 | public class CarAI : MonoBehaviour 8 | { 9 | [SerializeField] 10 | private List path = null; 11 | [SerializeField] 12 | private float arriveDistance = .3f, lastPointArriveDistance = .1f; 13 | [SerializeField] 14 | private float turningAngleOffset = 5; 15 | [SerializeField] 16 | private Vector3 currentTargetPosition; 17 | 18 | [SerializeField] 19 | private GameObject raycastStartingPoint = null; 20 | [SerializeField] 21 | private float collisionRaycastLength = 0.1f; 22 | 23 | internal bool IsThisLastPathIndex() 24 | { 25 | return index >= path.Count-1; 26 | } 27 | 28 | private int index = 0; 29 | 30 | private bool stop; 31 | private bool collisionStop = false; 32 | 33 | public bool Stop 34 | { 35 | get { return stop || collisionStop; } 36 | set { stop = value; } 37 | } 38 | 39 | [field: SerializeField] 40 | public UnityEvent OnDrive { get; set; } 41 | 42 | private void Start() 43 | { 44 | if(path == null || path.Count == 0) 45 | { 46 | Stop = true; 47 | } 48 | else 49 | { 50 | currentTargetPosition = path[index]; 51 | } 52 | } 53 | 54 | public void SetPath(List path) 55 | { 56 | if(path.Count == 0) 57 | { 58 | Destroy(gameObject); 59 | return; 60 | } 61 | this.path = path; 62 | index = 0; 63 | currentTargetPosition = this.path[index]; 64 | 65 | Vector3 relativepoint = transform.InverseTransformPoint(this.path[index + 1]); 66 | 67 | float angle = Mathf.Atan2(relativepoint.x, relativepoint.z) * Mathf.Rad2Deg; 68 | 69 | transform.rotation = Quaternion.Euler(0, angle, 0); 70 | Stop = false; 71 | } 72 | 73 | private void Update() 74 | { 75 | CheckIfArrived(); 76 | Drive(); 77 | CheckForCollisions(); 78 | } 79 | 80 | private void CheckForCollisions() 81 | { 82 | if(Physics.Raycast(raycastStartingPoint.transform.position, transform.forward,collisionRaycastLength, 1 << gameObject.layer)) 83 | { 84 | collisionStop = true; 85 | } 86 | else 87 | { 88 | collisionStop = false; 89 | } 90 | } 91 | 92 | private void Drive() 93 | { 94 | if (Stop) 95 | { 96 | OnDrive?.Invoke(Vector2.zero); 97 | } 98 | else 99 | { 100 | Vector3 relativepoint = transform.InverseTransformPoint(currentTargetPosition); 101 | float angle = Mathf.Atan2(relativepoint.x, relativepoint.z) * Mathf.Rad2Deg; 102 | var rotateCar = 0; 103 | if(angle > turningAngleOffset) 104 | { 105 | rotateCar = 1; 106 | }else if(angle < -turningAngleOffset) 107 | { 108 | rotateCar = -1; 109 | } 110 | OnDrive?.Invoke(new Vector2(rotateCar, 1)); 111 | } 112 | } 113 | 114 | private void CheckIfArrived() 115 | { 116 | if(Stop == false) 117 | { 118 | var distanceToCheck = arriveDistance; 119 | if(index == path.Count - 1) 120 | { 121 | distanceToCheck = lastPointArriveDistance; 122 | } 123 | if(Vector3.Distance(currentTargetPosition,transform.position) < distanceToCheck) 124 | { 125 | SetNextTargetIndex(); 126 | } 127 | } 128 | } 129 | 130 | private void SetNextTargetIndex() 131 | { 132 | index++; 133 | if(index >= path.Count) 134 | { 135 | Stop = true; 136 | Destroy(gameObject); 137 | } 138 | else 139 | { 140 | currentTargetPosition = path[index]; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Scripts/AI/CarAI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b07c9c03ed6ed104c9c79355ef33bba7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AI/CarController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | [RequireComponent(typeof(Rigidbody))] 6 | public class CarController : MonoBehaviour 7 | { 8 | Rigidbody rb; 9 | 10 | [SerializeField] 11 | private float power = 5; 12 | [SerializeField] 13 | private float torque = 0.5f; 14 | [SerializeField] 15 | private float maxSpeed = 5; 16 | 17 | [SerializeField] 18 | private Vector2 movementVector; 19 | 20 | private void Awake() 21 | { 22 | rb = GetComponent(); 23 | } 24 | 25 | public void Move(Vector2 movementInput) 26 | { 27 | this.movementVector = movementInput; 28 | } 29 | 30 | private void FixedUpdate() 31 | { 32 | if(rb.velocity.magnitude < maxSpeed) 33 | { 34 | rb.AddForce(movementVector.y * transform.forward * power); 35 | } 36 | rb.AddTorque(movementVector.x * Vector3.up * torque * movementVector.y); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Scripts/AI/CarController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1ce851434c504124c9e41ec05d8b53aa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AI/CarSpawner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using Random = UnityEngine.Random; 6 | 7 | public class CarSpawner : MonoBehaviour 8 | { 9 | public GameObject[] carPrefabs; 10 | 11 | private void Start() 12 | { 13 | Instantiate(SelectACarPrefab(), transform); 14 | } 15 | 16 | private GameObject SelectACarPrefab() 17 | { 18 | var randomIndex = Random.Range(0, carPrefabs.Length); 19 | return carPrefabs[randomIndex]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scripts/AI/CarSpawner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c1ac252ad06c2e34a914cc24c8016b0a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/AI/SmartCrosswalks.cs: -------------------------------------------------------------------------------- 1 | using SimpleCity.AI; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | using UnityEngine.Events; 7 | 8 | public class SmartCrosswalks : MonoBehaviour 9 | { 10 | List pedestrianList = new List(); 11 | 12 | [field: SerializeField] 13 | public UnityEvent OnPedestrianEnter { get; set; } 14 | 15 | [field: SerializeField] 16 | public UnityEvent OnPedestrianExit { get; set; } 17 | 18 | private void OnTriggerEnter(Collider other) 19 | { 20 | var pedestrian = other.GetComponent(); 21 | if(pedestrian != null && pedestrianList.Contains(pedestrian) == false) 22 | { 23 | pedestrianList.Add(pedestrian); 24 | pedestrian.Stop = true; 25 | OnPedestrianEnter?.Invoke(); 26 | } 27 | } 28 | 29 | private void OnTriggerExit(Collider other) 30 | { 31 | var pedestrian = other.GetComponent(); 32 | if(pedestrian != null) 33 | { 34 | RemovePedestrian(pedestrian); 35 | } 36 | } 37 | 38 | private void RemovePedestrian(AiAgent pedestrian) 39 | { 40 | pedestrianList.Remove(pedestrian); 41 | if (pedestrianList.Count <= 0) 42 | OnPedestrianExit?.Invoke(); 43 | } 44 | 45 | public void MovePedestrians() 46 | { 47 | foreach (var pedestrian in pedestrianList) 48 | { 49 | pedestrian.Stop = false; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Scripts/AI/SmartRoad.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.Events; 6 | 7 | public class SmartRoad : MonoBehaviour 8 | { 9 | Queue trafficQueue = new Queue(); 10 | public CarAI currentCar; 11 | 12 | [SerializeField] 13 | private bool pedestrianWaiting = false, pedestrianWalking = false; 14 | 15 | [field: SerializeField] 16 | public UnityEvent OnPedestrianCanWalk { get; set; } 17 | 18 | private void OnTriggerEnter(Collider other) 19 | { 20 | if (other.CompareTag("Car")) 21 | { 22 | var car = other.GetComponent(); 23 | if(car != null && car != currentCar && car.IsThisLastPathIndex() == false) 24 | { 25 | trafficQueue.Enqueue(car); 26 | car.Stop = true; 27 | } 28 | } 29 | } 30 | 31 | private void Update() 32 | { 33 | if(currentCar == null) 34 | { 35 | if(trafficQueue.Count > 0 && pedestrianWaiting == false && pedestrianWalking == false) 36 | { 37 | currentCar = trafficQueue.Dequeue(); 38 | currentCar.Stop = false; 39 | }else if(pedestrianWalking || pedestrianWaiting) 40 | { 41 | OnPedestrianCanWalk?.Invoke(); 42 | pedestrianWalking = true; 43 | } 44 | } 45 | } 46 | 47 | private void OnTriggerExit(Collider other) 48 | { 49 | if (other.CompareTag("Car")) 50 | { 51 | var car = other.GetComponent(); 52 | if(car != null) 53 | { 54 | RemoveCar(car); 55 | } 56 | } 57 | } 58 | 59 | private void RemoveCar(CarAI car) 60 | { 61 | if(car == currentCar) 62 | { 63 | currentCar = null; 64 | } 65 | } 66 | 67 | 68 | public void SetPedestrianFlag(bool val) 69 | { 70 | if (val) 71 | { 72 | pedestrianWaiting = true; 73 | } 74 | else 75 | { 76 | pedestrianWaiting = false; 77 | pedestrianWalking = false; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Scripts/AiDirector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using UnityEngine; 6 | 7 | namespace SimpleCity.AI 8 | { 9 | public class AiDirector : MonoBehaviour 10 | { 11 | public PlacementManager placementManager; 12 | public GameObject[] pedestrianPrefabs; 13 | 14 | public GameObject carPrefab; 15 | 16 | AdjacencyGraph pedestrianGraph = new AdjacencyGraph(); 17 | AdjacencyGraph carGraph = new AdjacencyGraph(); 18 | 19 | List carPath = new List(); 20 | 21 | public void SpawnAllAagents() 22 | { 23 | foreach (var house in placementManager.GetAllHouses()) 24 | { 25 | TrySpawningAnAgent(house, placementManager.GetRandomSpecialStrucutre()); 26 | } 27 | foreach (var specialStructure in placementManager.GetAllSpecialStructures()) 28 | { 29 | TrySpawningAnAgent(specialStructure, placementManager.GetRandomHouseStructure()); 30 | } 31 | } 32 | 33 | private void TrySpawningAnAgent(StructureModel startStructure, StructureModel endStructure) 34 | { 35 | if(startStructure != null && endStructure != null) 36 | { 37 | var startPosition = ((INeedingRoad)startStructure).RoadPosition; 38 | var endPosition = ((INeedingRoad)endStructure).RoadPosition; 39 | 40 | var startMarkerPosition = placementManager.GetStructureAt(startPosition).GetPedestrianSpawnMarker(startStructure.transform.position); 41 | var endMarkerPosition = placementManager.GetStructureAt(endPosition).GetNearestPedestrianMarkerTo(endStructure.transform.position); 42 | 43 | var agent = Instantiate(GetRandomPedestrian(), startMarkerPosition.Position, Quaternion.identity); 44 | var path = placementManager.GetPathBetween(startPosition, endPosition, true); 45 | if(path.Count > 0) 46 | { 47 | path.Reverse(); 48 | List agentPath = GetPedestrianPath(path, startMarkerPosition.Position, endMarkerPosition); 49 | var aiAgent = agent.GetComponent(); 50 | aiAgent.Initialize(agentPath); 51 | } 52 | } 53 | } 54 | 55 | public void SpawnACar() 56 | { 57 | foreach (var house in placementManager.GetAllHouses()) 58 | { 59 | TrySpawninACar(house, placementManager.GetRandomSpecialStrucutre()); 60 | } 61 | } 62 | 63 | private void TrySpawninACar(StructureModel startStructure, StructureModel endStructure) 64 | { 65 | if (startStructure != null && endStructure != null) 66 | { 67 | var startRoadPosition = ((INeedingRoad)startStructure).RoadPosition; 68 | var endRoadPosition = ((INeedingRoad)endStructure).RoadPosition; 69 | 70 | var path = placementManager.GetPathBetween(startRoadPosition, endRoadPosition, true); 71 | path.Reverse(); 72 | 73 | if (path.Count == 0 && path.Count>2) 74 | return; 75 | 76 | var startMarkerPosition = placementManager.GetStructureAt(startRoadPosition).GetCarSpawnMarker(path[1]); 77 | 78 | var endMarkerPosition = placementManager.GetStructureAt(endRoadPosition).GetCarEndMarker(path[path.Count-2]); 79 | 80 | carPath = GetCarPath(path, startMarkerPosition.Position, endMarkerPosition.Position); 81 | 82 | if(carPath.Count > 0) 83 | { 84 | var car = Instantiate(carPrefab, startMarkerPosition.Position, Quaternion.identity); 85 | car.GetComponent().SetPath(carPath); 86 | } 87 | } 88 | } 89 | 90 | private List GetPedestrianPath(List path, Vector3 startPosition, Vector3 endPosition) 91 | { 92 | pedestrianGraph.ClearGraph(); 93 | CreatAPedestrianGraph(path); 94 | Debug.Log(pedestrianGraph); 95 | return AdjacencyGraph.AStarSearch(pedestrianGraph,startPosition,endPosition); 96 | } 97 | 98 | 99 | private void CreatAPedestrianGraph(List path) 100 | { 101 | Dictionary tempDictionary = new Dictionary(); 102 | 103 | for (int i = 0; i < path.Count; i++) 104 | { 105 | var currentPosition = path[i]; 106 | var roadStructure = placementManager.GetStructureAt(currentPosition); 107 | var markersList = roadStructure.GetPedestrianMarkers(); 108 | bool limitDistance = markersList.Count == 4; 109 | tempDictionary.Clear(); 110 | foreach (var marker in markersList) 111 | { 112 | pedestrianGraph.AddVertex(marker.Position); 113 | foreach (var markerNeighbourPosition in marker.GetAdjacentPositions()) 114 | { 115 | pedestrianGraph.AddEdge(marker.Position, markerNeighbourPosition); 116 | } 117 | 118 | if(marker.OpenForconnections && i+1 < path.Count) 119 | { 120 | var nextRoadStructure = placementManager.GetStructureAt(path[i + 1]); 121 | if (limitDistance) 122 | { 123 | tempDictionary.Add(marker, nextRoadStructure.GetNearestPedestrianMarkerTo(marker.Position)); 124 | } 125 | else 126 | { 127 | pedestrianGraph.AddEdge(marker.Position, nextRoadStructure.GetNearestPedestrianMarkerTo(marker.Position)); 128 | } 129 | } 130 | } 131 | if(limitDistance && tempDictionary.Count == 4) 132 | { 133 | var distanceSortedMarkers = tempDictionary.OrderBy(x => Vector3.Distance(x.Key.Position, x.Value)).ToList(); 134 | for (int j = 0; j < 2; j++) 135 | { 136 | pedestrianGraph.AddEdge(distanceSortedMarkers[j].Key.Position, distanceSortedMarkers[j].Value); 137 | } 138 | } 139 | } 140 | } 141 | 142 | private List GetCarPath(List path, Vector3 startPosition, Vector3 endPosition) 143 | { 144 | carGraph.ClearGraph(); 145 | CreatACarGraph(path); 146 | Debug.Log(carGraph); 147 | return AdjacencyGraph.AStarSearch(carGraph, startPosition, endPosition); 148 | } 149 | 150 | private void CreatACarGraph(List path) 151 | { 152 | Dictionary tempDictionary = new Dictionary(); 153 | for (int i = 0; i < path.Count; i++) 154 | { 155 | var currentPosition = path[i]; 156 | var roadStructure = placementManager.GetStructureAt(currentPosition); 157 | var markersList = roadStructure.GetCarMarkers(); 158 | var limitDistance = markersList.Count > 3; 159 | tempDictionary.Clear(); 160 | 161 | foreach (var marker in markersList) 162 | { 163 | carGraph.AddVertex(marker.Position); 164 | foreach (var markerNeighbour in marker.adjacentMarkers) 165 | { 166 | carGraph.AddEdge(marker.Position, markerNeighbour.Position); 167 | } 168 | if(marker.OpenForconnections && i + 1 < path.Count) 169 | { 170 | var nextRoadPosition = placementManager.GetStructureAt(path[i + 1]); 171 | if (limitDistance) 172 | { 173 | tempDictionary.Add(marker, nextRoadPosition.GetNearestCarMarkerTo(marker.Position)); 174 | } 175 | else 176 | { 177 | carGraph.AddEdge(marker.Position, nextRoadPosition.GetNearestCarMarkerTo(marker.Position)); 178 | } 179 | } 180 | } 181 | if (limitDistance && tempDictionary.Count > 2) 182 | { 183 | var distanceSortedMarkers = tempDictionary.OrderBy(x => Vector3.Distance(x.Key.Position, x.Value)).ToList(); 184 | foreach (var item in distanceSortedMarkers) 185 | { 186 | Debug.Log(Vector3.Distance(item.Key.Position, item.Value)); 187 | } 188 | for (int j = 0; j < 2; j++) 189 | { 190 | carGraph.AddEdge(distanceSortedMarkers[j].Key.Position, distanceSortedMarkers[j].Value); 191 | } 192 | } 193 | } 194 | } 195 | 196 | private GameObject GetRandomPedestrian() 197 | { 198 | return pedestrianPrefabs[UnityEngine.Random.Range(0, pedestrianPrefabs.Length)]; 199 | } 200 | 201 | private void Update() 202 | { 203 | //DrawGraph(carGraph); 204 | for (int i = 1; i < carPath.Count; i++) 205 | { 206 | Debug.DrawLine(carPath[i - 1] + Vector3.up, carPath[i] + Vector3.up, Color.magenta); 207 | } 208 | } 209 | 210 | private void DrawGraph(AdjacencyGraph graph) 211 | { 212 | foreach (var vertex in graph.GetVertices()) 213 | { 214 | foreach (var vertexNeighbour in graph.GetConnectedVerticesTo(vertex)) 215 | { 216 | Debug.DrawLine(vertex.Position + Vector3.up, vertexNeighbour.Position + Vector3.up, Color.red); 217 | } 218 | } 219 | } 220 | 221 | 222 | } 223 | } 224 | 225 | -------------------------------------------------------------------------------- /Scripts/Marker.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace SimpleCity.AI 8 | { 9 | public class Marker : MonoBehaviour 10 | { 11 | public Vector3 Position { get => transform.position;} 12 | 13 | public List adjacentMarkers; 14 | 15 | [SerializeField] 16 | private bool openForConnections; 17 | 18 | public bool OpenForconnections 19 | { 20 | get { return openForConnections; } 21 | } 22 | 23 | public List GetAdjacentPositions() 24 | { 25 | return new List(adjacentMarkers.Select(x => x.Position).ToList()); 26 | } 27 | 28 | private void OnDrawGizmos() 29 | { 30 | if(Selection.activeObject == gameObject) 31 | { 32 | Gizmos.color = Color.red; 33 | if (adjacentMarkers.Count > 0) 34 | { 35 | foreach (var item in adjacentMarkers) 36 | { 37 | Gizmos.DrawLine(transform.position, item.Position); 38 | } 39 | } 40 | Gizmos.color = Color.white; 41 | } 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Scripts/RoadHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | namespace SimpleCity.AI 7 | { 8 | public class RoadHelper : MonoBehaviour 9 | { 10 | [SerializeField] 11 | protected List pedestrianMarkers; 12 | [SerializeField] 13 | protected List carMarkers; 14 | [SerializeField] 15 | protected bool isCorner; 16 | [SerializeField] 17 | protected bool hasCrosswalks; 18 | 19 | float approximateThresholdCorner = 0.3f; 20 | 21 | [SerializeField] 22 | private Marker incomming, outgoing; 23 | 24 | public virtual Marker GetpositioForPedestrianToSpwan(Vector3 structurePosition) 25 | { 26 | return GetClosestMarkeTo(structurePosition, pedestrianMarkers); 27 | } 28 | 29 | public virtual Marker GetPositioForCarToSpawn(Vector3 nextPathPosition) 30 | { 31 | return outgoing; 32 | } 33 | 34 | public virtual Marker GetPositioForCarToEnd(Vector3 previousPathPosition) 35 | { 36 | return incomming; 37 | } 38 | 39 | protected Marker GetClosestMarkeTo(Vector3 structurePosition, List pedestrianMarkers, bool isCorner = false) 40 | { 41 | if (isCorner) 42 | { 43 | foreach (var marker in pedestrianMarkers) 44 | { 45 | var direction = marker.Position - structurePosition; 46 | direction.Normalize(); 47 | if(Mathf.Abs(direction.x) < approximateThresholdCorner || Mathf.Abs(direction.z) < approximateThresholdCorner) 48 | { 49 | return marker; 50 | } 51 | } 52 | return null; 53 | } 54 | else 55 | { 56 | Marker closestMarker = null; 57 | float distance = float.MaxValue; 58 | foreach (var marker in pedestrianMarkers) 59 | { 60 | var markerDistance = Vector3.Distance(structurePosition, marker.Position); 61 | if(distance > markerDistance) 62 | { 63 | distance = markerDistance; 64 | closestMarker = marker; 65 | } 66 | } 67 | return closestMarker; 68 | } 69 | } 70 | 71 | public Vector3 GetClosestPedestrainPosition(Vector3 currentPosition) 72 | { 73 | return GetClosestMarkeTo(currentPosition, pedestrianMarkers, isCorner).Position; 74 | } 75 | 76 | public Vector3 GetClosestCarMarkerPosition(Vector3 currentPosition) 77 | { 78 | return GetClosestMarkeTo(currentPosition, carMarkers, false).Position; 79 | } 80 | 81 | 82 | public List GetAllPedestrianMarkers() 83 | { 84 | return pedestrianMarkers; 85 | } 86 | 87 | public List GetAllCarMarkers() 88 | { 89 | return carMarkers; 90 | } 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /Scripts/RoadHelperStraight.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | namespace SimpleCity.AI 7 | { 8 | public class RoadHelperStraight : RoadHelper 9 | { 10 | [SerializeField] 11 | private Marker leftLaneMarker90, rightLaneMarker90; 12 | 13 | public override Marker GetPositioForCarToSpawn(Vector3 nextPathPosition) 14 | { 15 | int angle = (int)transform.rotation.eulerAngles.y; 16 | var direction = nextPathPosition - transform.position; 17 | return GetCorrectMarker(angle, direction); 18 | } 19 | 20 | 21 | public override Marker GetPositioForCarToEnd(Vector3 previousPathPosition) 22 | { 23 | int angle = (int)transform.rotation.eulerAngles.y; 24 | var direction = transform.position - previousPathPosition; 25 | return GetCorrectMarker(angle, direction); 26 | } 27 | 28 | 29 | private Marker GetCorrectMarker(int angle, Vector3 directionVector) 30 | { 31 | var direction = GetDirection(directionVector); 32 | if (angle == 0) 33 | { 34 | if (direction == Directon.left) 35 | { 36 | return rightLaneMarker90; 37 | } 38 | else 39 | { 40 | return leftLaneMarker90; 41 | } 42 | } else if (angle == 90) 43 | { 44 | if (direction == Directon.up) 45 | { 46 | return rightLaneMarker90; 47 | } 48 | else 49 | { 50 | return leftLaneMarker90; 51 | } 52 | }else if(angle == 270) 53 | { 54 | if (direction == Directon.left) 55 | { 56 | return leftLaneMarker90; 57 | } 58 | else 59 | { 60 | return rightLaneMarker90; 61 | } 62 | } 63 | else 64 | { 65 | if (direction == Directon.up) 66 | { 67 | return leftLaneMarker90; 68 | } 69 | else 70 | { 71 | return rightLaneMarker90; 72 | } 73 | } 74 | } 75 | 76 | public enum Directon 77 | { 78 | up, 79 | down, 80 | left, 81 | right 82 | } 83 | 84 | public Directon GetDirection(Vector3 direction) 85 | { 86 | if(Mathf.Abs(direction.z) > .5f) 87 | { 88 | if(direction.z > 0.5f) 89 | { 90 | return Directon.up; 91 | } 92 | else 93 | { 94 | return Directon.down; 95 | } 96 | } 97 | else 98 | { 99 | if(direction.x > 0.5f) 100 | { 101 | return Directon.right; 102 | } 103 | else 104 | { 105 | return Directon.left; 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | --------------------------------------------------------------------------------