Home > Back-end >  Problems with C # Lists - RemoveAt
Problems with C # Lists - RemoveAt

Time:11-12

I am practicing with C # Lists in Unity and I have encountered a problem. My test script, instantiates 5 prefabs which are added in a gameobject list. I then wrote a code that generates a random int and from that number moves the prefab instantiated with that index (indexof). Everything works correctly, but the method that moves and deletes the prefab is repeated for all the gameobjects in the scene with an index higher than the one chosen. I enclose the two scripts to better explain the problem. (I would need the unlist method to be done only once.

how can i solve this problem and remove one item from the list at a time? (one each time the button is pressed, not all as it is now. Thanks)

script:

  • NpcController: Added in each instantiated prefab
  • ListCOntroller: added in the scene.

    public class ListCOntroller : MonoBehaviour
{
    public GameObject cubePrefab;
    private GameObject cubeInstance;
    public static List<GameObject> cubeList = new List<GameObject> ();

    public TextMeshProUGUI checkText;

    public static event Action chooseNpc;
    public static int randNpcValue;

    int rand;
    private void Start()
    {
        for(int i =0; i < 5; i  )
        {
            cubeInstance = Instantiate(cubePrefab, new Vector3(i, -2, 0), Quaternion.identity);
            cubeList.Add(cubeInstance);
        }
    }

    public void CheckListText()
    {
        checkText.text = "Npc in list: "   cubeList.Count.ToString();
    }

    public static void SelectRandomCube()
    {
        
        randNpcValue = Random.Range(0, cubeList.Count);
        chooseNpc?.Invoke();
    }

    
}

public class NpcController : MonoBehaviour {

void Start()
{
    ListCOntroller.chooseNpc  = NpcEvent;
}

private void NpcEvent()
{

    if (ListCOntroller.cubeList.IndexOf(gameObject) == ListCOntroller.randNpcValue)
    {
        transform.localPosition = new Vector3(transform.position.x, 2, 0);
        DeleteFromList();
        
    }
}

private void DeleteFromList()
{
    ListCOntroller.cubeList.RemoveAt(ListCOntroller.randNpcValue);

    Debug.Log($"Delete from list: {ListCOntroller.randNpcValue}");
    
}

}


the int random number generated in the attached images is: 2

enter image description here

button pressed once.

CodePudding user response:

Because events are executed one after another.

Let's say you have 3 NPCs: NPC0, NPC1, NPC2

Now the random number you choosen is 1, when NPC1's NpcEvent runs, ListCOntroller.cubeList.IndexOf(gameObject) is 1 which equals to the randNpcValue, and then NPC1 will be removed from the list.

Note that now the list has 2 items left: NPC0, NPC2. Next NPC2's NpcEvent runs in turn, at this time, ListCOntroller.cubeList.IndexOf(gameObject) is still 1 because the list has only 2 items, so NPC2 is also removed from the list.

A solution is you can change the randNpcValue to an invalid value when a NPC is removed.

if (ListCOntroller.cubeList.IndexOf(gameObject) == ListCOntroller.randNpcValue)
{
    transform.localPosition = new Vector3(transform.position.x, 2, 0);
    DeleteFromList();
    ListCOntroller.randNpcValue = -2;
}

CodePudding user response:

In addition to this answer in general I don't really see the purpose mixing the the logic into two different scripts.

You have one for storing a list and raising an event and the other one listens to the event and manipulates the stored list -> This doesn't sounds right.

You could as well simply do `why don't you pass a long the the according object into the event in the first place and rather do something like

public class ListCOntroller : MonoBehaviour
{
    // Singleton instance
    private static ListCOntroller _instance;
    // Read-only getter
    public static ListCOntroller Instance => _instance;

    // pass in the target object reference instead of doing things index based
    public event Action<GameObject> chooseNpc;

    public GameObject cubePrefab;

    // THIS is the list controller -> nobody else should be able to manipulate this list
    private readonly List<GameObject> cubeList = new ();

    public TextMeshProUGUI checkText;
    
    private void Awake()
    {
        if(_instance && _instance != this)
        {
            Destroy(this);
            return;
        }

        _instance = this;
    }
    
    private void Start()
    {
        for(int i =0; i < 5; i  )
        {
            cubeInstance = Instantiate(cubePrefab, new Vector3(i, -2, 0), Quaternion.identity);
            cubeList.Add(cubeInstance);
        }
    }
    
    public static void SelectRandomCube()
    {
        if(cubeList.Count == 0)
        {
            Debug.LogWarning("Trying to pick item from empty list");
            return;
        }

        var randomIndex = Random.Range(0, cubeList.Count);
        var randomItem = cubeList[randomIndex];

        // remove from the list yourself instead of relying on others to work correctly
        cubeList.RemoveAt(randomIndex);

        // pass along the target object
        chooseNpc?.Invoke(randomItem);
    }
}

and then

public class NpcController : MonoBehaviour 
{
    void Start()
    {
        ListCOntroller.Instance.chooseNpc  = NpcEvent;
    }
    
    private void NpcEvent(GameObject target)
    {
        // check if you are the target object
        if (target == gameObject))
        {
            // adjust your position - you don't care about other NPCs or the existence of a list
            transform.localPosition = new Vector3(transform.position.x, 2, 0);
        }
    }
}
  • Related