I want to change rect transform top value of GameObject(ui pannel) dependely of Instantiated objects in it.
I found that GameObject.transform.localPosition is not useful for that. localScale stratch the elements inside of scaled GameObject.
How it can be done correctly??
public void OnMouseEnter()
{
{
ResoursesTipsNewPannel = Instantiate(ResoursesTipsPanel);
ResoursesTipsNewPannel.transform.SetParent(ResoursesPanelTransform, false);
for (i = 0; i < n; i++)
{
//RIGHT HERE I GUESS MUST BE SOME CODE THAT CHANGE TOP VALUE OF MY PANNEL
ResoursesNewTipText = Instantiate(ResoursesTipText);
ResoursesNewTipText.transform.SetParent(ResoursesTipsNewPannel.transform, false);
ResoursesNewTipText.text = "Exemple text, " + i;
}
i = 0;
}
}
Maybe some existing component that is attached to "ResoursesTipsNewPannel"(from example code) that change scale of panel automatically?
To solve I added this into code from my example:
RectTransform ChangeRectTransform;
ChangeRectTransform = ResoursesTipsNewPannel.GetComponent<RectTransform>();
ChangeRectTransform.offsetMax = new Vector2(ChangeRectTransform.offsetMax.x, ChangeRectTransform.offsetMax.y+20);
Related
I have a GameObject with a vertical layout component, and when I instantiate its children for content, the children overlap with each other which means it does not follow the vertical layout order and just on top of each other (see the photo below).
The GameObject (parent)
The child (ArticleCard) that I instantiate or cloned also has a vertical layout because I wanted it to have a responsive height depending on the length of its children's texts. You can see it in the editor of the second photo.
The ArticleCard (clone)
Both the parent and the child that I instantiate or clone have vertical layout and content size fitter set to Min Size. The rest of the information about their inspector is in the two previous photos for reference.
But when I changed or tweaked just a single component like the padding in the vertical layout or anything in their inspector or re-arranged them in the hierarchy, even though I changed it back, the vertical layout suddenly worked just fine and removed the bug.
When the GameObjects are just instantiated, the vertical layout will not work, but when I edit or change just a single component, it will snap to how it is supposed to be. In this third photo, I unchecked and checked the Child Force Expand height value in the Vertical Layout Group, which snaps to how it is supposed to be.
ArticleCard (Clone)
This is the script for Instantiating the ArticleCard object, in the final if statement which checks if the name of the GameObject is "FireFacts".
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class TryInstantiate : MonoBehaviour
{
private GameObject articleTemplate, MG_ArticleTemplate, divider, objTxt, tipTxt, g;
[SerializeField] private GameData gameData;
//private string[] articleSplit;
private void OnEnable()
{
if (gameObject.name == "Articles")
{
articleTemplate = transform.GetChild(0).gameObject;
if (!(gameData.articlesList == null))
{
for (int i = 0; i < gameData.articlesList.Count; i++)
{
//articleSplit = gameData.articlesList[i].Split("#");
g = Instantiate(articleTemplate, transform);
g.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = gameData.getArticle(gameData.articlesList[i]).title;
g.transform.GetChild(2).GetComponent<TextMeshProUGUI>().text = "Published by " +
gameData.getPublisherDetail(gameData.articlesList[i]).type + " on " +
gameData.getArticle(gameData.articlesList[i]).date;
g.SetActive(true);
}
}
// Destroy(articleTemplate);
articleTemplate.SetActive(false);
}
else if (gameObject.name == "Objectives")
{
divider = transform.GetChild(0).gameObject;
objTxt = transform.GetChild(1).gameObject;
if (!(gameData.objectivesList == null))
{
for (int i = 0; i < gameData.objectivesList.Count; i++)
{
g = Instantiate(objTxt, transform);
g.transform.GetComponent<TextMeshProUGUI>().text = gameData.objectivesList[i];
g.SetActive(true);
g = Instantiate(divider, transform);
g.SetActive(true);
}
}
// Destroy(objTxt);
// Destroy(divider);
objTxt.SetActive(false);
//divider.SetActive(false);
}
else if (gameObject.name == "Clues")
{
divider = transform.GetChild(0).gameObject;
tipTxt = transform.GetChild(1).gameObject;
if (!(gameData.cluesList == null))
{
for (int i = 0; i < gameData.cluesList.Count; i++)
{
g = Instantiate(tipTxt, transform);
g.transform.GetComponent<TextMeshProUGUI>().text = gameData.cluesList[i];
g.SetActive(true);
g = Instantiate(divider, transform);
g.SetActive(true);
}
}
tipTxt.SetActive(false);
//divider.SetActive(false);
}else if(gameObject.name == "FireFacts")
{
MG_ArticleTemplate = transform.GetChild(0).gameObject;
//Debug.Log()
if (!(gameData.mgArticlesList == null))
{
for (int i = 0; i < gameData.mgArticlesList.Count; i++)
{
g = Instantiate(MG_ArticleTemplate, transform);
g.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = gameData.getMG_Article(gameData.mgArticlesList[i]).title;
g.transform.GetChild(2).GetComponent<TextMeshProUGUI>().text = "Published by <link=publisher><color=blue><u><b><font=\"Fredoka-Bold SDF\">" +
gameData.getMG_Publisher(gameData.mgArticlesList[i]).name + "</font></b></u></color></link> on <font=\"Fredoka-Bold SDF\">" +
gameData.getMG_Article(gameData.mgArticlesList[i]).date + "</font>";
g.transform.GetChild(3).GetComponent<TextMeshProUGUI>().text = gameData.getMG_Article(gameData.mgArticlesList[i]).description;
g.SetActive(true);
}
transform.GetComponent<VerticalLayoutGroup>().padding.top = 15;
transform.GetComponent<VerticalLayoutGroup>().spacing = 15;
}
}
}
private void OnDisable()
{
foreach (Transform clone in gameObject.transform)
{
if (clone.name == "ArticleCard(Clone)" || clone.name == "ObjectiveText(Clone)" || clone.name == "CluesText(Clone)" || clone.name == "Divider(Clone)")
{
Destroy(clone.gameObject);
}
}
}
}
I solved my problem by using Coroutines. The problem is fixed, or the layout is fixed every time I tweak or edit anything at runtime. I turned a layout off and then on via script, but it did not work. This is because the code for enabling and disabling the component happens within a frame, even though it works when I disable and enable it at runtime. This is because it happens with a frame delay, so it works when doing it at runtime. So I created a coroutine to re-enable the Vertical Layout component within 0.001 seconds; with this time frame, it will not be noticeable that it has a delay.
if(gameObject.name == "FireFacts")
{
transform.GetComponent<VerticalLayoutGroup>().enabled = false;
MG_ArticleTemplate = transform.GetChild(0).gameObject;
if (!(gameData.mgArticlesList == null))
{
for (int i = 0; i < gameData.mgArticlesList.Count; i++)
{
g = Instantiate(MG_ArticleTemplate, transform);
g.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = gameData.getMG_Article(gameData.mgArticlesList[i]).title;
g.transform.GetChild(2).GetComponent<TextMeshProUGUI>().text = "Published by <link=publisher><color=blue><u><b><font=\"Fredoka-Bold SDF\">" +
gameData.getMG_Publisher(gameData.mgArticlesList[i]).name + "</font></b></u></color></link> on <font=\"Fredoka-Bold SDF\">" +
gameData.getMG_Article(gameData.mgArticlesList[i]).date + "</font>";
g.transform.GetChild(3).GetComponent<TextMeshProUGUI>().text = gameData.getMG_Article(gameData.mgArticlesList[i]).description;
g.SetActive(true);
}
}
StartCoroutine(refresh());
}
private IEnumerator refresh()
{
yield return new WaitForSeconds(0.001f);
transform.GetComponent<VerticalLayoutGroup>().enabled = true;
}
I have a code for a crafting system that checks if the inventory has the ingredients needed to craft an item and adds a button to craft it. The problem is when I want to position my button it goes way off the canvas. I have seen some people saying that it has something to do with rect transform. I've been stuck with it for over an hour. Any help is appreciated.
I have tried
removing the setparent() function,
using anchoredPosition,
using localPosition
My code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Crafting : MonoBehaviour
{
public List<recipe> recipes = new List<recipe>();
public GameObject base_item, parent;
List<GameObject> items = new List<GameObject>();
public int y = 75;
public int x = -45;
public Inv inv;
private void Start()
{
inv = GetComponent<Inv>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
checkitems();
Debug.Log("y = " + y + " x = " + (x - 40));
}
}
public void checkitems()
{
for (int i = 0; i < recipes.Count; i++)
{
recipe r = recipes[i];
for (int x = 0; x < r.ingredients.Count; x++)
{
if (!inv.hasitem(r.ingredients[x])){
return;
}
}
showitem(r.result);
}
}
public void onClick(int _slot)
{
recipe r = recipes[_slot];
for (int i = 0; i < r.ingredients.Count; i++)
{
inv.removeitem(inv.getitem(r.ingredients[i]));
}
inv.additem(inv.getFirstAvailable(), r.result, r.stack);
}
public void showitem(string name)
{
GameObject obj = Instantiate(base_item);
if (items.Count != 0)
{
if (((items.Count) % 3) != 0)
{
Debug.Log("first thing");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
else if (((items.Count + 1) % 3) == 0)
{
Debug.Log("second thing");
x = -45;
Debug.Log("x + 40");
y = y + 40;
Debug.Log(" y + 40");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
}
}else
{
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
}
}
Blue circle where it spawns. Red circle where I want it to be
Seems you are confusing a bunch of terms for being the issue of your problem. Firstly I want to address the red X over your scroll bar. Whenever this occurs, it means that your RectTransform of this UI object has been dragged from its positive vertices to negative or vice versa, causing it to almost invert. I would correct this but it is not the reason your objects are not childing correctly.
Generally, with UI objects, I would never use LocalPosition, just AnchoredPosition. LocalPosition is a field from Transform which I believe RectTransform inherits from. As RectTransforms have a lot of modifications to their position from pivots, anchors, and anchored positions, the LocalPosition will most likely need to recalculate data to properly move the object, whereas AnchoredPosition has already done these calculations.
I believe the issue with your current code is how you are using SetParent. There is a second parameter of SetParent which governs whether the object keeps the same position based in world space after being childed. As you are not passing in a new bool for this parameter, it is defaulting to true. As you want your objects to be childed to the parent but not keep their world space positions, you would want to pass in false.
In your case, as it looks as if you want to set objects in a grid-like pattern childed to this ScrollRect, I would attach a GridLayoutGroup to the Content of your scroll and child the new objects to this object. You can set the max columns of this grid and spacing to give the same layout you are attempting to achieve in code.
To summarize, I would remove all the hand placement you are doing in code with LocalPosition and AnchorPosition and just attach a GridLayoutGroup. To fix the current positioning of your objects relative to the parent, change all lines of obj.transform.SetParent(parent.transform); to obj.transform.SetParent(parent.transform, false);. If you want to keep changing position locally in code instead of a layout element, use SetParent first, and use AnchoredPosition instead of LocalPosition as the SetParent with false passed in will override the position you set.
I want to create a new photo at the point I touch it and I want it to be done with every touch so I wrote the following line inside the void Update () function.
public Canvas cv;
public Image im;
I have defined the UI elements above.
for (var i = 0; i < Input.touchCount; ++i)
{ Touch touch = Input.GetTouch(i);
if (touch.phase == TouchPhase.Began)
{
Instantiate(im, Input.GetTouch(i).position, Quaternion.identity).transform.SetParent(cv.transform, false);
}
}
And when I try it with the unity remote app, I take the picture about 3-4 fingers above the point I click. what's the problem? please help!
Vector2 scaleSomething()
{
var screenPosition = Camera.main.WorldToScreenPoint(worldPosition);
var scaler = cv.GetComponentInParent<CanvasScaler>();
var guiScale = 1.0f;
if (Mathf.Approximately(scaler.matchWidthOrHeight, 0.0f))
guiScale = scaler.referenceResolution.x / (float) Screen.width;
else if (Mathf.Approximately(scaler.matchWidthOrHeight, 1.0f))
guiScale = scaler.referenceResolution.y / (float) Screen.height;
return new Vector2(
(screenPosition.x - (Screen.width* 0.5f)) * guiScale,
(screenPosition.y - (Screen.height* 0.5f)) * guiScale);
}
try removing the set parent part after instantiating
edit what is happening is when you instantiate your objects as children they get translated/scaled in local space to the parent. Because your canvas is getting stretched (scaled up on y) your children elements are also getting scaled up on y and are out of place.
I am attempting to use a custom FlowLayoutGroup as described in the answers of this question ( also up on GitHub) in a situation where it needs to resize vertically to contain it's children.
My setup looks like this:
ScrollableRect
Panel with VerticalLayoutGroup comp (content of parent scrollrect) that should resize vertically to fit children:
Panel with FlowLayoutGroup that should resize vertically to fit children
Panel with FlowLayoutGroup (2) also must resize...
etc...
I have added a content size fitter to the FlowLayoutGroup, tweaked the layout child size controls of the vertical group, but with no success.
The user may add and remove children of the groups while the app is running and I want the UI to respond so it is not possible to set the height of everything in advance.
I have also looked in the unity source code to try and figure out how to write this into the component myself. This is looking the best bet but taking me considerable time as I'm new to Unity and C#. Hoping someone has solved a similar problem already.
Everything functions as desired/expected except for the missing behaviour of LayoutGroups resizing to fit their children vertically.
How can I do this?
After some time and a tumbleweed badge I've decided to put the time in to make a solution, hopefully someone else benefits too.
Again, this is a modified version of the work done here. Thanks for that. This component now computes it's own preferred size.
Main changes:
I stripped it back quite severely:
All horizontal overrides are emptied, I only need the horizontal wrapping behaviour
Removed some apparent hangover variables from GridLayout class
Logic to calculate child positions and in turn number of rows, preferred height is in it's own method.
Child positions are stored in an Vector2 array to separate calculation from child setting.
This fixes the problem of height of the entire component not adjusting, it also immediately responds, with the original script because of the way children rectTransforms were set then accessed the script took two 'cycles' to recognize the dimensions of a child.
This suits all my needs, I imagine it can be fairly easily reworked to handle vertical wrap too...
using UnityEngine;
using UnityEngine.UI;
[AddComponentMenu("Layout/Wrap Layout Group", 153)]
public class WrapLayoutGroup : LayoutGroup
{
[SerializeField] protected Vector2 m_Spacing = Vector2.zero;
public Vector2 spacing { get { return m_Spacing; } set { SetProperty(ref m_Spacing, value); } }
[SerializeField] protected bool m_Horizontal = true;
public bool horizontal { get { return m_Horizontal; } set { SetProperty(ref m_Horizontal, value); } }
private float availableWidth { get { return rectTransform.rect.width - padding.horizontal + spacing.x; } }
private const float MIN_HEIGHT = 80;
private int preferredRows = 1;
private float calculatedHeight = MIN_HEIGHT;
private Vector2[] childPositions = new Vector2[0];
protected WrapLayoutGroup()
{ }
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
}
#endif
public override void CalculateLayoutInputVertical()
{
calculatePositionsAndRequiredSize();
SetLayoutInputForAxis(calculatedHeight, calculatedHeight, -1, 1);
}
public override void SetLayoutHorizontal() { }
public override void SetLayoutVertical()
{
SetChildren();
}
private void SetChildren()
{
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform child = rectChildren[i];
SetChildAlongAxis(child, 0, childPositions[i].x, LayoutUtility.GetPreferredWidth(child));
SetChildAlongAxis(child, 1, childPositions[i].y, LayoutUtility.GetPreferredHeight(child));
}
}
private void calculatePositionsAndRequiredSize()
{
childPositions = new Vector2[rectChildren.Count];
Vector2 startOffset = new Vector2(
GetStartOffset(0, 0),
GetStartOffset(1, 0)
);
Vector2 currentOffset = new Vector2(
startOffset.x,
startOffset.y
);
float childHeight = 0;
float childWidth = 0;
float maxChildHeightInRow = 0;
int currentRow = 1;
for (int i = 0; i < rectChildren.Count; i++)
{
childHeight = LayoutUtility.GetPreferredHeight(rectChildren[i]);
childWidth = LayoutUtility.GetPreferredWidth(rectChildren[i]);
//check for new row start
if (currentOffset.x + spacing.x + childWidth > availableWidth && i != 0)
{
currentOffset.x = startOffset.x;
currentOffset.y += maxChildHeightInRow + spacing.y;
currentRow++;
maxChildHeightInRow = 0;
}
childPositions[i] = new Vector2(
currentOffset.x,
currentOffset.y
);
//update offset
maxChildHeightInRow = Mathf.Max(maxChildHeightInRow, childHeight);
currentOffset.x += childWidth + spacing.x;
}
//update groups preferred dimensions
preferredRows = currentRow;
calculatedHeight = currentOffset.y + maxChildHeightInRow + padding.vertical - spacing.y;
}
}
I am trying to copy the Zelda health system. The code looks really fine and works fine.
But the heart containers are placed wrong. They get instantiated below the canvas.
This is the important code, the heart containers are correct, just at the wrong position.
The calculation of x and y is correct, but on the canvas it is not.
private Transform healthBar = GameObject.FindGameObjectWithTag("HealthController").transform; // container for the heartContainers
private GameObject healthWrapperObject = Resources.Load("HealthContainer") as GameObject; // the backgroundImage and parent of the heart
private List<Image> healthContainers = new List<Image>(); // list of hearts for later usages
private int maxHealth = 6;
private int currentHealth;
private int healthPerHealthContainer = 4; // 4 lifepoints per heart
private int healthContainersPerRow = 5; // 5 hearts per row
private int healthContainerStartPositionX = 0; // Healthbar starts at 0 on x
private int healthContainerStartPositionY = 0; // Healthbar starts at 0 on y
private int healthContainerSpacingX = 10; // horizontal spacing
private int healthContainerSpacingY = -10; // vertical spacing
private void Start()
{
currentHealth = maxHealth;
InitializeHealthBar();
}
public void InitializeHealthBar()
{
int neededHealthContainers = maxHealth % healthPerHealthContainer == 0 ? maxHealth / healthPerHealthContainer : maxHealth / healthPerHealthContainer + 1; // Calculate the needed container count
int counter = 0; // counts the hearts per row
int x = healthContainerStartPositionX; // horizontal position of the heartContainer
int y = healthContainerStartPositionY; // vertical position of the heartContainer
for (int i = 0; i < neededHealthContainers; i++)
{
counter++;
if (counter >= healthContainersPerRow) // start a new line after 5 hearts per row
{
x = healthContainerStartPositionX; // move back to the left
y += healthContainerSpacingY; // go for the next line
counter = 0; // reset the counter
}
else
x += healthContainerSpacingX; // place the new container right next to the previous
Transform newHealthContainerTransform = Instantiate(healthWrapperObject, new Vector2(x, y), healthWrapperObject.transform.rotation).transform; // create the healthContainer parent / backgroundImage
newHealthContainerTransform.SetParent(healthBar); // take the container and make it a child of the healthBar
healthContainers.Add(newHealthContainerTransform.GetChild(0).GetComponent<Image>()); // get the heart of the heartContainer and add it to the heartList
}
}
I added the transform settings for the healthBar, the healthContainer / backgroundImage and the heart ("healthfill").
On all 3 elements I pressed Strg+Alt and Shift for anchoring them.
The heartcontainter should be added to the healthbar, the heart is a child of the heartcontainer and is set to stretch (it should be the same size as its parent)
Why are the UI prefab Objects instantiated below the canvas?
I assume you are getting something like this:
You fix this by pass false to the second parameter of the SetParent function. By doing this, you will make the Transform keep its local orientation rather than its global orientation.
Simply replace :
newHealthContainerTransform.SetParent(healthBar);
with:
newHealthContainerTransform.SetParent(healthBar, false)
You can also set the parent Object and make the instantiated Object's Transform keep its local orientation in the Instantiate function. The only disadvantage of this is that you now have to set the position of object in another line of code instead of the Instantiate function like before.
Transform newHealthContainerTransform = Instantiate(healthWrapperObject, healthBar, false).transform;
newHealthContainerTransform.GetComponent<RectTransform>().anchoredPosition3D = new Vector2(x, y);
When moving a UI Object you should be modifying it's RectTransform variables instead of the Transform variables.
Below are other useful variables that determines where to position the UI:
These are anchoredPosition, anchoredPosition3D, anchorMax and anchorMin which can be modified with:
yourUIObj.GetComponent<RectTransform>().anchoredPosition = ...
yourUIObj.GetComponent<RectTransform>().anchoredPosition3D = ...
yourUIObj.GetComponent<RectTransform>().anchorMax = ...
yourUIObj.GetComponent<RectTransform>().anchorMin = ...