#if UNITY_EDITOR using MonKey.Editor.Internal; using MonKey.Extensions; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using Object = UnityEngine.Object; using Random = UnityEngine.Random; namespace MonKey.Editor.Commands { public static class MeasurementUtilities { [Command("Measure Distances Between", "Displays the distances between the selected objects in the scene view", AlwaysShow = true, DefaultValidation = DefaultValidation.AT_LEAST_TWO_TRANSFORMS, QuickName = "MCB", Category = "Measurement")] public static void ShowDistances( [CommandParameter(Help = "The color of the debug on the scene view", DefaultValueMethod = "DefaultDebugColor", DefaultValueNameOverride = "Debug Green")] Color debugColor) { MonkeyEditorUtils. AddSceneCommand(new DistanceShowingSceneCommand(Selection.gameObjects, debugColor, 0)); } public static Color DefaultDebugColor() { // return ColorUtils.HTMLColor("#228B22"); return Color.green; } public class DistanceShowingSceneCommand : TimedSceneCommand { private List objectsToMeasure; private readonly Color color; public DistanceShowingSceneCommand(GameObject[] objectsToMeasure, Color color, float duration) : base(duration) { SceneCommandName = "Measurement"; this.objectsToMeasure = new List(objectsToMeasure); this.color = color; } public override void DisplayParameters() { base.DisplayParameters(); DisplayObjectListOption("Objects to Measure", objectsToMeasure); } public override void OnSceneGUI() { base.OnSceneGUI(); objectsToMeasure.RemoveAll(_ => !_); for (int i = 0; i < objectsToMeasure.Count - 1; i++) { GameObject gameObject = objectsToMeasure[i]; for (int j = i + 1; j < objectsToMeasure.Count; j++) { GameObject otherObject = objectsToMeasure[j]; if (otherObject == gameObject) continue; Handles.color = color; Handles.DrawDottedLine(gameObject.transform.position, otherObject.transform.position, 10); Vector3 distance = gameObject.transform.position - otherObject.transform.position; Handles.Label((gameObject.transform.position + otherObject.transform.position) / 2, "Distance: " + distance.magnitude, new GUIStyle() { normal = { textColor = color , background = MonkeyStyle.Instance.WindowBackgroundTex} }); Handles.color = Color.white; } } } } [Command("Measure Collision Distance", QuickName = "MCD", Help = "Displays the distance of objects to any collision" + " in the axis specified (default down)", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, Category = "Measurement")] public static void MeasureCollisionDistance( [CommandParameter(Help = "The color of the debug on the scene view", DefaultValueMethod = "DefaultDebugColor", DefaultValueNameOverride = "Debug Green")] Color debugColor) { MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand( Selection.gameObjects, debugColor, Vector3.down, false, 0)); } [Command("Measure Collision Distance 2D", QuickName = "MCD2", Help = "Displays the distance of 2d objects to any collision" + " in the axis specified (default down)", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, Category = "Measurement")] public static void MeasureCollisionDistance2D( [CommandParameter(Help = "The color of the debug on the scene view", DefaultValueMethod = "DefaultDebugColor", DefaultValueNameOverride = "Debug Green")] Color debugColor) { MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand2D( Selection.gameObjects, debugColor, Vector3.down, false, 0)); } [Command("Show Position Handles", "Shows handles on all the specified objects (by default the selection)", QuickName = "SH", Category = "Measurement")] public static void ShowHandles( [CommandParameter("The Objects to show the handle on", DefaultValueMethod = "SelectionDefault", ForceAutoCompleteUsage = true)] GameObject[] objects) { MonkeyEditorUtils.AddSceneCommand(new TransformHandleShowingSceneCommand(objects)); } private static GameObject[] SelectionDefault() { return Selection.gameObjects; } [Command("Measure Collision Distance All Axis", "Displays the distance of an objects to any collision" + " in all axis (global by default)", QuickName = "MCA", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, Category = "Measurement")] public static void MeasureAllCollisionDistance( [CommandParameter(Help = "if true, considers the local axis instead" + " of the global one", DefaultValueMethod = "DefaultLocal",DefaultValueNameOverride = "Global")] AxisReference localAxisReference) { MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand( Selection.gameObjects, Color.green.DarkerBrighter(-0.2f), Vector3.up, localAxisReference == AxisReference.LOCAL, 0)); MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand( Selection.gameObjects, Color.green.DarkerBrighter(-0.2f), Vector3.down, localAxisReference == AxisReference.LOCAL, 0)); MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand( Selection.gameObjects, Color.blue, Vector3.forward, localAxisReference == AxisReference.LOCAL, 0)); MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand( Selection.gameObjects, Color.blue, Vector3.back, localAxisReference == AxisReference.LOCAL, 0)); MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand( Selection.gameObjects, Color.red, Vector3.left, localAxisReference == AxisReference.LOCAL, 0)); MonkeyEditorUtils. AddSceneCommand(new DistanceAxisShowingSceneCommand( Selection.gameObjects, Color.red, Vector3.right, localAxisReference == AxisReference.LOCAL, 0)); } private static AxisReference DefaultLocal() { return AxisReference.GLOBAL; } private static Vector3 DefaultAxis() { return Vector3.down; } public class TransformHandleShowingSceneCommand : TimedSceneCommand { private GameObject[] objectsToHandle; public TransformHandleShowingSceneCommand(GameObject[] objectsToHandle) : base(0) { this.objectsToHandle = objectsToHandle; SceneCommandName = "Handles"; HideGUI = true; } public override void OnSceneGUI() { base.OnSceneGUI(); foreach (GameObject gameObject in objectsToHandle) { if (!gameObject) continue; Vector3 position = Handles.DoPositionHandle(gameObject.transform.position, gameObject.transform.localRotation); Handles.Label(gameObject.transform.position + Vector3.up, gameObject.name); if ((position - gameObject.transform.position).magnitude > Mathf.Epsilon) { Undo.RecordObject(gameObject.transform, "move"); } gameObject.transform.position = position; } } } public class DistanceAxisShowingSceneCommand : TimedSceneCommand { private List objectsToMeasure; private Vector3 axis; private bool local; private Color color; public DistanceAxisShowingSceneCommand(GameObject[] objectsToMeasure, Color color, Vector3 axis, bool local, float duration) : base(duration) { SceneCommandName = "Distance Measurement"; this.objectsToMeasure = objectsToMeasure.ToList(); this.color = color; this.local = local; this.axis = axis; } public override void DisplayParameters() { base.DisplayParameters(); DisplayVectorOption("Direction", ref axis); DisplayBoolOption("Local Axis", ref local); DisplayObjectListOption("Objects To Measure", objectsToMeasure); } public override void OnSceneGUI() { base.OnSceneGUI(); objectsToMeasure = objectsToMeasure.Where(_ => _).ToList(); for (int i = 0; i < objectsToMeasure.Count; i++) { GameObject gameObject = objectsToMeasure[i]; if (!gameObject) return; Ray ray = new Ray(gameObject.transform.position, (local) ? gameObject.transform.TransformDirection(axis) : axis); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { Handles.color = color; #if UNITY_2017_1_OR_NEWER Handles.zTest = CompareFunction.LessEqual; #endif Handles.DrawDottedLine(gameObject.transform.position, hit.point, 5); Handles.DrawWireDisc(hit.point, hit.normal, 0.5f); #if UNITY_2017_1_OR_NEWER Handles.zTest = CompareFunction.Always; #endif Vector3 distance = gameObject.transform.position - hit.point; Handles.Label((gameObject.transform.position + hit.point) / 2, "Distance: " + distance.magnitude, new GUIStyle() { margin = new RectOffset(2, 2, 2, 2), normal = { textColor = color, background = MonkeyStyle.Instance.WindowBackgroundTex } }); Handles.color = color.Inverted(); #if UNITY_2017_1_OR_NEWER Handles.zTest = CompareFunction.Greater; #endif Handles.DrawDottedLine(gameObject.transform.position, hit.point, 5); Handles.DrawWireDisc(hit.point + hit.normal * 0.1f, hit.normal, 0.5f); Handles.color = Color.white; } } } } public class DistanceAxisShowingSceneCommand2D : TimedSceneCommand { private List objectsToMeasure; private Vector2 axis; private bool local; private Color color; public DistanceAxisShowingSceneCommand2D(GameObject[] objectsToMeasure, Color color, Vector3 axis, bool local, float duration) : base(duration) { SceneCommandName = "Distance Measurement"; this.objectsToMeasure = objectsToMeasure.ToList(); this.color = color; this.local = local; this.axis = axis; } public override void DisplayParameters() { base.DisplayParameters(); DisplayVector2Option("Direction", ref axis); DisplayBoolOption("Local Axis", ref local); DisplayObjectListOption("Objects To Measure", objectsToMeasure); } public override void OnSceneGUI() { base.OnSceneGUI(); objectsToMeasure = objectsToMeasure.Where(_ => _).ToList(); for (int i = 0; i < objectsToMeasure.Count; i++) { GameObject gameObject = objectsToMeasure[i]; if (!gameObject) return; RaycastHit2D hit = Physics2D.Raycast(gameObject.transform.position, (local) ? (Vector2)gameObject.transform.TransformDirection(axis) : axis); if (hit.collider != null) { Handles.color = color; #if UNITY_2017_1_OR_NEWER Handles.zTest = CompareFunction.LessEqual; #endif Handles.DrawDottedLine(gameObject.transform.position, hit.point, 5); Handles.DrawWireDisc(hit.point, hit.normal, 0.5f); #if UNITY_2017_1_OR_NEWER Handles.zTest = CompareFunction.Always; #endif Vector2 distance = (Vector2)gameObject.transform.position - hit.point; Handles.Label(((Vector2)gameObject.transform.position + hit.point) / 2, "Distance: " + distance.magnitude, new GUIStyle() { margin = new RectOffset(2, 2, 2, 2), normal = { textColor = color, background = MonkeyStyle.Instance.WindowBackgroundTex } }); Handles.color = color.Inverted(); #if UNITY_2017_1_OR_NEWER Handles.zTest = CompareFunction.Greater; #endif Handles.DrawDottedLine(gameObject.transform.position, hit.point, 5); Handles.DrawWireDisc(hit.point + hit.normal * 0.1f, hit.normal, 0.5f); Handles.color = Color.white; } } } } [Command("Count Selected", "Counts the amount of objects selected and outputs the result in the console", QuickName = "COS", Category = "Measurement")] public static void CountSelectedObject() { Debug.LogFormat("MonKey Counted:".Colored(MonkeyStyle.Instance.SelectedResultFieldColor).Bold() + "\n {0} Objects Selected" + ", {1} GameObjects Selected " + ", {2} Transforms Selected", Selection.objects.Length, Selection.gameObjects.Length, Selection.transforms.Length); } [Command("Randomize Scale", "Randomizes the local scale of the selected objects " + "within a specified range (by default .8 to 1.2)", QuickName = "RAS", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, IgnoreHotKeyConflict = true, MenuItemLink = "RandomizeScale", MenuItemLinkTypeOwner = "MonkeyMenuItems", Category = "Transform/Set")] public static void RandomizeScale() { MonkeyEditorUtils.AddSceneCommand( new InteractiveRandomScale(Selection.gameObjects)); } [Command("Set Local Scale", "Set the specified scale on the selected objects", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, QuickName = "SLS", MenuItemLink = "SetLocalScale", MenuItemLinkTypeOwner = "MonkeyMenuItems", Category = "Transform/Set")] public static void SetScale( [CommandParameter(Help = "The local scale to set on the selected objects")] Vector3 scale) { foreach (GameObject gameObject in Selection.gameObjects) { gameObject.transform.localScale = scale; } } [Command("Set Local Rotation", "Set the specified rotation on the selected objects", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, QuickName = "SLR", MenuItemLink = "SetLocalRotation", MenuItemLinkTypeOwner = "MonkeyMenuItems", Category = "Transform/Set")] public static void SetRotation( [CommandParameter(Help = "The local rotation to set on the selected objects")] Vector3 rotation) { foreach (GameObject gameObject in Selection.gameObjects) { gameObject.transform.localRotation = Quaternion.Euler(rotation); } } [Command("Set Local Position", "Set the specified rotation on the selected objects", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, QuickName = "SLP", MenuItemLink = "SetLocalPosition", MenuItemLinkTypeOwner = "MonkeyMenuItems", Category = "Transform/Set")] public static void SetPosition( [CommandParameter(Help = "The local position to set on the selected objects")] Vector3 position) { int id = MonkeyEditorUtils.CreateUndoGroup("MonKey : Local Position Set"); foreach (GameObject gameObject in Selection.gameObjects) { Undo.RecordObject(gameObject.transform, " tr"); gameObject.transform.localPosition = position; } Undo.CollapseUndoOperations(id); } private static Vector3 OneVector() { return Vector3.one; } [Command("Randomize Rotation", "Randomizes the local rotation of the selected objects " + "within a specified range (by default 0 to 360)", QuickName = "RAR", DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, IgnoreHotKeyConflict = true, MenuItemLink = "RandomizeRotation", MenuItemLinkTypeOwner = "MonkeyMenuItems", Category = "Transform/Set")] public static void RandomizeRotation() { MonkeyEditorUtils.AddSceneCommand( new InteractiveRandomRotation(Selection.gameObjects)); } [Command("Randomize Position", "Randomizes the local position of the selected objects " + "within a specified range (by default -1 to 1)", QuickName = "RAP", IgnoreHotKeyConflict = true, DefaultValidation = DefaultValidation.AT_LEAST_ONE_TRANSFORM, MenuItemLink = "RandomizePosition", MenuItemLinkTypeOwner = "MonkeyMenuItems", Category = "Transform/Set")] public static void RandomizePosition() { MonkeyEditorUtils.AddSceneCommand( new InteractiveRandomPosition(Selection.gameObjects)); } public static Vector2 DefaultScaleRange() { return new Vector2(.8f, 1.2f); } public static Vector2[] RandomRotationRangeDefaultValue() { return new Vector2[] { new Vector2(-180, 180f) }; } public static CommandParameterAutoComplete RandomRotationRangeAutoComplete() { return new CommandParameterAutoComplete(). AddValue("-180 to 180", new Vector2(-180, 180f)) .AddValue("0 to 180", new Vector2(0, 180)) .AddValue("-90 to +90", new Vector2(-90, 90)) .AddValue("0 to 360", new Vector2(0, 360)); } public static CommandParameterAutoComplete RandomScaleRangeAutoComplete() { return new CommandParameterAutoComplete().AddValue("0 to 1", Vector2.up) .AddValue("1 to 2", new Vector2(1, 2)) .AddValue("1 to 5", new Vector2(1, 5)) .AddValue("0.5 to 1.5", new Vector2(.5f, 1.5f)) .AddValue("0.8 to 1.2", new Vector2(.8f, 1.2f)); } public static CommandParameterAutoComplete RandomPositionRangeAutoComplete() { return new CommandParameterAutoComplete() .AddValue("-0.25 to 0.25", new Vector2(-.25f, .25f)) .AddValue("-0.5 to 0.5", new Vector2(-.5f, .5f)) .AddValue("-1 to 1", new Vector2(-1, 1)) .AddValue("-2 to 2", new Vector2(-2, 2)) .AddValue("-5 to 5", new Vector2(-5, 5)) .AddValue("-10 to 10", new Vector2(-10, 10)) .AddValue("-50 to 50", new Vector2(-50, 50)); } public class InteractiveRandomScale : InteractiveCommand { private Vector2 scaleRandom; private List toRandomize; public float Multiplier = 1; public InteractiveRandomScale(GameObject[] toRandomize) { ConfirmationMode = ActionConfirmationMode.ESCAPE; SceneCommandName = "Transform Randomization"; ActionOnSpace = "to randomize transforms"; this.scaleRandom = new Vector2(0.8f, 1.1f); this.toRandomize = toRandomize.ToList(); } public override void DisplayParameters() { base.DisplayParameters(); float min = scaleRandom.x; float max = scaleRandom.y; DisplayFloatOption("Min Scale", ref min); DisplayFloatOption("Max Scale", ref max); scaleRandom = new Vector2(min, max); DisplayFloatOption("Multiplier", ref Multiplier); DisplayObjectListOption("Objects To Randomize", toRandomize); } public override void ApplyFunction() { OnSpaceDownAction(); } protected override void OnSpaceDownAction() { base.OnSpaceDownAction(); int undoID = MonkeyEditorUtils.CreateUndoGroup("Randomization"); toRandomize.RemoveAll(_ => !_); foreach (GameObject gameObject in toRandomize) { Undo.RecordObject(gameObject.transform, "Randomization trans"); gameObject.transform.localScale = Vector3.one * (Random.Range(scaleRandom.x, scaleRandom.y) * Multiplier); } Undo.CollapseUndoOperations(undoID); } } public class InteractiveRandomRotation : InteractiveCommand { private readonly Axis[] randomAxes = { Axis.X, Axis.Y, Axis.Z }; private readonly Vector2[] randomPerAxis; private List toRandomize; private readonly Quaternion[] previousAngles; public float Multiplier = 1f; public InteractiveRandomRotation(GameObject[] toRandomize) { ConfirmationMode = ActionConfirmationMode.ESCAPE; SceneCommandName = "Transform Randomization"; ActionOnSpace = "to randomize transforms"; this.randomPerAxis = new Vector2[] { new Vector2(0, 0), new Vector2(-180, 180), new Vector2(0, 0) }; this.toRandomize = new List(toRandomize); previousAngles = toRandomize.Convert(_ => _.transform.localRotation).ToArray(); } public override void DisplayParameters() { base.DisplayParameters(); DisplayVector2Option("X Axis Random Range", ref randomPerAxis[0]); DisplayVector2Option("Y Axis Random Range", ref randomPerAxis[1]); DisplayVector2Option("Z Axis Random Range", ref randomPerAxis[2]); DisplayFloatOption("Multiplier", ref Multiplier); DisplayObjectListOption("Objects To Randomize", toRandomize); } public override void ApplyFunction() { OnSpaceDownAction(); } protected override void OnSpaceDownAction() { base.OnSpaceDownAction(); int undoID = MonkeyEditorUtils.CreateUndoGroup("Randomization"); int objectID = 0; toRandomize.RemoveAll(_ => !_); foreach (GameObject gameObject in toRandomize) { Undo.RecordObject(gameObject.transform, "Randomization trans"); if (randomPerAxis != null && randomPerAxis.Length > 0) { int i = 0; gameObject.transform.localRotation = previousAngles[objectID]; Vector3 previousUp = gameObject.transform.up; Vector3 previousRight = gameObject.transform.right; Vector3 previousForward = gameObject.transform.forward; foreach (Axis axis in randomAxes) { int j = i; if (i >= randomPerAxis.Length) { j = 0; } float angle = Random.Range(randomPerAxis[j].x, randomPerAxis[j].y) * Multiplier; switch (axis) { case Axis.X: gameObject.transform.Rotate(previousRight, angle, Space.World); break; case Axis.Y: gameObject.transform.Rotate(previousUp, angle, Space.World); break; case Axis.Z: gameObject.transform.Rotate(previousForward, angle, Space.World); break; } i++; } } objectID++; } Undo.CollapseUndoOperations(undoID); } } public class InteractiveRandomPosition : InteractiveCommand { private readonly Axis[] randomAxes; private readonly Vector2[] randomPerAxis; private List toRandomize; private readonly Vector3[] previousPositions; public float Multiplier = 1f; public bool LocalSpace = true; public InteractiveRandomPosition(GameObject[] toRandomize) { ConfirmationMode = ActionConfirmationMode.ESCAPE; SceneCommandName = "Transform Randomization"; ActionOnSpace = "to randomize transforms"; randomAxes = new[] { Axis.X, Axis.Y, Axis.Z }; this.randomPerAxis = new[] { new Vector2(-0.1f, 0.1f), new Vector2(-0.1f, 0.1f), new Vector2(-0.1f, 0.1f) }; ; this.toRandomize = new List(toRandomize); LocalSpace = true; previousPositions = toRandomize.Convert(_ => _.transform.position).ToArray(); } public override void DisplayParameters() { base.DisplayParameters(); DisplayBoolOption("Local Space", ref LocalSpace); DisplayVector2Option("X Axis Random Range", ref randomPerAxis[0]); DisplayVector2Option("Y Axis Random Range", ref randomPerAxis[1]); DisplayVector2Option("Z Axis Random Range", ref randomPerAxis[2]); DisplayFloatOption("Multiplier", ref Multiplier); DisplayObjectListOption("Objects To Randomize", toRandomize); } public override void ApplyFunction() { OnSpaceDownAction(); } protected override void OnSpaceDownAction() { base.OnSpaceDownAction(); int undoID = MonkeyEditorUtils.CreateUndoGroup("Randomization"); int objectID = 0; toRandomize.RemoveAll(_ => !_); foreach (GameObject gameObject in toRandomize) { Undo.RecordObject(gameObject.transform, "Randomization trans"); if (randomPerAxis != null && randomPerAxis.Length > 0) { int i = 0; gameObject.transform.position = previousPositions[objectID]; Vector3 previousUp = LocalSpace? gameObject.transform.up:Vector3.up; Vector3 previousRight = LocalSpace ? gameObject.transform.right : Vector3.right; Vector3 previousForward = LocalSpace ? gameObject.transform.forward : Vector3.forward; foreach (Axis axis in randomAxes) { int j = i; if (i >= randomPerAxis.Length) { j = 0; } float positionJitter = Random.Range(randomPerAxis[j].x, randomPerAxis[j].y)*Multiplier; switch (axis) { case Axis.X: gameObject.transform.position = gameObject.transform.position + positionJitter * previousRight; break; case Axis.Y: gameObject.transform.position = gameObject.transform.position + positionJitter * previousUp; break; case Axis.Z: gameObject.transform.position = gameObject.transform.position + positionJitter * previousForward; break; } i++; } } objectID++; } Undo.CollapseUndoOperations(undoID); } } public class InteractiveRandomScaleRotationPosition : InteractiveCommand { public bool randomizeScale = false; private Vector2 scaleRandom; private readonly Axis[] randomAxes; private readonly Vector2[] randomPerAxis; private List toRandomize; private readonly Quaternion[] previousAngles; private readonly Vector3[] previousPositions; private bool randomizePosition; public float Multiplier = 1f; public InteractiveRandomScaleRotationPosition(GameObject[] toRandomize, Vector2 scaleRandom, Vector2[] randomPerAxis, bool randomizePosition = false) { ConfirmationMode = ActionConfirmationMode.ESCAPE; SceneCommandName = "Transform Randomization"; ActionOnSpace = "to randomize transforms"; this.scaleRandom = scaleRandom; if (scaleRandom != Vector2.zero) randomizeScale = true; this.randomAxes = new Axis[] { Axis.X, Axis.Y, Axis.Z }; this.randomPerAxis = randomPerAxis; this.toRandomize = new List(toRandomize); this.randomizePosition = randomizePosition; previousAngles = toRandomize.Convert(_ => _.transform.localRotation).ToArray(); previousPositions = toRandomize.Convert(_ => _.transform.position).ToArray(); } public override void DisplayParameters() { base.DisplayParameters(); float min = scaleRandom.x; float max = scaleRandom.y; DisplayFloatOption("Min Scale", ref min); DisplayFloatOption("Max Scale", ref max); scaleRandom = new Vector2(min, max); DisplayFloatOption("Multiplier", ref Multiplier); DisplayObjectListOption("Objects To Randomize", toRandomize); } protected override void OnSpaceDownAction() { base.OnSpaceDownAction(); int undoID = MonkeyEditorUtils.CreateUndoGroup("Randomization"); int objectID = 0; toRandomize.RemoveAll(_ => !_); foreach (GameObject gameObject in toRandomize) { Undo.RecordObject(gameObject.transform, "Randomization trans"); if (randomizeScale) gameObject.transform.localScale = Vector3.one * Random.Range(scaleRandom.x, scaleRandom.y) * Multiplier; if (randomizePosition) { if (randomPerAxis != null && randomPerAxis.Length > 0) { int i = 0; gameObject.transform.position = previousPositions[objectID]; Vector3 previousUp = gameObject.transform.up; Vector3 previousRight = gameObject.transform.right; Vector3 previousForward = gameObject.transform.forward; foreach (Axis axis in randomAxes) { int j = i; if (i >= randomPerAxis.Length) { j = 0; } float positionJitter = Random.Range(randomPerAxis[j].x, randomPerAxis[j].y) * Multiplier; switch (axis) { case Axis.X: gameObject.transform.position = gameObject.transform.position + positionJitter * previousRight; break; case Axis.Y: gameObject.transform.position = gameObject.transform.position + positionJitter * previousUp; break; case Axis.Z: gameObject.transform.position = gameObject.transform.position + positionJitter * previousForward; break; } i++; } } } else { if (randomPerAxis != null && randomPerAxis.Length > 0) { int i = 0; gameObject.transform.localRotation = previousAngles[objectID]; Vector3 previousUp = gameObject.transform.up; Vector3 previousRight = gameObject.transform.right; Vector3 previousForward = gameObject.transform.forward; foreach (Axis axis in randomAxes) { int j = i; if (i >= randomPerAxis.Length) { j = 0; } float angle = Random.Range(randomPerAxis[j].x, randomPerAxis[j].y); switch (axis) { case Axis.X: gameObject.transform.Rotate(previousRight, angle, Space.World); break; case Axis.Y: gameObject.transform.Rotate(previousUp, angle, Space.World); break; case Axis.Z: gameObject.transform.Rotate(previousForward, angle, Space.World); break; } i++; } } } objectID++; } Undo.CollapseUndoOperations(undoID); } } [Command("Spread And Align Between", QuickName = "SA", Help = "Spreads equally and aligns selected objects between two others", DefaultValidation = DefaultValidation.AT_LEAST_TWO_GAME_OBJECTS, Category = "Transform/Align")] public static void SpreadBetween() { GameObject[] selected = MonkeyEditorUtils.OrderedSelectedGameObjects.ToArray(); MonkeyEditorUtils.AddSceneCommand(new SpreadBetweenSceneCommand(null, null, selected)); } public static GameObject FirstSelected() { return MonkeyEditorUtils.OrderedSelectedGameObjects.First(); } public static GameObject SecondSelected() { return MonkeyEditorUtils.OrderedSelectedGameObjects.ElementAt(1); } public class SpreadBetweenSceneCommand : TimedSceneCommand { private GameObject first; private GameObject second; private List selected; private List previousPos; private List previousRot; public SpreadBetweenSceneCommand(GameObject first, GameObject second, GameObject[] selected) : base(0) { SceneCommandName = "Spread Between"; this.first = first; this.second = second; this.selected = selected.ToList(); previousPos = selected.Convert(_ => _.transform.position).ToList(); previousRot = selected.Convert(_ => _.transform.rotation).ToList(); } public override void DisplayParameters() { DisplayObjectOption("Start Object", ref first); DisplayObjectOption("End Object", ref second); int id = DisplayObjectListOption("Objects To Spread", selected); if (id != -1) { if (previousPos.Count <= id) { previousPos.Add(Vector3.zero); previousRot.Add(Quaternion.identity); } previousPos[id] = selected[id].transform.position; previousRot[id] = selected[id].transform.rotation; } } public override void Update() { base.Update(); if (!first || !second) return; Vector3 direction = second.transform.position - first.transform.position; float distance = direction.magnitude / (selected.Count + 1); Vector3 currentPosition = first.transform.position + distance * direction.normalized; selected = selected.Where(_ => _).ToList(); foreach (var gameObject in selected) { gameObject.transform.position = currentPosition; currentPosition += distance * direction.normalized; } } public override void OnSceneGUI() { base.OnSceneGUI(); if (!first || !second) return; Handles.DrawDottedLine(first.transform.position, second.transform.position, 1f); } public override void Stop() { base.Stop(); Vector3[] newPositions = selected.Convert(_ => _.transform.position).ToArray(); Quaternion[] newRotations = selected.Convert(_ => _.transform.rotation).ToArray(); for (int i = 0; i < previousRot.Count; i++) { selected[i].transform.position = previousPos[i]; selected[i].transform.rotation = previousRot[i]; } int undoIndex = MonkeyEditorUtils.CreateUndoGroup("Spread Between"); for (int i = 0; i < selected.Count; i++) { Undo.RecordObject(selected[i].transform, "Applying new position"); selected[i].transform.position = newPositions[i]; selected[i].transform.rotation = newRotations[i]; } Undo.CollapseUndoOperations(undoIndex); } } [Command("Visualize Colliders", "Adds temporary visualizations to all the colliders selected", QuickName = "VC", DefaultValidation = DefaultValidation.AT_LEAST_ONE_GAME_OBJECT, Category = "Physics")] public static void VisualizeColliders( [CommandParameter("The material to set for the debug meshes (by default the default material)" ,DefaultValueMethod = "DefaultMat")] Material mat) { List objToDebug = new List(); foreach (var o in Selection.gameObjects) { var cols = o.GetComponentsInChildren(); objToDebug.AddRange(cols.Convert(_ => _.gameObject)); } MonkeyEditorUtils.AddSceneCommand(new TemporaryMeshSceneCommand(objToDebug.ToArray(), mat, 0)); } private static Material DefaultMat() { return AssetDatabase.GetBuiltinExtraResource("Default-Diffuse.mat"); } public class TemporaryMeshSceneCommand : TimedSceneCommand { private readonly List addedRenderers = new List(); private readonly Dictionary transformsPerBoxCollider = new Dictionary(); private readonly Dictionary transformsPerSphereCollider = new Dictionary(); private readonly Dictionary transformsPerCapsuleCollider = new Dictionary(); public TemporaryMeshSceneCommand(GameObject[] objects, Material mat, float duration) : base(duration) { TimeBetweenUpdate = 0.2; SceneCommandName = "Collider Visualization"; foreach (GameObject o in objects) { Collider col = o.GetComponent(); GameObject renderer = null; //ugly type checking but heh, it works. if (col is BoxCollider box) { renderer = GameObject.CreatePrimitive(PrimitiveType.Cube); renderer.transform.SetParent(o.transform, false); UpdateBoxScale(renderer, box); transformsPerBoxCollider.Add(box, renderer); } else if (col is SphereCollider sphere) { renderer = GameObject.CreatePrimitive(PrimitiveType.Sphere); renderer.transform.SetParent(o.transform, false); UpdateSphereScale(renderer, sphere); transformsPerSphereCollider.Add(sphere, renderer); } else if (col is CapsuleCollider caps) { renderer = GameObject.CreatePrimitive(PrimitiveType.Capsule); renderer.transform.SetParent(o.transform, false); UpdateCapsScale(renderer, caps); transformsPerCapsuleCollider.Add(caps, renderer); } else if (col is MeshCollider mesh) { renderer = new GameObject(); MeshFilter filter = renderer.AddComponent(); filter.sharedMesh = mesh.sharedMesh; renderer.AddComponent(); } if (renderer != null) { renderer.name = "MonKey Debug " + renderer.name; renderer.GetComponent().material = mat; Object.DestroyImmediate(renderer.GetComponent()); addedRenderers.Add(renderer); } } } private static void UpdateCapsScale(GameObject renderer, CapsuleCollider capsuleCollider) { renderer.transform.localScale = capsuleCollider.radius * 2 * Vector3.one; //for capsules the mesh is going to stretch: //could be possible to generate a proper mesh, but to isolated of a problem to care renderer.transform.localScale += Vector3.up * (capsuleCollider.height - 2); } private static void UpdateSphereScale(GameObject renderer, SphereCollider sphereCollider) { renderer.transform.localScale = sphereCollider.radius * 2 * Vector3.one; } private static void UpdateBoxScale(GameObject renderer, BoxCollider boxCollider) { renderer.transform.localScale = boxCollider.size; } public override void Update() { base.Update(); foreach (var tpb in transformsPerBoxCollider) { UpdateBoxScale(tpb.Value, tpb.Key); } foreach (var tps in transformsPerSphereCollider) { UpdateSphereScale(tps.Value, tps.Key); } foreach (var tpc in transformsPerCapsuleCollider) { UpdateCapsScale(tpc.Value, tpc.Key); } } public override void Stop() { foreach (var renderer in addedRenderers) { Object.DestroyImmediate(renderer); } base.Stop(); } } } } #endif