I have certain amount of sprites in my scene. It is a 2D Game. I want to display some text on the sprite each Sprite's GameObject is attached with a Script. In the Script I have the following OnGUI() Method
void OnGUI ()
{
GUI.contentColor = Color.black;
Rect label = new Rect ();
label.x = worldToRect (transform.position).x;
label.y = worldToRect (transform.position).y;
label.width = 100;
label.height = 100;
GUI.Label (label, number.ToString ());
}
Vector2 worldToRect (Vector3 WorldPosition)
{
Vector2 v = Camera.main.WorldToScreenPoint (WorldPosition);
return GUIUtility.ScreenToGUIPoint (v);
}
But the position is not quite right. The two white Sprites should display 3 at the centre.I checked their transform in the editor and they align exactly at the centre of the sprite. How do I correct this ?
The x and y in the Rect for the label are the top-left position, which is aligned with the center of the GameObjects.
To center them, subtract half the width from x and half the height from y, and make sure the label's style has Alignment set to "Middle Center"
Related
I have currently started a developing a pixel art program in unity. I started with getting the mouse coordinates(x, y) on the ui image(texture) I used the code below but the get the mouse position in world space. I wanna get it in the space of the ui image only. The texture image is 32, 32 pixel wide and tall (x, y respected)
[SerializeField] private Text coordinatesText;
private Vector3 mousePos;
private float mouseX, mouseY;
void Update()
{
mousePos = Input.mousePosition;
mouseX = mousePos.x;
mouseY = mousePos.y;
coordinatesText.text = (mouseX + ", " + mouseY).ToString();
}
For like, when I hover the mouse on the very bottom left of the texture, which is a ui image, I get 0, 0 and when it's in the very top right, it's 32, 32.
Thanks in advance for your help!
Depending on the Render Mode your Canvas is set to, the answer differs ever so slightly. I also want to point out, RectTransform by default has a pivot point on its center, so the (0,0) will be in the center, not on the bottom-left.
The function you are looking for is RectTransformUtility. ScreenPointToLocalPointInRectangle. To use it, you need the RectTransform of your UI object, if the Render Mode of your Canvas is set to ScreenSpaceCamera, you will need the camera and the screen space position, which is the mouse position. I mentioned that it was different depending on the RenderMode due to the camera parameter needing to be null if your RenderMode is OverlaySpace for the function to work.
Another note, you will most likely want to implement IHandlers to know when the cursor is inside of the specific UI element.
Here is some example code:
using UnityEngine;
using UnityEngine.EventSystems;
public class TestScript : MonoBehaviour, IPointerClickHandler
{
[SerializeField] private Canvas parentCanvas = null; // the parent canvas of this UI - only needed to determine if we need the camera
[SerializeField] private RectTransform rect = null; // the recttransform of the UI object
// you can serialize this as well - do NOT assign it if the canvas render mode is overlay though
private Camera UICamera = null; // the camera that is rendering this UI
private void Start()
{
if (rect == null)
rect = GetComponent<RectTransform>();
if (parentCanvas == null)
parentCanvas = GetComponentInParent<Canvas>();
if (UICamera == null && parentCanvas.renderMode == RenderMode.ScreenSpaceCamera)
UICamera = parentCanvas.worldCamera;
}
public void OnPointerClick(PointerEventData eventData)
{
// this UI element has been clicked by the mouse so determine the local position on your UI element
RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, eventData.position, UICamera, out Vector2 localPos);
// we now have the local click position of our rect transform, but as you want the (0,0) to be bottom-left aligned, need to adjust it
localPos.x += rect.rect.width / 2f;
localPos.y += rect.rect.height / 2f;
Debug.Log(localPos);
}
}
Here is a gif of the code working in a test scene. The image I have has a width and height of 32. I zoomed in the window so the clicks could be as closer to the edge to show the coordinates are properly set.
I recently started using Unity and C# and am currently working on a Vertical 2D mobile Game. I'm struggling to get my background to scale with different aspect ratios. The background sprite is 19,5/9 and the playable area is 16/9. At the moment the background is scaling to fit the top and bottom of the screen, but the idea is to have the background anchored to the sides and bottom and for the view to extend upwards if needed (Hence the tall sprite). Any ideas? Thanks in advance.
Here is the code im trying, its attached to the Camera.
public SpriteRenderer background;
private void Start()
{
float screenRatio = (float)Screen.width / (float)Screen.height;
float targetRatio = background.bounds.size.x / background.bounds.size.y;
if(screenRatio >= targetRatio)
{
Camera.main.orthographicSize = background.bounds.size.y / 2;
}
else
{
float differenceInSize = targetRatio / screenRatio;
Camera.main.orthographicSize = background.bounds.size.y / 2 * differenceInSize;
}
}
My solution is to use a worldspace UI canvas.
Set the canvas to world space, place it at the desired depth in your scene (dimensions won't matter as we can set them in the script), and either add an image object as a child or add an image component to the canvas object. Add your sprite as the source for the image like so.
void Awake()
{
RectTransform rt = GetComponent<RectTransform>();
rt.position = new Vector3(0, 0, rt.position.z);
float camHeight = Camera.main.orthographicSize * 2;
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, camHeight);
float targetRectWidth = camHeight * Camera.main.aspect;
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, targetRectWidth);
}
The Steps are:
sets the rect transform to be positioned in the centre of the screen at whatever depth you set it at in the editor.
get 2xCamera height (as that is the distance from the top to bottom of the screen)
sets the anchors so that it lines the UI object up with the top and bottom of the screen
gets the target width by multiplying the height by the aspect ratio of the camera
sets the correct width based the target width
This can be done in update instead of Awake or Start to dynamically size the background while playing if necessary.
Here it is at 1080p 16:9 and at 5:4.The red cube is to show that it is in the background behind objects in the scene.
I want to add a line to my game separating the left and right regions. I added a Gameobject to my Canvas and added the following script to it:
public class DrawLine : MonoBehaviour
{
public Color c1 = Color.red;
public Color c2 = Color.white;
Vector3 topPoint;
Vector3 bottomPoint;
// Start is called before the first frame update
void Start()
{
topPoint = new Vector3(Screen.width / 4, Screen.height);
bottomPoint = new Vector3(Screen.width / 4, 0);
LineRenderer lineRenderer = gameObject.AddComponent<LineRenderer>();
lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
lineRenderer.widthMultiplier = 2.2f;
lineRenderer.positionCount = 40;
// A simple 2 color gradient with a fixed alpha of 1.0f.
float alpha = 1.0f;
Gradient gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(c1, 0.0f), new GradientColorKey(c2, 1.0f) },
new GradientAlphaKey[] { new GradientAlphaKey(alpha, 0.0f), new GradientAlphaKey(alpha, 1.0f) }
);
lineRenderer.colorGradient = gradient;
lineRenderer.SetPosition(0, topPoint);
lineRenderer.SetPosition(1, bottomPoint);
}
}
When I run the game. A LineRenderer is added to the gameObject with the required colours and width, but there is no line drawn.
What am I doing wrong?
Thanks
Just tested the code you posted and it's working fine. I was originally thinking that the line was being drawn too thin and was going to recommend resizing the line to be larger at the start and end points, but after testing I do see the line being drawn in editor and game.
If the LineRenderer component is being created and you are not able to see the line either editor or game view, then post an image of the inspector for the gameobject that has the LineRenderer component at runtime.
Response to your comments
Using the LineRenderer draws a line at runtime in 3D-Space based on the positions selected in your code when you do the SetPosition calls.
When talking about a Canvas, I assume you are talking about a UI Canvas.
The LineRenderer draws based on the positions you set and not based on the Canvas unless you program it that way.
So, if your camera is not positioned in a location to see the line in front of it then it won't be displayed in the game view.
Hey,
I am working on a 3D room editor where you can grab an object from a menu and put it back in a room. You also get the option to give these objects a different color, this is done by the color picker. I now have a script that works the way I want it only if I enlarge the color picker, then he does not pick up the colors anymore and he does not move the selector circle anymore.
How do I solve this?
GIF Color picker at 1.8 scale
GIF Color picker at above 2 scale
Color picker script:
Color[] Data;
SpriteRenderer SpriteRenderer;
GameObject ColorPicker;
GameObject Selector;
BoxCollider Collider;
public GameObject target;
Ray rayray;
private Plane MyPlane;
public int Width { get { return SpriteRenderer.sprite.texture.width; } }
public int Height { get { return SpriteRenderer.sprite.texture.height; } }
public Color Color;
void Awake()
{
ColorPicker = transform.Find("ColorPicker").gameObject;
SpriteRenderer = ColorPicker.GetComponent<SpriteRenderer>();
Selector = transform.Find("Selector").gameObject;
Collider = ColorPicker.GetComponent<BoxCollider>();
Data = SpriteRenderer.sprite.texture.GetPixels();
Color = Color.white;
Debug.Log(Collider);
MyPlane = new Plane(transform.TransformDirection(Vector3.forward), transform.position);
}
void Update()
{
if (Input.GetMouseButton(0))
{
rayray = Camera.main.ScreenPointToRay(Input.mousePosition);
MyPlane = new Plane(transform.TransformDirection(Vector3.forward), transform.position);
Vector3 screenPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
screenPos = new Vector3(screenPos.x, screenPos.y);
//check if we clicked this picker control
RaycastHit[] ray = Physics.RaycastAll(rayray.origin, rayray.direction);
foreach (RaycastHit h in ray)
{
Debug.Log(h.collider.name);
if (h.collider.name == "ColorPicker")
{
Selector.transform.position = screenPos;
//get color data
screenPos -= ColorPicker.transform.position;
int x = (int)(screenPos.x * Width);
int y = (int)(screenPos.y * Height) + Height;
if (x > 0 && x < Width && y > 0 && y < Height)
{
Color = Data[y * Width + x];
target.GetComponent<Renderer>().material.color = Color;
Debug.Log(Width);
Debug.Log(Height);
}
}
}
}
}
EDIT:
GIF
This is the inspector from the color picker color field
This is the inspector from the main camera
IMAGE:
This is the inspector from the color picker color field
This is the inspector from the main camera
It is hard to know for sure without seeing your scene in the Unity editor but I have a few things I would check that may solve your problem.
Check to make sure that when you are scaling your GameObject that the BoxCollider is being scaled correctly. You should be able to see the Gizmo when selecting your GameObject after you scale it. Make sure it is covering the same areas of the UI before/after you scale it. The BoxCollider is used to detect mouse clicks on the object as a whole and unless the ray hits that collider, none of the other functionality will work.
It doesn't look like the script is taking into account scale changes for the color picker. Take a look where you get your X/Y coordinates for picking the color (under the //get color data comment). You will notice it multiplies the screen position by Height and Width which are taken from the size of your texture. This would need to be scaled accordingly to sample the correct area of the texture.
If you still can't get it to work, I would suggest posting a screen shot of your scene hierarchy and relevant GameObjects so we can see how it is all setup.
I have an image UI in a canvas with Screen Space - Camera render mode. What I like to do is move my LineRenderer to the image vertical position by looping through all the LineRenderer positions and changing its y axis. My problem is I cant get the correct position of the image that the LineRenderer can understand. I've tried using ViewportToWorldPoint and ScreenToWorldPoint but its not the same position.
Vector3 val = Camera.main.ViewportToWorldPoint(new Vector3(image.transform.position.x, image.transform.position.y, Camera.main.nearClipPlane));
for (int i = 0; i < newListOfPoints.Count; i++)
{
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
Screenshot result using Vector3 val = Camera.main.ScreenToWorldPoint(new Vector3(image.transform.localPosition.x, image.transform.localPosition.y, -10));
The green LineRenderer is the result of changing the y position. It should be at the bottom of the square image.
Wow, this was annoying and complicated.
Here's the code I ended up with. The code in your question is the bottom half of the Update() function. The only thing I changed is what was passed into the ScreenToWorldPoint() method. That value is calculated in the upper half of the Update() function.
The RectTransformToScreenSpace() function was adapted from this Unity Answer post1 about getting the screen space coordinates of a RectTransform (which is exactly what we want in order to convert from screen space coordinates back into world space!) The only difference is that I was getting inverse Y values, so I changed from Screen.height - transform.position.y to just transform.position.y which did the trick perfectly.
After that it was just a matter of grabbing that rectangle's lower left corner, making it a Vector3 instead of a Vector2, and passing it back into ScreenToWorldPoint(). The only trick there was because of the perspective camera, I needed to know how far away the line was from the camera originally in order to maintain that same distance (otherwise the line moves up and down the screen faster than the image). For an orthographic camera, this value can be anything.
void Update () {
//the new bits:
float dist = (Camera.main.transform.position - newListOfPoints[0]).magnitude;
Rect r = RectTransformToScreenSpace((RectTransform)image.transform);
Vector3 v3 = new Vector3(r.xMin, r.yMin, dist);
//more or less original code:
Vector3 val = Camera.main.ScreenToWorldPoint(v3);
for(int i = 0; i < newListOfPoints.Count; i++) {
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
}
//helper function:
public static Rect RectTransformToScreenSpace(RectTransform transform) {
Vector2 size = Vector2.Scale(transform.rect.size, transform.lossyScale);
Rect rect = new Rect(transform.position.x, transform.position.y, size.x, size.y);
rect.x -= (transform.pivot.x * size.x);
rect.y -= ((1.0f - transform.pivot.y) * size.y);
return rect;
}
1And finding that post from a generalized search on "how do I get the screen coordinates of a UI object" was not easy. A bunch of other posts came up and had some code, but none of it did what I wanted (including converting screen space coordinates back into world space coordinates of the UI object which was stupid easy and not reversibe, thanks RectTransformUtility!)