What I want to be able to do is this: the user clicks on the screen once and that becomes the start point of a 2D line. Then as he moves his mouse around, the line stretches and rotates to follow the mouse cursor. Then when he clicks the mouse the second time, that becomes the end point of the line. Unity doesn't have a 2D line per se and Unity GUI doesn't support lines. I searched the past threads and found two ways to go, but am having problems with both: 1. Use a "Line Renderer". The problem with this is that it will be covered/masked by any Unity GUI elements I may use. I may be able to work around this by redesigning my GUI, but the work around will be a PITA. 2. Use a "GUI.Label". GUIUtility.RotateAroundPivot would allow me rotate the label, but the problem with this is that I haven't found any way of scaling it to follow the mouse cursor? I thougt that GUIUtility.ScaleAroundPivot might do this, but so far haven't figured out how it's supposed to work. Any help or suggestions?
I'm afraid I'm having problems with #2 and the docs are pretty lean on the topic. Could you give me an example line of code that works? Thanks!
BigK, to be sure you're using the GUIUtility class to scale and rotate as needed? What have you tried? An alternate method might be to have a camera off in the distance somewhere doing little more than rendering a game object with the Line Rendering component attached. Draw the line, camera is orthographic, render the camera to a texture, draw texture as part of your GUI.
I'm just trying to get a feel for how this is supposed to work. This is what I've got so far but the texture isn't scaling? Code (csharp): var textureToDisplay : Texture2D; function OnGUI () { GUI.Label (Rect (200, 200, 32, 32), textureToDisplay); GUIUtility.ScaleAroundPivot (Vector2(1,1), Vector2(3,1)); }
Reverse the two lines of code in your function and use something other than a scaling of 1 (the first parameter). Edit: here's my example: Code (csharp): var textureToDisplay : Texture2D; function OnGUI () { GUIUtility.ScaleAroundPivot (Vector2(0.5, 0.5), Vector2(328.0, 328.0)); GUI.Label (Rect (200, 200, 256, 256), textureToDisplay); } My texture is 256x256 and the code above draws it at half-scale.
Ok, got it. Now how do I get the start and end point to be relative to the screen? I'm guessing I need "GUIUtility.ScreenToGUIPoint" but how would I use this? (I wish there were some sample scripts in the docs, would save us some time on this).
Ok, now I'm totally confused. Given what I'm trying to do (see my first post) how should I tackle this? Although what I want to do should be pretty straightforward (draw a line from point a in screen units to point b in screen units) , this seems to be getting more complex by the minute.
You only need ScreenToGUIPoint (or GUIToScreenPoint) if you have this or other GUI elements in groups and/or windows. You use them in-line within your code to get "absolute" or "local" coordinates. Code (csharp): // Untested forum code example function OnGUI () { GUI.BeginGroup(Rect(5, 5, 100, 100)); Debug.Log(GUIUtility.GUIToScreenPoint(Vector2(5,5))); Debug.Log(GUIUtility.ScreenToGUIPoint(Vector(20,15))); GUI.EndGroup(); } The first Debug.Log() would show an output of 10,10. The point specified (5,5) is relative to the group, which itself is located at (5,5), so the local GUI point is converted to screen coordinates, thus 10,10. The second Debug.Log() would show an output of 15,10. The point specified (20,15) is relative to the screen and when you convert that into a "group relative" position it takes into account the group's location of (5,5) and so the result is (15,10). And yes, the docs need improving on this front and I'll look into an improvement there even if it's some simple examples like the above. Yup, as I mentioned above it's an option to consider using the Line Renderer in a camera that's only there to draw lines (render that camera to a texture, then use those texutres in your GUI).
I would strongly suggest using a dedicated camera that does nothing other than rendering of Line Renderer elements that are the lines you want to draw in your GUI. Render that camera into textures as needed and use those textures in your GUI. To me that seems like something you can set up and use with much greater ease and flexibility than doing lots of rotation and scale juggling via the GUIUtility class.
The link is actually talking about using GL.LINES for real 2D lines. It requires Pro, but bigk has Pro so that's not an issue. I think it's the simplest solution since you'd just specify the start and end point of the line. --Eric
Well there you go, I learned something in school today. Thanks, I'll go give that thread a nice close read!
Eric Tom, I think I may be catching on (at least I hope I am). So I'd use the GL.line instead of a line renderer. But then: 1. Can I adjust this line to be 2 or 3 pixels thick? 2. It looks like I would use the same method Tom suggested, by applying this script to a render texture and using that as a texture in my GUI (label or box). 3. I'm guessing that I will still have to do some sort of screen coordinate conversion to find a cursor click location and use that as the start and end of the line. Hmmm... gotta say that this is pretty complicated...
Using GL.LINES is only 1 pixel, sorry. I have a script for drawing a line using viewport coords: Code (csharp): var linePoints : Vector2[]; var lineColor = Color.white; var lineDrawOn = false; private var lineMaterial : Material; function Awake () { lineMaterial = new Material( "Shader \"Lines/Colored Blended\" {" + "SubShader { Pass {" + " BindChannels { Bind \"Color\",color }" + " Blend SrcAlpha OneMinusSrcAlpha" + " ZWrite Off Cull Off Fog { Mode Off }" + "} } }"); lineMaterial.hideFlags = HideFlags.HideAndDontSave; lineMaterial.shader.hideFlags = HideFlags.HideAndDontSave; } function OnPostRender () { if (!lineDrawOn || linePoints.Length < 2) {return;} var cam = camera; var nearClip = cam.nearClipPlane+.01; lineMaterial.SetPass(0); GL.Begin(GL.LINES); GL.Color(lineColor); var end = linePoints.Length-1; for (i = 0; i < end; i++) { var v = cam.ViewportToWorldPoint(Vector3(linePoints[i].x, linePoints[i].y, nearClip)); GL.Vertex3(v.x, v.y, v.z); v = cam.ViewportToWorldPoint(Vector3(linePoints[i+1].x, linePoints[i+1].y, nearClip)); GL.Vertex3(v.x, v.y, v.z); } GL.End(); } @script RequireComponent(Camera) If you did something like: Code (csharp): function Start () { linePoints = new Vector2[2]; linePoints[0] = Vector2(.5, .5); lineDrawOn = true; } function Update () { var m = Input.mousePosition; linePoints[1] = Vector2(m.x/Screen.width, m.y/Screen.height); } Then it would draw a line from the center of the screen to wherever the mouse pointer is. You could probably make a thicker line by drawing more than one, slightly offset, but offhand I'm not quite sure how the offset would be done. --Eric
Thanks Eric, that's very helpful. Now if only there were an easy way to make this 2 or 3 pixels wide I'd be done!
Since I happened to take another look at this topic, I had an idea of how to do lines thicker than one pixel, and couldn't resist implementing it (though I'm sure you've long since used another solution). The script's on the wiki here. The width calculation is a bit of a kludge since I'm using viewport coordinates, but it works in all resolutions and aspect ratios I tested in (4:3 through 2:1) without any ugly gaps in the line. --Eric
Thanks Eric. As you already guessed, I did manage to put together a working solution using a linerenderer displayed on a camera that renders to a render texture. Then that render texture is used as a texture in the GUI. The advantage this has is that I can change the look of the line by simply changing the texture applied to it. The disadvantage is that it's a kludge at best and my code isn't anywhere as pretty as yours.
The class: Code (csharp): public class GUIHelper { protected static bool clippingEnabled; protected static Rect clippingBounds; protected static Material lineMaterial; /* @ Credit: "http://cs-people.bu.edu/jalon/cs480/Oct11Lab/clip.c" */ protected static bool clip_test(float p, float q, ref float u1, ref float u2) { float r; bool retval = true; if (p < 0.0) { r = q / p; if (r > u2) retval = false; else if (r > u1) u1 = r; } else if (p > 0.0) { r = q / p; if (r < u1) retval = false; else if (r < u2) u2 = r; } else if (q < 0.0) retval = false; return retval; } protected static bool segment_rect_intersection(Rect bounds, ref Vector2 p1, ref Vector2 p2) { float u1 = 0.0f, u2 = 1.0f, dx = p2.x - p1.x, dy; if (clip_test(-dx, p1.x - bounds.xMin, ref u1, ref u2)) if (clip_test(dx, bounds.xMax - p1.x, ref u1, ref u2)) { dy = p2.y - p1.y; if (clip_test(-dy, p1.y - bounds.yMin, ref u1, ref u2)) if (clip_test(dy, bounds.yMax - p1.y, ref u1, ref u2)) { if (u2 < 1.0) { p2.x = p1.x + u2 * dx; p2.y = p1.y + u2 * dy; } if (u1 > 0.0) { p1.x += u1 * dx; p1.y += u1 * dy; } return true; } } return false; } public static void BeginGroup(Rect position) { clippingEnabled = true; clippingBounds = new Rect(0, 0, position.width, position.height); GUI.BeginGroup(position); } public static void EndGroup() { GUI.EndGroup(); clippingBounds = new Rect(0, 0, Screen.width, Screen.height); clippingEnabled = false; } public static void DrawLine(Vector2 pointA, Vector2 pointB, Color color) { if (clippingEnabled) if (!segment_rect_intersection(clippingBounds, ref pointA, ref pointB)) return; if (!lineMaterial) { /* Credit: */ lineMaterial = new Material("Shader \"Lines/Colored Blended\" {" + "SubShader { Pass {" + " BindChannels { Bind \"Color\",color }" + " Blend SrcAlpha OneMinusSrcAlpha" + " ZWrite Off Cull Off Fog { Mode Off }" + "} } }"); lineMaterial.hideFlags = HideFlags.HideAndDontSave; lineMaterial.shader.hideFlags = HideFlags.HideAndDontSave; } lineMaterial.SetPass(0); GL.Begin(GL.LINES); GL.Color(color); GL.Vertex3(pointA.x, pointA.y, 0); GL.Vertex3(pointB.x, pointB.y, 0); GL.End(); } }; Example Code: Code (csharp): /* The "GUIHelper.BeginGroup(Rect)" method can be used now instead of GUI.BeginGroup(Rect) */ GUIHelper.BeginGroup(new Rect(0, 0, 100, 100)); /* The "GUIHelper.DrawLine(Vector2, Vector2, Color);" method will draw a 2D line, with a specified color */ GUIHelper.DrawLine(new Vector2(0, 0), new Vector2(200, 200), Color.red); /* The "GUIHelper.EndGroup()" method will pop the clipping, and disable it. Must be called for every "GUIHelper.BeginGroup(Rect);" */ GUIHelper.EndGroup();
this code doesnt filter gui events and therefor is drawn in the zero coords in the world you should add Code (csharp): if (Event.current == null) return; if (Event.current.type != EventType.repaint) return; to the DrawLine func
Try following code... Here Texture2D named point represents 2x2 texture filled with white so that any color can be given to line. (namely, just use GUI.color) Code (csharp): private void DrawLine(Vector2 start, Vector2 end, int width) { Vector2 d = end - start; float a = Mathf.Rad2Deg * Mathf.Atan(d.y / d.x); if (d.x < 0) a += 180; int width2 = (int) Mathf.Ceil(width / 2); GUIUtility.RotateAroundPivot(a, start); GUI.DrawTexture(new Rect(start.x, start.y - width2, d.magnitude, width), point); GUIUtility.RotateAroundPivot(-a, start); }
Thanks Sylvan for that code. I've expanded on it to allow the line to be composed of a solid color, a stretched texture, or a tiled texture Code (csharp): using UnityEngine; using System.Collections; public static class GuiHelper { // The texture used by DrawLine(Color) private static Texture2D _coloredLineTexture; // The color used by DrawLine(Color) private static Color _coloredLineColor; /// <summary> /// Draw a line between two points with the specified color and a thickness of 1 /// </summary> /// <param name="lineStart">The start of the line</param> /// <param name="lineEnd">The end of the line</param> /// <param name="color">The color of the line</param> public static void DrawLine(Vector2 lineStart, Vector2 lineEnd, Color color) { DrawLine(lineStart, lineEnd, color, 1); } /// <summary> /// Draw a line between two points with the specified color and thickness /// Inspired by code posted by Sylvan /// http://forum.unity3d.com/threads/17066-How-to-draw-a-GUI-2D-quot-line-quot?p=407005&viewfull=1#post407005 /// </summary> /// <param name="lineStart">The start of the line</param> /// <param name="lineEnd">The end of the line</param> /// <param name="color">The color of the line</param> /// <param name="thickness">The thickness of the line</param> public static void DrawLine(Vector2 lineStart, Vector2 lineEnd, Color color, int thickness) { if (_coloredLineTexture == null || _coloredLineColor != color) { _coloredLineColor = color; _coloredLineTexture = new Texture2D(1, 1); _coloredLineTexture.SetPixel(0, 0, _coloredLineColor); _coloredLineTexture.wrapMode = TextureWrapMode.Repeat; _coloredLineTexture.Apply(); } DrawLineStretched(lineStart, lineEnd, _coloredLineTexture, thickness); } /// <summary> /// Draw a line between two points with the specified texture and thickness. /// The texture will be stretched to fill the drawing rectangle. /// Inspired by code posted by Sylvan /// http://forum.unity3d.com/threads/17066-How-to-draw-a-GUI-2D-quot-line-quot?p=407005&viewfull=1#post407005 /// </summary> /// <param name="lineStart">The start of the line</param> /// <param name="lineEnd">The end of the line</param> /// <param name="texture">The texture of the line</param> /// <param name="thickness">The thickness of the line</param> public static void DrawLineStretched(Vector2 lineStart, Vector2 lineEnd, Texture2D texture, int thickness) { Vector2 lineVector = lineEnd - lineStart; float angle = Mathf.Rad2Deg * Mathf.Atan(lineVector.y / lineVector.x); if (lineVector.x < 0) { angle += 180; } if (thickness < 1) { thickness = 1; } // The center of the line will always be at the center // regardless of the thickness. int thicknessOffset = (int)Mathf.Ceil(thickness / 2); GUIUtility.RotateAroundPivot(angle, lineStart); GUI.DrawTexture(new Rect(lineStart.x, lineStart.y - thicknessOffset, lineVector.magnitude, thickness), texture); GUIUtility.RotateAroundPivot(-angle, lineStart); } /// <summary> /// Draw a line between two points with the specified texture and a thickness of 1 /// The texture will be repeated to fill the drawing rectangle. /// </summary> /// <param name="lineStart">The start of the line</param> /// <param name="lineEnd">The end of the line</param> /// <param name="texture">The texture of the line</param> public static void DrawLine(Vector2 lineStart, Vector2 lineEnd, Texture2D texture) { DrawLine(lineStart, lineEnd, texture, 1); } /// <summary> /// Draw a line between two points with the specified texture and thickness. /// The texture will be repeated to fill the drawing rectangle. /// Inspired by code posted by Sylvan and ArenMook /// http://forum.unity3d.com/threads/17066-How-to-draw-a-GUI-2D-quot-line-quot?p=407005&viewfull=1#post407005 /// http://forum.unity3d.com/threads/28247-Tile-texture-on-a-GUI?p=416986&viewfull=1#post416986 /// </summary> /// <param name="lineStart">The start of the line</param> /// <param name="lineEnd">The end of the line</param> /// <param name="texture">The texture of the line</param> /// <param name="thickness">The thickness of the line</param> public static void DrawLine(Vector2 lineStart, Vector2 lineEnd, Texture2D texture, int thickness) { Vector2 lineVector = lineEnd - lineStart; float angle = Mathf.Rad2Deg * Mathf.Atan(lineVector.y / lineVector.x); if (lineVector.x < 0) { angle += 180; } if (thickness < 1) { thickness = 1; } // The center of the line will always be at the center // regardless of the thickness. int thicknessOffset = (int)Mathf.Ceil(thickness / 2); Rect drawingRect = new Rect(lineStart.x, lineStart.y - thicknessOffset, Vector2.Distance(lineStart, lineEnd), (float) thickness); GUIUtility.RotateAroundPivot(angle, lineStart); GUI.BeginGroup(drawingRect); { int drawingRectWidth = Mathf.RoundToInt(drawingRect.width); int drawingRectHeight = Mathf.RoundToInt(drawingRect.height); for (int y = 0; y < drawingRectHeight; y += texture.height) { for (int x = 0; x < drawingRectWidth; x += texture.width) { GUI.DrawTexture(new Rect(x, y, texture.width, texture.height), texture); } } } GUI.EndGroup(); GUIUtility.RotateAroundPivot(-angle, lineStart); } } Read more Unity articles on my blog.
Is it possible to add a Collider to the textured Line? I worked on this for hours but couldn't get it done.
I'm currently having a go at trying to draw 2D lines on screen but i'm having a difficult time getting the positioning of the lines correct. Do i need to use WorldtoScreenPoint or WorldtoViewportPoint if i want the line to start at the origin of an object to say.. where the label is?
Stephane, Excellent code you have there. One change I might suggest (it's originally Pixelplacement's) is that to undo the GUI transformation, instead of using (for instance) GUIUtility.RotateAroundPivot(-angle, lineStart); you should set up Matrix4x4 matrixBackup = GUI.matrix; at the very beginning (this will save the untransformed matrix), and then apply GUI.matrix = matrixBackup; at the end instead. This will guarantee that you'll always end up back with your starting Identity matrix. I just forsee bad rounding errors propagating when trying to rotate by an angle and then the negative angle when you're drawing a bunch of lines on a loop.
GUI stuff isn't made for collisions. In unity (unless you do all the physics yourself) you'll have to do collisions in 3d world space. There are ways, but they'll amount to having a 3d collision mesh in the background, possibly constrained to move in a plane. If you're making a side-on 2d game and want to collide with a line segment, check out the 2d gameplay tutorial example on the unity site.
You can try "Dream Paint" ( https://www.assetstore.unity3d.com/#/content/13175). It allows to draw 2D and 3D primitives in game and in editor, and many others features ...
I found this quite usefull, but does anyone have a clue on how to use this when I have rescaled the gui matrix? it seems the pivots get messed up after the scaling matrix is applyed. I'm using scaling matrix to achieve a native resolution, so that when ever a screen size is changed, the gui elements stay in theyr designed positions and scaling. So any ideas?