using Gley.TrafficSystem.Internal; using Gley.UrbanSystem.Editor; using Gley.UrbanSystem.Internal; using UnityEditor; using UnityEngine; namespace Gley.TrafficSystem.Editor { [CustomEditor(typeof(VehicleComponent))] public class VehicleComponentEditor : UnityEditor.Editor { const string _carHolderName = "CarHolder"; const string _frontTriggerHolderName = "FrontTriggerHolder"; const string _frontTriggerName = "FrontTrigger"; const string _wheelsHolderName = "Wheels"; const string _shadowHolderName = "ShadowHolder"; const string _maxTriggerLengthName = "Max Trigger Length"; const string _updateTriggerName = "Update Trigger"; const string _trailerComponentName = "Trailer Component"; const string _trailerAnchorPointName = "Trailer Connection Point"; private VehicleComponent _targetScript; private void OnEnable() { _targetScript = (VehicleComponent)target; } public override void OnInspectorGUI() { base.OnInspectorGUI(); EditorGUI.BeginChangeCheck(); _targetScript.updateTrigger = EditorGUILayout.Toggle(new GUIContent(_updateTriggerName, "It checked, vehicle trigger length will increase with speed"), _targetScript.updateTrigger); if (_targetScript.updateTrigger) { _targetScript.maxTriggerLength = EditorGUILayout.FloatField(new GUIContent(_maxTriggerLengthName, "The length of the front trigger at max speed"), _targetScript.maxTriggerLength); } _targetScript.trailer = (TrailerComponent)EditorGUILayout.ObjectField(_trailerComponentName, _targetScript.trailer, typeof(TrailerComponent), true); if(_targetScript.trailer) { _targetScript.trailerConnectionPoint = (Transform)EditorGUILayout.ObjectField(_trailerAnchorPointName, _targetScript.trailerConnectionPoint, typeof(Transform), true); } EditorGUILayout.Space(); EditorGUILayout.LabelField("Automatically Computed", EditorStyles.boldLabel); EditorGUILayout.LabelField("Length: " + _targetScript.length.ToString()); EditorGUILayout.LabelField("Collider Height: " + _targetScript.coliderHeight.ToString()); EditorGUILayout.LabelField("Wheel Distance: " + _targetScript.wheelDistance.ToString()); EditorGUILayout.LabelField("Excluded: " + _targetScript.excluded.ToString()); if (EditorGUI.EndChangeCheck()) { Save(_targetScript); } EditorGUILayout.Space(); if (GUILayout.Button("Configure Car")) { ConfigureCar(_targetScript); } EditorGUILayout.Space(); if (GUILayout.Button("View Tutorial")) { Application.OpenURL("https://youtu.be/moGHcd2Jaa4"); } } public static void ConfigureCar(VehicleComponent targetScript) { bool correct = true; SetCarHolder(targetScript, ref correct); SetupRigidbody(targetScript, ref correct); AssignWheels(targetScript, ref correct); CreateFrontTrigger(targetScript, ref correct); AddShadow(targetScript); SetPivotOnBackWheels(targetScript, ref correct); SetWheelDimensions(targetScript, ref correct); CheckForZero(targetScript.accelerationTime, nameof(targetScript.accelerationTime), ref correct); CheckForZero(targetScript.minPossibleSpeed, nameof(targetScript.minPossibleSpeed), ref correct); CheckForZero(targetScript.maxPossibleSpeed, nameof(targetScript.maxPossibleSpeed), ref correct); AssignLights(targetScript); AssignVisibilityScript(targetScript); CheckColliers(targetScript, ref correct); ConnectTrailer(targetScript, ref correct); if (correct) { Debug.Log("Success! All references for " + targetScript.name + " ware correct"); Save(targetScript); } else { Debug.LogError(targetScript.name + " will not work correctly. See above messages for details"); } } private static void ConnectTrailer(VehicleComponent targetScript, ref bool correct) { if (!correct) return; if (targetScript.trailer == null) return; if(targetScript.trailerConnectionPoint==null) { LogError(ref correct, "Trailer Connection Point is not assigned. " + "Please assign it on Vehicle Component."); return; } targetScript.trailer.transform.position = new Vector3(targetScript.trailerConnectionPoint.position.x,targetScript.carHolder.position.y, targetScript.trailerConnectionPoint.position.z); if(targetScript.trailer.joint==null) { LogError(ref correct, "No joint assigned. Please Configure Trailer first."); return; } if(targetScript.trailer.truckConnectionPoint==null) { LogError(ref correct, "No truckConnectionPoint assigned. Please Configure Trailer first."); return; } targetScript.trailer.joint.connectedBody = targetScript.rb; targetScript.trailer.joint.autoConfigureConnectedAnchor = false; targetScript.trailer.joint.anchor = targetScript.trailer.truckConnectionPoint.GetLocalPositionRelativeToTopParent(targetScript.trailer.transform); targetScript.trailer.joint.connectedAnchor = targetScript.trailerConnectionPoint.GetLocalPositionRelativeToTopParent(targetScript.transform); targetScript.trailer.rb.mass = targetScript.rb.mass; #if UNITY_6000_0_OR_NEWER targetScript.trailer.rb.linearDamping = targetScript.rb.linearDamping; targetScript.trailer.rb.angularDamping = targetScript.rb.angularDamping; #else targetScript.trailer.rb.drag = targetScript.rb.drag; targetScript.trailer.rb.angularDrag = targetScript.rb.angularDrag; #endif } static void Save(VehicleComponent targetScript) { EditorUtility.SetDirty(targetScript); AssetDatabase.SaveAssets(); } private static void CheckColliers(VehicleComponent targetScript, ref bool correct) { if (!correct) return; Collider[] allColliders = targetScript.carHolder.GetComponentsInChildren(); targetScript.coliderHeight = 0; bool hasColliders = false; for (int i = 0; i < allColliders.Length; i++) { if (!allColliders[i].isTrigger) { float colliderDimension = allColliders[i].bounds.size.y * allColliders[i].transform.lossyScale.y; if (targetScript.coliderHeight < colliderDimension) { targetScript.coliderHeight = colliderDimension; } hasColliders = true; } } if (!hasColliders) { LogError(ref correct, "No collider found -> Please assign a collider on the car."); } if (targetScript.coliderHeight == 0) { LogError(ref correct, "Collider height is 0 -> Verify your colliders or press configure car in prefab mode."); } } private static void AssignVisibilityScript(VehicleComponent targetScript) { VisibilityScript[] allComponents = targetScript.gameObject.GetComponentsInChildren(); if (allComponents.Length > 1) { for (int i = 1; i < allComponents.Length; i++) { DestroyImmediate(allComponents[i]); } } if (allComponents.Length == 1) { if (allComponents[0].GetComponent() != null) { targetScript.visibilityScript = allComponents[0]; return; } else { DestroyImmediate(allComponents[0]); } } Renderer renderer = targetScript.GetComponentInChildren(); targetScript.visibilityScript = renderer.gameObject.AddComponent(); } private static void AssignLights(VehicleComponent targetScript) { VehicleLightsComponent vehicleLights = targetScript.GetComponent(); if (vehicleLights) { AssignGameobject(targetScript, ref vehicleLights.frontLights, "FrontLights"); AssignGameobject(targetScript, ref vehicleLights.reverseLights, "ReverseLights"); AssignGameobject(targetScript, ref vehicleLights.rearLights, "RearLights"); AssignGameobject(targetScript, ref vehicleLights.stopLights, "StopLights"); AssignGameobject(targetScript, ref vehicleLights.blinkerLeft, "BlinkersLeft"); AssignGameobject(targetScript, ref vehicleLights.blinkerRight, "BlinkersRight"); } } private static void AssignGameobject(VehicleComponent targetScript, ref GameObject objToAssign, string objectName) { Transform go = targetScript.transform.FindDeepChild(objectName); if (go == null) { Debug.Log(objectName + " not found"); return; } objToAssign = go.gameObject; } private static void SetWheelDimensions(VehicleComponent targetScript, ref bool correct) { if (!correct) return; for (int i = 0; i < targetScript.allWheels.Length; i++) { MeshFilter[] allMeshes = targetScript.allWheels[i].wheelTransform.GetComponentsInChildren(); if (allMeshes.Length == 0) { Debug.LogWarning("No meshes found inside " + targetScript.allWheels[0].wheelTransform); } float maxSize = 0; float wheelRadius = 0; //create mesh for (int j = 0; j < allMeshes.Length; j++) { float xSize = allMeshes[j].sharedMesh.bounds.size.x * allMeshes[j].transform.lossyScale.x; float ySize = allMeshes[j].sharedMesh.bounds.size.y * allMeshes[j].transform.lossyScale.y; float zSize = allMeshes[j].sharedMesh.bounds.size.z * allMeshes[j].transform.lossyScale.z; bool changed = false; if (xSize > maxSize) { maxSize = xSize; changed = true; } if (ySize > maxSize) { maxSize = ySize; changed = true; } if (zSize > maxSize) { maxSize = zSize; changed = true; } if (changed) { wheelRadius = Mathf.Max(xSize, ySize, zSize); wheelRadius /= 2; } } if (targetScript.allWheels[i].wheelRadius == 0) { targetScript.allWheels[i].wheelRadius = wheelRadius; } if (targetScript.maxSuspension == 0) { targetScript.allWheels[i].maxSuspension = wheelRadius / 2; } else { targetScript.allWheels[i].maxSuspension = targetScript.maxSuspension; } targetScript.allWheels[i].raycastLength = targetScript.allWheels[i].wheelRadius + targetScript.allWheels[i].maxSuspension; targetScript.allWheels[i].wheelCircumference = targetScript.allWheels[i].wheelRadius * Mathf.PI * 2; } } private static void CheckForZero(float value, string propertyName, ref bool correct) { if (value <= 0) { LogError(ref correct, propertyName + " needs to be > 0"); } } private static void SetPivotOnBackWheels(VehicleComponent targetScript, ref bool correct) { Vector3 poz = new Vector3(); int nr = 0; for (int i = 0; i < targetScript.allWheels.Length; i++) { if (targetScript.allWheels[i].wheelPosition == Wheel.WheelPosition.Back) { nr++; poz += targetScript.allWheels[i].wheelTransform.localPosition; } } if (nr == 0) { LogError(ref correct, "No back wheels set. Mark Wheel Position as Back for the back car wheels"); return; } targetScript.carHolder.localPosition = new Vector3(0, 0, -(poz / nr).z); } private static void AssignWheels(VehicleComponent targetScript, ref bool correct) { if (!correct) return; Transform wheelsHolder = targetScript.carHolder.Find(_wheelsHolderName); if (wheelsHolder == null) { for (int i = 0; i < targetScript.allWheels.Length; i++) { if (targetScript.allWheels[i].wheelTransform == null) { LogError(ref correct, "A GameObject named " + _wheelsHolderName + " was not found under " + targetScript.name); return; } } Debug.Log("All wheels ware manually assigned, make sure they are correct"); return; } //verify if wheels ware already assigned bool allAssigned = true; if (targetScript.allWheels != null) { if (targetScript.allWheels.Length < 2) { allAssigned = false; } for (int i = 0; i < targetScript.allWheels.Length; i++) { if (targetScript.allWheels[i].wheelTransform == null) { allAssigned = false; } } } else { allAssigned = false; } if (allAssigned == true) { Debug.Log("All wheels ware manually assigned, make sure they are correct"); return; } if (wheelsHolder.childCount == 0) { LogError(ref correct, "No GameObject was not found under " + _wheelsHolderName); } targetScript.allWheels = new Wheel[wheelsHolder.childCount]; for (int i = 0; i < wheelsHolder.childCount; i++) { targetScript.allWheels[i] = new Wheel(); targetScript.allWheels[i].wheelTransform = wheelsHolder.GetChild(i); if (targetScript.allWheels[i].wheelTransform.name.Contains("Front")) { targetScript.allWheels[i].wheelPosition = Wheel.WheelPosition.Front; } if (targetScript.allWheels[i].wheelTransform.name.Contains("Back") || targetScript.allWheels[i].wheelTransform.name.Contains("Rear")) { targetScript.allWheels[i].wheelPosition = Wheel.WheelPosition.Back; } try { targetScript.allWheels[i].wheelGraphics = wheelsHolder.GetChild(i).GetChild(0); } catch { LogError(ref correct, "No GameObject was not found under " + wheelsHolder.GetChild(i).name); } } } private static void AddShadow(VehicleComponent targetScript) { Transform shadowHolder = targetScript.carHolder.transform.Find(_shadowHolderName); if (shadowHolder) { targetScript.shadowHolder = shadowHolder; } else { Debug.Log("Shadow not found. Please assign the shadow manually if you need it"); } } private static void CreateFrontTrigger(VehicleComponent targetScript, ref bool correct) { if (!correct) return; float triggerLength = targetScript.distanceToStop + targetScript.triggerLength; float triggerHeight = 1; Transform frontTriggerHolder = targetScript.carHolder.transform.Find(_frontTriggerHolderName); GleyPrefabUtilities.DestroyTransform(frontTriggerHolder); if (frontTriggerHolder == null) { if (GleyPrefabUtilities.EditingInsidePrefab()) { GameObject prefabRoot = GleyPrefabUtilities.GetScenePrefabRoot(); frontTriggerHolder = new GameObject(_frontTriggerHolderName).transform; frontTriggerHolder.SetParent(prefabRoot.transform.Find(targetScript.carHolder.name).transform, false); } else { frontTriggerHolder = new GameObject(_frontTriggerHolderName).transform; frontTriggerHolder.SetParent(targetScript.carHolder.transform, false); } } Vector3 triggerPoz = new Vector3(); int nr = 0; for (int i = 0; i < targetScript.allWheels.Length; i++) { if (targetScript.allWheels[i].wheelPosition == Wheel.WheelPosition.Front) { nr++; triggerPoz += targetScript.allWheels[i].wheelTransform.position; } } if (nr == 0) { LogError(ref correct, "No front wheels set. Mark Wheel Position as Front for the front car wheels"); return; } triggerPoz = triggerPoz / nr; frontTriggerHolder.position = triggerPoz; targetScript.frontTrigger = frontTriggerHolder; targetScript.wheelDistance = targetScript.transform.InverseTransformPoint(triggerPoz).z; if (targetScript.wheelDistance == 0) { LogError(ref correct, " Distance between wheels is 0. Make sure your wheel assignments are correct"); return; } Transform frontTrigger = frontTriggerHolder.Find(_frontTriggerName); if (frontTrigger == null) { MeshFilter[] allMeshes = targetScript.carHolder.transform.GetComponentsInChildren(); if (allMeshes.Length == 0) { LogError(ref correct, "No meshes found inside " + targetScript.name); return; } float maxSize = 0; float triggerSize = 0; //create mesh for (int i = 0; i < allMeshes.Length; i++) { float xSize = allMeshes[i].sharedMesh.bounds.size.x * allMeshes[i].transform.localScale.x; float ySize = allMeshes[i].sharedMesh.bounds.size.y * allMeshes[i].transform.localScale.y; float zSize = allMeshes[i].sharedMesh.bounds.size.z * allMeshes[i].transform.localScale.z; bool changed = false; if (xSize > maxSize) { maxSize = xSize; changed = true; } if (zSize > maxSize) { maxSize = zSize; changed = true; } if (changed) { triggerSize = Mathf.Min(xSize, zSize); targetScript.length = Mathf.Max(xSize, zSize); } if (ySize > triggerHeight) { triggerHeight = ySize; } } frontTrigger = new GameObject(_frontTriggerName).transform; frontTrigger.SetParent(frontTriggerHolder); BoxCollider boxCollider = frontTrigger.gameObject.AddComponent(); boxCollider.size = new Vector3(triggerSize, triggerHeight, triggerLength); boxCollider.center = new Vector3(0, 0, triggerLength / 2); boxCollider.isTrigger = true; frontTriggerHolder.gameObject.SetLayer(targetScript.gameObject.layer); frontTrigger.transform.localPosition = new Vector3(0, triggerHeight / 2, 0); } } private static void SetupRigidbody(VehicleComponent targetScript, ref bool correct) { if (!correct) return; Rigidbody rb = targetScript.GetComponent(); if (rb == null) { LogError(ref correct, "RigidBody not found on " + targetScript.name); } else { targetScript.rb = rb; #if UNITY_6000_0_OR_NEWER if (rb.linearDamping == 0) { targetScript.rb.linearDamping = 0.1f; targetScript.rb.angularDamping = 3; } #else if (rb.drag == 0) { targetScript.rb.drag = 0.1f; targetScript.rb.angularDrag = 3; } #endif } } private static void SetCarHolder(VehicleComponent targetScript, ref bool correct) { if (!correct) return; Transform carHolder = targetScript.transform.Find(_carHolderName); if (carHolder == null) { if (targetScript.carHolder != null) { Debug.Log("A GameObject was manually assigned inside " + nameof(targetScript.carHolder) + ". Make sure is the root GameObject"); } else { LogError(ref correct, "A GameObject named " + _carHolderName + " was not found under " + targetScript.name); } } else { targetScript.carHolder = carHolder; } } private static void LogError(ref bool correct, string message) { correct = false; Debug.LogError(message + " Auto assign will stop now."); Debug.Log("Please fix the above errors and press Configure Car again or assign missing references and values manually."); } } }