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

Properly adding to lists?

Discussion in 'Scripting' started by JasonDN, May 18, 2019.

  1. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    Hi. First, I'd like to ask, is there a scripting api page for list like there is for array? I cannot seem to find it.

    I've got a list of snap points for a building system. Upon placing a new building piece new snap points are created. I'm wondering how I would properly add them to a list that is already populated. Would I just use Clear and then populate the list again? Is there a way to do it by comparing the count and then just adding the new ones? Which would be better??

    Also, after I'm done building should I clear the list each time? Maybe inside an isBuilding check?

    Code below showing how I'm currently doing it.


    Code (CSharp):
    1. public void AddSnapPointsToList()
    2.     {
    3.         foundationSnapPoints.Clear();
    4.         wallSnapPoints.Clear();
    5.         ceilingSnapPoints.Clear();
    6.  
    7.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Foundation_SnapPoint"))
    8.         {
    9.             foundationSnapPoints.Add(go);
    10.         }
    11.  
    12.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Wall_SnapPoint"))
    13.         {
    14.             wallSnapPoints.Add(go);
    15.         }
    16.  
    17.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Ceiling_SnapPoint"))
    18.         {
    19.             ceilingSnapPoints.Add(go);
    20.         }
    21.     }
    Thanks.
     
  2. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Take a look at this page. There is a link there for all the collection types including
    List
    .

    Use the
    Add
    method to do that. There is no need to clear and recreate the
    List
    .

    That code will work, but is highly inefficient and will not scale well. As game objects increase in number this method will take longer to complete. Also, it is not recommended to make excessive calls to
    GameObject.FindGameObjectsWithTag
    as it is slow.

    Rather what you want to do is when a new snap point gets created is register it with a "snap point handler". Internally, that handler object will simply call the
    Add
    on its appropriate internal store. Similarly, if the snap point gets destroyed, it could tell the handler so that can remove it (using the
    Remove
    method).
     
    JasonDN likes this.
  3. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    As Doug_B implies, the reason we have Lists is so we can easily make small changes using Add (it puts things on the end, but Insert can put them anywhere) and RemoveAt. The official C# manual isn't so great for figuring this out.

    But, for your exact Q, there's a shortcut to make a list from an array, no loop needed:
    GameObject[] G = FindItemsWithTag.....; // an array
    foundationSnapPoints = new List<GameObject>(G); // make list from array
     
    Last edited: Jan 7, 2021
    mysticvalleycsa, JasonDN and Doug_B like this.
  4. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    Doug_B, thank you for the link. Unfortunately, every thing you said about a snap point handler went over my head. It does sound like something I want though, if you could point me in the direction of something further explaining it, I'd love to see it.

    Owen, I don't know if you meant "FindItemsWithTag" or "GameObject.FindItemsWithTag", but the closest I could find was "GameObject.FindWithTag." So I tried:
    Code (CSharp):
    1. GameObject G = GameObject.FindWithTag("Foundation_SnapPoint");
    2.         foundationSnapPoints = new List<GameObject>(G);
    It gives me the following error "
    Argument 1: cannot convert from 'UnityEngine.GameObject' to 'System.Collections.Generic.IEnumerable<UnityEngine.GameObject>'" I'm not for sure if I'm doing it wrong, in the wrong place (trying it in place of lines 7-10 of my op,) or if possibly it is because of how I'm declaring my list at the top of the script: "public List<GameObject> foundationSnapPoints = new List<GameObject>();"

    Let me elaborate on what I have going on and what the exact issue I'm having is. I have 3 prefabs, foundation, wall, ceiling. They each have snap points on them as children. I have a single foundation in my scene on start so I'm using foreach loops to find and add the snap points to their respective lists. I then use more loops to deactivate the snap point gameobjects.When I instantiate a preview piece I set my currentPreviewType based on the piece and based on the type I reactivate the appropriate snap points. Performance considerations aside, this is all working. When I start the scene it has 4 foundation and wall snap points each, which have been added to the lists and deactivated. When I hit 1 to instantiate my foundation preview, it activates the foundation snap points and leaves the wall snap points deactivated (as intended.) Once I place the foundation I use foreach loops to add the snap points to the lists. The problem is that it adds all previous foundation snap points plus the new ones to the list. So on start I have 4, place one foundation I have 12, place another foundation I have 28 and so on. The wall snap points are deactivated during this, "FindGameObjectsWithTag" cannot find deactivated game objects, so it actually adds the correct amount (4) to the list each time. That wasn't planned, I didn't realize it had that limitation, but it works out.

    Performance and elegance aside, my only real issue here is that I don't know how to simply add the child snap points on the foundation individually, only by using the foreach loops, this problem is why I was using .Clear btw. I'm a novice and literally just started learning about lists and enums the other night based off some other kind suggestions. Really, extremely novice. Keep that in mind any of you who bother to look at the code I'm about to paste. lol


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class BuildingSystem : MonoBehaviour
    6. {
    7.  
    8.     public enum CurrentPreviewType {None, Foundation, Wall, Ceiling};
    9.     public CurrentPreviewType currentPreviewType;
    10.  
    11.     public GameObject foundationPreview;
    12.     public GameObject wallPreview;
    13.     public GameObject ceilingPreview;
    14.     private GameObject previewGameObject = null;
    15.  
    16.     public GameObject prefab = null;
    17.     public GameObject foundationPrefab;
    18.     public GameObject wallPrefab;
    19.     public GameObject ceilingPrefab;
    20.  
    21.  
    22.     public List<GameObject> foundationSnapPoints = new List<GameObject>();
    23.     public List<GameObject> wallSnapPoints = new List<GameObject>();
    24.     public List<GameObject> ceilingSnapPoints = new List<GameObject>();
    25.  
    26.     public bool isBuilding;
    27.  
    28.     public LayerMask layer;
    29.    
    30.  
    31.     // Start is called before the first frame update
    32.     void Start()
    33.     {
    34.  
    35.  
    36.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Foundation_SnapPoint"))
    37.         {
    38.             foundationSnapPoints.Add(go);
    39.         }
    40.  
    41.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Wall_SnapPoint"))
    42.         {
    43.             wallSnapPoints.Add(go);
    44.         }
    45.  
    46.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Ceiling_SnapPoint"))
    47.         {
    48.             ceilingSnapPoints.Add(go);
    49.         }
    50.  
    51.  
    52.  
    53.         foreach (GameObject go in foundationSnapPoints)
    54.         {
    55.             go.SetActive(false);
    56.         }
    57.  
    58.         foreach (GameObject go in wallSnapPoints)
    59.         {
    60.             go.SetActive(false);
    61.         }
    62.  
    63.         foreach (GameObject go in ceilingSnapPoints)
    64.         {
    65.             go.SetActive(false);
    66.         }
    67.  
    68.  
    69.     }
    70.  
    71.     // Update is called once per frame
    72.     void Update()
    73.     {
    74.  
    75.         if (Input.GetKeyDown(KeyCode.Alpha1))
    76.         {
    77.             CancelBuilding();
    78.             currentPreviewType = CurrentPreviewType.Foundation;
    79.             NewBuilding(foundationPreview);          //Instantiate the prefab in the foundation slot
    80.  
    81.             prefab = foundationPrefab;
    82.         }
    83.  
    84.         if (Input.GetKeyDown(KeyCode.Alpha2))
    85.         {
    86.             CancelBuilding();
    87.             currentPreviewType = CurrentPreviewType.Wall;
    88.             NewBuilding(wallPreview);                //Instantiate the prefab in the wall slot        
    89.  
    90.             prefab = wallPrefab;
    91.         }
    92.  
    93.         if (Input.GetKeyDown(KeyCode.Alpha3))
    94.         {
    95.             CancelBuilding();
    96.             currentPreviewType = CurrentPreviewType.Ceiling;
    97.             NewBuilding(ceilingPreview);             //Instantiate the prefab in the ceiling slot
    98.            
    99.             prefab = ceilingPrefab;
    100.         }
    101.  
    102.         if (Input.GetKeyDown(KeyCode.R) && isBuilding)             //Rotate the instantiated object; Doesn't work when "snapped" atm. Gotta figure out how to address this, if I want to bother even.
    103.         {
    104.             previewGameObject.transform.Rotate(0, 90f, 0);
    105.         }
    106.  
    107.         if (Input.GetKeyDown(KeyCode.Q) && isBuilding)
    108.         {
    109.             CancelBuilding();
    110.         }
    111.  
    112.         if (Input.GetMouseButtonDown(0) && isBuilding)
    113.         {
    114.             PlaceBuilding();
    115.         }
    116.  
    117.         if (isBuilding)                       //If isBuilding is true, cast ray
    118.         {
    119.             CastBuildRay();
    120.         }
    121.     }
    122.  
    123.     private void CastBuildRay()
    124.     {
    125.         Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);                //Creates a ray from the cameras position shooting forward
    126.         RaycastHit hit;                                                                                  //Creates a raycasthit so we can get information from what the ray hits
    127.  
    128.         if (Physics.Raycast(ray, out hit, 25f, layer))
    129.         {
    130.             //Debug.Log(hit.collider.tag);
    131.  
    132.             if ((currentPreviewType == CurrentPreviewType.Foundation && hit.collider.CompareTag("Foundation_SnapPoint")) ||   //If the object and snap point tags are of the same type
    133.                 (currentPreviewType == CurrentPreviewType.Wall && hit.collider.CompareTag("Wall_SnapPoint")) ||              
    134.                 (currentPreviewType == CurrentPreviewType.Ceiling && hit.collider.CompareTag("Ceiling_SnapPoint")))
    135.  
    136.             {
    137.                 previewGameObject.transform.position = hit.collider.transform.position;                  //Snap the object to the snap point
    138.                 previewGameObject.transform.rotation = hit.collider.transform.rotation;
    139.             }
    140.             else
    141.             {
    142.                 //Use 3 lines below for primitives
    143.                 float y = hit.point.y + (previewGameObject.transform.localScale.y / 2f);                 //If colliding, instantiated objects position is at collision point
    144.                 Vector3 pos = new Vector3(hit.point.x, y, hit.point.z);
    145.                 previewGameObject.transform.position = pos;
    146.  
    147.                 //previewGameObject.transform.position = hit.point; //use this if importing properly anchored models
    148.             }
    149.         }
    150.         else
    151.         {
    152.             var pos = ray.origin + ray.direction * 25f;                                                  //Else if not colliding, instantiated objects position is the end of the ray
    153.             previewGameObject.transform.position = pos;
    154.         }
    155.  
    156.     }
    157.  
    158.     public void NewBuilding(GameObject _go)
    159.     {
    160.         var lookPos = Camera.main.transform.position + Camera.main.transform.forward * 25f; //Creates a variable for where we are looking
    161.        
    162.         previewGameObject = Instantiate(_go, lookPos, Quaternion.identity);                 //Instantiate _go (the slotted object) e.g. NewBuilding(foundation) for the foundation slot
    163.         isBuilding = true;                                                                  //Sets isBuilding to true so our ray casts
    164.         ShowSnapPoints();
    165.     }
    166.  
    167.     public void PlaceBuilding()
    168.     {
    169.         Instantiate(prefab, previewGameObject.transform.position, previewGameObject.transform.rotation);
    170.  
    171.         AddSnapPointsToList();
    172.         CancelBuilding();
    173.        
    174.     }
    175.  
    176.     public void CancelBuilding()
    177.     {
    178.  
    179.         foreach (GameObject go in foundationSnapPoints)
    180.         {
    181.             go.SetActive(false);
    182.         }
    183.  
    184.         foreach (GameObject go in wallSnapPoints)
    185.         {
    186.             go.SetActive(false);
    187.         }
    188.  
    189.         foreach (GameObject go in ceilingSnapPoints)
    190.         {
    191.             go.SetActive(false);
    192.         }
    193.  
    194.         prefab = null;
    195.         currentPreviewType = CurrentPreviewType.None;
    196.         isBuilding = false;
    197.         Destroy(previewGameObject);
    198.     }
    199.  
    200.     public void AddSnapPointsToList()
    201.     {
    202.         //foundationSnapPoints.Clear();
    203.         //wallSnapPoints.Clear();
    204.         //ceilingSnapPoints.Clear();
    205.  
    206.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Foundation_SnapPoint"))
    207.         {
    208.             foundationSnapPoints.Add(go);
    209.         }
    210.  
    211.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Wall_SnapPoint"))
    212.         {
    213.             wallSnapPoints.Add(go);
    214.         }
    215.  
    216.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Ceiling_SnapPoint"))
    217.         {
    218.             ceilingSnapPoints.Add(go);
    219.         }
    220.     }
    221.  
    222.     public void ShowSnapPoints()
    223.     {
    224.        if (currentPreviewType == CurrentPreviewType.Foundation)
    225.         {
    226.             foreach (GameObject go in foundationSnapPoints)
    227.             {
    228.                 go.SetActive(true);
    229.             }
    230.         }
    231.         if (currentPreviewType == CurrentPreviewType.Wall)
    232.         {
    233.             foreach (GameObject go in wallSnapPoints)
    234.             {
    235.                 go.SetActive(true);
    236.             }
    237.         }
    238.         if (currentPreviewType == CurrentPreviewType.Ceiling)
    239.         {
    240.             foreach (GameObject go in ceilingSnapPoints)
    241.             {
    242.                 go.SetActive(true);
    243.             }
    244.         }
    245.     }
    246. }
    247.  
     
  5. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    With the code as pasted there, line 38 adds the snap points for the foundation. Line 202 has been commented out and so does not remove it, then line 208 adds not only the new ones but also the exact same original ones again as well.

    You could try changing
    AddSnapPointsToList
    to look something like this (you will need to give the appropriate
    SnapPointsType
    depending on what type you have set them up as in the prefabs). This would mean you do not need to clear the list and it should only add in the new snap points :
    Code (CSharp):
    1.     void AddSnapPointsToList()
    2.     {
    3.         switch (currentPreviewType)
    4.         {
    5.             default:
    6.             case CurrentPreviewType.None:
    7.                 break;
    8.  
    9.             case CurrentPreviewType.Foundation:
    10.                 foreach (var snapPoint in prefab.GetComponentsInChildren<SnapPointsType>())
    11.                 {
    12.                     foundationSnapPoints.Add(snapPoint);
    13.                 }
    14.                 break;
    15.  
    16.             case CurrentPreviewType.Wall:
    17.                 foreach (var snapPoint in prefab.GetComponentsInChildren<SnapPointsType>())
    18.                 {
    19.                     wallSnapPoints.Add(snapPoint);
    20.                 }
    21.                 break;
    22.  
    23.             case CurrentPreviewType.Ceiling:
    24.                 foreach (var snapPoint in prefab.GetComponentsInChildren<SnapPointsType>())
    25.                 {
    26.                     ceilingSnapPoints.Add(snapPoint);
    27.                 }
    28.                 break;
    29.         }
    30.     }
    If I may be so bold, try to keep as much of the class private as possible to restrict their visibility from outside. This helps reduce outside code accidentally meddling with and upsetting the class contents. In particular, tend to make all non-const variables private.

    Also, chucking variables at the end of the class rather than at the beginning may help to focus the mind on the class methods rather than the internal data. But that's just my tuppence worth. :)
     
    JasonDN likes this.
  6. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Here
    FindWithTag
    is returning only one item. I think Owen-Reynolds was referring to GameObject.FindGameObjectsWithTag. That will return an array (although unfortunately, the documentation declares this as "Returns a list" which is not to be confused with "Returns a List<>" :)). A
    List
    can be constructed directly from an array.
     
    mysticvalleycsa and JasonDN like this.
  7. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    I was saying to use the exact code you had, except to remove the loop: "make a list from an array, no loop needed". i'm guessing you're weak on arrays and lists, and, since the internet is 90% junk, you're getting weird, conflicting advice that's confusing you even more. Playing with some basic examples might help.
     
    JasonDN likes this.
  8. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    I'm getting embarrassed now. You've both written code for me and I just can't get it to work.

    I commented it out when you said I didn't need to clear the list and started trying to figure out how to do it without clearing.

    I am failing hard. I don't have the snap points set up as any type in the prefabs. I only have this one script. So I created a new script called SnapPointsType:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SnapPointsType : MonoBehaviour
    6. {
    7.     public enum SnapPointType { Foundation, Wall, Ceiling };
    8. }
    I then attached the script to each of the 4 wall and foundation snap points on my foundation prefab and set their types accordingly in the drop down on the component. Back in AddSnapPointsToList() I copied your code and commented out all the cases but Foundation and changed it to :
    Code (CSharp):
    1. case CurrentPreviewType.Foundation:
    2.                 foreach (var snapPoint in prefab.GetComponentsInChildren<SnapPointsType.SnapPointType.Foundation>())
    3.                 {
    4.                     foundationSnapPoints.Add(snapPoint);
    5.                 }
    6.                 break;
    That wasn't the only thing I tried, tried writing it several different ways. Getting errors any way I try, but this way seemed the most logical to me and was one of the only ways I tried that stopped making "snapPoint" give me an error. It is giving me an error at ".Foundation" though: "The type name 'Foundation' does not exist in the type 'SnapPointsType.SnapPointType'"

    I'm not for sure what I'm doing wrong, or not doing. As Owen said, the more I google this or watch youtube vids trying to figure it out, the more confused I'm becoming. There's something that just isn't "clicking" for me yet and I have no clue what that could be even. Starting to wonder how I made it this far. Lol. Thank you both for your help.
     
  9. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    No worries, keep chipping away at it and you'll get there. :)

    Ok, so to do this, you must have created a variable of type
    SnapPointType
    (maybe in the
    SnapPointsType
    of the prefabs). In which case, does something like this work? (it will require some way of reading the snap point type, I have just used
    getSnapPointType
    as a place holder for that) :
    Code (CSharp):
    1.         void AddSnapPointsToList()
    2.         {
    3.             foreach (var snapPoint in prefab.GetComponentsInChildren<SnapPointsType>())
    4.             {
    5.                 switch (snapPoint.getSnapPointType)
    6.                 {
    7.                     case SnapPointsType.SnapPointType.Foundation:
    8.                         foundationSnapPoints.Add(snapPoint);
    9.                         break;
    10.                 }
    11.             }
    12.         }
     
    JasonDN likes this.
  10. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    I applaud your patience. Have felt like giving up several times now but it's hard to do so when you're not giving up on me.

    So I took that as I only need to figure out what goes where you have "getSnapPointType." I copied that code over and clicked show potential fixes and it showed 3. I chose the bottom which changed my SnapPointsType script to this.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SnapPointsType : MonoBehaviour
    6. {
    7.     public SnapPointType getSnapPointType { get; internal set; }
    8.     public enum SnapPointType { Foundation, Wall, Ceiling };
    9. }
    I also tried:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SnapPointsType : MonoBehaviour
    6. {
    7.     public enum SnapPointType { Foundation, Wall, Ceiling };
    8.     public SnapPointType snapPointType;
    9. }
    and changed "snapPoint.getSnapPointType" to "snapPoint.snapPointType". Both of those attempts give me an error at line 8 (of your last post) at (snapPoint) saying "
    Argument 1: cannot convert from 'SnapPointsType' to 'UnityEngine.GameObject'
    "
    Am I at least on the right track or am I looking at this completely wrong?
     
  11. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    On the right track, I think. But I wonder if it might be worth trying a slightly different approach. Maybe give this a try and see if you think you can make it work for you :
    1. Create a new scene.
    2. Create a cube in the scene. Call it "AFoundation".
    3. Under "AFoundation" create a number of child empty game objects. To each of these children, add the
      SnapPoint
      script below.
    4. Now make AFoundation into a prefab and delete the object from the scene.
    5. Onto the MainCamera, add the
      Building
      script below. (Note, this is not really sensible but purely for simplicity right now).
    6. Drag the "AFoundation" prefab into the
      prefabFoundation
      slot in the MainCamera's
      Building
      component.
    Now run the scene. In the console window in Unity, do you see the number of snap points growing as the prefabs are instantiated?

    If you now create another prefab (AWall) and add some child SnapPoints to that, could you see how you could make walls and add their child snap points into a list of wall snap points in
    Building
    ? Maybe give that a try (without all the complications of your main code for now just to see if you can get the types working). If so, could you adjust your main code to use this method?
    Code (CSharp):
    1. public class Building : MonoBehaviour
    2. {
    3.     void Start()
    4.     {
    5.         Debug.Log("I started with " + myFoundationSnapPoints.Count + " foundation snap points.");
    6.  
    7.         myCurrentPrefab = Instantiate(prefabFoundation);
    8.         myFoundationSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    9.         Debug.Log("I have " + myFoundationSnapPoints.Count + " foundation snap points.");
    10.  
    11.         myCurrentPrefab = Instantiate(prefabFoundation);
    12.         myFoundationSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    13.         Debug.Log("I now have " + myFoundationSnapPoints.Count + " foundation snap points.");
    14.     }
    15.  
    16.     [SerializeField] GameObject prefabFoundation;
    17.     List<SnapPoint> myFoundationSnapPoints = new List<SnapPoint>();
    18.     GameObject myCurrentPrefab;
    19. }
    Code (CSharp):
    1. public class SnapPoint : MonoBehaviour
    2. {
    3. }
     
    JasonDN likes this.
  12. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    One thing jumped out at me immediately. In your lists you have <SnapPoint> in mine I have <GameObject>. Is that one of the problems I was having? Should mine also be the equivalent of my snap point script? <SnapPointsType> rather than <GameObject>? If I am looking at this correctly, that seems to be the cause of that last error I posted (Argument 1: cannot convert from 'SnapPointsType' to 'UnityEngine.GameObject'.) Haven't tried changing that in my script yet as I wanted to focus on the task you prescribed me.

    As simple as enums are, or seem to be, I think they're what is really tripping me up. More specifically, checking what enum type the child objects are. Which from what I'm seeing, I need to figure that out since in my project some items will have multiple snap point types, namely the foundation which has foundation snap points and wall snap points. Either that, or I could create a separate SnapPoint class for each of the snap points, such as FoundationSnapPoint, WallSnapPoint, etc.. then change the lists accordingly, e.g. "List<WallSnapPoint> myWallSnapPoints = new List<WallSnapPoint>();" I don't know if that makes any sense or would work, seems like it would, but there's a good chance I'm understanding stuff wrong again. lol

    Anyways, I have to take a break, I will continue looking over this and trying to comprehend it 100% when I get the chance and also maybe start trying to change my scripts based on it. Just figured I'd give an update and show my work. I think I understand everything that is going on here. Btw, I serialized myCurrentPrefab just so I could see it in the editor.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. public class Building : MonoBehaviour
    7. {
    8.     void Start()
    9.     {
    10.         Debug.Log("I started with " + myFoundationSnapPoints.Count + " foundation snap points.");
    11.         Debug.Log("I started with " + myWallSnapPoints.Count + " wall snap points.");
    12.  
    13.         myCurrentPrefab = Instantiate(prefabFoundation);
    14.         myFoundationSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    15.         Debug.Log("I have " + myFoundationSnapPoints.Count + " foundation snap points.");
    16.  
    17.         myCurrentPrefab = Instantiate(prefabFoundation);
    18.         myFoundationSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    19.         Debug.Log("I now have " + myFoundationSnapPoints.Count + " foundation snap points.");
    20.  
    21.         myCurrentPrefab = Instantiate(prefabWall);
    22.         myWallSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    23.         Debug.Log("I have " + myWallSnapPoints.Count + " wall snap points.");
    24.  
    25.         myCurrentPrefab = Instantiate(prefabWall);
    26.         myWallSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    27.         Debug.Log("I now have " + myWallSnapPoints.Count + " wall snap points.");
    28.     }
    29.  
    30.     private void Update()
    31.     {
    32.         if (Input.GetKeyDown(KeyCode.Alpha1))
    33.         {
    34.             myCurrentPrefab = prefabFoundation;
    35.  
    36.             if (myCurrentPrefab == prefabFoundation)
    37.             {
    38.                 Debug.Log("myCurrentPrefab = preFabFoundation");
    39.             }
    40.         }
    41.  
    42.         if (Input.GetKeyDown(KeyCode.Alpha2))
    43.         {
    44.             myCurrentPrefab = prefabWall;
    45.  
    46.             if (myCurrentPrefab == prefabWall)
    47.             {
    48.                 Debug.Log("myCurrentPrefab = prefabWall");
    49.             }
    50.         }
    51.  
    52.         if (Input.GetMouseButtonDown(0))
    53.         {
    54.             Instantiate(myCurrentPrefab);
    55.             AddSnapPoints();
    56.         }
    57.     }
    58.  
    59.     private void AddSnapPoints()
    60.     {
    61.         if (myCurrentPrefab == prefabFoundation)
    62.         {
    63.             myFoundationSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    64.             Debug.Log("I have " + myFoundationSnapPoints.Count + " foundation snap points.");
    65.             Debug.Log("I have " + myWallSnapPoints.Count + " wall snap points.");
    66.         }
    67.  
    68.         if (myCurrentPrefab == prefabWall)
    69.         {
    70.             myWallSnapPoints.AddRange(myCurrentPrefab.GetComponentsInChildren<SnapPoint>());
    71.             Debug.Log("I have " + myFoundationSnapPoints.Count + " foundation snap points.");
    72.             Debug.Log("I now have " + myWallSnapPoints.Count + " wall snap points.");
    73.         }
    74.     }
    75.  
    76.     [SerializeField] GameObject prefabFoundation;
    77.     [SerializeField] GameObject prefabWall;
    78.     List<SnapPoint> myFoundationSnapPoints = new List<SnapPoint>();
    79.     List<SnapPoint> myWallSnapPoints = new List<SnapPoint>();
    80.     [SerializeField] GameObject myCurrentPrefab;
    81. }
    82.  
     
    Doug_B likes this.
  13. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Possibly so. C# is a strongly typed language. Introducing an enum creates a new type in much the same way creating a class does. These types are (generally speaking) not compatible.

    That would seem one way to do it, yes.

    Coming back at it fresh can be a useful thing to do. Best of luck with it. :)
     
    JasonDN likes this.
  14. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    I messed with my code a bunch, kept getting cannot convert errors trying to get that last way you showed me to work with my code. I finally arrived at a way to do it, can't even tell you how I got there though, just kept messing with it and changing little things and experimenting until finally I didn't have any errors. Seems to be adding fine now, even properly adding the foundation and wall snap points to their respective lists when I place a foundation.

    I have a new problem now though. I really don't think it's my code, but I'm not for certain. I'm starting with 1 foundation. I hit play and it adds the 4 foundation and wall snap points to their lists and then hides them by deactivating them. After I place a foundation or a wall, it adds the snap points as it should but any new snap points to the list don't seem to be being affected by my code. The snap points that were added on start still function and react as they should, will deactivate and activate when switching between pieces or canceling building. I have absolutely no clue what the problem could be. I can start another thread if need be, just figured I'd post about it here first since it isn't that far disconnected. Current code below.


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class BuildingSystem : MonoBehaviour
    6. {
    7.  
    8.     public enum CurrentPreviewType {None, Foundation, Wall, Ceiling};
    9.     public CurrentPreviewType currentPreviewType;
    10.  
    11.     public GameObject foundationPreview;
    12.     public GameObject wallPreview;
    13.     public GameObject ceilingPreview;
    14.     private GameObject previewGameObject = null;
    15.  
    16.     public Transform prefab = null;
    17.     public Transform foundationPrefab;
    18.     public Transform wallPrefab;
    19.     public Transform ceilingPrefab;
    20.  
    21.  
    22.     public List<GameObject> foundationSnapPoints = new List<GameObject>();
    23.     public List<GameObject> wallSnapPoints = new List<GameObject>();
    24.     public List<GameObject> ceilingSnapPoints = new List<GameObject>();
    25.  
    26.     public bool isBuilding;
    27.  
    28.     public LayerMask layer;
    29.    
    30.  
    31.     // Start is called before the first frame update
    32.     void Start()
    33.     {
    34.  
    35.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Foundation_SnapPoint"))
    36.         {
    37.             foundationSnapPoints.Add(go);
    38.         }
    39.  
    40.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Wall_SnapPoint"))
    41.         {
    42.             wallSnapPoints.Add(go);
    43.         }
    44.  
    45.         foreach (GameObject go in GameObject.FindGameObjectsWithTag("Ceiling_SnapPoint"))
    46.         {
    47.             ceilingSnapPoints.Add(go);
    48.         }
    49.  
    50.         foreach (GameObject go in foundationSnapPoints)
    51.         {
    52.             go.SetActive(false);
    53.         }
    54.  
    55.         foreach (GameObject go in wallSnapPoints)
    56.         {
    57.             go.SetActive(false);
    58.         }
    59.  
    60.         foreach (GameObject go in ceilingSnapPoints)
    61.         {
    62.             go.SetActive(false);
    63.         }
    64.  
    65.     }
    66.  
    67.     // Update is called once per frame
    68.     void Update()
    69.     {
    70.  
    71.         if (Input.GetKeyDown(KeyCode.Alpha1))
    72.         {
    73.             CancelBuilding();
    74.             currentPreviewType = CurrentPreviewType.Foundation;
    75.             NewBuilding(foundationPreview);          //Instantiate the prefab in the foundation slot
    76.  
    77.             prefab = foundationPrefab;
    78.         }
    79.  
    80.         if (Input.GetKeyDown(KeyCode.Alpha2))
    81.         {
    82.             CancelBuilding();
    83.             currentPreviewType = CurrentPreviewType.Wall;
    84.             NewBuilding(wallPreview);                //Instantiate the prefab in the wall slot        
    85.  
    86.             prefab = wallPrefab;
    87.         }
    88.  
    89.         if (Input.GetKeyDown(KeyCode.Alpha3))
    90.         {
    91.             CancelBuilding();
    92.             currentPreviewType = CurrentPreviewType.Ceiling;
    93.             NewBuilding(ceilingPreview);             //Instantiate the prefab in the ceiling slot
    94.            
    95.             prefab = ceilingPrefab;
    96.         }
    97.  
    98.         if (Input.GetKeyDown(KeyCode.R) && isBuilding)             //Rotate the instantiated object; Doesn't work when "snapped" atm. Gotta figure out how to address this, if I want to bother even.
    99.         {
    100.             previewGameObject.transform.Rotate(0, 90f, 0);
    101.         }
    102.  
    103.         if (Input.GetKeyDown(KeyCode.Q) && isBuilding)
    104.         {
    105.             CancelBuilding();
    106.         }
    107.  
    108.         if (Input.GetMouseButtonDown(0) && isBuilding)
    109.         {
    110.             PlaceBuilding();
    111.         }
    112.  
    113.         if (isBuilding)                       //If isBuilding is true, cast ray
    114.         {
    115.             CastBuildRay();
    116.         }
    117.  
    118.     }
    119.  
    120.     private void CastBuildRay()
    121.     {
    122.         Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);                //Creates a ray from the cameras position shooting forward
    123.         RaycastHit hit;                                                                                  //Creates a raycasthit so we can get information from what the ray hits
    124.  
    125.         if (Physics.Raycast(ray, out hit, 25f, layer))
    126.         {
    127.             //Debug.Log(hit.collider.tag);
    128.  
    129.             if ((currentPreviewType == CurrentPreviewType.Foundation && hit.collider.CompareTag("Foundation_SnapPoint")) ||   //If the object and snap point tags are of the same type
    130.                 (currentPreviewType == CurrentPreviewType.Wall && hit.collider.CompareTag("Wall_SnapPoint")) ||              
    131.                 (currentPreviewType == CurrentPreviewType.Ceiling && hit.collider.CompareTag("Ceiling_SnapPoint")))
    132.  
    133.             {
    134.                 previewGameObject.transform.position = hit.collider.transform.position;                  //Snap the object to the snap point
    135.                 previewGameObject.transform.rotation = hit.collider.transform.rotation;
    136.             }
    137.             else
    138.             {
    139.                 //Use 3 lines below for primitives
    140.                 float y = hit.point.y + (previewGameObject.transform.localScale.y / 2f);                 //If colliding, instantiated objects position is at collision point
    141.                 Vector3 pos = new Vector3(hit.point.x, y, hit.point.z);
    142.                 previewGameObject.transform.position = pos;
    143.  
    144.                 //previewGameObject.transform.position = hit.point; //use this if importing properly anchored models
    145.             }
    146.         }
    147.         else
    148.         {
    149.             var pos = ray.origin + ray.direction * 25f;                                                  //Else if not colliding, instantiated objects position is the end of the ray
    150.             previewGameObject.transform.position = pos;
    151.         }
    152.  
    153.     }
    154.  
    155.     public void NewBuilding(GameObject _go)
    156.     {
    157.         var lookPos = Camera.main.transform.position + Camera.main.transform.forward * 25f; //Creates a variable for where we are looking
    158.        
    159.         previewGameObject = Instantiate(_go, lookPos, Quaternion.identity);                 //Instantiate _go (the slotted object) e.g. NewBuilding(foundation) for the foundation slot
    160.         isBuilding = true;                                                                  //Sets isBuilding to true so our ray casts
    161.         ShowSnapPoints();
    162.     }
    163.  
    164.     public void PlaceBuilding()
    165.     {
    166.         Instantiate(prefab, previewGameObject.transform.position, previewGameObject.transform.rotation);
    167.  
    168.         AddSnapPointsToList();
    169.         CancelBuilding();      
    170.     }
    171.  
    172.     public void CancelBuilding()
    173.     {
    174.  
    175.         foreach (GameObject go in foundationSnapPoints)
    176.         {
    177.             go.SetActive(false);
    178.         }
    179.  
    180.         foreach (GameObject go in wallSnapPoints)
    181.         {
    182.             go.SetActive(false);
    183.         }
    184.  
    185.         foreach (GameObject go in ceilingSnapPoints)
    186.         {
    187.             go.SetActive(false);
    188.         }
    189.  
    190.         prefab = null;
    191.         currentPreviewType = CurrentPreviewType.None;
    192.         isBuilding = false;
    193.         Destroy(previewGameObject);
    194.     }
    195.  
    196.     public void AddSnapPointsToList()
    197.     {
    198.         if (currentPreviewType == CurrentPreviewType.Foundation)
    199.         {
    200.             foreach (Transform child in prefab)
    201.             {
    202.                 if (child.CompareTag("Foundation_SnapPoint"))
    203.                 {
    204.                     foundationSnapPoints.Add(child.gameObject);
    205.                 }
    206.  
    207.                 if (child.CompareTag("Wall_SnapPoint"))
    208.                 {
    209.                     wallSnapPoints.Add(child.gameObject);
    210.                 }
    211.             }
    212.         }
    213.  
    214.         if (currentPreviewType == CurrentPreviewType.Wall)
    215.         {
    216.             foreach (Transform child in prefab)
    217.             {
    218.                 if (child.CompareTag("Ceiling_SnapPoint"))
    219.                 {
    220.                     ceilingSnapPoints.Add(child.gameObject);
    221.                 }
    222.             }
    223.         }
    224.  
    225.         if (currentPreviewType == CurrentPreviewType.Ceiling)
    226.         {
    227.             foreach (Transform child in prefab)
    228.             {
    229.                 if (child.CompareTag("Ceiling_SnapPoint"))
    230.                 {
    231.                     ceilingSnapPoints.Add(child.gameObject);
    232.                 }
    233.             }
    234.         }
    235.     }
    236.  
    237.     public void ShowSnapPoints()
    238.     {
    239.         if (currentPreviewType == CurrentPreviewType.Foundation)
    240.         {
    241.             foreach (GameObject go in foundationSnapPoints)
    242.             {
    243.                 go.SetActive(true);
    244.             }
    245.         }
    246.  
    247.         if (currentPreviewType == CurrentPreviewType.Wall)
    248.         {
    249.             foreach (GameObject go in wallSnapPoints)
    250.             {
    251.                 go.SetActive(true);
    252.             }
    253.         }
    254.  
    255.         if (currentPreviewType == CurrentPreviewType.Ceiling)
    256.         {
    257.             foreach (GameObject go in ceilingSnapPoints)
    258.             {
    259.                 go.SetActive(true);
    260.             }
    261.         }
    262.     }
    263. }
    264.  
     
  15. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    I'm not exactly sure how they are not being affected, but I do notice that line 214 seems to be looking at a "wall" but then on line 218 and 220 seems to be working with "ceiling" types. Is that expected?

    If not, do you have a debugger available (the community edition of Visual Studio is free)? It is really worth using a good one as they allow you to step line-by-line through your code and see exactly what is going on. Ideal for problems such as this. :)
     
    JasonDN likes this.
  16. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    Whoops, the perils of copy and paste. Thought I caught all of those.

    That's what I'm using. I don't really know how to use the debugger. Been meaning to watch a tutorial or two on it but haven't gotten around to it yet.

    I would think it is possibly my order of operations, but I tried commenting certain stuff out and then hiding and unhiding (setting active and inactive) with a key press, but was still having the same results, items put in list on start would hide and unhide, but those added to the list during play won't.

    Got 20 minutes left in a show then I'll try to hunt down a good tutorial on how to use the debugger.
     
  17. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    Haven't looked at a tutorial yet, but something occurred to me suddenly and I had to check it. Found the culprit, just not for sure what to do about it. If I click on one of the snap points in the list that are added on start, it highlights the snap point in the hierarchy. If I click on one of the snap points in the list that were added during play, it doesn't highlight them in the hierarchy, instead it highlights the folder in the project panel that has the prefab in it.
     
  18. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    Ah, that's a common mistake. You're probably confusing actual objects with prefabs. The simple version of the mistake looks like this:

    gameObject g = Instantiate(catPrefab); // g is the created object. We are done with catPrefab
    catPrefab.transform.position = ... // using the wrong object! Use g.

    Think of g as a regular gameObject, that happens to have being created using a prefab. But now it's a 100% normal gameObject.
     
    Doug_B and JasonDN like this.
  19. JasonDN

    JasonDN

    Joined:
    Mar 11, 2013
    Posts:
    35
    Yup, figured that all out! Seems to be working correctly now. Though I kind of had to do away with my AddSnapPointsToList() as I'm not for sure how to get it and the PlaceBuilding() working together now. Don't think I need it, should only be adding snap points when I place something...

    I also realized that bit of code from 214-220 was actually correct... my walls only have ceiling snap points on them. lol.

    Changes to PlaceBuilding() below. Removed AddSnapPointsToList():


    Code (CSharp):
    1. public void PlaceBuilding()
    2.     {
    3.         Transform prefabClone = Instantiate(prefab, previewGameObject.transform.position, previewGameObject.transform.rotation);
    4.  
    5.         if (currentPreviewType == CurrentPreviewType.Foundation)
    6.         {
    7.             foreach (Transform child in prefabClone)
    8.             {
    9.                 if (child.CompareTag("Foundation_SnapPoint"))
    10.                 {
    11.                     foundationSnapPoints.Add(child.gameObject);
    12.                 }
    13.  
    14.                 if (child.CompareTag("Wall_SnapPoint"))
    15.                 {
    16.                     wallSnapPoints.Add(child.gameObject);
    17.                 }
    18.             }
    19.         }
    20.  
    21.         if (currentPreviewType == CurrentPreviewType.Wall)
    22.         {
    23.             foreach (Transform child in prefabClone)
    24.             {
    25.                 if (child.CompareTag("Ceiling_SnapPoint"))
    26.                 {
    27.                     ceilingSnapPoints.Add(child.gameObject);
    28.                 }
    29.             }
    30.         }
    31.  
    32.         if (currentPreviewType == CurrentPreviewType.Ceiling)
    33.         {
    34.             foreach (Transform child in prefabClone)
    35.             {
    36.                 if (child.CompareTag("Ceiling_SnapPoint"))
    37.                 {
    38.                     ceilingSnapPoints.Add(child.gameObject);
    39.                 }
    40.             }
    41.         }
    42.  
    43.         CancelBuilding();  
    44.     }
    This code is rough. I'm sure it could be improved and polished a ton. It still needs some work, such as adding the ability to toggle between snap points when "snapped" and honestly, to remove snap points or not add snap points in spaces already occupied by one. Not for sure how to do either of those, but will be working on trying to figure it out. With that in mind, if anyone wants to use this code, please feel free to. It's not like I could stop you anyways, but permission granted none the less. ;)

    Thank you all for your help. Doug_B, you're a hero. Thank you so much.

    Edit: Forgot to change the code back that I mentioned before pasting it. Fixed.
     
    Last edited: May 22, 2019
    Doug_B likes this.