- Home /
Confused about why my UI Text is not updating.
Hello,
I'm trying to prototype an inventory and having an issue updating my UI Text. All I want to achieve for now is to list my inventory down the side of the screen and allow the user to scroll through the inventory which will add a ">" to the item's text field.
I've managed to do most of this. When the user picks up items in the scene, the UI refreshes and draws them to the screen. Pressing Q after having both items in the inventory draws a ">" in front of the first picked up item. Pressing Q again should remove the ">" from the first item and add it to the second. However, both items get the ">". Pressing Q a third time correctly removes ">" from both items as I now have nothing selected.
I tried calling the method that I use to redraw the inventory (thus grabbing their original names and removing the ">" like I do when no items are selected) but then no ">" draw at all despite me debugging and can see that the object I am trying to effect seems correct and the .text seems to be set correctly.
I'm confused my adding this method call UpdateItemInventory() in UpdateItemSelection() makes my Text components not update at all.
Hopefully I've explained this well enough, any help would be greatly appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Inventory : MonoBehaviour {
private GameObject player;
// Temp variables for placeholder UI
public GameObject tempInventoryText;
private Transform tempInventoryUI;
public int itemSelected = 0;
// Create an array to store picked up items
private static List<GameObject> inventoryItems = new List<GameObject>();
void Start()
{
player = gameObject.GetPlayer();
// Temp UI gameObject has a tag UI_Parent. This has a vertical layout so each Text script added to it when
// the UI is updated gets put into a new row.
tempInventoryUI = GameObject.FindGameObjectWithTag("UI_Parent").transform;
}
void Update()
{
if (Input.GetButtonDown("ItemSelect"))
{
if(inventoryItems.Count > 0)
{
if (itemSelected < inventoryItems.Count)
{
itemSelected++;
UpdateItemSelection();
}
else
{
itemSelected = 0;
UpdateItemSelection();
}
}
}
}
// Method that allows gameobjects to be added to the players inventory, can be called from other scripts.
public void AddItemToInventory(GameObject itemObject)
{
inventoryItems.Add(itemObject);
print("The player has " + inventoryItems.Count + " items in their inventory!");
int i = 0;
foreach(GameObject inventoryItem in inventoryItems)
{
print("Inventory item [" + i + "] is : " + inventoryItem);
i++;
}
// Once items have been added to the inventory, update the temp UI and update selection
UpdateItemInventory();
UpdateItemSelection();
}
private void UpdateItemInventory()
{
// Delete all inventory from the screen before replacing it.
// For every transform under UI_Parent, destroy.
foreach(Transform trans in tempInventoryUI)
{
Destroy(trans.gameObject);
}
// For each item in the inventory, instanciate empty text prefab and put it under the tagged GameObject, "UI_Parent"
foreach(GameObject inventoryItem in inventoryItems)
{
GameObject tempItemUI = Instantiate(tempInventoryText, tempInventoryUI) as GameObject;
Text tempUIText = tempItemUI.GetComponentInChildren<Text>();
// Set the text of the empty Text prefab to the current for each loop item name.
tempUIText.text = inventoryItem.name;
}
}
private void UpdateItemSelection()
{
if (inventoryItems.Count > 1)
{
if (itemSelected != 0)
{
//Leaving the below call in makes no ">" draw to the screen at all. Taking away the method allows me to draw them to the screen but
// I need to try and clear the UI before adding them so that I don't get all items with a ">"
UpdateItemInventory();
Text tempItemSelected = tempInventoryUI.GetChild(itemSelected - 1).gameObject.GetComponentInChildren<Text>();
string currentItemUIText = tempItemSelected.text;
tempItemSelected.text = ">" + currentItemUIText;
}
else
{
// Redraw inventory
UpdateItemInventory();
}
}
}
}
No answers :( I'm still not sure why this was happening but I've fixed it by adding the instanced Text objects to a List so I can access them later on.
// Create a list to store prefab UI for inventoty items when they get drawn to the screen
private static List<Text> uiChildren = new List<Text>();
...
private void UpdateItemInventory()
{
// Delete all inventory from the screen before replacing it.
// For every transform under UI_Parent, destroy.
foreach (Transform trans in tempInventoryUI)
{
Destroy(trans.gameObject);
}
// clear the global list of UI prefabs and rebuild them as they are spawned
uiChildren.Clear();
// For each item in the inventory, instanciate empty text prefab and put it under the tagged GameObject, "UI_Parent"
foreach (GameObject inventoryItem in inventoryItems)
{
GameObject tempItemUI = Instantiate(tempInventoryText, tempInventoryUI) as GameObject;
Text tempUIText = tempItemUI.GetComponentInChildren<Text>();
// Set the text of the empty Text prefab to the current for each loop item name.
tempUIText.text = inventoryItem.name;
// Add the instansiated prefab into global list
uiChildren.Add(tempUIText);
}
// If item selected is greater than 0, update the UI
if(itemSelected > 0)
{
UpdateItemSelection();
}
}
...
private void UpdateItemSelection()
{
// if the UI transform has UI inventory prefabs
if (uiChildren.Count > 0)
{
// Go through each child and set the selected item to have an > sign before it
for (int i = 0; i < inventoryItems.Count; i++)
{
if((itemSelected - 1) == i)
{
uiChildren[i].text = " > " + inventoryItems[i].name;
currentItem = inventoryItems[i];
}
// Otherwise, set it to it's original name
else
{
uiChildren[i].text = inventoryItems[i].name;
}
}
}
// If no items should be selected, null out the public field
if (itemSelected == 0)
{
currentItem = null;
}
}
Answer by GrayMatterTutorials · Jan 11, 2017 at 03:03 AM
Ok, I'm going to attempt to really help you out here with all of this UI stuff.
Text tempItemSelected = tempInventoryUI.GetChild(itemSelected - 1).gameObject.GetComponentInChildren<Text>();
This is crazy difficult to follow! Not to mention, it's unnecessary. I highly recommend that you create more classes to deal with this situation more appropriately. Because it's been 3 days and no one has answered you, I spend about a half hour typing the following out for you. Please ask questions, and I hope many more can use my code as well.
Updated Inventory Class:
using UnityEngine;
using System.Collections.Generic;
using System;
public class Inventory : MonoBehaviour
{
[SerializeField]
private GameObject _player; //A reference to the Player GameObject
[SerializeField]
private RectTransform _rectTransfrom; //A reference to the Inventory UI parent transform
[SerializeField]
private GameObject _itemHolderPrefab; //A reference to the Item UI prefab
[SerializeField]
private List<InventoryItemUI> _items; //A reference to the Item UIs (Dynamic Generic Collection) in the inventory
private int _selectedIndex; //Index of the selected item within _items
/// <summary>
/// I use the Awake method to get references for all of my objects.
/// </summary>
private void Awake()
{
//Allow the Player to be dragged on in the Inspector, but if it's null, get the player a different way.
//Note: This gives me an error, so just remove the comments below.
//if (_player == null)
//_player = gameObject.GetPlayer();
//Allow the Rect Transform (UI Parent Transform) to be dragged on in the Inspector, but if it's null, assume it's on this object and get the component.
if (_rectTransfrom == null)
_rectTransfrom = GetComponent<RectTransform>();
if (_itemHolderPrefab == null)
{
_itemHolderPrefab = new GameObject();
_itemHolderPrefab.AddComponent<InventoryItemUI>();
}
_items = new List<InventoryItemUI>();
_selectedIndex = -1;
}
/// <summary>
/// Just checking if we should increment the selected index. If we should, do so, then update.
/// </summary>
private void Update()
{
if (Input.GetButtonDown("ItemSelect"))
{
if (_items.Count > 0)
{
UpdateSelection();
}
}
}
/// <summary>
/// Instantiates a new itemHodler from the prefab.
/// Then we create a reference to the ItemUI.
/// Then we set the ItemUI's item to the passed in item.
/// Then we add that ItemUI to the list of other UIs.
/// </summary>
/// <param name="item">The item data for the new Item Holder</param>
public void AddItem(InventoryItem item)
{
GameObject itemHolder = Instantiate(_itemHolderPrefab);
InventoryItemUI itemUI = itemHolder.GetComponent<InventoryItemUI>();
itemUI.source = item;
_items.Add(itemUI);
}
/// <summary>
/// Use FindItem to get the Index that this item is associated with.
/// </summary>
/// <param name="item"></param>
public void RemoveItem(InventoryItem item)
{
RemoveItemAt(FindItem(item));
}
/// <summary>
/// If the index is within valid range, delete the item.
/// We first get a reference to the Game Object of the ItemUI.
/// We then remove the ItemUI.
/// Lastly, delete the Game Object.
/// </summary>
/// <param name="index"></param>
public void RemoveItemAt(int index)
{
if (index > 0 && index < _items.Count)
{
GameObject itemHolder = _items[index].gameObject;
_items.RemoveAt(index);
Destroy(itemHolder);
}
}
/// <summary>
/// Used to find the index of the ItemUI that this Item is associated with.
/// Returns -1 if it doesn't exist.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
private int FindItem(InventoryItem item)
{
for (int i = 0; i < _items.Count; i++)
if (_items[i].source == item)
return i;
return -1;
}
/// <summary>
/// This will deselect an item if the selected index is within range.
/// Then it will add to the the selection index.
/// Lastly it selects the the new ItemUI.
/// </summary>
private void UpdateSelection()
{
if (_selectedIndex >= 0 && _selectedIndex < _items.Count)
_items[_selectedIndex].Deselect();
if (_selectedIndex < _items.Count)
_selectedIndex++;
else
_selectedIndex = 0;
_items[_selectedIndex].Select();
}
}
InventoryItemUI Class:
using UnityEngine;
using UnityEngine.UI;
public class InventoryItemUI : MonoBehaviour
{
[SerializeField]
private Text _displayText;
private InventoryItem _data;
public InventoryItem source { get { return _data; } set { _data = value; } }
public string displayText { get { return _displayText.text; } set { _displayText.text = value; } }
/// <summary>
/// This will add a carrot to the source name on the Text object.
/// </summary>
public void Select()
{
displayText = "> " + source.name;
}
/// <summary>
/// This will set the Text object's text to the source name.
/// </summary>
public void Deselect()
{
displayText = source.name;
}
}
InventoryItem Class:
public class InventoryItem
{
public string name = "New Item";
//This is just a class to hold all of your data and pass it around without being coupled with specific UI functionality.
}
I hope this helps out a lot! Good luck!
Everyone note, I did forget something. When adding an Item, a GameObject gets created. Set this GameObject's transform.parent to the _rectTransform. If not that, then the _rectTransform.transform or something like that. I don't remember what the parenting for UI stuff is, but do that!
Wow, thanks for that! It's great to see a pro's code against my own. There's lots of stuff from this I would like to implement into $$anonymous$$e. Your code is definitely more readable and manageable than $$anonymous$$e! I'm going to slowly try and take from it what I can.
However, my original question was about why my text on screen wasn't changing when I could clearly print out to the console the correct text. I added a list to store them in like you did rather than trying to read the child object like you pointed out at the top of your answer which works fine, but there was still the niggle of not understanding why!
Thanks.
Your answer
Follow this Question
Related Questions
Graphic problem with UI Text 2 Answers
New UI: change text 2 Answers
UGUI Find tapped character in textfield? 1 Answer