I am creating a 3D game which is going to be a strategy game. I want to create a panel (UI group of a bunch of texts and buttons) by code (C#) and then move them so they are on top of a GameObject in the Canvas layout. So I am doing this:
if (ThisBuilding == null)
ThisBuilding = this.gameObject;
Panel = Canvas.Instantiate(Panel);
Panel.transform.SetParent(canvas.transform, false); //false to prevent stupid scaling issues created by the parent change
Vector3 centerPosition = ThisBuilding.GetComponentInChildren<MeshRenderer>().bounds.center; //the building GameObject contains a MeshRenderer component in its only children.
panelRect.anchoredPosition = Vector3.Lerp(panelRect.anchoredPosition, centerPosition, 1.0f);
The thing is that this script is run (on Start()) once per building, hoping that the panel would get instantiated 3 times and each panel corresponds to a building, but for some strange reason this does not work.
EXPECTED RESULT: each time the panel gets instantiated, the position is the same as of the building that instantiated it (the building GameObject holds the script and activates / deactivates the panel)
ACTUAL RESULT: even though the panel does get instantiated 3 times as it should, they are all in the same position and do not change even when my Update() function changes its position. What am I doing wrong?
Giving the world position of the buildings results in the panels being positioned basically in the same location on screen. What is needed is to calculate the world position to the screen position and use the calculated screen position to place the panels.
Try something like this:
panel.transform.position = Camera.main.WorldToScreenPoint(this.gameObject.transform.position);
Related
I have a simple parent object {Buttons Holder} that has a Horizontal Layout Group component attached to it, so when the game starts I instantiate multiple buttons in the Buttons Holder so that they line up perfectly.
And just for example lets say that I have 2 buttons instantiated and I want at some point these 2 buttons to switch positions so that the first button goes to the second button's position and the second button's switches to the first button's position.
And heres what i'm trying to do:
Vector3 pos1 = buttonPrefabs[0].transform.position;
Vector3 pos2 = buttonPrefabs[1].transform.position;
buttonPrefabs[0].transform.position = Vector3.Lerp(pos1, pos2, Time.deltaTime * 2);
I mean I don't want to snap them to their new positions I just want them to smoothly travel to their new positions using Vector2.lerp or something.
Since the parent object has a Horizontal Layout Group attached to it i'm not able to do that.
Help please.
You can not lerp them to their new position while the position is controlled by the Layout Group. Only thing I can think of is
temporarily disable the Layout Group
then move the child objects smoothly via e.g. MoveTowards
update their child index values to reflect their new order accordingly
enable the layout group again
A more sophisticated approach would require you writing your own Layout Group that offers functionality to let child objects switch positions. That way your own Layout Group could still keep all other children properly positioned, even while two children switch positions.
In a project I'm currently working on, I am having users click on an area for where they want to place things into the environment later on down the line. I'd like to visualize what they're placing with simple markers placed on the canvas, so that as they add and remove points the markers come and go as well.
I've found some resources on how to start, listing how to instantiate prefabs into the canvas, but it never seems to work for me. I feel it must have something to do with how I'm using coordinates but I'm not entirely sure.
public GameObject markerPrefab;
Then later on in another function
GameObject boatMarker = Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity);
boatMarker.transform.SetParent(GameObject.FindGameObjectWithTag("Canvas").transform, false);
The code runs, and the prefabs do spawn into the scene, but they all appear in the top right hand corner of the canvas, all sort of one on top of the other. Any ideas what I've done wrong here? Also, while I don't want to ask you guys to write my code for me, any suggestions for a jumping point on how to remove specific instances of the prefab later down the line?
The main issue I'ld say is that you are using SetParent with the second parameter false
If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.
In your case you want to keep the same world space position.
Since your canvas is Screenspace overlay its width and height (in Unity units) match exactly the display/window pixel width and height. Therefore when you do
GameObject boatMarker = Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity);
The object already is in the correct position. To visualize that I just gave it a cube as child so you see it spawns already where I clicked (you can't see the image yet beacuase it's not a child of a Canvas):
What happens if you pass in that false parameter to the SetParent is that it doesn't keep it's current worldspace position but instead keeps its current localPosition and moves to that relative position within its parent. Since it is a Canvas and your prefab probably also using a RectTransform the resulting position depends on a lot of things like the e.g. the pivot and anchor settings of the prefab's RectTransform but also e.g. the Canvas Scaler -> Scale Factor.
If your prefab e.g. is anchored on the center (usually the default) and you click exactly on the center of your window it will appear on the upper right corner instead.
Why?
you click at (windowWidth / 2, WindowHeight / 2). So the prefab is originally spawned here
Than you use SetParent with false so it keeps that position but this time relative to the center of the Canvas
=> Center of canvas position is (windowWidth / 2, WindowHeight / 2) so adding the local prefab coordinates (windowWidth / 2, WindowHeight / 2) results in the final position (WindowWidth, WindowHeight) == upper right corner.
So you could fix that either by making the prefab beiing anchored on the lower left corner
or do not pass false as parameter to SetParent.
boatMarker.transform.SetParent(_canvas.transform);
you could actually then also do it in one single call
Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity, _canvas.transform);
Additionally you should not use FindObjectWidthTag again and again. I would rather only get it once or even reference it via the Inspector if possible:
public GameObject markerPrefab;
[SerializeField] private Canvas _canvas;
private void Awake()
{
// If no canvas is provided get it by tag
if (!_canvas) _canvas = GameObject.FindGameObjectWithTag("Canvas").GetComponent<Canvas>();
}
// Update is called once per frame
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity, _canvas.transform);
}
}
Try This :
1- insert the button into the Canvas
2- get a Gameobject reference for this button
3- try this code after assigning your button to the button variable and the Game Canvas to the code Canvas variable, and it will work fine
public GameObject refButton;
public GameObject canvas;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject button = Instantiate(refButton, Input.mousePosition,Quaternion.identity);
button.transform.SetParent(canvas.transform);
}
}
I have an object in my scene's sky and want to prevent it from getting cut out of the view, no matter the screen ratio. So I set up a canvas and a PlaceHolder RectTransform on it, that - as being UI element - gets scaled and repositioned in respect to the screen aspect ratio. My goal is to use this position to determine where to put my scene object by using the following script:
Camera mainCam;
RectTransform placeHolder;
private void Start()
{
mainCam = GameObject.FindGameObjectWithTag("MainCamera")
.GetComponent<Camera>();
placeHolder = GameObject.FindGameObjectWithTag("SkyObjectPlaceholder")
.GetComponent<RectTransform>();
var screenPos = mainCam.ScreenToWorldPoint(
new Vector3(placeHolder.rect.x, placeHolder.rect.y, 0));
transform.position = new Vector3(screenPos.x, screenPos.y,
transform.position.z);
}
The issue is that my sky object is always placed in the center of the screen. It is not a child of anything on the scene, I just put it there. The placeholder is to the upper right in the canvas. Note that I tried playing with anchors for the UI element, set it to be left bottom, right top, no change, the scene object is always in the center of the screen.
What am I doing wrong? How to make a Scene element match up the position of a Canvas element, so it gets right behind it?
Note: I'm using a perspective camera. I don't know if it matters, but I thought I mention it, just in case.
Yet another edit: after messing around with the issue a bit, I found that the problem is possibly linked to the Z input I give the ScreenToWorldPoint, in my case it's 0. I couldn't get a viable Z to input though.
Hmmm... i think you should change the sorting layer of that object
I am working on a 2D game and the text prefab that I instatiate doesn't position itself over the gameObject clicked (which is the goal). I've set the Canvas as parent of the prefab via script after spawning it and it doesnt change position.
// creating hit text
GameObject canvas = GameObject.Find("Canvas");
GameObject hit = (GameObject)Instantiate(hitText, transform.position,Quaternion.identity);
hit.transform.SetParent(canvas.transform, false);
hit.transform.position = transform.position;
P.S: this sample code worked with a text made with the Unity Text Editor. Does that mean TexhMesh Pro won't support this function?
For anyone who might have this problem please understand that I made sure that the instantiated text is a child object of the canvas. The problem was that my text has been changed on start by an attached animation. The script above was working fine but the animation was changing it.
You can easely fix this problem by setting the parent of the text to an empty GameObject, which then serves as a container for the text, allowing the text to change its position relatively to it's parent only.
I am instantiating an object with following code
public Object ball;
Instantiate(ball, hit.point , Quaternion.identity) ;
Where hit.point is a position and ball is a prefab I created, but unfortunately this prefab has a Position animation - jumping ball - and because of that as it animates it stays in animation's position. I can't even Translate it.
How can I move it or change animation somehow ?
There is more than one way to solve this problem, depending on your other goals/constraints if any.
One simple approach is to separate this problem into two spaces, via the introduction of an empty parent node.
If you construct..
[Empty Parent Node] (created dynamically)
|- [Ball] (created from your prefab)
..then you can still apply the animation to [Ball], now within a "local space" defined by [Empty Parent Node]. Further, you can now specify an arbitrary position for the parent node, which acts to place the ball overall.
Another possible solution is to change your animation from a position animation to a localPosition animation. Then, you'll be able to change its Transform.position attribute with scripts.