Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Adding multiple listeners with for not working

Discussion in 'Scripting' started by pantang, Jan 21, 2020.

  1. pantang

    pantang

    Joined:
    Sep 1, 2016
    Posts:
    219
    "error CS0234: The type or namespace name 'Events' does not exist in the namespace 'UnityEditor' (are you missing an assembly reference?"

    I keep getting this error when I go to build my project and it complains about me using UnityEditor.Events

    but I need it so I can use UnityEventTools.AddIntPersistentListener any ideas on what is causing the problem? It all works fine in the editor.
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I would hazard a guess that many functions designed to be used in editing a project are probably not supported in a standalone build. Why do you need to add persistent listeners at runtime?
     
  3. pantang

    pantang

    Joined:
    Sep 1, 2016
    Posts:
    219
    Yeah I figured it was because I was using unityeditor, but where else can I get the persistent buttons?

    I generate some of my menus and inventory screens dynamically and the temporary listeners just weren't cutting it with my methods.

    How can I add a persistent listener at runtime?
     
  4. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,297
    You can use UnityEvent.AddListener to add a non-persistent listener at runtime.

    And you can use a lambda expression to invoke a method with an int argument.

    Code (CSharp):
    1. myEvent.AddListener(()=>MyMethod(5));
     
  5. pantang

    pantang

    Joined:
    Sep 1, 2016
    Posts:
    219
    Thanks for the advice but how would that convert to my case? I am currently using the bellow code in my for loop which works in the editor

    UnityEventTools.AddIntPersistentListener(product.GetComponentInChildren<Slider>().onValueChanged, gameObject.GetComponent<ItemStorage>().UpdateInventory, i);


    I get a "Delegate 'UnityAction<float>' does not take 0 arguments " error, any idea how to convert my above line into the correct method?


    product.GetComponentInChildren<Slider>().onValueChanged.AddListener(() => UpdateInventory(i));


    Anyway if your interested here is the complete code that works in the editor but not the player, might help understand what I am trying to do as when I previously tried to add normal listeners they all ended up with array max length as their values :( Sorry about the mess.

    Code (CSharp):
    1. using UnityEngine;
    2. using TMPro;
    3. using UnityEngine.UI;
    4. using UnityEditor.Events;
    5.  
    6. namespace UnityStandardAssets.Characters.ThirdPerson {
    7.     public class ItemStorage : MonoBehaviour
    8.     {
    9.         //Options for the container
    10.         public int storageCapacity;
    11.  
    12.         public GameObject storageOwner;
    13.         public bool storageLocked;
    14.  
    15.         public string storageName;
    16.         public string storagePrompt;
    17.  
    18.         //UI references for the objects
    19.         public float productSpacing;
    20.         public GameObject storageUIPrefab;
    21.         public GameObject inventoryUIPrefab;
    22.         public RectTransform storageScrollContent;
    23.         public RectTransform inventoryScrollContent;
    24.  
    25.         //hold the quantity to transfere
    26.         int[] storageTransfere;
    27.         int[] inventoryTransfere;
    28.  
    29.         //arrays for our storage
    30.         GameObject[] storageArray;
    31.         Slider[] storageQuantity;
    32.         TextMeshProUGUI[] storageQuantityText;
    33.  
    34.         //arrays for our inventory
    35.         GameObject[] inventoryArray;
    36.         Slider[] inventoryQuantity;
    37.         TextMeshProUGUI[] inventoryQuantityText;
    38.  
    39.         //the object we are trading with
    40.         public GameObject playerHolder;
    41.  
    42.         private void OnTriggerEnter(Collider other)
    43.         {
    44.             //set the playerUI
    45.             if (other.gameObject.CompareTag("Player") && other.gameObject.GetComponent<PlayerData>().menuActive == false)
    46.             {
    47.                 other.gameObject.GetComponent<PlayerUI>().uiOneLineText.text = storagePrompt + storageName;
    48.                 other.gameObject.GetComponent<PlayerUI>().uiOneLine.SetActive(true);
    49.             }
    50.         }
    51.  
    52.         private void OnTriggerStay(Collider other)
    53.         {
    54.             //should we open the menu
    55.             if (Input.GetKeyDown(KeyCode.F) && other.gameObject.CompareTag("Player") && other.gameObject.GetComponent<PlayerData>().menuActive == false)
    56.             {
    57.                 //disable player control
    58.                 other.gameObject.GetComponent<TopDownControl>().enabled = false;
    59.  
    60.                 //enable the menu and tell other npcs our menu is active
    61.                 other.gameObject.GetComponent<PlayerUI>().storageDisplay.SetActive(true);
    62.                 other.gameObject.GetComponent<PlayerUI>().uiOneLine.SetActive(false);
    63.                 other.gameObject.GetComponent<PlayerData>().menuActive = true;
    64.  
    65.                 //set the objecy we are dealing with
    66.                 playerHolder = other.gameObject;
    67.  
    68.                 //open the store
    69.                 OpenCrate();
    70.             }
    71.         }
    72.  
    73.         private void OnTriggerExit(Collider other)
    74.         {
    75.             if (other.gameObject.CompareTag("Player") && other.gameObject.GetComponent<PlayerData>().menuActive == true)
    76.             {
    77.                 CloseStore();
    78.                 other.gameObject.GetComponent<PlayerUI>().uiOneLine.SetActive(false);
    79.             }
    80.             else if (other.gameObject.CompareTag("Player"))
    81.             {
    82.                 other.gameObject.GetComponent<PlayerUI>().uiOneLine.SetActive(false);
    83.             }
    84.         }
    85.  
    86.         public void SetStorage()
    87.         {
    88.             //get position from our prefrab instance
    89.             float posY = storageUIPrefab.GetComponent<RectTransform>().localPosition.y;
    90.             float posX = storageUIPrefab.GetComponent<RectTransform>().localPosition.x;
    91.             float posZ = storageUIPrefab.GetComponent<RectTransform>().localPosition.z;
    92.             //posY = (productSpacing / 2);
    93.  
    94.             //match our arrays to inventory size
    95.             System.Array.Resize(ref storageQuantity, gameObject.GetComponent<Inventory>().itemName.Length);
    96.             System.Array.Resize(ref storageQuantityText, gameObject.GetComponent<Inventory>().itemName.Length);
    97.             System.Array.Resize(ref storageArray, gameObject.GetComponent<Inventory>().itemName.Length);
    98.  
    99.             //create the product buttons
    100.             for (int i = 0; i < gameObject.GetComponent<Inventory>().itemName.Length; i++)
    101.             {
    102.                 if (gameObject.GetComponent<Inventory>().quantity[i] > 0)
    103.                 {
    104.                     //create new product if we have some and set the canvas active and add to array for detruction & editing later
    105.                     GameObject product = Instantiate(storageUIPrefab);
    106.                     product.SetActive(true);
    107.                     product.transform.SetParent(storageUIPrefab.transform.parent);
    108.                     storageArray[i] = product;
    109.  
    110.                     //set position and scale and increment for next canvas
    111.                     product.GetComponent<RectTransform>().localPosition = storageUIPrefab.GetComponent<RectTransform>().localPosition;
    112.                     product.GetComponent<RectTransform>().localPosition = new Vector3(posX, posY, posZ);
    113.                     product.GetComponent<RectTransform>().localScale = storageUIPrefab.GetComponent<RectTransform>().localScale;
    114.                     posY = posY - productSpacing;
    115.  
    116.                     //set the text fields for the contact
    117.                     TextMeshProUGUI[] text = product.GetComponentsInChildren<TextMeshProUGUI>();
    118.                     text[0].text = gameObject.GetComponent<Inventory>().itemName[i];
    119.                     text[1].text = gameObject.GetComponent<Inventory>().quantity[i].ToString();
    120.  
    121.                     //set the sliders max value
    122.                     product.GetComponentInChildren<Slider>().maxValue = gameObject.GetComponent<Inventory>().quantity[i];
    123.  
    124.                     //create listenering for our slider
    125.                     UnityEventTools.AddIntPersistentListener(product.GetComponentInChildren<Slider>().onValueChanged, gameObject.GetComponent<ItemStorage>().UpdateStorage, i);
    126.                    
    127.                     //add our items for later to the array
    128.                     storageQuantityText[i] = text[1];
    129.                     storageQuantity[i] = product.GetComponentInChildren<Slider>();
    130.                 }
    131.             }
    132.             //set the height of the scroll canvas
    133.             storageScrollContent.sizeDelta = new Vector2(5, Mathf.Abs(posY));
    134.         }
    135.  
    136.         public void SetInventory()
    137.         {
    138.             //get position from our prefrab instance
    139.             float posY = inventoryUIPrefab.GetComponent<RectTransform>().localPosition.y;
    140.             float posX = inventoryUIPrefab.GetComponent<RectTransform>().localPosition.x;
    141.             float posZ = inventoryUIPrefab.GetComponent<RectTransform>().localPosition.z;
    142.  
    143.             //match our arrays to inventory size
    144.             System.Array.Resize(ref inventoryQuantity, playerHolder.gameObject.GetComponent<Inventory>().itemName.Length);
    145.             System.Array.Resize(ref inventoryQuantityText, playerHolder.gameObject.GetComponent<Inventory>().itemName.Length);
    146.             System.Array.Resize(ref inventoryArray, playerHolder.gameObject.GetComponent<Inventory>().itemName.Length);
    147.  
    148.             //create the product buttons
    149.             for (int i = 0; i < playerHolder.gameObject.GetComponent<Inventory>().itemName.Length; i++)
    150.             {
    151.                 if (playerHolder.gameObject.GetComponent<Inventory>().quantity[i] > 0)
    152.                 {
    153.                     //create new product if we have some and set the canvas active and add to array for detruction & editing later
    154.                     GameObject product = Instantiate(inventoryUIPrefab);
    155.                     product.SetActive(true);
    156.                     product.transform.SetParent(inventoryUIPrefab.transform.parent);
    157.                     inventoryArray[i] = product;
    158.  
    159.                     //set position and scale and increment for next canvas
    160.                     product.GetComponent<RectTransform>().localPosition = storageUIPrefab.GetComponent<RectTransform>().localPosition;
    161.                     product.GetComponent<RectTransform>().localPosition = new Vector3(posX, posY, posZ);
    162.                     product.GetComponent<RectTransform>().localScale = storageUIPrefab.GetComponent<RectTransform>().localScale;
    163.                     posY = posY - productSpacing;
    164.  
    165.                     //set the text fields for the contact
    166.                     TextMeshProUGUI[] text = product.GetComponentsInChildren<TextMeshProUGUI>();
    167.                     text[0].text = playerHolder.gameObject.GetComponent<Inventory>().itemName[i];
    168.                     text[1].text = playerHolder.gameObject.GetComponent<Inventory>().quantity[i].ToString();
    169.  
    170.                     //set the sliders max value
    171.                     product.GetComponentInChildren<Slider>().maxValue = playerHolder.gameObject.GetComponent<Inventory>().quantity[i];
    172.  
    173.                     //create listenering for our slider
    174.                     UnityEventTools.AddIntPersistentListener(product.GetComponentInChildren<Slider>().onValueChanged, gameObject.GetComponent<ItemStorage>().UpdateInventory, i);
    175.                     //product.GetComponentInChildren<Slider>().onValueChanged.AddListener(() => UpdateInventory(i));
    176.  
    177.                     //add our items for later to the array
    178.                     inventoryQuantityText[i] = text[1];
    179.                     inventoryQuantity[i] = product.GetComponentInChildren<Slider>();
    180.                 }
    181.             }
    182.             //set the height of the scroll canvas
    183.             inventoryScrollContent.sizeDelta = new Vector2(5, Mathf.Abs(posY));
    184.         }
    185.  
    186.         public void CloseStore()
    187.         {
    188.             //delete any menu object if it was active
    189.             if (playerHolder.gameObject.GetComponent<PlayerData>().menuActive == true)
    190.             {
    191.                 DestroyItems();
    192.             }
    193.  
    194.             //delete the menu buttons
    195.             Button[] button = playerHolder.GetComponent<PlayerUI>().storageButtons.GetComponentsInChildren<Button>();
    196.             UnityEventTools.RemovePersistentListener(button[0].onClick, gameObject.GetComponent<ItemStorage>().CloseStore);
    197.             UnityEventTools.RemovePersistentListener(button[1].onClick, gameObject.GetComponent<ItemStorage>().TakeInventory);
    198.             UnityEventTools.RemovePersistentListener(button[2].onClick, gameObject.GetComponent<ItemStorage>().StoreInventory);
    199.  
    200.             //disable the UI and enable the players control
    201.             playerHolder.gameObject.GetComponent<PlayerUI>().uiOneLine.SetActive(false);
    202.             playerHolder.gameObject.GetComponent<PlayerUI>().storageDisplay.SetActive(false);
    203.             playerHolder.gameObject.GetComponent<PlayerData>().menuActive = false;
    204.             playerHolder.gameObject.GetComponent<TopDownControl>().enabled = true;
    205.             playerHolder = null;
    206.         }
    207.  
    208.         public void DestroyItems()
    209.         {
    210.             //destroy the obejects we created for the menu
    211.             for (int i = 0; i < storageArray.Length; i++)
    212.             {
    213.                 Destroy(storageArray[i]);
    214.             }
    215.             for (int i = 0; i < inventoryArray.Length; i++)
    216.             {
    217.                 Destroy(inventoryArray[i]);
    218.             }
    219.             //reset the size of the arrays as we arent using them
    220.             System.Array.Resize(ref storageQuantity, 0);
    221.             System.Array.Resize(ref storageQuantityText, 0);
    222.             System.Array.Resize(ref storageArray, 0);
    223.             System.Array.Resize(ref inventoryQuantity, 0);
    224.             System.Array.Resize(ref inventoryQuantityText, 0);
    225.             System.Array.Resize(ref inventoryArray, 0);
    226.         }
    227.  
    228.         public void UpdateStorage(int i)  //called when slider moves update quantity to display and store the ammount to transfere
    229.         {
    230.             storageQuantityText[i].text = "Quantity <b>" + storageQuantity[i].value.ToString() + "/" + gameObject.GetComponent<Inventory>().quantity[i] + "</b>";
    231.             storageTransfere[i] = storageQuantity[i].value.ToInt();
    232.         }
    233.         public void UpdateInventory(int i)  //called when slider moves update quantity to display and store the ammount to transfere
    234.         {
    235.             inventoryQuantityText[i].text = "Quantity <b>" + inventoryQuantity[i].value.ToString() + "/" + playerHolder.gameObject.GetComponent<Inventory>().quantity[i] + "</b>";
    236.             inventoryTransfere[i] = inventoryQuantity[i].value.ToInt();
    237.         }
    238.         public void StoreInventory()  //Stores our goods
    239.         {
    240.             for (int i = 0; i < storageTransfere.Length; i++)
    241.             {
    242.                 //check that the player actually has enough to store and then transfere the goods
    243.                 if (playerHolder.gameObject.GetComponent<Inventory>().quantity[i] >= inventoryTransfere[i])
    244.                 {
    245.                     playerHolder.gameObject.GetComponent<Inventory>().quantity[i] = playerHolder.gameObject.GetComponent<Inventory>().quantity[i] - inventoryTransfere[i];
    246.                     gameObject.GetComponent<Inventory>().quantity[i] = gameObject.GetComponent<Inventory>().quantity[i] + inventoryTransfere[i];
    247.                 }
    248.  
    249.             }
    250.             //refresh the store since items have changed
    251.             DestroyItems();
    252.             SetStorage();
    253.             SetInventory();
    254.         }
    255.         public void TakeInventory()  //Takes our goods
    256.         {
    257.             for (int i = 0; i < inventoryTransfere.Length; i++)
    258.             {
    259.                 //check that the container actually has enough to take and then transfere the goods
    260.                 if (gameObject.GetComponent<Inventory>().quantity[i] >= storageTransfere[i])
    261.                 {
    262.                     playerHolder.gameObject.GetComponent<Inventory>().quantity[i] = playerHolder.gameObject.GetComponent<Inventory>().quantity[i] + storageTransfere[i];
    263.                     gameObject.GetComponent<Inventory>().quantity[i] = gameObject.GetComponent<Inventory>().quantity[i] - storageTransfere[i];
    264.                 }
    265.             }
    266.             //refresh the store since items have changed
    267.             DestroyItems();
    268.             SetStorage();
    269.             SetInventory();
    270.         }
    271.  
    272.         public void OpenCrate()
    273.         {
    274.             //set our arrays to the correct length
    275.             System.Array.Resize(ref inventoryTransfere, playerHolder.gameObject.GetComponent<Inventory>().itemName.Length);
    276.             System.Array.Resize(ref storageTransfere, playerHolder.gameObject.GetComponent<Inventory>().itemName.Length);
    277.  
    278.             //create buttons for control
    279.             Button[] button = playerHolder.GetComponent<PlayerUI>().storageButtons.GetComponentsInChildren<Button>();
    280.             UnityEventTools.AddPersistentListener(button[0].onClick, gameObject.GetComponent<ItemStorage>().CloseStore);
    281.             UnityEventTools.AddPersistentListener(button[1].onClick, gameObject.GetComponent<ItemStorage>().TakeInventory);
    282.             UnityEventTools.AddPersistentListener(button[2].onClick, gameObject.GetComponent<ItemStorage>().StoreInventory);
    283.  
    284.             //load the container and players inventory
    285.             SetStorage();
    286.             SetInventory();
    287.         }
    288.     }
    289. }
     
    Last edited: Jan 21, 2020
  6. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,297
    Slider.onValueChanged is of type UnityEvent<float>, so you'll need to edit the lambda expression a bit to include a float parameter. Try this:

    Code (CSharp):
    1. var slider = product.GetComponentInChildren<Slider>();
    2. slider.onValueChanged.AddListener((float value)=>UpdateInventory(i));
     
    pantang likes this.
  7. pantang

    pantang

    Joined:
    Sep 1, 2016
    Posts:
    219
    Thats adds the listener but the problem is its value is set to the max length of the array and not what "i" was when it was first set so which ever object I change the value of it always reports back that I changed an object out of range and not the object I set originally. Why I ended up using the persistent as I could not figure out why it was happening with the sliders created in the for loop however I did not realise they would be editor only.
     
    Last edited: Jan 21, 2020
  8. pantang

    pantang

    Joined:
    Sep 1, 2016
    Posts:
    219
    btw I Added your lines at 175 and removed 174 when I tested it.

    var slider = product.GetComponentInChildren<Slider>();
    slider.onValueChanged.AddListener((float value) => UpdateInventory(i));
     
  9. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,297
    Ah yes, I fell into the old trap of using an iteration variable with an lambda expression. As you noticed, this results in the unexpected outcome of every lambda expression using the final value of the iteration variable.

    To fix this the iteration variable just needs to be assigned to a local variable before being used in the lambda expression.

    Code (CSharp):
    1. var slider = product.GetComponentInChildren<Slider>();
    2. int inventorySlot = i;
    3. slider.onValueChanged.AddListener((float value)=>UpdateInventory(inventorySlot));
     
    pantang likes this.
  10. pantang

    pantang

    Joined:
    Sep 1, 2016
    Posts:
    219
    Works perfectly thank you been struggling with that one for that long I went and used the persistent ones :p

    Do you know why in this case I can't use the iteration variable?

    Thank you.
     
    SisusCo likes this.
  11. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,297
    Lambda expressions can refer to outer variables. If a variable is used with a lambda expression and then modified, the value change will also take effect within the lambda expression. This is true even when the variable is a value type.

    For example consider this code:

    Code (CSharp):
    1. int value = 0;
    2. Action action = ()=> Debug.Log("value : "+value);
    3. value = 1;
    4. action();
    This will print "value : 1", not "value : 0".

    When using an iteration variable in a lambda expression, every created lambda expression refers to the same variable. If however we create a new local variable inside the loop and use that in the lambda expression instead, each lambda expression will refer to a different variable.
     
    pantang likes this.