| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 |
- namespace Shapes2D {
- using UnityEngine;
- using UnityEditor;
- using UnityEngine.UI;
- using System.Collections.Generic;
- [CustomEditor(typeof(Shape))]
- [CanEditMultipleObjects]
- public class ShapeEditor : Editor {
- // used during sprite conversion to keep track of objects that we have
- // to temporarily modify
- class GraphicState {
- public Graphic graphic;
- public bool hasMask;
- public bool showMaskGraphic;
- }
- SerializedProperty shapeTypeProp, outlineSizeProp, blurProp,
- outlineColorProp, fillTypeProp, fillColorProp,
- fillColor2Prop, gradientTypeProp, roundnessProp, roundnessTLProp,
- roundnessTRProp, roundnessBLProp, roundnessBRProp, roundnessPerCornerProp,
- fillRotationProp, fillOffsetProp, gradientStartProp, fillTextureProp,
- gridSizeProp, lineSizeProp, triangleOffsetProp, fillScaleProp,
- gradientAxisProp, polygonPresetProp, usePolygonMapProp,
- startAngleProp, endAngleProp, invertArcProp, innerCutoutProp,
- pathThicknessProp, fillPathLoopsProp;
- bool isEditing; // true if we're in polygon/path edit mode in the scene view
- Tool preEditTool = Tool.None; // the tool the user had selected before clicking edit
- void OnEnable () {
- Shape shape = (Shape) serializedObject.targetObject;
- if (!shape.GetComponent<SpriteRenderer>()
- && !shape.GetComponent<Image>()) {
- if (shape.GetComponentInParent<Canvas>() == null) {
- Undo.AddComponent<SpriteRenderer>(shape.gameObject);
- } else {
- Undo.AddComponent<Image>(shape.gameObject);
- }
- // collapse into the operation that made this happen
- Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
- shape.Configure();
- }
-
- shapeTypeProp = serializedObject.FindProperty("settings._shapeType");
- outlineSizeProp = serializedObject.FindProperty("settings._outlineSize");
- blurProp = serializedObject.FindProperty("settings._blur");
- outlineColorProp = serializedObject.FindProperty("settings._outlineColor");
- roundnessPerCornerProp = serializedObject.FindProperty("settings._roundnessPerCorner");
- roundnessProp = serializedObject.FindProperty("settings._roundness");
- roundnessTLProp = serializedObject.FindProperty("settings._roundnessTopLeft");
- roundnessTRProp = serializedObject.FindProperty("settings._roundnessTopRight");
- roundnessBLProp = serializedObject.FindProperty("settings._roundnessBottomLeft");
- roundnessBRProp = serializedObject.FindProperty("settings._roundnessBottomRight");
- innerCutoutProp = serializedObject.FindProperty("settings._innerCutout");
- startAngleProp = serializedObject.FindProperty("settings._startAngle");
- endAngleProp = serializedObject.FindProperty("settings._endAngle");
- invertArcProp = serializedObject.FindProperty("settings._invertArc");
- fillTypeProp = serializedObject.FindProperty("settings._fillType");
- fillColorProp = serializedObject.FindProperty("settings._fillColor");
- fillColor2Prop = serializedObject.FindProperty("settings._fillColor2");
- fillRotationProp = serializedObject.FindProperty("settings._fillRotation");
- fillOffsetProp = serializedObject.FindProperty("settings._fillOffset");
- fillScaleProp = serializedObject.FindProperty("settings._fillScale");
- gradientTypeProp = serializedObject.FindProperty("settings._gradientType");
- gradientStartProp = serializedObject.FindProperty("settings._gradientStart");
- gradientAxisProp = serializedObject.FindProperty("settings._gradientAxis");
- fillTextureProp = serializedObject.FindProperty("settings._fillTexture");
- gridSizeProp = serializedObject.FindProperty("settings._gridSize");
- lineSizeProp = serializedObject.FindProperty("settings._lineSize");
- triangleOffsetProp = serializedObject.FindProperty("settings._triangleOffset");
- polygonPresetProp = serializedObject.FindProperty("settings._polygonPreset");
- usePolygonMapProp = serializedObject.FindProperty("settings._usePolygonMap");
- pathThicknessProp = serializedObject.FindProperty("settings._pathThickness");
- fillPathLoopsProp = serializedObject.FindProperty("settings._fillPathLoops");
- }
-
- // blend two colors in the same way the shape shader would (premultiplied alpha,
- // but for this process we have turned off the premultiply step so it's just
- // normal alpha blending). note that for layered semi-transparent regions this
- // will not result in the same color you see in unity. that's because the color
- // you see includes blending with the background, i.e. the end result when drawn
- // with a shader is: blend(blend(bg_color, shape1_color), shape2_color) whereas
- // when drawn from the converted sprite it's:
- // blend(bg_color, blend(shape1_color, shape2_color)).
- private static Color BlendColors(Color dst, Color src) {
- Color c = src * src.a + dst * (1 - src.a);
- c.a = src.a + dst.a;
- return c;
- }
-
- private static void BlendTextures(Texture2D dstTex, Texture2D srcTex) {
- for (int x = 0; x < dstTex.width; x++) {
- for (int y = 0; y < dstTex.height; y++) {
- Color src = srcTex.GetPixel(x, y);
- if (src.a == 0) {
- // source pixel is fully transparent, so nothing to do
- continue;
- }
- if (src.a == 1) {
- // src pixel is fully opaque, so use the child's
- dstTex.SetPixel(x, y, src);
- continue;
- }
- Color dst = dstTex.GetPixel(x, y);
- Color result;
- if (dst.a == 0) {
- // parent pixel is fully transparent, so use the child's
- result = src;
- } else {
- // both pixels have alpha, so blend them in the same way
- // the shader would
- result = BlendColors(dst, src);
- }
- dstTex.SetPixel(x, y, result);
- }
- }
- }
-
- private static List<GraphicState> DisableUIGraphics(Canvas canvas) {
- List<GraphicState> graphicStates = new List<GraphicState>();
- List<Graphic> graphics = new List<Graphic>();
- graphics.AddRange(canvas.GetComponentsInChildren<Graphic>());
- graphics.RemoveAll(g => !g.enabled);
- foreach (Graphic g in graphics) {
- GraphicState gs = new GraphicState();
- graphicStates.Add(gs);
- gs.graphic = g;
- Mask mask = g.GetComponent<Mask>();
- gs.hasMask = mask != null && mask.enabled;
- if (gs.hasMask) {
- gs.showMaskGraphic = mask.showMaskGraphic;
- mask.showMaskGraphic = false;
- } else {
- g.enabled = false;
- }
- }
- return graphicStates;
- }
- // fixme - this needs more error handling so we don't leave objects in a weird place if something goes wrong
- private static Vector2 RenderToTexture2D(string path, Shape shape, float pixelsPerUnit = 100) {
- // reset the shape's rotation
- Quaternion oldRotation = shape.transform.rotation;
- shape.transform.rotation = Quaternion.identity;
- // get the desired pixel size of our shape and all its children, which will be the size of our texture
- Vector2 size = shape.GetShapePixelSize(pixelsPerUnit: pixelsPerUnit);
- int w = (int) size.x;
- int h = (int) size.y;
-
- // get all the shapes in draw order
- List<Shape> shapes = shape.GetShapesInDrawOrder();
- // if the shape is a UI component, we need to set up the canvas in a way
- // that the camera can point to the image only without the UI
- // components moving around based on the camera
- Canvas canvas = shape.GetComponentInParent<Canvas>();
- int oldCanvasLayer = -1;
- RenderMode oldRenderMode = 0;
- Vector3 oldCanvasScale = Vector3.one;
- List<GraphicState> modifiedGraphics = null;
- if (canvas) {
- oldCanvasLayer = canvas.gameObject.layer;
- canvas.gameObject.layer = 31;
- oldRenderMode = canvas.renderMode;
- // fixme - what happens with nested canvases?
- canvas.renderMode = RenderMode.WorldSpace;
- // if the canvas was in RenderMode.ScreenSpaceCamera then the scale will be weird now that we switched
- // to WorldSpace. in that case we set the scale to one.
- if (oldRenderMode == RenderMode.ScreenSpaceCamera) {
- oldCanvasScale = canvas.transform.localScale;
- canvas.transform.localScale = Vector3.one;
- }
- // without a way to selectively show just the shape we want, this is
- // the only way I can think of to do it. even this won't work if the
- // user has a UI component not found by this function.
- modifiedGraphics = DisableUIGraphics(canvas);
- }
- // make a new render texture
- RenderTexture rt = new RenderTexture(w, h, 32, RenderTextureFormat.ARGB32);
- rt.filterMode = FilterMode.Point;
- #if UNITY_5_6_OR_NEWER
- rt.autoGenerateMips = false;
- #else
- rt.generateMips = false;
- #endif
- rt.Create();
-
- // figure out the world space bounds of the shape so we can point the camera at it
- Bounds bounds = shape.GetShapeBounds();
- // set up the camera to point exactly at the object's bounds and set its
- // culling layer to show only layer 31
- // note that if the user has anything on layer 31 then it will also
- // show up in the png, but in that event they can just move it away
- Camera cam = new GameObject().AddComponent<Camera>();
- cam.backgroundColor = new Color(1, 1, 1, 0);
- cam.clearFlags = CameraClearFlags.SolidColor;
- cam.transform.position = bounds.center;
- cam.transform.position -= new Vector3(0, 0, 10);
- cam.orthographic = true;
- cam.orthographicSize = bounds.extents.y;
- cam.aspect = bounds.size.x / bounds.size.y;
- cam.targetTexture = rt;
- cam.cullingMask = 1 << 31;
- // make the render texture active so calls to Texture2D.ReadPixels() will
- // read from it
- RenderTexture oldRT = RenderTexture.active;
- RenderTexture.active = rt;
- // draw each shape with blending turned off, and layer them on top of each
- // other with blending between shapes but not between a shape and the
- // camera's background color, which is what would happen if we just let the
- // camera render all of them - the camera's background color affects the
- // color of semi-transparent pixels even if the background color has an
- // alpha value of 0. this is because the shader's blending is trying to
- // alias against the background color, but in the png we don't want that.
- // there's no shader option I'm aware of that would be able to blend between
- // shapes but not between a shape and the background color, so that's why
- // we have to do it manually.
- Texture2D dstTex = null;
- foreach (Shape s in shapes) {
- if (canvas) {
- Graphic g = s.GetComponent<Graphic>();
- g.enabled = true;
- if (s.GetComponent<Mask>() != null) {
- foreach (GraphicState gs in modifiedGraphics) {
- if (gs.graphic == g && gs.showMaskGraphic)
- s.GetComponent<Mask>().showMaskGraphic = true;
- }
- }
- }
- if (dstTex == null) {
- dstTex = s.DrawToTexture2D(cam, w, h);
- } else {
- Texture2D srcTex = s.DrawToTexture2D(cam, w, h);
- BlendTextures(dstTex, srcTex);
- DestroyImmediate(srcTex);
- }
- if (canvas) {
- if (s.GetComponent<Mask>() == null) {
- s.GetComponent<Graphic>().enabled = false;
- } else {
- s.GetComponent<Mask>().showMaskGraphic = false;
- }
- }
- }
-
- // put the old render texture back
- RenderTexture.active = oldRT;
- // grab the sprite's pivot point based on the top object's location
- // relative to its children
- Vector2 pivot = shape.GetPivot();
- // restore the shape's rotation
- shape.transform.rotation = oldRotation;
- // restore the canvas
- if (canvas) {
- canvas.gameObject.layer = oldCanvasLayer;
- if (oldRenderMode == RenderMode.ScreenSpaceCamera)
- canvas.transform.localScale = oldCanvasScale;
- canvas.renderMode = oldRenderMode;
- foreach (GraphicState gs in modifiedGraphics) {
- gs.graphic.enabled = true;
- if (gs.hasMask)
- gs.graphic.GetComponent<Mask>().showMaskGraphic = gs.showMaskGraphic;
- }
- }
- // save the png
- byte[] bytes = dstTex.EncodeToPNG();
- System.IO.File.WriteAllBytes(path, bytes);
- // clean up
- DestroyImmediate(dstTex);
- DestroyImmediate(cam.gameObject);
- DestroyImmediate(rt);
- return pivot;
- }
-
- private static Material GetDefaultSpriteMaterial() {
- // this doesn't work on 5.3.3 but does on 5.4
- // AssetDatabase.GetBuiltinExtraResource<Material>("Sprites-Default.mat")
- // tried various things like creating a new SpriteRenderer and using its
- // material but Unity doesn't seem to like that and will do weird things
- // like destroy the material when you hit play. so we have a Sprite
- // Template prefab and we'll grab the material from that.
- SpriteRenderer sr = (SpriteRenderer) Resources.Load(
- "Shapes2D/Sprite Template", typeof(SpriteRenderer));
- if (!sr) {
- Debug.LogError("Shapes2D: Couldn't get the sprite template from "
- + "Shapes2D/Resources. You'll have to manually assign the "
- + "SpriteRenderer's material.");
- return null;
- }
- return sr.sharedMaterial;
- }
- private void EditShape() {
- isEditing = true;
- preEditTool = Tools.current;
- Tools.current = Tool.None;
- SceneView.RepaintAll();
- }
- private void StopEditingShape(bool restoreTool) {
- isEditing = false;
- if (restoreTool)
- Tools.current = preEditTool;
- UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
- }
- // verts has the first point duplicated at the end as well
- int GetClosestLineToPoint(Vector3 pos, List<Vector3> verts) {
- Vector2 p = HandleUtility.WorldToGUIPoint(pos);
- int closest = -1;
- float distance = -1;
- for (int i = 0; i < verts.Count - 1; i++) {
- Vector2 v1 = HandleUtility.WorldToGUIPoint(verts[i]);
- Vector2 v2 = HandleUtility.WorldToGUIPoint(verts[i + 1]);
- float testDistance = HandleUtility.DistancePointToLineSegment(p, v1, v2);
- if (closest == -1 || testDistance < distance) {
- closest = i;
- distance = testDistance;
- }
- }
- return closest;
- }
- // show an outline when in edit mode
- void DrawShapeBorders(Shape shape) {
- Vector3[] corners = new Vector3[5];
- shape.GetWorldCorners(corners);
- Handles.color = Color.white;
- Handles.DrawAAPolyLine(2f, corners);
- }
- Vector3 GetMouseWorldPos() {
- Vector2 mouseScreenPos = Event.current.mousePosition;
- return HandleUtility.GUIPointToWorldRay(mouseScreenPos).origin;
- }
- void EditPolygon(Shape shape) {
- HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
- // get the existing verts
- Vector3[] oldVerts = shape.GetPolygonWorldVertices();
- List<Vector3> verts = new List<Vector3>(oldVerts);
- bool hasMaxVerts = verts.Count == Shape.MaxPolygonVertices;
- // add the first vert at the end as well so Unity will draw it right etc
- verts.Add(verts[0]);
- // are we in delete mode? what color should handles be?
- Color pink = new Color(1, 0, 0.75f);
- bool deleteMode = false;
- if ((Event.current.control || Event.current.command) && verts.Count > 4) {
- Handles.color = Color.red;
- deleteMode = true;
- } else {
- Handles.color = pink;
- }
- // draw the shape
- Handles.DrawAAPolyLine(3f, verts.ToArray());
- // drag handle result for getting info from our handles
- CustomHandles.DragHandleResult dhResult;
- // draw handles for each existing vert and check if they've been moved or clicked
- bool changed = false;
- for (int i = verts.Count - 2; i >= 0; i--) {
- Vector3 v = verts[i];
- #if UNITY_5_6_OR_NEWER
- Vector3 newPos = CustomHandles.DragHandle(v, 0.05f * HandleUtility.GetHandleSize(v),
- Handles.DotHandleCap, pink, out dhResult);
- #else
- Vector3 newPos = CustomHandles.DragHandle(v, 0.05f * HandleUtility.GetHandleSize(v),
- Handles.DotCap, pink, out dhResult);
- #endif
- if (deleteMode && dhResult == CustomHandles.DragHandleResult.LMBPress) {
- // the user clicked on the handle while in delete mode, so delete the vert
- verts.RemoveAt(i);
- changed = true;
- } else if (!deleteMode && newPos != v) {
- // the handle has been dragged, so move the vert to the new position
- verts[i] = new Vector2(newPos.x, newPos.y);
- changed = true;
- }
- }
- // check if the mouse is hovering over a space where we could add a new vert,
- // and draw it if so
- bool snapped = false;
- Vector3 closestPos = HandleUtility.ClosestPointToPolyLine(verts.ToArray());
- float distance = HandleUtility.DistanceToPolyLine(verts.ToArray());
- bool isCloseToLine = distance < 25;
- if (!changed && isCloseToLine && !hasMaxVerts && !deleteMode) {
- // todo - ClosestPointToPolyLine doesn't work very well in 3D...
- foreach (Vector3 v in verts) {
- // if close to an existing vert, we don't want to add a new one
- if (Vector2.Distance(HandleUtility.WorldToGUIPoint(closestPos),
- HandleUtility.WorldToGUIPoint(v)) < 15) {
- snapped = true;
- break;
- }
- }
- if (!snapped) {
- // not too close to an existing vert, so draw a new one. don't
- // use an actual handle cause we want to intercept nearby clicks
- // and not just clicks directly on the handle.
- Rect rect = new Rect();
- float dim = 0.05f * HandleUtility.GetHandleSize(closestPos);
- rect.center = closestPos - new Vector3(dim, dim, 0);
- rect.size = new Vector2(dim * 2, dim * 2);
- Handles.color = Color.white; // remove the weird tint it does
- Handles.DrawSolidRectangleWithOutline(rect, Color.green, Color.clear);
- if (Event.current.type == EventType.MouseDown) {
- // the user has clicked the new vert, so add it for real
- // figure out which line segment it's on
- int lineStart = GetClosestLineToPoint(closestPos, verts);
- verts.Insert(lineStart + 1, closestPos);
- changed = true;
- }
- }
- }
- // something has been changed, so apply the new verts back to the shape
- if (changed) {
- // make sure to remove the duplicated last vert we added
- Undo.RecordObject(shape, "Edit Shapes2D Polygon Vertices");
- shape.SetPolygonWorldVertices(
- verts.GetRange(0, verts.Count - 1).ToArray());
- EditorUtility.SetDirty(target);
- } else {
- HandleUtility.Repaint(); // to draw the new vert placeholder handle
- if (Event.current.type == EventType.MouseDown && !isCloseToLine)
- StopEditingShape(true);
- }
- }
- void DoMovePathPoint(List<PathSegment> segments, int i, Vector2 newPos) {
- PathSegment seg = segments[i / 3];
- Vector2 offset = seg.p1 - seg.midpoint;
- if (i % 3 == 0)
- seg.p0 = newPos;
- if (i % 3 == 2)
- seg.p2 = newPos;
- seg.p1 = seg.midpoint + (Vector3) offset;
- segments[i / 3] = seg;
- }
- void MovePathPointsAtPosition(List<PathSegment> segments, Vector2 oldPos, Vector2 newPos) {
- for (int i = 0; i < segments.Count; i++) {
- PathSegment seg = segments[i];
- if ((Vector2) seg.p0 == oldPos)
- DoMovePathPoint(segments, i * 3, newPos);
- if ((Vector2) seg.p2 == oldPos)
- DoMovePathPoint(segments, i * 3 + 2, newPos);
- }
- }
- void MovePathPoint(List<PathSegment> segments, int i, Vector3 newPos, bool moveConnected = true) {
- PathSegment segment = segments[i / 3];
- if (i % 3 == 1) {
- segment.p1 = (Vector2) newPos;
- segments[i / 3] = segment;
- return;
- }
- if (moveConnected)
- MovePathPointsAtPosition(segments, i % 3 == 0 ? segment.p0 : segment.p2, newPos);
- else
- DoMovePathPoint(segments, i, newPos);
- }
- void EditPath(Shape shape) {
- // get the existing verts
- PathSegment[] oldSegments = shape.GetPathWorldSegments();
- List<PathSegment> segments = new List<PathSegment>(oldSegments);
- bool hasMaxSegments = segments.Count == Shape.MaxPathSegments;
- // are we in delete mode? what color should handles be?
- Color pink = new Color(1, 0, 0.75f);
- bool deleteMode = false;
- if ((Event.current.control || Event.current.command) && segments.Count > 1) {
- Handles.color = Color.red;
- deleteMode = true;
- } else {
- Handles.color = pink;
- }
- bool splitMode = Event.current.shift;
- Vector3 mouseWorldPos = GetMouseWorldPos();
- bool isWithinBounds = shape.PointIsWithinShapeBounds(mouseWorldPos);
- // drag handle result for getting info from our handles
- CustomHandles.DragHandleResult dhResult;
- // draw handles for each existing point and check if they've been moved or clicked
- bool changed = false;
- for (int i = segments.Count * 3 - 1; i >= 0; i--) {
- PathSegment segment = segments[i / 3];
- Vector3 p = segment.p0;
- bool isInfluencePoint = false;
- if (i % 3 == 1) {
- p = segment.p1;
- isInfluencePoint = true;
- } else if (i % 3 == 2) {
- p = segment.p2;
- }
- if (deleteMode && isInfluencePoint)
- continue;
- float size = 0.04f * HandleUtility.GetHandleSize(p);
- #if UNITY_5_6_OR_NEWER
- Handles.CapFunction cap = Handles.RectangleHandleCap;
- #else
- Handles.DrawCapFunction cap = Handles.RectangleCap;
- #endif
- if (isInfluencePoint) {
- #if UNITY_5_6_OR_NEWER
- cap = Handles.CircleHandleCap;
- #else
- cap = Handles.CircleCap;
- #endif
- size = 0.05f * HandleUtility.GetHandleSize(p);
- }
- if (isInfluencePoint) {
- Color oldColor = Handles.color;
- Handles.color = Color.grey;
- Handles.DrawDottedLine(p, segment.p0, HandleUtility.GetHandleSize(p));
- Handles.DrawDottedLine(p, segment.p2, HandleUtility.GetHandleSize(p));
- Handles.color = oldColor;
- }
- Vector3 newPos = CustomHandles.DragHandle(p, size, cap, pink, out dhResult);
- if (deleteMode && !isInfluencePoint && dhResult == CustomHandles.DragHandleResult.LMBPress) {
- // the user clicked on the handle while in delete mode, so delete the segment
- segments.RemoveAt(i / 3);
- changed = true;
- break;
- } else if (!deleteMode && isInfluencePoint && dhResult == CustomHandles.DragHandleResult.LMBDoubleClick) {
- segment.MakeLinear();
- segments[i / 3] = segment;
- changed = true;
- } else if (!deleteMode && newPos != p) {
- // the handle has been dragged, so move the point to the new position
- if (isInfluencePoint || isWithinBounds) {
- MovePathPoint(segments, i, newPos, moveConnected: !splitMode);
- changed = true;
- }
- } else if (!splitMode && !deleteMode && !isInfluencePoint
- && dhResult == CustomHandles.DragHandleResult.LMBRelease) {
- // the handle has been released. snap it to any nearby points.
- for (int c = 0; c < segments.Count; c++) {
- PathSegment seg2 = segments[c];
- if (seg2.p0 != newPos && Vector2.Distance(seg2.p0, newPos) < HandleUtility.GetHandleSize(newPos) * 0.25f) {
- newPos = seg2.p0;
- break;
- }
- if (seg2.p2 != newPos && Vector2.Distance(seg2.p2, newPos) < HandleUtility.GetHandleSize(newPos) * 0.25f) {
- newPos = seg2.p2;
- break;
- }
- }
- MovePathPoint(segments, i, newPos, moveConnected: true);
- changed = true;
- }
- }
- // check if the mouse is hovering over a space where we could add a new point,
- // and draw it if so
- bool closeToExistingPoint = false;
- if (!changed && !hasMaxSegments && !deleteMode) {
- foreach (PathSegment s in segments) {
- // if close to an existing point, we don't want to add a new one
- if (Vector2.Distance(HandleUtility.WorldToGUIPoint(mouseWorldPos),
- HandleUtility.WorldToGUIPoint(s.p0)) < 15) {
- closeToExistingPoint = true;
- break;
- }
- if (Vector2.Distance(HandleUtility.WorldToGUIPoint(mouseWorldPos),
- HandleUtility.WorldToGUIPoint(s.p1)) < 15) {
- closeToExistingPoint = true;
- break;
- }
- if (Vector2.Distance(HandleUtility.WorldToGUIPoint(mouseWorldPos),
- HandleUtility.WorldToGUIPoint(s.p2)) < 15) {
- closeToExistingPoint = true;
- break;
- }
- }
- if (!closeToExistingPoint && isWithinBounds) {
- // not too close to an existing vert, so draw a new one
- // find the closest point
- float closestDistance = 99999;
- Vector2 closestPoint = Vector2.zero;
- for (int i = 0; i < segments.Count; i++) {
- float dist = Vector2.Distance(segments[i].p0, (Vector2) mouseWorldPos);
- if (dist < closestDistance) {
- closestPoint = segments[i].p0;
- closestDistance = dist;
- }
- dist = Vector2.Distance(segments[i].p2, (Vector2) mouseWorldPos);
- if (dist < closestDistance) {
- closestPoint = segments[i].p2;
- closestDistance = dist;
- }
- }
- // don't use an actual handle cause we want to intercept nearby clicks
- // and not just clicks directly on the handle.
- Rect rect = new Rect();
- float dim = 0.05f * HandleUtility.GetHandleSize(mouseWorldPos);
- rect.center = mouseWorldPos - new Vector3(dim, dim, 0);
- rect.size = new Vector2(dim * 2, dim * 2);
- Handles.color = Color.white; // remove the weird tint it does
- Handles.DrawSolidRectangleWithOutline(rect, Color.green, Color.clear);
- Color oldColor = Handles.color;
- Handles.color = Color.grey;
- Handles.DrawDottedLine(rect.center, closestPoint, HandleUtility.GetHandleSize(closestPoint));
- Handles.color = oldColor;
- if (Event.current.type == EventType.MouseDown && !Event.current.alt
- && !Event.current.shift && !Event.current.command && !Event.current.control) {
- // the user has clicked to add a new segment, so add it for real
- segments.Add(new PathSegment(closestPoint, mouseWorldPos));
- changed = true;
- }
- }
- }
- // something has been changed, so apply the new points back to the shape
- if (changed) {
- Undo.RecordObject(shape, "Edit Shapes2D Path Points");
- shape.SetPathWorldSegments(segments.GetRange(0, segments.Count).ToArray());
- EditorUtility.SetDirty(target);
- } else {
- HandleUtility.Repaint(); // to draw the new point placeholder handle
- if (Event.current.type == EventType.MouseDown && !isWithinBounds)
- StopEditingShape(true);
- }
- }
- void OnSceneGUI() {
- Shape shape = (Shape) target;
- if (!isEditing || (shape.settings.shapeType != ShapeType.Polygon && shape.settings.shapeType != ShapeType.Path))
- return;
- if (Tools.current != Tool.None) {
- StopEditingShape(false);
- return;
- }
- // draw some borders so the user knows where the shape should live
- DrawShapeBorders(shape);
- if (shape.settings.shapeType == ShapeType.Polygon)
- EditPolygon(shape);
- else
- EditPath(shape);
- // this prevents the user selecting another object when they are
- // adding poly/path nodes
- if (Event.current.type == EventType.Layout)
- HandleUtility.AddDefaultControl(GUIUtility.GetControlID(GetHashCode(), FocusType.Passive));
- }
- private Shapes2DPrefs GetPreferences() {
- string path = AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this));
- int index = path.IndexOf("Shapes2D");
- if (index == -1)
- return null;
- string prefsPath = path.Substring(0, index + 8) + "/Preferences.asset";
- return AssetDatabase.LoadAssetAtPath<Shapes2DPrefs>(prefsPath);
- }
- private void SetPolygonCollider2D(Shape shape) {
- PolygonCollider2D pc2d = shape.GetComponent<PolygonCollider2D>();
- if (shape.settings.shapeType == ShapeType.Polygon) {
- if (!pc2d)
- pc2d = shape.gameObject.AddComponent<PolygonCollider2D>();
- Vector3[] points = shape.GetPolygonWorldVertices();
- Vector2[] colliderPoints = new Vector2[points.Length];
- for (int i = 0; i < points.Length; i++)
- colliderPoints[i] = shape.transform.InverseTransformPoint(points[i]);
- Undo.RecordObject(pc2d, "Set PolygonCollider2D Points");
- pc2d.points = colliderPoints;
- }
- }
- private void FromPolygonCollider2D(Shape shape) {
- PolygonCollider2D pc2d = shape.GetComponent<PolygonCollider2D>();
- if (shape.settings.shapeType == ShapeType.Polygon) {
- if (pc2d.points.Length >= 64) {
- EditorUtility.DisplayDialog("Too many points",
- "The PolygonCollider2D has too many points (max 64).", "Okay");
- return;
- }
- Vector3[] points = new Vector3[pc2d.points.Length];
- for (int i = 0; i < pc2d.points.Length; i++)
- points[i] = shape.transform.TransformPoint(pc2d.points[i]);
- Undo.RecordObject(shape, "Edit Shapes2D Polygon Vertices");
- shape.SetPolygonWorldVertices(points);
- EditorUtility.SetDirty(target);
- } else if (shape.settings.shapeType == ShapeType.Path) {
- if (pc2d.points.Length >= 32) {
- EditorUtility.DisplayDialog("Too many points",
- "The PolygonCollider2D has too many points (max 32).", "Okay");
- return;
- }
- PathSegment[] segments = new PathSegment[pc2d.points.Length];
- for (int i = 0; i < pc2d.points.Length; i++) {
- Vector3 p0 = shape.transform.TransformPoint(pc2d.points[i]);
- Vector3 p2 = i == pc2d.points.Length - 1
- ? shape.transform.TransformPoint(pc2d.points[0])
- : shape.transform.TransformPoint(pc2d.points[i + 1]);
- segments[i] = new PathSegment(p0, p2);
- }
- Undo.RecordObject(shape, "Edit Shapes2D Path Segments");
- shape.SetPathWorldSegments(segments);
- EditorUtility.SetDirty(target);
- }
- }
- private void ConvertToSprite(Shape shape) {
- string dname = "Assets/Resources/Shapes2D Sprites";
- string fname = dname + "/" + shape.name + ".png";
- string rname = "Shapes2D Sprites/" + shape.name;
- if (!System.IO.Directory.Exists(dname))
- System.IO.Directory.CreateDirectory(dname);
- if (System.IO.File.Exists(fname)
- && !EditorUtility.DisplayDialog("Overwrite File?",
- "A file with the name " + fname + " already exists. "
- + "Are you sure you want to overwrite it?", "Yes", "Cancel"))
- return;
-
- float pixelsPerUnit = 100;
- Shapes2DPrefs prefs = GetPreferences();
- if (prefs) {
- pixelsPerUnit = prefs.pixelsPerUnit;
- } else {
- Debug.LogWarning("Can't find Shapes2D Preferences in Shapes2D/Preferences. Please re-import Shapes2D.");
- }
- Vector2 pivot = RenderToTexture2D(fname, shape, pixelsPerUnit: pixelsPerUnit);
- // refresh the asset
- AssetDatabase.ImportAsset(fname);
-
- // set the sprite's pivot point so any rotations/position stay the same
- TextureImporter textureImporter = AssetImporter.GetAtPath(fname)
- as TextureImporter;
- TextureImporterSettings texSettings = new TextureImporterSettings();
- textureImporter.ReadTextureSettings(texSettings);
- #if UNITY_5_5_OR_NEWER
- texSettings.ApplyTextureType(TextureImporterType.Sprite);
- #else
- texSettings.ApplyTextureType(TextureImporterType.Sprite, true);
- #endif
- texSettings.spritePixelsPerUnit = pixelsPerUnit;
- if (Vector2.Distance(pivot, new Vector2(0.5f, 0.5f)) < 0.01f) {
- texSettings.spriteAlignment = (int) SpriteAlignment.Center;
- textureImporter.SetTextureSettings(texSettings);
- } else {
- texSettings.spriteAlignment = (int) SpriteAlignment.Custom;
- textureImporter.SetTextureSettings(texSettings);
- textureImporter.spritePivot = pivot;
- }
- AssetDatabase.ImportAsset(fname, ImportAssetOptions.ForceUpdate);
-
- Sprite sprite = Resources.Load<Sprite>(rname);
- Undo.RecordObjects(shape.GetUndoObjects().ToArray(),
- "Convert to Sprite");
-
- shape.SetAsSprite(sprite, GetDefaultSpriteMaterial());
-
- // exit the gui routine because otherwise we get annoying errors because
- // we deleted a material and unity still wants to draw it
- EditorGUIUtility.ExitGUI();
- }
- public override void OnInspectorGUI() {
- serializedObject.Update(); // dunno what this does but it's in the examples?
-
- Shape shape = (Shape) serializedObject.targetObject;
- EditorGUI.BeginDisabledGroup(!shape.enabled);
- // shape type
- EditorGUILayout.PropertyField(shapeTypeProp);
- ShapeType shapeType = (ShapeType) shapeTypeProp.enumValueIndex;
- if (shapeType == ShapeType.Rectangle) {
- // rectangle props
- EditorGUILayout.PropertyField(roundnessPerCornerProp);
- if (shape.settings.roundnessPerCorner) {
- EditorGUILayout.PropertyField(roundnessTLProp);
- EditorGUILayout.PropertyField(roundnessTRProp);
- EditorGUILayout.PropertyField(roundnessBLProp);
- EditorGUILayout.PropertyField(roundnessBRProp);
- } else {
- EditorGUILayout.PropertyField(roundnessProp);
- roundnessTLProp.floatValue = roundnessProp.floatValue;
- roundnessTRProp.floatValue = roundnessProp.floatValue;
- roundnessBLProp.floatValue = roundnessProp.floatValue;
- roundnessBRProp.floatValue = roundnessProp.floatValue;
- }
- } else if (shapeType == ShapeType.Ellipse) {
- //ellipse props
- EditorGUILayout.PropertyField(startAngleProp);
- EditorGUILayout.PropertyField(endAngleProp);
- EditorGUILayout.PropertyField(invertArcProp);
- EditorGUILayout.PropertyField(innerCutoutProp);
- } else if (shapeType == ShapeType.Polygon) {
- // polygon props
- EditorGUILayout.PropertyField(polygonPresetProp);
- EditorGUI.BeginDisabledGroup(Selection.objects.Length != 1);
- if (GUILayout.Toggle(isEditing, "Edit Shape", "Button")) {
- if (!isEditing)
- EditShape();
- GUIStyle helpStyle = new GUIStyle(GUI.skin.label);
- helpStyle.wordWrap = true;
- helpStyle.normal.textColor = Color.green;
- EditorGUILayout.LabelField("Click on a segment to add a new node (up to 64).\nCtrl-click nodes to delete.\nSee docs about performance!", helpStyle);
- } else {
- if (isEditing)
- StopEditingShape(true);
- }
- if (shape.GetComponent<PolygonCollider2D>() && GUILayout.Button("From Polygon Collider 2D"))
- FromPolygonCollider2D(shape);
- if (GUILayout.Button("Set Polygon Collider 2D"))
- SetPolygonCollider2D(shape);
- EditorGUI.EndDisabledGroup();
- EditorGUILayout.PropertyField(usePolygonMapProp,
- new GUIContent("Optimize rendering (see docs!)"));
- } else if (shapeType == ShapeType.Triangle) {
- // triangle props
- EditorGUILayout.PropertyField(triangleOffsetProp);
- } else if (shapeType == ShapeType.Path) {
- EditorGUILayout.PropertyField(pathThicknessProp);
- EditorGUILayout.PropertyField(fillPathLoopsProp);
- EditorGUI.BeginDisabledGroup(Selection.objects.Length != 1);
- if (GUILayout.Toggle(isEditing, "Edit Path", "Button")) {
- if (!isEditing)
- EditShape();
- GUIStyle helpStyle = new GUIStyle(GUI.skin.label);
- helpStyle.wordWrap = true;
- helpStyle.normal.textColor = Color.green;
- EditorGUILayout.LabelField("Click to add a new segment (up to 32).\nShift-drag to separate connected nodes or prevent snapping.\nCtrl-click nodes to delete.\nDouble-click a circular node to make it linear.\nSee docs about performance!", helpStyle);
- } else {
- if (isEditing)
- StopEditingShape(true);
- }
- if (shape.GetComponent<PolygonCollider2D>() && GUILayout.Button("From Polygon Collider 2D"))
- FromPolygonCollider2D(shape);
- EditorGUI.EndDisabledGroup();
- }
- // common props
- EditorGUILayout.PropertyField(blurProp);
- EditorGUILayout.PropertyField(outlineSizeProp);
- EditorGUILayout.PropertyField(outlineColorProp);
- // fill props
- EditorGUILayout.PropertyField(fillTypeProp);
- FillType fillType = (FillType) fillTypeProp.enumValueIndex;
- if (fillType == FillType.Gradient) {
- EditorGUILayout.PropertyField(gradientTypeProp);
- if ((GradientType) gradientTypeProp.enumValueIndex < GradientType.Radial)
- EditorGUILayout.PropertyField(gradientAxisProp);
- EditorGUILayout.PropertyField(gradientStartProp);
- }
- if (fillType >= FillType.SolidColor && fillType < FillType.Texture) {
- EditorGUILayout.PropertyField(fillColorProp);
- }
- if (fillType == FillType.Texture) {
- EditorGUILayout.PropertyField(fillTextureProp);
- EditorGUILayout.PropertyField(fillScaleProp);
- }
- if (fillType >= FillType.Gradient && fillType < FillType.Texture) {
- EditorGUILayout.PropertyField(fillColor2Prop);
- }
- if (fillType >= FillType.Gradient) {
- EditorGUILayout.PropertyField(fillOffsetProp);
- EditorGUILayout.PropertyField(fillRotationProp);
- }
- if (fillType == FillType.Grid || fillType == FillType.Stripes) {
- EditorGUILayout.PropertyField(lineSizeProp);
- }
- if (fillType == FillType.Grid || fillType == FillType.Stripes
- || fillType == FillType.CheckerBoard) {
- EditorGUILayout.PropertyField(gridSizeProp);
- }
-
- EditorGUI.BeginDisabledGroup(Selection.objects.Length != 1);
- if (GUILayout.Button("Convert to Sprite"))
- ConvertToSprite(shape);
- EditorGUI.EndDisabledGroup();
-
- EditorGUI.EndDisabledGroup();
- serializedObject.ApplyModifiedProperties();
- // if the material has been destroyed, configure everything again.
- // or if the shape has been re-enabled after being converted to a sprite,
- // attempt to restore the scale it had previously
- if (shape.enabled && (!shape.IsConfigured() || shape.wasConverted)) {
- Undo.RecordObjects(shape.GetUndoObjects().ToArray(),
- "Re-enable Shapes2D Component");
- shape.Configure();
- if (shape.wasConverted)
- shape.RestoreFromConversion();
- // combine with the re-enable action
- Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
- }
- }
- }
- }
|