Unity: Change UI gameObject parent and position without moving Child gameObject - c#

I have a UI in Unity where I constantly move items from one place to another.
Some of said items are containers, and I decided to give each containers their own child gameObject representing and containing the actual container's content once opened (aka activated the gameObject). I chose to do it this way to be easier to track each container's actual containers.
What I want: When changing the item's parent (aka moving it around since all my parents have layout groups), the child gameObject should remain at the same world position
What I have: When changing the item's parent, the child gameObject changes world position
What I can do: I could reset the position of the child gameObject each time, as I do the first time the gameObject is instantiated using this code (the relative gameObject is stretched)
Vector3[] corners = new Vector3[4];
transform.parent.parent.GetComponent<RectTransform>().GetWorldCorners(corners);
float width = corners[2].x - corners[0].x;
float height = corners[1].y - corners[0].y;
RectTransform itemTransform = item.transform.GetChild(1).GetComponent<RectTransform>();
itemTransform.position = new Vector3(width / 2, height / 2);
What I want to know: Is there something built in Monobehavior or Unity that prevents a child gameObject from moving
Here are images step by step of one of the cases it would apply:
Here I have a bag item called "sac a dos" in my room
Now I have opened The bag, which shows up in the middle of the screen
I now have created a chest item called "Coffre" at the same place, causing the bag to be put as child of the chest's container child gameObject
Here I opened the chest, positioned at the right place, and showing the bag that was put as child of the chest's container child gameObject
Here I opened the bag in the chest, causing the bag's child gameObject to be misplaced since the bag's position changed

I think You should separate items data from their display.
When You have a common display of your elements write a Show method, where you could pass showed item data.
Example:
public class ItemDisplay : MonoBehaviour
{
public void Show(ItemData itemData)
{
this.gameObject.SetActive(true);
foreach (var property in itemData.properties)
{
DisplayProperty(property);
}
}
private void DisplayProperty(){...}
}

Related

How can I affect the transparency of many sprites at once through the parent GameObject?

I have a player that can move around a randomly generated map of rooms. I've been trying to figure out a way to keep all the rooms fully transparent/invisible, and then as soon as the player enters the room, that room becomes opaque.
I already have a way to detect the room the player is in, the main issue I'm having is changing the transparency of the room. Each room is made up of 30 or so "wall" tiles, which are just default square 2D sprites for now. These are the only thing I need to change the visibility of for now.
I saw that I can change the "material render value" or something similar to that per sprite, but I'm not sure of an easy way to do that for a whole lot of sprites at once that only have the default sprite renderer component and not a custom material.
Do I need to completely overhaul my rooms' walls to be one prefab for the whole wall? Or is there some way I could easily loop through each wall in a targeted room and change the transparency/visibility?
You could use the GameObject.SetActive(bool) it has the utility to disable and enable the visibility of the GameObject.
(I believe you have already done this, but if not) In your hierarchy you would first create an Empty and add your room to it, rename it with the name of your room and you could use the following script when detecting the room the player is in.
public GameObject room; //link your room here
void Start()
{
room.SetActive(false);
}
void Update()
{
//When detecting the room
room.SetActive(true);
}
The bool parameter of the SetActive() void sets whether the object is visible or not, when false it is invisible, and when true it is visible. I don't have your script as a reference, so I used an example
You should use GetChild() and for {}. Get child gets the child of the index you chose, and accesses it. Here is the script:
void Update()
{
for (int i = 0, i <= transform.childCount, i += 1)
{
GameObject child = GetChild(i);
// change the values here using child.GetComponent<>()
}
}
This is what the computer does: sets int i. Does this object have less children than the variable, i? Yes, run the code underneath. set child to the child of index i. i increases by 1. Repeat until i is greater than the child count.
Links:
https://docs.unity3d.com/ScriptReference/Transform.GetChild.html
https://www.w3schools.com/cs/cs_for_loop.asp

Instantiating a U.I object at the position of another object while keeping the Canvas as the parent

Ran into a U.I problem that I am struggling to solve (UI is always an issue for me). Gist of the project is that I have an array of spawn points (Positions[i]), an array composed of enemy prefabs(Enemies[j]) and just a UI panel prefab with a text component (EnemyHUD). When I pass string names through a certain function, as long as there is a prefab with the same name and position available to it, it will load in the enemy. Now, for every enemy prefab loaded in, I would like a "EnemyHUD" prefab to instantiate at at the enemy positions and with text displaying the name of the enemy.
GameObject HUD = Instantiate(EnemyHUB, Positions[i].position, Positions[i].rotation);
This line spawns the EnemyHUD prefab at the right location but its Instantiates them outside of the canvas so they show up as red x's. So I added this:
HUD.transform.SetParent(GameObject.Find("Canvas").transform, false);
This fixes the issue of Instantiating outside of the canvas but also (irritatingly)resets the Instantiate position and embarrassingly, I'm not sure how to set it back while making sure it stays a child of the Canvas. Have not event touched the name change part yet.
I have been working on this this since last night and while I got a lot of different results experimenting, none have been the one that I want. Very much still a novice, so I am sure it is looking me in the face so please help me find it.
First of all I would strongly recommend to avoid Find whenever possible! Rather already reference the according Canvas and store its reference. Especially the i lets assume you are doing this in a loop so using Find repeatedly is extremely inefficient!
// Drag you Canvas object into this field via the Inspector in Unity
[SerializeField] private Canvas _parentCanvas;
private void Awake()
{
// use this only as fallback an only ONCE
if(!_parentCanvas) _parentCanvas = GameObject.Find("Canvas");
if(!_parentCanvas) Debug.LogError("Could not get Canvas!", this);
}
Well and then the second parameter of Transform.SetParent is called
worldPositionStaysIf 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.
And
The default value of worldPositionStays argument is true.
Simply either leave it out like
HUD.transform.SetParent(_parentCanvas);
// this equals
//HUD.transform.SetParent(_parentCanvas, true);
or you could even do it in one single go already in Object.Instantiate which takes an additional parameter
parent Parent that will be assigned to the new object.
// This directly instantiates the new hud as child of _parentCanvas
// while still taking the position and rotation in absolute worldspace
GameObject HUD = Instantiate(EnemyHUB, Positions[i].position, Positions[i].rotation, _parentCanvas);

How to destroy gameobject that is below some height

I'm trying to destroy any gameobject that is under some height (let's say -5).
I tried foreach method that takes all gameobjects and than compares it using if. If comparison is TRUE, then there is simple Destroy(go);
public float MinimalYLocation;
GameObject[] Objects;
void Start () {
Scene scena = SceneManager.GetActiveScene();
Objects = scena.GetRootGameObjects();
}
void Update () {
foreach (GameObject gobject in Objects) {
float Height = gobject.transform.position.y;
if (Height < MinimalYLocation){
Destroy(gobject);
}
}
}
I set up a scene that contains 3 cubes in different heights and a camera. Script is in camera. When first cube falls, it is destroyed. Then I get massive spam to console:
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
And the second and third cubes are not destroyed. Expected response would be that all gameobjects will be destroyed as they touch height -5
This is not going to be a scalable approach. You will find that it has a severe impact on your game's performance as more and more GameObjects are active in the scene.
I recommend you add a plane that extends to all game area bounds, and sits at this "-5" z position. Give the plane a collider trigger and destroy all objects that touch it. Leave the plane without a texture (aka invisible).
This will eliminate what is going to be a massive load in an update that happens every single frame.

Moving every child

I have a game object that has a lot of children (spheres)
At some stage of the game I'd like to move every child down smoothly so it would look like it's falling apart. What I've tried is using foreach loop to move every element with Vector3.Lerp. Sadly everything starts lagging very badly.
Would be great to have them fall down smoothly like this
foreach (Transform child in GameObject.Find("Carbon").transform)
{
child.transform.position = Vector3.Lerp(child.transform.position,
new Vector3(child.transform.position.x,
child.transform.position.y - 200,
child.transform.position.z), 0.2f);
}
I think the main problem comes from GameObject.Find(). From the look of your code, it finds carbon transform in every foreach. If you have 100 objects it will use "GameObject.Find" 100 times in one frame.
This can be fixed by caching transform:
List<Transform> carbonList = new List<Transform>();
void Start () {
//cache carbon tranform in Start() or OnEnable()
carbonList.AddRange( transform.GetComponentsInChildren<Transform>());//use this if you attach your code in object parent
//loop transform form caching list so it will not keep finding transform and cause lagging
foreach (Transform child in carbonList)
{
var childPos = child.position;
child.position = Vector3.Lerp(childPos, new Vector3(childPos.x, childPos.y - 200, childPos.z), 0.2f);
}
}
if your script is not in parent object you can use find tag instead.
http://answers.unity3d.com/questions/24257/how-do-i-find-all-game-objects-with-the-same-name.html
Your animation graphic seems to show that all of your spheres maintain the same relative position to each other. Rather than "falling apart", they seem to be falling together as a body. If that's want you want, rather than animate each individual sphere, put them all under a single parent object and animate that parent.

How to use UI.Text as a prefab in Unity?

I've tried using this
Instantiate(entry, new Vector3(x, y, 0), Quaternion.identity);
It successfully created objects, as seen in my hierarchy. But I cannot see the text on the screen, even though there is a text assigned to it. I can only see an empty game object on the screen.
These screenshots show the game on play, and the selected object is the object instantiated through the script.
When I drag the prefab, it does not show anything on the scene. This happens to all my prefabs. The following are its components:
In new Unity3D UI system, every UI (Text, Button, Panel etc.) component that will be renderer on screen must have a parent Canvas component.
You actually do use such approach in your project, where you have Toolbar, List etc. that have parent called Canvas and which I suppose has Canvas component attached.
What you need to do is to move instantiated gameObject to be child of other gameObject that holds Canvas component.
As there can be multiple components of Canvas type, I would suggest to add possibility to assign root for instantiated objects inside your Script.
Script:
//Drag and drop this from inspector; make sure that dragged gameObject
//has Canvas component in it or above it (in parent)
public Transform EntriesRoot;
void Start()
{
var entry = Instantiate(entry, new Vector3(x, y, 0), Quaternion.identity);
//Put parent to our EntriesRoot
//EntriesRoot will be our new parent for entry in hierarchy
entry.transform.SetParent(EntriesRoot)
}
they need to be a child of a Canvas, that displays them.
// find canvas
GameObject canvas = GameObject.Find("Canvas");
// clone your prefab
GameObject text = Instantiate(entry, new Vector3(x, y, 0), Quaternion.identity);
// set canvas as parent to the text
text.transform.SetParent(canvas.transform);
For each instance, you might have to manually enable the guiTexture.

Categories

Resources