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. Dismiss Notice

Question Can't access contents of VS List in C# script

Discussion in 'Visual Scripting' started by futurepooch, Jun 23, 2023.

  1. futurepooch

    futurepooch

    Joined:
    May 3, 2023
    Posts:
    2
    Hello!

    I have an AotList scene variable tracking a set of booleans in my visual script that I want to access in a C# script, but attempting to pull that list into a C# script is giving me the error:

    InvalidCastException: Specified cast is not valid.

    Here's the problem code. The error is on the line defining nodeMap in the Start function:

    Code (CSharp):
    1. private List<bool> nodeMap = new List <bool>();
    2.  
    3. void Start ()
    4. {
    5.  
    6. nodeMap = (List<bool>)Variables.ActiveScene.Get("NodeProgressMap");
    7.  
    I'll add that I have
    using Unity.VisualScripting
    and am using other VS variables in this script successfully. I only just became familiar with the concept of casting so I don't totally know what I'm doing wrong here?
     

    Attached Files:

  2. PanthenEye

    PanthenEye

    Joined:
    Oct 14, 2013
    Posts:
    1,754
    The code is failing because an AotList can't be directly cast to a List<bool> since it inherits from ArrayList. An ArrayList is not type-safe, it can contain any kind of object. Therefore, when you try to cast an ArrayList to List<bool>, the system does not know how to handle that operation and throws an exception.

    Option #1 - use LINQ Cast:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.VisualScripting;
    3. using UnityEngine;
    4. using System.Linq;
    5.  
    6. public class NodeMap : MonoBehaviour
    7. {
    8.     private List<bool> nodeMap = new();
    9.  
    10.     void Start()
    11.     {
    12.         // Retrieve the node progress map. Variables.Get always returns type object
    13.         object nodeProgressMapObject = Variables.ActiveScene.Get("NodeProgressMap");
    14.         // Cast the retrieved object to an AotList.
    15.         AotList nodeProgressMapAotList = (AotList)nodeProgressMapObject;
    16.         // Cast each element in the AotList to bool, creating a new sequence of bools.
    17.         IEnumerable<bool> nodeProgressMapEnumerable = nodeProgressMapAotList.Cast<bool>();
    18.         // Create a new list of bools from the enumerable.
    19.         nodeMap = new List<bool>(nodeProgressMapEnumerable);
    20.     }
    21. }
    The above step-by-step process can be summarized in this single line of code:
    Code (CSharp):
    1. nodeMap = new List<bool>(((AotList)Variables.ActiveScene.Get("NodeProgressMap")).Cast<bool>());
    Option #2 - foreach loop. If the collection is relatively small and is not processed every frame, then LINQ is fine. But if you need a more performant version without LINQ's allocations, then you can also do it the old-fashioned way:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.VisualScripting;
    3. using UnityEngine;
    4.  
    5. public class NodeMap : MonoBehaviour
    6. {
    7.     private List<bool> nodeMap = new();
    8.  
    9.     void Start()
    10.     {
    11.         var nodeProgressMap = (AotList)Variables.ActiveScene.Get("NodeProgressMap");
    12.         foreach (var node in nodeProgressMap)
    13.         {
    14.             nodeMap.Add((bool)node);
    15.         }
    16.     }
    17. }
     
    Last edited: Jun 23, 2023
    futurepooch likes this.
  3. futurepooch

    futurepooch

    Joined:
    May 3, 2023
    Posts:
    2
    @PanthenEye Thank you so much! The "old fashioned" approach was exactly what I needed. I didn't realize that the C# var and the Get call to the VS var would use their respective types. I had tried changing both to list, and both to AotList, but not mixing the two.
     
  4. NamelessfictionGames

    NamelessfictionGames

    Joined:
    Aug 22, 2021
    Posts:
    2
    @PanthenEye I have this script attached to my enemies. I get the aotlist cast it to the new list and then set it. It seems to only work with a single enemy if there are more I get an error. Do you know a solution to this?


    InvalidCastException: Specified cast is not valid.
    EnemyBehavior.DestroyGameObject () (at Assets/Levels/Gameplay/Enemy/EnemyBehavior.cs:38)
    EnemyBehavior.Update () (at Assets/Levels/Gameplay/Enemy/EnemyBehavior.cs:80)

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using UnityEngine;
    4. using Unity.VisualScripting;
    5. using System.Linq;
    6. using System.Collections.Generic;
    7. using UnityEngine.UI;
    8.  
    9.  
    10. public class EnemyBehavior :MonoBehaviour
    11. {
    12.  
    13.     //public GameObject SceneVariables;
    14.  
    15.     public int EnemyHealth;
    16.     public Vector3 PointA;
    17.     public int MaxLimit;
    18.     public int MinLimit;
    19.     int x = 1;
    20.  
    21.  
    22.     public bool EnemyDestroyed = false;
    23.  
    24.     private List<Image> nodeMap = new();
    25.  
    26.     private List<GameObject> Enemylist = new();
    27.  
    28.  
    29.     public void DamageEnemy(int damage)
    30.     {
    31.  
    32.         EnemyHealth = EnemyHealth - damage;
    33.  
    34.     }
    35.     public void DestroyGameObject()
    36.     {
    37.  
    38.          Enemylist = new List<GameObject>(((AotList)Variables.ActiveScene.Get("EnemyList")).Cast<GameObject>());
    39.          nodeMap = new List<Image>(((AotList)Variables.ActiveScene.Get("EnemyPositionCamera")).Cast<Image>());
    40.  
    41.  
    42.  
    43.         Destroy(nodeMap[Enemylist.IndexOf(gameObject)]);
    44.         nodeMap.RemoveAt(Enemylist.IndexOf(gameObject));
    45.  
    46.  
    47.  
    48.         Enemylist.Remove(gameObject);
    49.  
    50.  
    51.  
    52.         Variables.ActiveScene.Set("EnemyList", Enemylist);
    53.  
    54.         Variables.ActiveScene.Set("EnemyPositionCamera",nodeMap);
    55.  
    56.  
    57.  
    58.         //EnemyDestroyed = true;
    59.          Destroy(gameObject);
    60.        
    61.     }
    62.  
    63.     void Start()
    64.     {
    65.  
    66.  
    67.  
    68.  
    69.  
    70.     }
    71.  
    72.     void Update()
    73.     {
    74.        
    75.  
    76.         //Debug.Log(Enemylist.Count);
    77.  
    78.         if (EnemyHealth < 1 && gameObject == true)
    79.         {
    80.             DestroyGameObject();
    81.         }
    82.  
    83.        
    84.  
    85.        // Debug.Log(nodeMap.Count);
    86.         //Debug.Log(Enemylist.IndexOf(gameObject));
    87.  
    88.  
    89.  
    90.  
    91.         transform.Translate(PointA * Time.deltaTime * x);
    92.  
    93.  
    94.         if(transform.position.x > MaxLimit )
    95.         {
    96.  
    97.  
    98.             x = -1;
    99.  
    100.  
    101.         }
    102.  
    103.  
    104.  
    105.         if (transform.position.x < MinLimit)
    106.         {
    107.  
    108.  
    109.             x = 1;
    110.  
    111.  
    112.         }
    113.  
    114.  
    115.  
    116.     }
    117.  
    118.  
    119.  
    120.  
    121.  
    122. }
    123.  
     
  5. PanthenEye

    PanthenEye

    Joined:
    Oct 14, 2013
    Posts:
    1,754
    First, make sure that the objects you're getting from Variables.ActiveScene.Get("EnemyList") and Variables.ActiveScene.Get("EnemyPositionCamera") are indeed of type AotList.

    Secondly, check the items inside the AotList: Even if the outer list is of type AotList, the elements inside might not be of the expected type. You have to ensure that every element inside Variables.ActiveScene.Get("EnemyList") is a GameObject and every element inside Variables.ActiveScene.Get("EnemyPositionCamera") is an Image.

    Thridly, it might be a good idea to separate the concerns. Add an EnemyManager script that adds and removes from the enemy lists. A single enemy shouldn't have access to other enemy data unless necessary for AI or similar utility in which case it could still communicate with EnemyManager.

    EDIT: Also, "gameobject == true" in your Update() function is not a valid syntax. Use "gameObject.activeSelf" instead.
     
    Last edited: Jul 25, 2023
    NamelessfictionGames likes this.