Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

New Unity UI List that is dynamically generated and updated.

Discussion in 'UGUI & TextMesh Pro' started by KrayonZA, May 11, 2015.

  1. KrayonZA

    KrayonZA

    Joined:
    Mar 5, 2013
    Posts:
    52
    Hi

    I'm switching from the legacy GUI system to the new UI system and i"m having trouble creating a list which is dynamically created and updated every 5 seconds.

    I just cant seem to wrap my head around how to do this efficiently the code below is working but obviously i'm getting too many items instantiated as each frame updates.

    Any advise on a better way to do this would be appreciated.


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. //-----------------------------------------------------------------------------------------------------
    7. public class GUIManager : MonoBehaviour
    8. {
    9.     public GameObject PlayerListItem;
    10.     public GameObject ScoresPanel;
    11.     public GameManager GM;
    12.  
    13.     //-----------------------------------------------------------------------------------------------------
    14.     public void Update()
    15.     {
    16.  
    17.         if (CurrentWindow == 5) // Score window open
    18.         {
    19.  
    20.             if (GM.GameMode == "Team Death Match")
    21.             {
    22.  
    23.                 StartCoroutine(PopulateTeam1());
    24.             }
    25.  
    26.             if (GM.GameMode == "Death Match")
    27.             {
    28.  
    29.             }
    30.  
    31.             if (GM.GameMode == "Cooperative")
    32.             {
    33.  
    34.             }
    35.  
    36.         }
    37.  
    38.     }
    39.     //-----------------------------------------------------------------------------------------------------
    40.     public IEnumerator PopulateTeam1()
    41.     {
    42.  
    43.         Vector3 localPos = new Vector3(0, 0, 0);
    44.         List<GameObject> playerListItems = new List<GameObject>();
    45.  
    46.         // Destroy the items and clear the temporary list
    47.         foreach (var listitem in playerListItems)
    48.         {
    49.             Destroy(listitem);
    50.         }
    51.         playerListItems.Clear();
    52.  
    53.         // Regenerate the player list
    54.         foreach (var player in GM.AllPlayers)
    55.         {
    56.  
    57.             if ((string)player.customProperties["TeamName"] == GM.Team1.TeamName)
    58.             {
    59.  
    60.                 GameObject playerList = Instantiate(PlayerListItem);
    61.                 playerListItems.Add(playerList);
    62.                 // Rename the list item to the player name
    63.                 playerList.gameObject.name = player.name;
    64.                 playerList.transform.SetParent(ScoresPanel.transform);
    65.                 playerList.transform.position = ScoresPanel.transform.position + localPos;
    66.                 var playerName = playerList.transform.FindChild("PlayerName").GetComponent<Text>();
    67.                 var playerKills = playerList.transform.FindChild("Kills").GetComponent<Text>();
    68.                 var playerDeaths = playerList.transform.FindChild("Deaths").GetComponent<Text>();
    69.                 var playerPing = playerList.transform.FindChild("Ping").GetComponent<Text>();
    70.                 //update offset for next loop cycle
    71.                 localPos = playerList.transform.position + new Vector3(20, 0, 0);
    72.  
    73.                 playerName.color = PhotonNetwork.player.name == player.name ? Color.blue : Color.white;
    74.                 playerName.text = player.name;
    75.                 playerKills.text = "Kills: " + ((int)player.customProperties["Kills"]);
    76.                 playerDeaths.text = "Deaths: " + ((int)player.customProperties["Deaths"]);
    77.                 playerList.SetActive(true);
    78.                 if (player.customProperties["Ping"] != null)
    79.                 {
    80.                     playerPing.text = "Ping: " + ((int)player.customProperties["Ping"]);
    81.                 }
    82.  
    83.                 playerList.SetActive(true); // Activate the list item
    84.             }
    85.  
    86.         }
    87.  
    88.         yield return new WaitForSeconds(5.0f);
    89.  
    90.     }
    91.  
    92. }
    93.  
     
  2. OldManAnimal

    OldManAnimal

    Joined:
    Jul 10, 2014
    Posts:
    45
    Well, Update runs every frame so you're starting a new coroutine that does the same thing every single frame. Although it sounds like you know that. The coroutine itself isn't looping so your 5 second yield doesn't do what you intend. What you have to do is make it so that you only start the coroutine once in a Start or OnEnable or something similar. So wherever it is that you're opening/closing the ScoresPanel, you could start/stop the coroutine from there. Then have the coroutine contain an infinite while loop that contains your 5 second yield.

    Also your foreach loop to Destroy stuff doesn't actually destroy anything since your playerListItems list is freshly created and contains nothing when you are doing the Destroys. You would have to increase the scope of playerListItems so it's a class variable like the ScoresPanel.

    But to go further, you don't want to be destroying and reinstantiating every 5 seconds. Best would be do the instantiating and/or destroying when you first show the ScoresPanel. Then each time through the while loop you just need to update the text components you want to update, kills, deaths, ping, etc. Although you probably would need to have to account for players joining/leaving somehow, but that would probably only require checking the size of the playerListItems list against the amount of child transforms in the ScoresPanel and if they don't match you can instantiate/destroy as needed.
     
    Kiwasi and KrayonZA like this.
  3. KrayonZA

    KrayonZA

    Joined:
    Mar 5, 2013
    Posts:
    52
    Hi Animal2

    Thanks for your advice you managed to point me in the right direction.

    I was thinking about it like a GUI and not gameobjects as soon as i started thinking about it like gameobjects it clicked for me.

    I ended up with the below, I trimmed out some unneeded stuff which isn't relevant to the problem i was having.

    Still need to do some more testing and fix a couple of layout issues like the offset not being reset

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using UnityEngine;
    6. using UnityEngine.UI;
    7.  
    8. public class GUIManager : MonoBehaviour
    9. {
    10.  
    11.     private bool _scoresGenerated;
    12.     public GameObject ScoresPanel;
    13.     public GameObject PlayerItemPrefab;
    14.     public List<GameObject> TempPlayerList = new List<GameObject>();
    15.     public GameManager GM;
    16.  
    17. public void Update()
    18. {
    19. if (CurrentWindow == 5) // Scores Display
    20.         {
    21.  
    22.             if (GM.GameMode == "Team Death Match")
    23.             {
    24.                 if (!_scoresGenerated)
    25.                 {
    26.                     Vector3 localPos = new Vector3(0, 0, 0);
    27.  
    28.                     foreach (var player in GM.AllPlayers.Where(player => (string) player.customProperties["TeamName"] == GM.Team1.TeamName))
    29.                     {
    30.                         GameObject playerItem = Instantiate(PlayerItemPrefab);
    31.                         playerItem.gameObject.name = player.name;
    32.                         playerItem.transform.SetParent(ScoresPanel.transform);
    33.                         playerItem.transform.position = ScoresPanel.transform.position + localPos;
    34.                         playerItem.transform.FindChild("PlayerName").GetComponent<Text>().color = PhotonNetwork.player.name == player.name ? Color.blue : Color.white;
    35.                         playerItem.transform.FindChild("PlayerName").GetComponent<Text>().text = player.name;
    36.                         playerItem.transform.FindChild("Kills").GetComponent<Text>().text = "Kills: " +((int)player.customProperties["Kills"]);
    37.                         playerItem.transform.FindChild("Deaths").GetComponent<Text>().text = "Deaths: " +((int)player.customProperties["Deaths"]);
    38.  
    39.                         if (player.customProperties["Ping"] != null)
    40.                         {
    41.                             playerItem.transform.FindChild("Ping").GetComponent<Text>().text = "Ping: " +((int)player.customProperties["Ping"]);
    42.                         }
    43.                         localPos = playerItem.transform.position + new Vector3(20, 0, 0); //update offset for next loop cycle
    44.                         TempPlayerList.Add(playerItem); // Add to temporary array
    45.                         playerItem.SetActive(true); // Activate the list item
    46.                     }
    47.                     _scoresGenerated = true;
    48.                 }
    49.                 else
    50.                 {
    51.  
    52.                     StartCoroutine(UpdateTeam1Scores());
    53.  
    54.                     if (TempPlayerList.Count != GM.AllPlayers.Count)
    55.                     {
    56.                         ClearTeam1Scores();
    57.                         _scoresGenerated = false;
    58.                     }
    59.  
    60.                 }
    61.              
    62.             }
    63.  
    64.             if (GM.GameMode == "Death Match")
    65.             {
    66.  
    67.             }
    68.  
    69.             if (GM.GameMode == "Cooperative")
    70.             {
    71.  
    72.             }
    73.  
    74. }
    75.  
    76. //-----------------------------------------------------------------------------------------------------
    77.     public void ClearTeam1Scores()
    78.     {
    79.         // Destroy the items and clear the temporary list
    80.         foreach (var item in TempPlayerList)
    81.         {
    82.             Destroy(item);
    83.         }
    84.         TempPlayerList.Clear();
    85.  
    86.     }
    87.  
    88.     //-----------------------------------------------------------------------------------------------------
    89.     public IEnumerator UpdateTeam1Scores()
    90.     {
    91.         foreach (var player in GM.AllPlayers)
    92.         {
    93.  
    94.             foreach (var tempPlayerListItem in TempPlayerList)
    95.             {
    96.                 if (player.name == tempPlayerListItem.gameObject.name)
    97.                 {
    98.                     tempPlayerListItem.transform.FindChild("PlayerName").GetComponent<Text>().color = PhotonNetwork.player.name == player.name ? Color.blue : Color.white;
    99.                     tempPlayerListItem.transform.FindChild("PlayerName").GetComponent<Text>().text = player.name;
    100.                     tempPlayerListItem.transform.FindChild("Kills").GetComponent<Text>().text = "Kills: " +((int)player.customProperties["Kills"]);
    101.                     tempPlayerListItem.transform.FindChild("Deaths").GetComponent<Text>().text = "Deaths: " +((int)player.customProperties["Deaths"]);
    102.  
    103.                     if (player.customProperties["Ping"] != null)
    104.                     {
    105.                         tempPlayerListItem.transform.FindChild("Ping").GetComponent<Text>().text = "Ping: " +((int)player.customProperties                                                                                                       ["Ping"]);
    106.                     }
    107.                 }
    108.             }
    109.  
    110.         }
    111.         yield return new WaitForSeconds(5.0f);
    112.  
    113.     }
    114.  
    115. }
     
  4. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,685
    My first question is why it would need to be updated every 5 seconds? Why not have it's update linked to something (an event perhaps) that identifies the view needs to be updated.

    A better implementation may implement databinding, so that the view is only recalculated when the data changes (or an update property is updated), thus reducing the work needed to refresh it.
    @nventimiglia 's MVVM library for Unity springs to mind as a good example of that.
    https://github.com/NVentimiglia/Unity3d-Databinding-Mvvm-Mvc
     
    nventimiglia likes this.