Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
-1
Question by ProtoTerminator · Mar 20, 2017 at 11:07 AM · scriptableobjecteditorwindowmeshesundo

Undo issues in EditorWindow

I'm working on a mesh deformation tool, and I've run into a problem where if I put in a check to not run code when it's not needed (line 147 of VertexDeformWindow), it breaks the Undo functionality. Can anyone help me figure out what's going on?

 public class VertexDeformWindow : EditorWindow
 {
     [MenuItem("Window/Vertex Deform")]
     public static void ShowWindow()
     {
         if (window != null)
         {
             return;
         }
         window = GetWindow<VertexDeformWindow>();
         window.selection = new Texture2D(32, 32, TextureFormat.ARGB32, false);
         var pixels = window.selection.GetPixels();
         for(int i = 0; i < pixels.Length; i++)
         {
             pixels[i] = new Color(0, 0, 0, 0.3f);
         }
         window.selection.SetPixels(pixels);
         window.selection.Apply();
         window.meshes = CreateInstance<MultiObjectVerts>() as MultiObjectVerts;
 
         window.meshes.softSize = EditorPrefs.GetFloat("softSize", window.meshes.softSize);
         window.meshes.showUnselected = EditorPrefs.GetBool("showUnselected", window.meshes.showUnselected);
         window.meshes.meshEditType = (EditType)EditorPrefs.GetInt("editType", (int)window.meshes.meshEditType);
         window.meshes.type = (FalloffType)EditorPrefs.GetInt("falloffType", (int)window.meshes.type);
 
         Undo.undoRedoPerformed -= UndoDetected;
         Undo.undoRedoPerformed += UndoDetected;
     }
 
     static bool active = true;
     static void UndoDetected()
     {
         /*if (active != meshes.active)
         {
             window.Active = meshes.active;
         }*/
         //meshes.UpdateStrengths();
         if (active)
         {
             window.meshes.UpdateSelectedVerts();
         }
     }
         
     bool Active
     {
         get
         {
             return meshes.active;
         }
         set
         {
             active = value;
             meshes.active = value;
             if (value)
             {
                 if (selection == null)
                 {
                     selection = new Texture2D(32, 32, TextureFormat.ARGB32, false);
                     var pixels = selection.GetPixels();
                     for (int i = 0; i < pixels.Length; i++)
                     {
                         pixels[i] = new Color(0, 0, 0, 0.3f);
                     }
                     selection.SetPixels(pixels);
                     selection.Apply();
                         
                 }
                 Tools.hidden = true;
                 meshes.GetMeshVerts();
             }
             else
             {
                 Tools.hidden = false;
                 meshes.ReleaseMeshVerts();
             }
         }
     }
     [SerializeField]
     MultiObjectVerts meshes;
     static VertexDeformWindow window;
 
     void OnDisable()
     {
         EditorPrefs.SetFloat("softSize", meshes.softSize);
         EditorPrefs.SetBool("showUnselected", meshes.showUnselected);
         EditorPrefs.SetInt("editType", (int) meshes.meshEditType);
         EditorPrefs.SetInt("falloffType", (int) meshes.type);
     }
 
     // Window has been selected
     void OnFocus()
     {
         // Remove delegate listener if it has previously
         // been assigned.
         SceneView.onSceneGUIDelegate -= OnSceneGUI;
         // Add (or re-add) the delegate.
         SceneView.onSceneGUIDelegate += OnSceneGUI;
     }
 
     void OnDestroy()
     {
         // When the window is destroyed, remove the delegate
         // so that it will no longer do any drawing.
         SceneView.onSceneGUIDelegate -= OnSceneGUI;
         Undo.undoRedoPerformed -= UndoDetected;
         Active = false;
     }
 
     Rect NormalizeRect(Vector2 start, Vector2 end)
     {
         Rect rect = new Rect(new Vector2(Mathf.Min(start.x, end.x), Mathf.Min(start.y, end.y)), new Vector2(Mathf.Abs(start.x - end.x), Mathf.Abs(start.y - end.y)));
         return rect;
     }
 
     Vector2 startPos;
     Vector2 endPos;
     Rect dragRect = Rect.zero;
     bool moved = false;
     // Draw gizmos here.
     void OnSceneGUI(SceneView sceneView)
     {
         if (window == null || meshes == null)
         {
             ShowWindow();
         }
         if (active)
         {
             meshes.DrawVerts(sceneView);
             if (meshes.SelectedVerts > 0)
             {
                 EditorGUI.BeginChangeCheck();
                 Vector3 pos = meshes.GetTransformGizmoPosition();
                 Vector3 relativePos = Handles.PositionHandle(pos, Quaternion.identity) - pos;
                 if (EditorGUI.EndChangeCheck())
                 {
                     Undo.RecordObject(meshes, "changed meshes");
                 }
                 if (GUIUtility.hotControl != 0)
                 {
                     moved = true;
                 }
                 else if (moved)
                 {
                     moved = false;
                     meshes.UpdateStrengths();
                 }
                 //if (relativePos != Vector3.zero) // If uncommented, this line causes the Undo to not work!
                 {
                     meshes.MoveSelectedVertices(relativePos);
                 }
             }
             Event current = Event.current;
             if (current.button == 0)
             {
                 Vector3 mousePosition = current.mousePosition;
                 int controlID = GUIUtility.GetControlID(FocusType.Passive);
                     
                 if (current.type == EventType.MouseDown)
                 {
                     startPos = mousePosition;
                     dragRect = Rect.zero;
                     GUIUtility.hotControl = controlID;
                     current.Use();
                 }
                 if (current.type == EventType.MouseDrag)
                 {
                     endPos = mousePosition;
                     dragRect = NormalizeRect(startPos, endPos);
                     current.Use();
                 }
                 if (current.type == EventType.MouseUp)
                 {
                     Undo.RecordObject(meshes, "changed meshes");
                     endPos = mousePosition;
                     if (endPos == startPos)
                     {
                         // normal click
                         if (current.control || current.shift)
                         {
                             meshes.AddToSelectedVerts(endPos);
                         }
                         else if (current.alt)
                         {
                             meshes.SubtractFromSelectedVerts(endPos);
                         }
                         else
                         {
                             meshes.SelectVerts(endPos);
                         }
                     }
                     else
                     {
                         // drag select
                         if (current.control || current.shift)
                         {
                             meshes.AddToSelectedVerts(dragRect, sceneView.camera);
                         }
                         else if (current.alt)
                         {
                             meshes.SubtractFromSelectedVerts(dragRect, sceneView.camera);
                         }
                         else
                         {
                             meshes.SelectVerts(dragRect, sceneView.camera);
                         }
                     }
                     dragRect = Rect.zero;
                     GUIUtility.hotControl = 0;
                     current.Use();
                 }
             }
             if (dragRect != Rect.zero)
             {
                 Handles.BeginGUI();
                 GUI.DrawTexture(dragRect, selection);
                 Handles.EndGUI();
             }
         }
         Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
         Undo.FlushUndoRecordObjects();
     }
 
     Texture2D selection;
 
     void OnGUI()
     {
         if (window == null || meshes == null)
         {
             ShowWindow();
         }
         Undo.RecordObject(meshes, "changed gizmo size");
         GUILayout.Label("Vertex Deform", EditorStyles.boldLabel);
         if (!Active)
         {
             if (Selection.gameObjects.Length == 0)
             {
                 GUILayout.Label("No objects selected. Please select an object with a MeshFilter and Renderer in the scene");
                 return;
             }
 
             foreach (var obj in Selection.gameObjects)
             {
                 if (!obj.GetComponent<MeshFilter>() || !obj.GetComponent<Renderer>())// || PrefabUtility.GetPrefabType(obj) == PrefabType.Prefab)
                 {
                     GUILayout.Label("Improper object(s) selected. Please select an object with a MeshFilter and Renderer in the scene");
                     return;
                 }
             }
 
             if (GUILayout.Button("Activate"))
             {
                 Active = true;
             }
 
             meshes.meshEditType = (EditType) EditorGUILayout.EnumPopup("Mesh Edit Type", meshes.meshEditType);
         }
         else
         {
             if (GUILayout.Button("Deactivate"))
             {
                 Active = false;
             }
 
             meshes.showUnselected = EditorGUILayout.Toggle("Show Unselected Verts", meshes.showUnselected);
             meshes.softSize = EditorGUILayout.FloatField("Soft Size: ", meshes.softSize);
             if (meshes.softSize < 0)
             {
                 meshes.softSize = 0;
             }
             else if (meshes.softSize > 0)
             {
                 meshes.type = (FalloffType) EditorGUILayout.EnumPopup("Falloff Type", meshes.type);
             }
 
             if (GUI.changed)
             {
                 meshes.UpdateStrengths();
             }
         }
         Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
         Undo.FlushUndoRecordObjects();
 
         if (GUI.changed)
         {
             SceneView.RepaintAll();
         }
     }
 
     private void OnInspectorUpdate()
     {
         Repaint();
     }
 }

...

 public enum FalloffType
 {
     Linear,
     Sine
 }
 public enum EditType
 {
     Original,
     Copy
 }
 public class MultiObjectVerts : ScriptableObject
 {
     public static int vertPixels; // how many pixels to draw vertex, more pixels on higher dpi screens
     public float softSize = 5;
     public bool showUnselected = true;
     public FalloffType type = FalloffType.Sine;
     public EditType meshEditType = EditType.Copy;
         
     public List<MeshFilter> meshes = new List<MeshFilter>();
     public List<Vector3[]> vertices = new List<Vector3[]>();
     List<VertIndices> selectedVerts = new List<VertIndices>();
     List<VertIndices> softSelectedVerts = new List<VertIndices>();
     List<VertIndices> unselectedVerts = new List<VertIndices>();
     public List<VertIndices> vertIndices = new List<VertIndices>();
     public bool active;
 
     Color vertColor = Color.blue;
 
     public void UpdateSelectedVerts()
     {
         selectedVerts.Clear();
         unselectedVerts.Clear();
         foreach(var vert in vertIndices)
         {
             if (vert.strength == 1)
             {
                 selectedVerts.Add(vert);
             }
             else if (vert.strength > 0)
             {
                 softSelectedVerts.Add(vert);
             }
             else
             {
                 unselectedVerts.Add(vert);
             }
         }
         UpdateStrengths();
     }
 
     public int SelectedVerts
     {
         get
         {
             return selectedVerts.Count;
         }
     }
 
     public void GetMeshVerts()
     {
         ReleaseMeshVerts();
 
         vertPixels = Mathf.RoundToInt(Screen.dpi / 25);
         if (vertPixels < 1)
         {
             vertPixels = 1;
         }
         HashSet<int> meshInstances = new HashSet<int>();
         int index = 0;
         int vertCounter = 0;
         foreach (var obj in Selection.gameObjects)
         {
             var filter = obj.GetComponent<MeshFilter>();
             if (meshEditType == EditType.Copy && (!filter.sharedMesh.name.Contains("(Clone)") || meshInstances.Contains(filter.sharedMesh.GetInstanceID())))
             {
                 filter.sharedMesh = Instantiate(filter.sharedMesh);
             }
             meshInstances.Add(filter.sharedMesh.GetInstanceID());
             meshes.Add(filter);
             var vertexes = filter.sharedMesh.vertices;
             vertices.Add(vertexes);
 
             for (int i = 0; i < vertexes.Length; i++)
             {
                 var vert = vertexes[i];
                 bool cnt = false;
                 // Check if the vertex is in the same position as another vertex in the same mesh.
                 for (int j = vertCounter; j < vertIndices.Count; j++)
                 {
                     var vi = vertIndices[j];
                     if (vi.position == vert)
                     {
                         cnt = true;
                         vi.indices.Add(i);
                         break;
                     }
                 }
                 if (!cnt)
                 {
                     VertIndices item = new VertIndices();
                     item.position = vert;
                     item.worldPosition = obj.transform.TransformPoint(vert);
                     item.indices.Add(i);
                     item.index = index;
                     vertIndices.Add(item);
                     unselectedVerts.Add(item);
                 }
             }
             index++;
             vertCounter += vertexes.Length;
         }
         UpdateRects(SceneView.lastActiveSceneView.camera);
     }
 
     public void ReleaseMeshVerts()
     {
         meshes.Clear();
         vertices.Clear();
         selectedVerts.Clear();
         unselectedVerts.Clear();
         vertIndices.Clear();
     }
 
     Vector3 cameraStart;
     Vector3 cameraPos;
     Vector3 camPos;
     Quaternion camRot;
     Vector2 camScreenSize;
 
     // Update where the verts are drawn on screen.
     void UpdateRects(Camera cam)
     {
         foreach (var vert in vertIndices)
         {
             Vector2 screenPos = cam.WorldToScreenPoint(vert.worldPosition);
             screenPos.y = cam.pixelHeight - screenPos.y;
             vert.screenPos = screenPos;
             vert.rect = new Rect(screenPos - Vector2.one * (vertPixels / 2), Vector2.one * vertPixels);
         }
     }
 
     // Draw vertices on screen.
     public void DrawVerts(SceneView sceneView)
     {
         Camera camera;
         camera = sceneView.camera;
         if (camera.transform.position != camPos || camera.transform.rotation != camRot || camScreenSize != new Vector2(camera.pixelWidth, camera.pixelHeight))
         {
             UpdateRects(camera);
         }
         camPos = camera.transform.position;
         camRot = camera.transform.rotation;
         camScreenSize = new Vector2(camera.pixelWidth, camera.pixelHeight);
 
         Handles.BeginGUI();
         foreach (var vert in vertIndices)
         {
             if (!showUnselected && vert.strength == 0)
             {
                 continue;
             }
             if (vert.strength == 0)
             {
                 vertColor = Color.black;
             }
             else if (vert.strength == 1)
             {
                 vertColor = Color.red;
             }
             else
             {
                 vertColor = Color.Lerp(Color.green, Color.blue, vert.strength);
             }
             EditorGUI.DrawRect(vert.rect, vertColor);
         }
         Handles.EndGUI();
     }
 
     // Get position of move handle while vertices are selected.
     public Vector3 GetTransformGizmoPosition()
     {
         Vector3 point = Vector3.zero;
         foreach(var vert in selectedVerts)
         {
             point += vert.worldPosition;
         }
         point /= selectedVerts.Count;
         return point;
     }
 
     // Remove selected vertices.
     public void RemoveAllSelectedVerts()
     {
         int counter = selectedVerts.Count - 1;
         while (counter >= 0)
         {
             unselectedVerts.Add(selectedVerts[counter]);
             selectedVerts[counter].strength = 0;
             selectedVerts.RemoveAt(counter);
             counter--;
         }
     }
 
     // Select a vertex by a single mouse click.
     public void SelectVerts(Vector2 mousePosition)
     {
         RemoveAllSelectedVerts();
         AddToSelectedVerts(mousePosition);
     }
 
     // Select a vertex by a single mouse click while holding shift or ctrl.
     public void AddToSelectedVerts(Vector2 mousePosition)
     {
         for (int i = 0; i < unselectedVerts.Count; i++)
         {
             var vert = unselectedVerts[i];
             if (vert.rect.Contains(mousePosition))
             {
                 selectedVerts.Add(vert);
                 unselectedVerts.Remove(vert);
                 i--;
                 vert.strength = 1;
                 break;
             }
         }
         for (int i = 0; i < softSelectedVerts.Count; i++)
         {
             var vert = softSelectedVerts[i];
             if (vert.rect.Contains(mousePosition))
             {
                 selectedVerts.Add(vert);
                 unselectedVerts.Remove(vert);
                 i--;
                 vert.strength = 1;
                 break;
             }
         }
         UpdateStrengths();
     }
 
     // Unselect a vertex by a single mouse click while holding alt.
     public void SubtractFromSelectedVerts(Vector2 mousePosition)
     {
         for (int i = 0; i < selectedVerts.Count; i++)
         {
             var vert = selectedVerts[i];
             if (vert.rect.Contains(mousePosition))
             {
                 unselectedVerts.Add(vert);
                 selectedVerts.Remove(vert);
                 i--;
                 vert.strength = 0;
                 break;
             }
         }
         UpdateStrengths();
     }
         
     // Select vertices by mouse drag.
     public void SelectVerts(Rect rect, Camera cam)
     {
         RemoveAllSelectedVerts();
         AddToSelectedVerts(rect, cam);
     }
 
     // Select vertices by mouse drag while holding shift or ctrl.
     public void AddToSelectedVerts(Rect rect, Camera cam)
     {
         for (int i = 0; i < unselectedVerts.Count; i++)
         {
             var vert = unselectedVerts[i];
             if (rect.Contains(vert.screenPos, true))
             {
                 selectedVerts.Add(vert);
                 unselectedVerts.Remove(vert);
                 vert.strength = 1;
                 i--;
             }
         }
         for (int i = 0; i < softSelectedVerts.Count; i++)
         {
             var vert = softSelectedVerts[i];
             if (rect.Contains(vert.screenPos, true))
             {
                 selectedVerts.Add(vert);
                 unselectedVerts.Remove(vert);
                 vert.strength = 1;
                 i--;
             }
         }
         UpdateStrengths();
     }
 
     // Unselect vertices by mouse drag while holding shift or ctrl.
     public void SubtractFromSelectedVerts(Rect rect, Camera cam)
     {
         for (int i = 0; i < selectedVerts.Count; i++)
         {
             var vert = selectedVerts[i];
             if (rect.Contains(vert.screenPos, true))
             {
                 selectedVerts.Remove(vert);
                 unselectedVerts.Add(vert);
                 vert.strength = 0;
                 i--;
             }
         }
         UpdateStrengths();
     }
 
     // Update the softness of each vertex.
     public void UpdateStrengths()
     {
         if (softSize <= 0)
         {
             return;
         }
         for(int i = 0; i < unselectedVerts.Count; i++)
         {
             var vert = unselectedVerts[i];
             vert.strength = 0;
             foreach (var selectedVert in selectedVerts)
             {
                 float distPercent = Vector3.Distance(vert.worldPosition, selectedVert.worldPosition) / softSize;
                 float newStrength = 1 - distPercent;
                 if (distPercent < 1 && vert.strength < newStrength)
                 {
                     if (type == FalloffType.Sine)
                     {
                         newStrength = (Mathf.Cos(Mathf.PI * (newStrength - 1)) + 1) / 2;
                     }
                     vert.strength = newStrength;
                 }
             }
             if (vert.strength > 0)
             {
                 softSelectedVerts.Add(vert);
                 unselectedVerts.RemoveAt(i);
                 i--;
             }
         }
         for (int i = 0; i < softSelectedVerts.Count; i++)
         {
             var vert = softSelectedVerts[i];
             vert.strength = 0;
             foreach (var selectedVert in selectedVerts)
             {
                 float distPercent = Vector3.Distance(vert.worldPosition, selectedVert.worldPosition) / softSize;
                 float newStrength = 1 - distPercent;
                 if (distPercent < 1 && vert.strength < newStrength)
                 {
                     if (type == FalloffType.Sine)
                     {
                         newStrength = (Mathf.Cos(Mathf.PI * (newStrength - 1)) + 1) / 2;
                     }
                     vert.strength = newStrength;
                 }
             }
             if (vert.strength == 0)
             {
                 unselectedVerts.Add(vert);
                 softSelectedVerts.RemoveAt(i);
                 i--;
             }
         }
     }
         
     // Move selected vertices by offset. Moves soft selected vertices by a fraction of the offset.
     public void MoveSelectedVertices(Vector3 offset)
     {
         /*if (offset == Vector3.zero)
         {
             return;
         }*/
         var fixedOffset = meshes[selectedVerts[0].index].transform.InverseTransformVector(offset);
         foreach (var vert in selectedVerts)
         {
             foreach (var ind in vert.indices)
             {
                 vert.position += fixedOffset * vert.strength;
                 vert.worldPosition += offset * vert.strength;
                 vertices[vert.index][ind] = vert.position;
             }
             Vector2 screenPos = SceneView.lastActiveSceneView.camera.WorldToScreenPoint(vert.worldPosition);
             screenPos.y = SceneView.lastActiveSceneView.camera.pixelHeight - screenPos.y;
             vert.screenPos = screenPos;
             vert.rect = new Rect(screenPos - Vector2.one * (vertPixels / 2), Vector2.one * vertPixels);
         }
         foreach (var vert in softSelectedVerts)
         {
             foreach (var ind in vert.indices)
             {
                 vert.position += fixedOffset * vert.strength;
                 vert.worldPosition += offset * vert.strength;
                 vertices[vert.index][ind] = vert.position;
             }
             Vector2 screenPos = SceneView.lastActiveSceneView.camera.WorldToScreenPoint(vert.worldPosition);
             screenPos.y = SceneView.lastActiveSceneView.camera.pixelHeight - screenPos.y;
             vert.screenPos = screenPos;
             vert.rect = new Rect(screenPos - Vector2.one * (vertPixels / 2), Vector2.one * vertPixels);
         }
         for (int i = 0; i < meshes.Count; i++)
         {
             meshes[i].sharedMesh.vertices = vertices[i];
         }
     }
 }
 
 // Hold vertex information.
 [System.Serializable]
 public class VertIndices
 {
     public Vector2 screenPos;
     public Rect rect;
     public float strength = 0;
     public float distanceToSelected;
     public Vector3 position;
     public Vector3 worldPosition;
     public int index;
     public List<int> indices = new List<int>();
 }
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

0 Replies

· Add your reply
  • Sort: 

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

65 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Why do I get an empty Undo for my EditorWindow? 0 Answers

Save EditorWindow to disk 1 Answer

Repaint on Undo 5 Answers

How do I have an EditorWindow save it's data inbetween opening and closing Unity? C# 5 Answers

Changes to custom serializable object not saved to disk 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges