TrafficEditor.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // Traffic Simulation
  2. // https://github.com/mchrbn/unity-traffic-simulation
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using UnityEngine;
  8. using UnityEditor;
  9. namespace TrafficSimulation {
  10. [CustomEditor(typeof(TrafficSystem))]
  11. public class TrafficEditor : Editor {
  12. private TrafficSystem wps;
  13. //References for moving a waypoint
  14. private Vector3 startPosition;
  15. private Vector3 lastPoint;
  16. private Waypoint lastWaypoint;
  17. [MenuItem("Component/Traffic Simulation/Create Traffic Objects")]
  18. private static void CreateTraffic(){
  19. EditorHelper.SetUndoGroup("Create Traffic Objects");
  20. GameObject mainGo = EditorHelper.CreateGameObject("Traffic System");
  21. mainGo.transform.position = Vector3.zero;
  22. EditorHelper.AddComponent<TrafficSystem>(mainGo);
  23. GameObject segmentsGo = EditorHelper.CreateGameObject("Segments", mainGo.transform);
  24. segmentsGo.transform.position = Vector3.zero;
  25. GameObject intersectionsGo = EditorHelper.CreateGameObject("Intersections", mainGo.transform);
  26. intersectionsGo.transform.position = Vector3.zero;
  27. //Close Undo Operation
  28. Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
  29. }
  30. void OnEnable(){
  31. wps = target as TrafficSystem;
  32. }
  33. private void OnSceneGUI() {
  34. Event e = Event.current;
  35. if (e == null) return;
  36. Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
  37. if (Physics.Raycast(ray, out RaycastHit hit) && e.type == EventType.MouseDown && e.button == 0) {
  38. //Add a new waypoint on mouseclick + shift
  39. if (e.shift) {
  40. if (wps.curSegment == null) {
  41. return;
  42. }
  43. EditorHelper.BeginUndoGroup("Add Waypoint", wps);
  44. AddWaypoint(hit.point);
  45. //Close Undo Group
  46. Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
  47. }
  48. //Create a segment + add a new waypoint on mouseclick + ctrl
  49. else if (e.control) {
  50. EditorHelper.BeginUndoGroup("Add Segment", wps);
  51. AddSegment(hit.point);
  52. AddWaypoint(hit.point);
  53. //Close Undo Group
  54. Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
  55. }
  56. //Create an intersection type
  57. else if (e.alt) {
  58. EditorHelper.BeginUndoGroup("Add Intersection", wps);
  59. AddIntersection(hit.point);
  60. //Close Undo Group
  61. Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
  62. }
  63. }
  64. //Set waypoint system as the selected gameobject in hierarchy
  65. Selection.activeGameObject = wps.gameObject;
  66. //Handle the selected waypoint
  67. if (lastWaypoint != null) {
  68. //Uses a endless plain for the ray to hit
  69. Plane plane = new Plane(Vector3.up.normalized, lastWaypoint.GetVisualPos());
  70. plane.Raycast(ray, out float dst);
  71. Vector3 hitPoint = ray.GetPoint(dst);
  72. //Reset lastPoint if the mouse button is pressed down the first time
  73. if (e.type == EventType.MouseDown && e.button == 0) {
  74. lastPoint = hitPoint;
  75. startPosition = lastWaypoint.transform.position;
  76. }
  77. //Move the selected waypoint
  78. if (e.type == EventType.MouseDrag && e.button == 0) {
  79. Vector3 realDPos = new Vector3(hitPoint.x - lastPoint.x, 0, hitPoint.z - lastPoint.z);
  80. lastWaypoint.transform.position += realDPos;
  81. lastPoint = hitPoint;
  82. }
  83. //Release the selected waypoint
  84. if (e.type == EventType.MouseUp && e.button == 0) {
  85. Vector3 curPos = lastWaypoint.transform.position;
  86. lastWaypoint.transform.position = startPosition;
  87. Undo.RegisterFullObjectHierarchyUndo(lastWaypoint, "Move Waypoint");
  88. lastWaypoint.transform.position = curPos;
  89. }
  90. //Draw a Sphere
  91. Handles.SphereHandleCap(0, lastWaypoint.GetVisualPos(), Quaternion.identity, wps.waypointSize * 2f, EventType.Repaint);
  92. HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
  93. SceneView.RepaintAll();
  94. }
  95. //Set the current hovering waypoint
  96. if (lastWaypoint == null) {
  97. lastWaypoint = wps.GetAllWaypoints().FirstOrDefault(i => EditorHelper.SphereHit(i.GetVisualPos(), wps.waypointSize, ray));
  98. }
  99. //Update the current segment to the currently interacting one
  100. if (lastWaypoint != null && e.type == EventType.MouseDown) {
  101. wps.curSegment = lastWaypoint.segment;
  102. }
  103. //Reset current waypoint
  104. else if (lastWaypoint != null && e.type == EventType.MouseMove) {
  105. lastWaypoint = null;
  106. }
  107. }
  108. public override void OnInspectorGUI() {
  109. EditorGUI.BeginChangeCheck();
  110. //Register an Undo if changes are made after this call
  111. Undo.RecordObject(wps, "Traffic Inspector Edit");
  112. //Draw the Inspector
  113. TrafficEditorInspector.DrawInspector(wps, serializedObject, out bool restructureSystem);
  114. //Rename waypoints if some have been deleted
  115. if (restructureSystem) {
  116. RestructureSystem();
  117. }
  118. //Repaint the scene if values have been edited
  119. if (EditorGUI.EndChangeCheck()) {
  120. SceneView.RepaintAll();
  121. }
  122. serializedObject.ApplyModifiedProperties();
  123. }
  124. private void AddWaypoint(Vector3 position) {
  125. GameObject go = EditorHelper.CreateGameObject("Waypoint-" + wps.curSegment.waypoints.Count, wps.curSegment.transform);
  126. go.transform.position = position;
  127. Waypoint wp = EditorHelper.AddComponent<Waypoint>(go);
  128. wp.Refresh(wps.curSegment.waypoints.Count, wps.curSegment);
  129. //Record changes to the TrafficSystem (string not relevant here)
  130. Undo.RecordObject(wps.curSegment, "");
  131. wps.curSegment.waypoints.Add(wp);
  132. }
  133. private void AddSegment(Vector3 position) {
  134. int segId = wps.segments.Count;
  135. GameObject segGo = EditorHelper.CreateGameObject("Segment-" + segId, wps.transform.GetChild(0).transform);
  136. segGo.transform.position = position;
  137. wps.curSegment = EditorHelper.AddComponent<Segment>(segGo);
  138. wps.curSegment.id = segId;
  139. wps.curSegment.waypoints = new List<Waypoint>();
  140. wps.curSegment.nextSegments = new List<Segment>();
  141. //Record changes to the TrafficSystem (string not relevant here)
  142. Undo.RecordObject(wps, "");
  143. wps.segments.Add(wps.curSegment);
  144. }
  145. private void AddIntersection(Vector3 position) {
  146. int intId = wps.intersections.Count;
  147. GameObject intGo = EditorHelper.CreateGameObject("Intersection-" + intId, wps.transform.GetChild(1).transform);
  148. intGo.transform.position = position;
  149. BoxCollider bc = EditorHelper.AddComponent<BoxCollider>(intGo);
  150. bc.isTrigger = true;
  151. Intersection intersection = EditorHelper.AddComponent<Intersection>(intGo);
  152. intersection.id = intId;
  153. //Record changes to the TrafficSystem (string not relevant here)
  154. Undo.RecordObject(wps, "");
  155. wps.intersections.Add(intersection);
  156. }
  157. void RestructureSystem(){
  158. //Rename and restructure segments and waypoints
  159. List<Segment> nSegments = new List<Segment>();
  160. int itSeg = 0;
  161. foreach(Transform tS in wps.transform.GetChild(0).transform){
  162. Segment segment = tS.GetComponent<Segment>();
  163. if(segment != null){
  164. List<Waypoint> nWaypoints = new List<Waypoint>();
  165. segment.id = itSeg;
  166. segment.gameObject.name = "Segment-" + itSeg;
  167. int itWp = 0;
  168. foreach(Transform tW in segment.gameObject.transform){
  169. Waypoint waypoint = tW.GetComponent<Waypoint>();
  170. if(waypoint != null) {
  171. waypoint.Refresh(itWp, segment);
  172. nWaypoints.Add(waypoint);
  173. itWp++;
  174. }
  175. }
  176. segment.waypoints = nWaypoints;
  177. nSegments.Add(segment);
  178. itSeg++;
  179. }
  180. }
  181. //Check if next segments still exist
  182. foreach(Segment segment in nSegments){
  183. List<Segment> nNextSegments = new List<Segment>();
  184. foreach(Segment nextSeg in segment.nextSegments){
  185. if(nextSeg != null){
  186. nNextSegments.Add(nextSeg);
  187. }
  188. }
  189. segment.nextSegments = nNextSegments;
  190. }
  191. wps.segments = nSegments;
  192. //Check intersections
  193. List<Intersection> nIntersections = new List<Intersection>();
  194. int itInter = 0;
  195. foreach(Transform tI in wps.transform.GetChild(1).transform){
  196. Intersection intersection = tI.GetComponent<Intersection>();
  197. if(intersection != null){
  198. intersection.id = itInter;
  199. intersection.gameObject.name = "Intersection-" + itInter;
  200. nIntersections.Add(intersection);
  201. itInter++;
  202. }
  203. }
  204. wps.intersections = nIntersections;
  205. //Tell Unity that something changed and the scene has to be saved
  206. if (!EditorUtility.IsDirty(target)) {
  207. EditorUtility.SetDirty(target);
  208. }
  209. Debug.Log("[Traffic Simulation] Successfully rebuilt the traffic system.");
  210. }
  211. }
  212. }