I'm trying to get the global position of a UI element.
I've tried so many different ways to get the position but none of them seems to work. The problem comes with the anchors, as i'm moving them and not the UI element position itself (for resolution purposes), the position of the UI showing in the inspector is always 0,0,0. I also tried to get the anchoredPosition and also the Corners to make some calculations but it still not working, I always get (0,0,0) or some incoherent numbers.
Vector3 pointToTravel = Camera.main.WorldToScreenPoint(objectRectTransform.anchoredPosition);
This 'pointToTravel' variable should hold the screen position in pixels of the ui element.
anchoredPosition is the
position of the pivot of this RectTransform relative to the anchor reference point.
Since RectTransform inherits from Transform you can simply use
Vector3 pointToTravel = Camera.main.WorldToScreenPoint(objectRectTransform.position);
in order to get the absolute world position.
The position you see in the in the Inspector is always the
localPosition for Transform - relative to the parent GameObject.
or
anchoredPosition for RectTransform depending on the anchor settings - relative to the anchor reference point.
However in the case your Canvas is a Screen Space - Overlay all the contained UI (or better RectTransforms) already uses the screenspace coordinates in pixels so the WorldToScreenPoint is unnecessary or better said returns incorrect values.
So in that case you could instead simply use
Vector3 pointToTravel = objectRectTransform.position;
In order to get any information you could need about the dimensions and position of a UI Element from its RectTransform, I wrote this little utility function to convert a RectTransform to a Screen Space - Rect(For Canvas mode Screen Space - Camera).
public static Rect RectTransformToScreenSpace(RectTransform transform, Camera cam, bool cutDecimals = false)
{
var worldCorners = new Vector3[4];
var screenCorners = new Vector3[4];
transform.GetWorldCorners(worldCorners);
for (int i = 0; i < 4; i++)
{
screenCorners[i] = cam.WorldToScreenPoint(worldCorners[i]);
if (cutDecimals)
{
screenCorners[i].x = (int)screenCorners[i].x;
screenCorners[i].y = (int)screenCorners[i].y;
}
}
return new Rect(screenCorners[0].x,
screenCorners[0].y,
screenCorners[2].x - screenCorners[0].x,
screenCorners[2].y - screenCorners[0].y);
}
From this rect you can easily get all the information that the RectTransform does not give you that easily, such as Screen-Space-position and non-anchored size.
Related
I have been trying to experiment with moving UI Elements using the position of the mouse, Trying to make it behave like a cursor. I was able to move the Element when the Canvas Render Mode was set to "Screen Space". I was able to clamp it within the Canvas as well.
But I want it to work when the Canvas Render Mode is "World Space" as well. If I use the same code that I used for the Screen Space, the Element leaves its boundaries and gets messed up as the angles are being varied.
I really need help. Any clue how to do it?
You can view how the scene looks in this image below. : https://img.techpowerup.org/200624/screen.png
If Raycasting is the solution, can someone please help me out and provide a snippet of code or something.
What you are looking for is probably RectTransformUtility.ScreenPointToWorldPointInRectangle
Transform a screen space point to a position in world space that is on
the plane of the given RectTransform.
The cam parameter should be the camera associated with the screen
point. For a RectTransform in a Canvas set to Screen Space - Overlay
mode, the cam parameter should be null.
When ScreenPointToWorldPointInRectangle is used from within an event
handler that provides a PointerEventData object, the correct camera
can be obtained by using PointerEventData.enterEventData (for hover
functionality) or PointerEventData.pressEventCamera (for click
functionality). This will automatically use the correct camera (or
null) for the given event.
You didn't post your code but you could probably use it in a GraphicRaycaster.Raycast. Something like
GraphicRaycaster m_Raycaster;
PointerEventData m_PointerEventData;
EventSystem m_EventSystem;
Canvas canvas;
void Start()
{
//Fetch the Raycaster from the GameObject (the Canvas)
m_Raycaster = GetComponent<GraphicRaycaster>();
//Fetch the Event System from the Scene
m_EventSystem = GetComponent<EventSystem>();
canvas = GetComponnet<Canvas>();
//Set up the new Pointer Event
m_PointerEventData = new PointerEventData(m_EventSystem);
}
void Update()
{
//Check if the left Mouse button is clicked
if (Input.GetKey(KeyCode.Mouse0))
{
//Set the Pointer Event Position to that of the mouse position
m_PointerEventData.position = Input.mousePosition;
//Create a list of Raycast Results
List<RaycastResult> results = new List<RaycastResult>();
//Raycast using the Graphics Raycaster and mouse click position
m_Raycaster.Raycast(m_PointerEventData, results);
//For every result returned, output the name of the GameObject on the Canvas hit by the Ray
foreach (RaycastResult result in results)
{
Debug.Log("Hit " + result.gameObject.name);
// until here it was the API example for GraphicRaycaster.Raycast
// Now get the first hit object
var rect = result.gameObject.GetComponent<RectTransform>();
if(rect)
{
// check which camera to get (main or null for ScreenSpace Overlay)
var camera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : Camera.main;
if(RectTransformUtility.ScreenPointToWorldPointInRectangle(rect, result.screenPosition, camera, out var worldPosition))
{
cursor3D.transform.position = worldPosition;
cursor3D.transform.rotation = result.gameObject.transform.rotation;
break;
}
}
}
}
}
Especially note also
Attach this script to your Canvas GameObject.
Also attach a GraphicsRaycaster component to your canvas by clicking the Add Component button in the Inspector window.
Also make sure you have an EventSystem in your hierarchy.
so this is what happens when Im using vector 3 on my game I want my vector 3 to be on specific position for different screen sizes? is that possible? here is my codes
public virtual void ShuffleButton()
{
Vector3 buttonFirst = gameButtons[0].transform.position;
buttonFirst.x += 297f;
gameButtons[0].transform.position = buttonFirst;
Vector3 buttonSecond = gameButtons[1].transform.position;
buttonSecond.x -= 74.25f;
gameButtons[1].transform.position = buttonSecond;
Vector3 buttonThird = gameButtons[2].transform.position;
buttonThird.x += 74.25f;
gameButtons[2].transform.position = buttonThird;
Vector3 buttonFourth = gameButtons[3].transform.position;
buttonFourth.x -= 148.5f;
gameButtons[3].transform.position = buttonFourth;
Vector3 buttonFifth = gameButtons[4].transform.position;
buttonFifth.x -= 148.5f;
gameButtons[4].transform.position = buttonFifth;
}
You are looking for the position conversion functions like Camera.ScreenToWorldPoint(). These can be found in the Unity Camera class documentation here: https://docs.unity3d.com/ScriptReference/Camera.html
If, for example, you want to place a Sprite in the top-left corner, regardless of screen size, you would use the screen or viewport space. The position of the sprite will have to be translated from this screen/viewport space to world space. You could use Camera.ScreenToWorldPoint() for this.
However, Unity uses three viewspaces: Screen, World and Viewport. You should read up on all three as your problem stems from the fact that you are trying to use world coordinates (transform.position) to set the position of UI elements (which use either the screen or world space; this is dependent on the parent Canvas settings)
I want to get a GameObject's height. I have tried with:
this.GetComponent<MeshRenderer>().bounds.size.y
But the problem with bounds is that it works with static objects only. When you have a moving object and if the rotation of the object is not perfectly aligned, the bounds (height) is not accurate anymore because it returns the height of the bounds that is a square and if you tilt an object like a plate you get the bounds height which is not accurate to the height of the object.
It is an Axis-Aligned Bounding Box (also known as an "AABB").
Please check the image I attached, there you can see the problem with moving objects and if you rotate them how the height is not accurate anymore.
Did anyone else have this kind of problem?
Any advice on how to get the object's height accurately?
One option is to use the Game Object's Collider.
You can relatively easily find the height and width of a collider so this is one way you can measure the dimensions of a game object. The collider, for example a box collider on the game object, will have to fully encompasses the object. You can do this be resizing the collider until it snugly wraps around your game object. Then when you want to find a certain dimension of the object instead find that dimension on the collider and multiply by the Game Object's transform.scale.
Here are some examples I have tested.
Example 1:
CapsuleCollider m_Collider = GetComponent<CapsuleCollider>();
var height = m_Collider.height * transform.localScale.y;
https://docs.unity3d.com/ScriptReference/CapsuleCollider-height.html
or
Example 2:
BoxCollider m_Collider = GetComponent<BoxCollider>();
var height = m_Collider.size.y * transform.localScale.y;
var width = m_Collider.size.x * transform.localScale.x;
var breadth = m_Collider.size.z * transform.localScale.z;
https://docs.unity3d.com/ScriptReference/BoxCollider-size.html
Hopefully one of these will work for you.
If not, then a second inconvenient way you can find the height is to create 2 proxy objects as children at the top and bottom of your object. Afterwards find the scalar distance between them.
Solution 1: Use Mesh.bounds
You can get the height using Mesh.bounds. Unlike MeshRenderer.bounds (or Renderer.bounds), this is the axis-aligned bounding box of the mesh in its local space (i.e. not affected by the transform).
You still need to account for the scale of the GameObject using transform.lossyScale, as follows:
public class Example : MonoBehaviour
{
private Mesh _mesh;
private void Awake()
{
_mesh = GetComponent<MeshFilter>().mesh;
}
private void Update()
{
float height = _mesh.bounds.size.y * transform.lossyScale.y;
Debug.Log(height);
}
}
(Note when using transform.lossyScale, the value can be slightly inaccurate. This is something to note if you need extreme accuracy. See the documentation on transform.lossyScale:)
Please note that if you have a parent transform with scale and a child
that is arbitrarily rotated, the scale will be skewed. Thus scale can
not be represented correctly in a 3 component vector but only a 3x3
matrix. Such a representation is quite inconvenient to work with
however. lossyScale is a convenience property that attempts to match
the actual world scale as much as it can. If your objects are not
skewed the value will be completely correct and most likely the value
will not be very different if it contains skew too.
Solution 2 (Workaround): Cache the unrotated MeshRenderer bounds
Alternatively, if in your context you are able to instantiate the GameObject without a rotation and you only need to rotate the GameObject after initialization, one workaround solution is to cache the bounds from MeshRenderer in the Awake method, as follows:
public class Example : MonoBehaviour
{
private Bounds _initialUnrotatedBounds;
private void Awake()
{
InitializeUnrotatedBounds();
}
private void InitializeUnrotatedBounds()
{
Assert.IsTrue(transform.rotation == Quaternion.identity);
_initialUnrotatedBounds = GetComponent<MeshRenderer>().bounds;
}
private void Update()
{
// Use the cached bounds. Now, even for moving objects,
// it doesn't matter if the rotation changes
float height = _initialUnrotatedBounds.size.y;
Debug.Log(height);
}
}
I have a "Lift". While being in the game, you walk into the particle system and get moved up in the air (on y).
So the particle system is a child of the cube / the lift. So when scaling the cube, I don't want to change the settings of the particle system. It should scale itself on its own.
When the cube got the y position on 5 and a height / scaling on y of 10, the particle system should place itself down at the bottom.
As you can see, I want it being full automatic.
So, when heading into the code I got this
[SerializeField]
ParticleSystem liftParticles;
private void Start()
{
Vector3 objectScale = transform.localScale; // cube scaling
Vector3 particlePos = liftParticles.transform.position; // temp position
particlePos.y = (particlePos.y - objectScale.y) / 2; // move down on y
liftParticles.transform.position = particlePos; // set particle position
float transformScalingX = objectScale.x; // x scaling of the cube
float transformScalingZ = objectScale.z; // z scaling of the cube
var shape = liftParticles.shape; // set the cone radius now
shape.radius = transformScalingX > transformScalingZ ? transformScalingX : transformScalingZ;
liftParticles.shape = shape;
}
I want to go with the following example as mentioned above..
The cube got a scaling of (3,10,3) and its position is (0,5,0)
my current calculation particlePos.y returns a value of -0,75 but it has to be -0,5.
So do I have an error in my code? (yes obviously I do ...)
The second problem is, how do I change the radius of the particlesystem? When trying to reference the radius of the cone, it says I can't change it, it is readonly.
Is it? I hope I can change this somehow ...
Edit:
Obviously, the particlesystem just has to be always on -0,5f on y when having a scaling of (1,1,1). No need for a calculation anymore.
But I still need to change the radius of the shape and set the lifetime of the particles relative to the height of the lift. Means
private void Start()
{
Vector3 liftScale = transform.localScale; // Liftscaling
var shape = liftParticles.shape; // temp shape
shape.radius = liftScale.x > liftScale.z ? liftScale.x : liftScale.z; // set radius
liftParticles.shape = shape; // assign the temp shape to the real shape
liftParticles.main.startLifetime = ; // set the liftetime of the particles relative to its parent height on y
}
As I understand you made the particle system child of the lift (a cube) so it can move together. In case you just want that both of them move together, but they scale independently, you should consider to use an Empty GameObject as a parent.
You can placed this Empty GameObject in the middle of the Cube (your lift) and then make the lift and the particle filter children of that Empty GameObject. Then move the Empty GameObject instead of the lift, and the children will move as well.
About modifying the radio, try this script
GameObject myParticleGenerator;
ParticleSystem.ShapeModule pShape;
pShape = yParticleGenerator.GetComponent<ParticleSystem>().shape;
pShape.radius = 4.0f;
I'm writing a game using the 2d features of unity.
I'm designing a sort of inventory for the player character, and I have a gameobject with a number of placeholder images inside it. The intention is that when I actually load this gameobject, I'll replace the sprites of the placeholder images and I'll display what I want.
My issue is that when I change the sprite using code like this
var ren = item1.GetComponentInChildren<SpriteRenderer>();
ren.sprite = Resources.Load<Sprite>("DifferentSprite");
The image loaded is correct, however the scaling applies to the new sprite. The issue is that these sprites have all different sizes. So whilst the original placeholder image takes up a small square, the replacement might be tiny or massive enough to cover the whole screen depending on how the actual png was sized.
I basically want the sprite to replace the other and scale itself such that it has the same width and height as the placeholder image did. How can I do this?
EDIT - I've tried playing around with ratios. It's still not working perfectly, but its close.
var ren = item1.GetComponentInChildren<SpriteRenderer>();
Vector2 beforeSize = ren.transform.renderer.bounds.size;
ren.sprite = Resources.Load<Sprite>("Day0/LampOn");
Vector2 spriteSize = ren.sprite.bounds.size;
//Get the ratio between the wanted size and the sprite's size ratios
Vector3 scaleMultiplier = new Vector3 (beforeSize.x/spriteSize.x, beforeSize.y/spriteSize.y);
//Multiple the scale by this multiplier
ren.transform.localScale = new Vector3(ren.transform.localScale.x * scaleMultiplier.x,ren.transform.localScale.y * scaleMultiplier.y);
puzzle.Sprite = Sprite.Create(t, new Rect(0, 0, t.width, t.height),Vector2.one/2,256);
the last int is for the pixelperUnity, its the cause of changing size.
It's fixed in my project,
my default pixelPerUnit=256 and unity default = 100 so its bigger 2.56 times
tell me if it helps
How to modify width and height: use RectTransform.sizeDelta.
Example for this type of script
Vector3 originalDelta = imageToSwap.rectTransform.sizeDelta; //get the original Image's rectTransform's size delta and store it in a Vector called originalDelta
imageToSwap.sprite = newSprite; //swap out the new image
imageToSwap.rectTransform.sizeDelta = originalDelta; //size it as the old one was.
More about sizeDeltas here
I would expose two public variables in the script, with some default values:
public int placeholderWidth = 80;
public int placeholderHeight = 80;
Then, whenever you change sprites, set the width & height to those pre-defined values.