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)
Related
To keep it simple, I'm just trying to draw an arrow prefab along a quad that is at an angle. I need it to be flat against it (slightly raised) whatever position it's in.
What I'm doing right now is I've got a cube for the body of the arrow and a "triangle" prefab for the head. I click on the screen to get the "start position" (using a raycast hit point to make sure I'm over the my rotated quad below), then as I drag the mouse around it updates the "end position" vector 3. I simply calculate the distance between both points to see what "localScale.y" to expand it by, which works perfectly for the arrow length. For the head, I just attach it to the "end point", so it just follows the mouse.
The problem I have is that it isn't staying "flat" against the quad, which is rotated at X by 70 (in the inspector). And because of this, it rotates around length axis as I move and rotate the mouse in a circle.
Here my code:
if (Physics.Raycast(mouseRay, out hit))
{
arrowEndPoint = hit.point;
// Get rotation of Quad
quadRotation = rotatedQuad.transform.rotation.eulerAngles.x;
var arrowBodyRotation = simpleArrowBody.transform.rotation.eulerAngles;
var arrowHeadRotation = simpleArrowHead.transform.rotation.eulerAngles;
arrowBodyRotation.x = quadRotation; // Doesn't seem to be affecting it
arrowHeadRotation.x = quadRotation;
// Follow arrow head to mouse
simpleArrowHead.transform.position = arrowEndPoint;
// Get distance between Start and End point
arrowLength = Vector3.Distance(arrowStartPoint, arrowEndPoint);
// Direction based on the start and end points
var direction = (arrowEndPoint - arrowStartPoint).normalized;
// Adjust scale and rotation of Body
simpleArrowBody.transform.localScale = new Vector3(simpleArrowBody.transform.localScale.x, arrowLength, simpleArrowBody.transform.localScale.z);
simpleArrowBody.transform.up = direction;
// Adjust rotation of Head
simpleArrowHead.transform.up = direction;
simpleArrowHead.transform.up = -rotatedQuad.transform.forward; // Didn't work
}
quadRotation = rotatedQuad.transform.rotation.eulerAngles.x;
var arrowBodyRotation = simpleArrowBody.transform.rotation.eulerAngles;
var arrowHeadRotation = simpleArrowHead.transform.rotation.eulerAngles;
arrowBodyRotation.x = quadRotation; // Doesn't seem to be affecting it
arrowHeadRotation.x = quadRotation;
of course this has no effect at all!
You are only assigning this to a local variable Vector3 (which is a struct and thereby a copied value).
If you wanted to actually apply this back you would e.g. use
quadRotation = rotatedQuad.transform.rotation.eulerAngles.x;
var arrowBodyRotation = simpleArrowBody.transform.rotation.eulerAngles;
var arrowHeadRotation = simpleArrowHead.transform.rotation.eulerAngles;
arrowBodyRotation.x = quadRotation;
arrowHeadRotation.x = quadRotation;
simpleArrowBody.transform.rotation.eulerAngles = arrowBodyRotation;
simpleArrowHead.transform.rotation.eulerAngles = arrowHeadRotation;
BUT note that eulerAngles are pretty unreliable for his!
Working with rotation directly is often quite tricky. So I usually prefer to wok with vectors simply because I understand them better ;)
What I would do is
Rather design your arrow in a way that it is "normally" oriented. Meaning that it points into local Z (forward) direction and the one facing towards you is its local up direction.. Unity provides helper methods in this case.
And then you can simply use Quaternion.LookRotation with your direction and as the up vector pass in the -rotatedQuad.transform.forward
Something like e.g.
public class Example : MonoBehaviour
{
public Transform rotatedQuad;
public Transform simpleArrowHead;
public Transform simpleArrowBody;
private Vector3? arrowStartPoint;
private void Update()
{
var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Input.GetMouseButtonDown(0) && Physics.Raycast(mouseRay, out var hit))
{
arrowStartPoint = hit.point;
}
else if (arrowStartPoint.HasValue && Input.GetMouseButton(0) && Physics.Raycast(mouseRay, out hit))
{
var arrowEndPoint = hit.point;
// Get distance between Start and End point
var delta = arrowEndPoint - arrowStartPoint.Value;
var arrowLength = delta.magnitude;
// Direction based on the start and end points
var direction = delta.normalized;
// Adjust position, scale and rotation of Body
var scale = simpleArrowBody.localScale;
scale.z = arrowLength;
simpleArrowBody.localScale = scale;
// make the forward point into the direction while maintaining the
// up vector aligned with the quad surface
simpleArrowBody.rotation = Quaternion.LookRotation(direction, -rotatedQuad.forward);
// place at center between start and end
simpleArrowBody.position = arrowStartPoint.Value + delta / 2f;
// Follow arrow head to mouse and adjust rotation of Head
simpleArrowHead.position = arrowEndPoint;
// make the forward point into the direction while maintaining the
// up vector aligned with the quad surface
simpleArrowHead.rotation = Quaternion.LookRotation(direction, -rotatedQuad.forward);
}
}
}
(in Unity 3D)
Hi.
I want to show "HPBar" of the character on UI Canvas.
I tried the following method, but it didn't work normally.
Please tell me how to solve this problem...
/// <summary>
/// Follow target's position
/// </summary>
/// <param name="target">target(character's world position)</param>
void ChaseCharacter(Vector3 target)
{
// change world position to screen position (Main Camera)
Vector3 p1 = _worldCam.WorldToScreenPoint(target);
// change screen position to world position (UI Camera)
Vector3 p2 = _uiCam.ScreenToWorldPoint(p1);
// Apply UI Position
transform.position = p2;
}
I've built a minimal example, with a single camera. I guess with some small tweaks it should work on your multi-camera case.
public class UIFollow : MonoBehaviour
{
public GameObject target;
public Vector2 offset;
new private Camera camera;
void Start()
{
camera = GetComponentInParent<Canvas>().worldCamera;
}
void Update()
{
((RectTransform)transform).anchoredPosition = camera.WorldToScreenPoint(target.transform.position) + (Vector3)offset;
}
}
UIFollow is a Component on the "HealthBar" object inside the Canvas. Make sure it's anchors are set to the left bottom corner.
The offset vector is used to place the health bar above the object's center, it's public so it's easier to modify while designing the game.
From the hierarchy tab, add a new object, section UI then select Image, a canvas object should appear if not already created, inside should be the white image created , this Image should already follow your camera to every place because its in the UI screen section not near the player, just resize, make transparent and add your health bar inside it.
Thank you all for your advice.
I solved this problem, but I don't know how it was solved.
I'll upload the resolved code.
Vector3 p1 = _worldCam.WorldToScreenPoint(target);
p1.z = 100;
Vector3 p2 = _uiCam.ScreenToWorldPoint(p1);
transform.position = p2;
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.
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 have stuck in this simple problem but unable to understand that why i am unable to control it.
I have these line of code which is displaying my canvas object in front of my player(camRotationToWatch object name in code) at certain rotation of the player.
if (camRotationToWatch.transform.localEulerAngles.x >= navigationCanvasXMinmumLimit && camRotationToWatch.transform.localEulerAngles.x <= navigationCanvasXMaximumLimit)
{
if (!navCanvasHasDisplay)
{
navigationCanvas.SetActive(true);
//Debug.Log(camRotationToWatch.transform.forward);
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
navigationCanvas.transform.position = new Vector3(navCanvas.x, navCanvas.y, navCanvas.z);
navigationCanvas.transform.rotation = camRotationToWatch.transform.rotation;
navCanvasHasDisplay = true;
}
}
else
{
//navigationCanvas.SetActive(false);
if (locationPanel.activeSelf == false && infoPanel.activeSelf == false) {
navigationCanvas.SetActive(false);
navCanvasHasDisplay = false;
}
}
This code is actually work fine when camRotationToWatch object rotate from down to up and Canvas show at correct position but as I try to to rotate camRotationToWatch from up to down it display(active) Canvas at very top position. How can I restrict canvas to show at same position (No matter player rotate from up to down or down to up) but display on front of the player object?
Kinda hard trying to figure out what exactly you want to do. But this did what I think you where trying to do
public GameObject follow; // The object you want to rotate around
public float distance = 2; // Distance to keep from object
private void Update() {
Vector3 forward = follow.transform.forward;
forward.y = 0; // This will result in Vector3.Zero if looking straight up or down. Carefull
transform.position = follow.transform.position + forward * distance;
transform.rotation = Quaternion.LookRotation(forward, Vector3.up);
}
I believe your "unexpected behavior" is due to the use of euler angles since they are not always entirely predictable. Try using Quaternions or Vector3.Angle() when possible.
If you want to limit the angle (say... if looking down or up more than 45° disable the object) you could do the following:
if (Vector3.Angle(forward, follow.transform.forward) > maxAngle) { ... }
This probably isn't a complete answer but something that might help. This line:
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
You are creating a position at a fixed distance from camRotationToWatch. But if that object is looking up or down, that position is not horizontally at navCanvasDisplayDistanceFromCam. If it's looking straight up, then this position is in fact directly above.
So when you do this to set a fixed vertical height:
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
you aren't getting the distance from camRotationToWatch that you think you are.
Instead of using camRotationToWatch.transform.forward, create a vector from it and zero out the Y component, and normalize before using it to offset the position. (You will need to watch out for zero length vectors with that though).
Whether that fixes your problem or not, it's too hard to guess but it should help improve your results some.
EDIT: Here is an example of how you can avoid the issue with the canvas being too close:
Vector3 camForward = camRotationToWatch.transform.forward;
camForward.y = 0;
if (camForward.magnitude == 0)
{
//TODO: you'll need to decide how to deal with a straight up or straight down vector
}
camForward.Normalize();
//Note: now you have a vector lying on the horizontal plane, pointing in
//the direction of camRotationToWatch
Vector3 navCanvas = camRotationToWatch.transform.position + camForward *
navCanvasDisplayDistanceFromCam;
//Note: if you want the canvas to be at the player's height (or some offset from),
//use the player's y
navCanvas = new Vector3(navCanvas.x, Player.transform.y, navCanvas.z);
navigationCanvas.transform.position = navCanvas;
Again, this might not fix all your issues but will help to ensure your canvas lies at a set distance horizontally from the Player and will also compensate for the player's up and down motion.