Vertical Layout not working properly when children are instantiated - Unity - c#

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;
}

Related

Ui button doesn't move correctly

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.

Unity, how to change only "top" of GameObject rect transform

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);

How to make custom Unity LayoutGroup expand to fit contents

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;
}
}

How to detect overlap from randomly generated prefabs [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I have a working script which randomly generates:
PosX, PosY, ScaleX and ScaleY
I have my randomly generated prefabs into parallel arrays. From that randomly resized and positioned cubes are generated. However since it is randomly generated how can i detect there is a overlap by code ? And when there is an overlap how can i delete it ?
I need help writing the code. Ive tried using raycasting and OnTriggerEnter but im too confused as to how to do it. Ive also seen using the
Physics.CheckCapsule
Physics.CapsuleCastAll
Physics.CheckSphere
Bounds.Intersect
But i don't know how to do it.
I don't know how to use Physics.OverlapSphere on arrays.
What am i supposed to do.
void Detect_Collision_Two(int i) {
bool isOverLapped = false;
Collider[] platformsInRange = Physics.OverlapSphere(platformPrefabPosition[i], scaleX[i]/2);
foreach(Collider col in platformsInRange) {
if(col.gameObject == platformPrefab) {
continue;
}
if
}
}
Edit
Attempt
These are the attempt i have tried to do using the math methods. Which checks the center position and takes into consideration of the width and height of the platform. Then using that information to compare with other platforms.
The second method is using the OverlapSphere , but i have no idea how to implement that.
Please help
void Platform_Position_Scale_Generator(int i) {
posX[i] = Random.Range(minPosRange, maxPosRange + 1);
posY[i] = Random.Range(minPosRange, maxPosRange + 1);
posZ[i] = 0;
scaleX[i] = Random.Range(minScaleRange, maxScaleRange + 1);
scaleY[i] = 1;
scaleZ[i] = 1;
}
void Platform_Generator(int i) {
platformPrefabPosition[i].x = posX[i];
platformPrefabPosition[i].y = posY[i];
platformPrefabPosition[i].z = posZ[i];
Instantiate(platformPrefab, platformPrefabPosition[i], Quaternion.identity);
platformPrefab.transform.localScale = new Vector3(scaleX[i], 1, 1);
}
// Error with this
void Detect_Collision(int i) {
for(int f = 0; f < i; f++) {
for(int s = f + 1; s < i; s++) {
bool xOverlap = (posX[s] > posX[f] && posX[s] < posX[f] + scaleX[i]) || (posX[f] > posX[s] && posX[f] < posX[s] + scaleX[i]);
bool yOverlap = (posY[s] > posY[f] && posY[s] < posY[f] + scaleY[i]) || (posY[f] > posY[s] && posY[f] < posY[s] + scaleY[i]);
if(xOverlap && yOverlap) {
Debug.Log("xOverlap: " + xOverlap + " yOverlap: " + yOverlap);
}
else {
//Debug.Log("xOverlap: " + xOverlap + " yOverlap: " + yOverlap);
}
}
}
}
void Detect_Collision_Two(int i) {
bool isOverLapped = false;
Collider[] platformsInRange = Physics.OverlapSphere(platformPrefabPosition[i], scaleX[i]/2);
foreach(Collider col in platformsInRange) {
if(col.gameObject == platformPrefab) {
continue;
}
if
}
}
you need to make an overlapSphere with your object position center as its center and bounds extent magnitude as its radius and check for all the colliders in this boundry if thier bounds intersect with new object bounds
bool isOverlapped = false;
Bounds bounds = renderer.bounds;
Collider[] cols = Physics.OverlapSphere(transform.position, bounds.extents.magnitude);
foreach(Collider col in cols) {
if (col.gameObject == gameObject) {
continue;
}
if (bounds.Intersects(col.gameObject.renderer.bounds)) {
isOverlapped = true;
break;
}
}
after this you can use the isOverlapped bool to destroy your object like this
if(isOverlapped)
Destroy (gameObject);

How to make monster move left then right continuously C# XNA

i'm making my first game on XNA for my class. I'm trying to make the monsters move left and right automatically. I have total 4 monsters for now. I'm trying to get them move left then right within the screen.
//Monster movements
for (int i = 0; i < numOfMonster; i++)
{
if (destinationMonster[i].X >= screenWidth - 60)
{
while(destinationMonster[i].X != -10)
moveLeft = true;
}
else
{
moveRight = true;
}
if (moveLeft)
{
int temp = destinationMonster[i].X;
temp = destinationMonster[i].X - monsterSpeed;
//This prevents the object passing the screen boundary
if (!(temp < -10))
{
destinationMonster[i].X = temp;
}
moveLeft = false;
}
if (moveRight)
{
int temp = destinationMonster[i].X;
temp = destinationMonster[i].X + monsterSpeed;
//This prevents the object passing the screen boundary
if (!(temp > screenWidth - 50))
{
destinationMonster[i].X = temp;
}
moveRight = false;
}
}
Your first problem is your while statement, once you enter it you are not going to exit because you are not changing your X value. If it were me I would have a bool array variable corresponding to each of your monsters. I would also change your conditional to trigger the change of the boolean value once the monster has reached the extents at either end. Something like this.
if (destinationMonster[i].X >= screenWidth - 60)
{
moveRight[i] = false ;
}
else if (destinationMonster[i].X <= -10)
{
moveRight[i] = true ;
}

Categories