Position auto-scaled UI element with Camera.WorldToScreenPoint [duplicate] - unity3d

This question already has answers here:
UI Canvas Image with UI Buttons
(2 answers)
Closed 9 months ago.
I've got a Canvas.
I'm creating some Text elements right above 3D objects in the scene with this code:
Vector3 screenPos = Camera.main.WorldToScreenPoint(this.dices[x, z].transform.position);
screenPos.x -= 25;
screenPos.y += 10;
newScoreItem.GetComponent<RectTransform>().anchoredPosition = screenPos;
On Android my UI elements are tiny so I set the surrounding Canvas` UI Scale Mode to "Scale with screen size".
The problem is that the position I determine with the code above doesn't match the one of scaled canvas. My Text elements are scaled but at the completely wrong location.
How may I solve that problem?
Correct positioning (on Mac):
Wrong positioning (somewhere out of the screne) with Scale with screen size(on Android):

First check Anchors Min/Max of the Text "newScoreItem": point 0,0 on the screen is in the bottom left corner, so Anchors Min/Max should be 0 (bottom left).
Second - it matters how did you set Screen Match Mode.
If it is set to match width or height you can use the following script:
float refWidth = 800f; // reference resolution - width - set in Canvas Scaler
float refHeight = 480f; // reference resolution - height - set in Canvas Scaler
bool matchWidth = true; //true if screen match mode is set to match the width,
//false if is set to match the height
Vector3 screenPos = Camera.main.WorldToScreenPoint(this.dices[x,y].transform.position);
if (matchWidth) {
screenPos.x *= refWidth / Screen.width;
screenPos.y *= refWidth / Screen.width;
} else {
screenPos.x *= refHeight / Screen.height;
screenPos.y *= refHeight / Screen.height;
}
screenPos.x -= 25f;
screenPos.y += 10f;
newScoreItem.GetComponent<RectTransform>().anchoredPosition = screenPos;
It works for me, although maybe there is easier way to do it.

Related

Camera orthographic size certain width

How do we can set a certain size of units for defining the width of the Orthographic size of the camera in the portrait mode?
I want to set the horizontal dimension to the 5 meters exactly from the center-left to the center-right of the screen for any mobile device? how can I achieve this? I've found this piece of code from a video clip but I don't understand it and I think it does not help me since I don't have a game field and also my game world will be generated automatically during the game (it's an infinite 2D scrolling game).
[SerializeField] private SpriteRenderer gameField;
void Start()
{
float screenRatio = (float)Screen.width / (float)Screen.height;
float targetRatio = gameField.bounds.size.x / gameField.bounds.size.y;
if (screenRatio > targetRatio)
{
Camera.main.orthographicSize = gameField.bounds.size.y / 2;
}
else
{
float differenceInSize = targetRatio / screenRatio;
Camera.main.orthographicSize = gameField.bounds.size.y / 2 * differenceInSize;
}
}
I don't know why unity doesn't handle these issues automatically?
I have found the video you have been talking about and it is actually pretty simple. For reference, it is this video. I will try to explain it to you.
These 5 boxes represent all content that you want to show on your screen. You want your camera to always adjust itself so it fits all of those boxes inside of it.
To archive this you have to multiply the width of our boxes (all of our boxes are 1 meter wide in all dimensions) by the screen height devided by the screen width. You then want to divide that number by two.
boxes.width * screen.height / screen.width * 0.5
In our code it looks like this:
public float sizeInMeters;
void Start()
{
float orthoSize = sizeInMeters * Screen.height / Screen.width * 0.5f;
Camera.main.orthographicSize = orthoSize;
}
If you want to have five meters to the left and right from the middle then put in 10 into the sizeInMeters variable. If your total screen width from left to right should only cover 5 meters you have to put in 5 into the sizeInMeters variable.
The if inside of your code is only there if you want the user to be able to flip his phone into a vertical position.

Math: How to spin a wheel by drag&drop (Canvas, 2D)?

I'm struggling with probably simple math to spin/rotate a wheel using drag&drop.
There is a Radial Layout in a Canvas (Unity UI) and it can already be rotated by setting a property called StartAngle that is in a range from 0-360. In this Radial there are items, so the StartAngle is for the first item and places all the child elements around the layout radius.
I want to implement drag & drop for the items so that you can drag a child around and the Radial will spin accordingly (infinitely).
Right now, I have this as a starting point:
public void OnDrag(PointerEventData eventData)
{
var delta = eventData.delta.x * Time.deltaTime;
var newAngle = radialLayout.StartAngle + delta;
if (newAngle >= 360)
newAngle = newAngle - 360;
else if (newAngle < 0)
newAngle = Mathf.Abs(360 - newAngle);
radialLayout.StartAngle = newAngle;
}
It kind of works but doesn't feel very smooth. This is for mobile/touch, so I want both the X and Y delta of the drag operation to be taken into account. Apparently, the y delta is not considered in my example and I have no idea how to incorporate this correctly. The user might do a linear drag & drop on either axis or he/she might also do like a circular drag movement.
So how can I map mouse movement to a rotation angle from 0-360 so that it feels good?
Edit: Thanks for the help, I did it like this now:
public void OnDrag(PointerEventData eventData)
{
// Note the "Head-Minus-Tale rule for Vector subtraction, see http://www.maths.usyd.edu.au/u/MOW/vectors/vectors-3/v-3-7.html
// vSourceToDestination = vDestination - vSource;
// First, we draw a vector from the center point of the radial to the point where we started dragging
var from = dragStartPoint - (Vector2)radialLayout.transform.position;
// Next, we draw a vector from the center point of the radial to the point we are currently dragging on
var to = eventData.position - (Vector2)radialLayout.transform.position;
// Now, we calculate the angle between these two:
var dragAngle = Vector2.SignedAngle(from, to);
// Lerping makes movement fast at the beginning slow at the end
var lerpedAngle = Mathf.Round(Mathf.LerpAngle(radialLayout.StartAngle, dragAngle, 0.5f));
radialLayout.StartAngle = lerpedAngle;
}
I don't know all of your code and types but I would have an idea. I can't test this right now and can not garant that it even works like this but I hope the idea gets clear.
I would probably rather use something like
// This is the vector from the center of the object to the mouse/touch position
// (in screen pixel space)
var touchDirection = eventData.position - Camera.main.WorldToScreenPoint(transform.position);
// angle between the Up (Y) axis and this touchDirection
// for the angle the length of the up vector doesn't matter so there is
// no need to convert it to pixel space
var targetAngle = Vector2.SignedAngle(Vector2.up, touchDirection);
// since the returned angle might be negative wrap it to get values 0-360
if(targetAngle < 0) targetAngle += 360;
// Now either simply use Lerp
// this would simply interpolate each frame to the middle of both values
// the result is a fast movement at the beginning and a very slow at the end
radialLayout.StartAngle = Mathf.Lerp(radialLayout.StartAngle, targetAngle, 0.5f);
// or maybe use a fixed speed like 30°/second
var difference = targetAngle - radialLayout.StartAngle;
radialLayout.StartAngle += Mathf.Sign(difference) * Mathf.Min(30f * Time.deltaTime, Mathf.Abs(difference));
Typed on smartphone but I hope the idea gets clear

Unity C#: Line renderer from Gameobject (3D) to Canvas (Screen Space - Camera) [duplicate]

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!)

JavaFX 8, how to get center location of Scrollpane's Viewport

The Problem
I'm trying to figure out a way to get at which point in the content node the scroll pane's viewport is centered on.
To elaborate on the picture above, the big rectangle is the content (let's say a large image), and the small rectangle is the portion that is shown by the scroll pane. I'm trying to find x and y which would be coordinates from the top left of the content.
What I've Tried
My first thought was to use the getViewportBounds() method of the scroll pane and use its minX and maxX properties to determine the center x point:
Bounds b = scrollPane.getViewportBounds();
double centerX = (b.getMinX() + b.getMaxX()) / 2;
double centerY = (b.getMinY() + b.getMaxY()) / 2;
However, this doesn't work because these numbers are negative and don't seem to accurately describe the x and y I'm looking for anyways.
My next thought was to use the scroll pane's hValue and vValue to get the top left corner of the viewport relative to the content:
Bounds b = scrollPane.getViewportBounds();
double centerX = scrollPane.getHvalue() + b.getWidth() / 2;
double centerY = scrollPane.getVvalue() + b.getHeight() / 2;
This didn't work either though as the hValue and vValue seem to be way too large (when scrolled in only a few pixels, I'm getting numbers like 1600).
My Questions
I seem to have a fundamental misunderstanding of how the viewport works with a scroll pane.
What am I doing wrong here? Can someone explain where these numbers come from? How do I find x and y like in the picture above?
Let (x, y) be the be coordinates of the top, left point shown in the viewport. You can write this as
((contentWidth - viewportWidth) * hValueRel, (contentHeight - viewportHeight) * vValueRel)
vValueRel = vValue / vMax
hValueRel = hValue / hMax
This means assuming hmin and vmin remain 0 you can keep a circle in the center of like this:
// update circle position to be centered in the viewport
private void update() {
Bounds viewportBounds = scrollPane.getViewportBounds();
Bounds contentBounds = content.getBoundsInLocal();
double hRel = scrollPane.getHvalue() / scrollPane.getHmax();
double vRel = scrollPane.getVvalue() / scrollPane.getVmax();
double x = Math.max(0, (contentBounds.getWidth() - viewportBounds.getWidth()) * hRel) + viewportBounds.getWidth() / 2;
double y = Math.max(0, (contentBounds.getHeight() - viewportBounds.getHeight()) * vRel) + viewportBounds.getHeight() / 2;
Point2D localCoordinates = content.parentToLocal(x, y);
circle.setCenterX(localCoordinates.getX());
circle.setCenterY(localCoordinates.getY());
}
private Circle circle;
private Pane content;
private ScrollPane scrollPane;
#Override
public void start(Stage primaryStage) {
// create ui
circle = new Circle(10);
content = new Pane(circle);
content.setPrefSize(4000, 4000);
scrollPane = new ScrollPane(content);
Scene scene = new Scene(scrollPane, 400, 400);
// add listener to properties that may change
InvalidationListener l = o -> update();
content.layoutBoundsProperty().addListener(l);
scrollPane.viewportBoundsProperty().addListener(l);
scrollPane.hvalueProperty().addListener(l);
scrollPane.vvalueProperty().addListener(l);
scrollPane.hmaxProperty().addListener(l);
scrollPane.vmaxProperty().addListener(l);
scrollPane.hminProperty().addListener(l);
scrollPane.vminProperty().addListener(l);
primaryStage.setScene(scene);
primaryStage.show();
}

How to get height of 2D gamobject with sprite renderer component in unity3d 4.5?

I have a 2D gameobject and I want to find out what the height of my gameobject is in each screen size.
I am getting the height of my game character by calculating the distance between two empty gameobjects but i figure out this is wrong !
Is there a better way?
The default size (in px) for sprites of a unit in Unity is 100. If you select your sprite you can find the field "Pixels To Units" in the Inspector.
Let's say your object is at normal size, so x and y equal 1.0 . Usually you will give your object a 2d collider; Box 2D collider for this example. You will notice that the collider's default size won't equal 1.0 . Its bounds are based on the Pixels To Units value I mentioned before. In this example, let us have a sprite with the dimension of 128 x 128. The bounds of your collider would now equal 1.28, because 100px equal 1.0 unit (128 / 100 = 1.28).
Now we can calculate the on screen size in px very easily by dividing the width of our object by its collider's bounds: 1.0 / 1.28 = 0.78125
Now, just multiply by our Pixels To Units value (100): 0.78125 * 100 = 78.125px
Codewise it could look like this:
void OnMouseOver() {
Vector2 objDimensionInPX = GetDimensionInPX(GameObject.FindWithTag("Player"));
Debug.Log ("Sprite / GameObject size in pixels is (Width x Height): " + objDimensionInPX.x + " x " + objDimensionInPX.y);
}
private Vector2 GetDimensionInPX(GameObject obj) {
Vector2 tmpDimension;
tmpDimension.x = obj.transform.localScale.x / obj.GetComponent<SpriteRenderer>().sprite.bounds.size.x; // this is gonna be our width
tmpDimension.y = obj.transform.localScale.y / obj.GetComponent<SpriteRenderer>().sprite.bounds.size.y; // this is gonna be our height
return tmpDimension;
}