Destroy Prefab with UI Slider

HI Guys,

I managed to create a simple tower in Unity. It is really basic. Each time the user click or touch the screen a new prefab is instantiated.
I am trying to go forward with it and add ability to use sliders.

Say slider one will control height / number of floors being created. But don’t know how to use a slider to destroy GameObjects.

  1. Basically you drag slider to the right and it creates prefabs, drag to the left and it destroys it.
  2. Also how to control scale/translation/rotation of already instantiated prefabs in run time. I want be able to twist tower in run time with the slider.

I have attach two scripts I am currently using and link to a video.

Thank you

public class Instantiate : MonoBehaviour
{
    public GameObject floorPlate;
    public float floorHeight = 1f;
    public float towerTwist = 1f;
    public float slabHeight = 0.03f;
    public float slabSize = 3f;
    public float scale = 1f;

    private float offset = 0f;
    private float rotation = 0f;
 

    // Start is called before the first frame update
    void Start()
    {
      
    }

    // Update is called once per frame
    void Update()
    {
        if(Input.GetButtonUp("Fire1"))
        {
            GameObject floor;
            floor = Instantiate(floorPlate, new Vector3(0f, offset, 0f), Quaternion.Euler(0f,rotation, 0f));
            floorPlate.transform.localScale = new Vector3(slabSize, slabHeight, slabSize);

            offset = floorHeight + offset;
            rotation = rotation + towerTwist;
            slabSize = slabSize / scale;
         
        }
    }
}

In order to adjust or delete instantiated gameobject at a later time, you need to keep track of them. The easiest way to do this would be to add them to a List. When you instantiate a new plate, you simply add it to your List, and when you want to delete it you remove it from the List and use Destroy(removedObject) to destroy the gameobject instance. When you want to rotate the entire tower at runtime, you can now iterate over the whole list of objects and apply some rotation each frame.
The slider would simply control the length of the list by adding or removing+destroying objects.

Also i’d probably not call a class “Instanitate”, since this may lead to confusion between the class and function Instantiate().

1 Like

Thank you Yoreki. It will keep me going.
So I can create a list like this :

private List<GameObject> floorPlates;

How to dynamically add and remove prefabs from this list ? Do you have link to Unity Scripting API?

Cheers

This has nothing to do with Unity or the API at all. Lists are basically just dynamically resized Arrays, which are part of the C# language (and most other programming languages as well). So what you are looking for is this:
https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netframework-4.8

You can add objects of the type you defined for the list (“List” is a list of GameObjects) by calling Add(someGameObject) on the list.
The same goes for removing an object, you can either call Remove(someGameObjectInTheList), or remove the object from some index in that list using RemoveAt(i), to remove the element at the i’th position in the list. Since you’ll add your elements in the right order, your tower basically represents the order of elements in the list. Keep in mind tho, that we start counting at 0, so the first element you add to your list gets added to index 0.

Start by adding your “floor” object to the list, simply by calling floorPlates.Add(floor). Now, each time you add a new plate, it gets added to the list. If you want to rotate all the plates, you need to iterate over the list using a for loop. In case you are unfamiliar (forgive me if i wrongly assume you are new to programming), that would look something like this:

for(int i = 0; i < floorPlates.Count; i++){ // for all numbers from 0 to the length of the list-1 (since we start at 0, the last element is length-1)
    // you access any array /list element by writing arrayName[i] with i = index of the element
    flooarPlates[i].transform.Rotate(0,10,0, Space.Self); // do something with this element, like rotating it around the y axis
}

To remove the last added plate you would use, for example, floorPlates.RemoveAt(floorPlates.Count-1), however make sure you only call that if there are actually any elements in the list, since otherwise you’ll access an index of the list that does not exist, causing an IndexOutOfBounds exception. So use if(floorPlates.Count > 0) to check that.

Removing elements will also make you run into problems with your offset-calculation, since you only increase it right now. Instead, it should be based on the amount of added floor plates (floorPlates.Count). Otherwise the new plates (after deleting one) will get positioned at an unfitting position. So you either need to decrease your offset and so on when you remove a plate, or directly calculate the offset based on the amount of stacked up plates.

Hope this helps.

1 Like

Thank you! Understood.

Yes, I have some basic knowledge of Java and I do understand if/for/while statements and boolean, but it is first time for me in Unity and C Sharp.

It is all clear to be honest, just need to keep going with it.

Just last thing :
RemoveAt - looks like it is Java thing ? Is there analog in C Sharp ?
I assume I would use destroy for it?

Just reading your post again, very helpful , thank you.

Java and C# are very similar, nearly identical to the point where you can copy and paste code from one to the other with minor adjustments.

RemoveAt(i) is a function you can all on a list, to remove the element at position i. Before that, you’ll want to remember the element from that position, so that you can call Destroy on it afterwards, to get rid of its instance in Unity.

So I tired, and it looks like I can create a list and keep track of how many items in it.
I can then instantiate game objects.
But I can find a way to destroy Game objects. I can destroy the last one.

I tried this: Destroy(objects*.gameObject);*
But it doesn’t work.
I think I don’t understand a connection between Lists and GameObjects.
```csharp
*public class TowerConstructor : MonoBehaviour
{
// Public Variables
public GameObject floor;
public float floorH = 1;
//public int numFloors = 5;

//Private Variables
private float elevation;
private List<GameObject> levels = new List<GameObject>();
int numFloors;

// Start is called before the first frame update
void Start()
{
    elevation = 0;
    floor = Instantiate(floor, new Vector3(0f, elevation, 0f), Quaternion.identity);
    levels.Add(floor);
}

// Update is called once per frame
void Update()
{
   
    if (Input.GetKeyDown(KeyCode.Space))
    {
        elevation = elevation + floorH;
        floor = Instantiate(floor, new Vector3(0f, elevation, 0f), Quaternion.identity);
        levels.Add(floor);
        numFloors = levels.Count;
        Debug.Log(numFloors);
    }

    if (Input.GetKeyDown(KeyCode.UpArrow))
    {
        Destroy(floor); // how to add index numFloors to object ?
        levels.Remove(floor);
        numFloors = levels.Count;
        elevation = elevation - floorH;
    }
  
}

}*
```

Ok, so you need to first get the element you want to work with (or destroy) from the list.
In your case, that’s always the last element, meaning:

if(levels.Count > 0){
    // get the element on the last index, which is Count-1 since we start at 0
    GameObject toDelete = levels[levels.Count-1]; // [i] means we want to get the element from that list, at index i

    // Marked location. Continue here
}

Now you know the element you want to remove. Since we want to get rid of it, we need to do 2 things with this object: remove it from the list, and destroy it. Both of which is easy, since we now know what object we are talking about. So at the marked location, we continue with:

levels.Remove(toDelete); // EITHER do this
levels.RemoveAt(levels.Count-1); // OR this to remove the item from the list

After we removed the item from the list, we only need to remove it from the scene as well, by calling destroy for it.

Destroy(toDestroy);

Done :slight_smile: Even tho, this has nothing to do with “Lists and GameObjects” specifically. GameObjects are just the elements of that list or array. Did you work with arrays in the past? GameObjects are basically like any other object. If you’ve not worked with arrays in the past, i highly suggest you learn about them, as arrays and loops are probably the most important non-trivial tools a programmer can learn about - and rather basic too.

May i add that i find the up-arrow key to be a questionable key for decreasing the tower size? :stuck_out_tongue:

1 Like

Cheers. Will try it again. Will post what I’ve done over weekend.
Yep, agree “UpArrow” ? LOL :smile:

Hi Yoreki,

I managed to make it work. But I still have questions, and I assume the code could be optimised a bit better.

  1. Something wrong with counting list items relative to objects. Does list.Count keep track of changes outside of the function?

  2. Is there a way to declare gameObject from the list just one time? Currently I have to do it every time I want to associate gameObject with object in the list. floor = levels[levels.Count - 1]

  3. Also when rotating the objects last object doesn’t move? I want an opposite behaviour , first one doesn’t moves. Rotation starts from second cube (item 1).

  4. How would you go about controlling all of this with UI sliders?

Sorry for so many questions, just trying to get my head around new concepts.

Am I correct in the things below?
When I create a list it is empty, then I levels.Add(floor) this will add an object to the list.
Now I have list with 1 item index “0”
Object name is not important.
When I instantiate / destroy I can call for specific object from the list using its index .
For example:

levels [levels.Count-1] = Instantiate (floor, new Vector3(0f, elevation, 0f), Quaternion.identity)
// This will place GameObject with index 0 and name floor into Unity, The name "floor" doesn't matter , right? It could be anything.

or do I need to:

floor = levels[levels.Count - 1];
floor = Instantiate(floor, new Vector3(0f, elevation, 0f), Quaternion.identity);

Lists are FUN

public class TowerConstructor : MonoBehaviour
{
    // Public Variables
    public GameObject floor;
    public float floorH = 1;
    //public int numFloors = 5;


    //Private Variables
    private float elevation;
    private float rotation;
    private List<GameObject> levels = new List<GameObject>();
    //int numFloors;

    // Start is called before the first frame update
    void Start()
    {
        elevation = 0;
        levels.Add(floor);
        floor = Instantiate(floor, new Vector3(0f, elevation, 0f), Quaternion.identity);
        Debug.Log(levels.Count);
    }

    // Update is called once per frame
    void Update()
    {
        // Add floor plates and add a new element to the list.
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            levels.Add(floor);
            elevation = elevation + floorH;
            floor = Instantiate(floor, new Vector3(0f, elevation, 0f), Quaternion.identity);
            Debug.Log(levels.Count);
        }

        // Remove floor plates and remove element from the list.
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            if(levels.Count > 1)
            {
                levels.Remove(floor);
                Destroy(floor);
                elevation = elevation - floorH;
                floor = levels[levels.Count - 1];
                levels.Remove(floor);
                Debug.Log(levels.Count);
            }
        }

        // Rotates Floors to the left
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            rotation = 3;
            for(int i=2; i<=levels.Count; i++)
            {
                floor = levels[i];
                floor.transform.Rotate(0f, rotation*i, 0f, Space.Self);
            }

        }

    }
}

oh no, there are also big problems when I try to add objects then rotate then add /remove again :(:rage:
I assume I will have to add another “IF” statement to test for rotation and keep track of the rotation value relative to the last list item…

Yes, when you create a list, it is empty. You can then add items to it using levels.Add(someGameObject). This will insert a new item at the next logical index, so if the list was previously empty, you now have one item at index 0. The next item will be at index 1 and so on.
The Count property always keeps track of the list size, so when you Add something it increases, and when you Remove something it decreases. I’m not quite sure what you mean with “changes outside the function” tho.

Is the first part a question or a statement? Either way, you can be pretty sure that list.Count is always accurate, unless maybe when you really try to break things intentionally with multithreading or something along those lines. But for all itents and purposes it is always correct.

I believe this is your biggest misunderstanding here. Unless there is a very specific reason for why you need the floor variable, i would remove it entirely. Instead of assigning floor = levels*,* just use levels instead wherever you would use “floor” afterwards. Just to make sure you get what i’m saying: for example in line 57, you could just use level.transform.Rotate(…) instead of floor.transform.Rotate(…), which does the same thing. The assignment is unnecessary. Unless i’m missing something, this is true for everywhere in your code.

(Edit, thought this is a helpful example: ) If you create an int intArray = new int[10], then that’s the same as if you created 10 individual int variables int0, int1, …, int8, int9. And a list is basically just an array with a dynamic size, so instead of having 10 ints, you have dynamically as many as you need. So intArray[0] is the same as the int0 variable and can be used the same way. It IS already an int. The same way, levels[0] IS already a GameObject and can be used as such. You dont have to assign it to something, unless of course for conveniences or further processing.

[quote=“Artpen, post:11, topic: 758807, username:Artpen”]
3. Also when rotating the objects last object doesn’t move? I want an opposite behaviour , first one doesn’t moves. Rotation starts from second cube (item 1).
csharp*_</em> <em><em>*public class TowerConstructor : MonoBehaviour { // Rotates Floors to the left if (Input.GetKeyDown(KeyCode.LeftArrow)) { rotation = 3; for(int i=2; i<=levels.Count; i++) { floor = levels[i]; floor.transform.Rotate(0f, rotation*i, 0f, Space.Self); } }*</em></em> <em>_*
*[/quote]
*

A for-loop running over every element starts at i=0, and runs while i < levels.Count (smaller, not smaller equals). If you want to exclude the first element, start at 1 instead of 0. I gotta ask tho: is this not throwing exceptions? Since you execute the code for all 2 < i <= levels.Count, that means for a list length of 10, i will be 10 at the last step, however, the last element will be at index 9, so accessing index 10 should throw an IndexOutOfBoundsException. If you dont already, always have the console visible somewhere in Unity and pay attention to any errors or warnings it gives you.

Also, a bit less important, but if “rotation” is supposed to be constant 3, then just assign it at the top where you declare the variable. No need to reassign it every time, unless you plan on changing it dynamically. So far it’s basically a constant.

[quote=“Artpen, post:11, topic: 758807, username:Artpen”]
4. How would you go about controlling all of this with UI sliders?
*[/quote]
*

You should generally restructure your code. Create a function to “AddTowerElement” in which you do everything that should happen when you add an element. For example, instantiating it, adding it to the list, increasing the height and so on. The create the opposide of that, a “DestroyTowerElement” and as the name suggest, have it manage everything related to that. Now you only need to call the correct method when you detect a KeyCode.Space or Keycode.UpArrow (or whatever keys you end up using). This makes things easier to work with.
Controlling it from a slider should also make more sense now, as you only need to make your slider trigger some function that checks the current tower height (Count of your list), compares it to the slider value, and Adds or Destroys tower elements acoordingly. If this does not make sense to you, then i suggest watching some tutorial on sliders or UI elements in general to get an idea.

[quote=“Artpen, post:12, topic: 758807, username:Artpen”]
oh no, there are also big problems when I try to add objects then rotate then add /remove again :(:rage:
I assume I will have to add another “IF” statement to test for rotation and keep track of the rotation value relative to the last list item…
*[/quote]
*

This is most likely also caused by you having very unstructured code, where you try to adjust many things manually at the right places. As i stated above, creating functions to handle these things helps you organize your code, so do that first. Then you probably should also base the rotation of the i’th element on the rotation of the (i-1)'th element plus some offset. So for example, the first element has a 0° rotation, the next is 0°+45° = 45°, the next is 45°+45° = 90° and so on. You could simply create a function that iterated over all elements and sets their rotations based on the element under it in a loop. You can now easily make the lowest element rotate and call that function afterwards, which makes everything rotate with a fixed rotation offset between all elements.

So TL;DR: try to think a bit more modular, create and use your own function, prevent having to manually adjust many variables at various locations in your code, and possibly even go through some basic C# tutorials first. I personally dont mind answering these questions, but very little of it is actually Unity related, and most of your problems stem from a lacking understanding regarding lists, loops and so on. So that would probably be a good starting point.

Edit: Gosh this was super annoying. For some reason the Editor formatted my texts in italic after each quote.

For some reason this doesnt work. it removes the second cube from the top. even thought logic seems correct?

if(levels.Count > 0)
    {
    GameObject toDelete = levels[levels.Count - 1];
    levels.RemoveAt(levels.Count-1);
    Destroy(toDestroy);
     }

Yes the logic seems to be right, so it should definitely remove and delete the last element from the list when executed.
Maybe you are directly adding a cube afterwards? Or it may be a cube that’s for some reason not in the array. Or something along those lines. Try to make sense of the problem. Organizing your code into proper methods and calling these also helps with that. You can use Debug.Log() to learn about the problem, by logging some values to the console and checking if the program really does what you want it to.

I highly suggest trying to fix this yourself, as it would be a great learning experience for you and shouldnt be a huge bug. If you tried and dont know what to do anymore, then post your script and i’ll have a look at it. I dont know if i’ll get to it this evening anymore tho. Plus you probably wont learn a ton that way.

1 Like

thank you, I will try over weekend.
Have a good weekend yourself

Hi Yoreki,

I spent two evenings fighting with the script and reading my Java book regarding methods/functions and arrays/lists.
It is all very interesting and it looks like I’ve got the logic correct. I have also broken down the code and made it more organised.

It looks like the only problem I have is how to make reference of the prefab I am using for the inspector. And add it to the list as a first item?

If I just levels.Add(prefab) it won’t allow me to destroy it afterwards. And I am getting:

My understanding is that it shouldn’t matter what I am passing to my List as long as a correct type.
I assume it is a problem the way I instantiate objects?!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TowerConstructor : MonoBehaviour
{
    // PUBLIC VARIABLES
    public GameObject prefab; // reference GameObject in Unity Inspector
   

    //PRIVATE VARIABLES
    private List<GameObject> levels = new List<GameObject>(); // List of floors
    private float floorH = 1.5f; // to be controlled by Slider at some point
    private float elevation;


    //STATIC VARIABLES
    private const float BASE_ELEVATION = 0; //Base elevation for the tower first floor plate



    // Start is called before the first frame update
    void Start()
    {
        AddGF();
    }



    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            AddFloor();
            Debug.Log(levels.Count);
        }

        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            if(levels.Count > 1)
            {
                RemoveFloor();
                Debug.Log(levels.Count);
            }
           
        }
    }


    // Add ground floor
    void AddGF()
    {
        GameObject groundFloor = Instantiate(prefab, new Vector3(0f, BASE_ELEVATION, 0f), Quaternion.identity);
        levels.Add(groundFloor);
    }


    // Add tower floors method
    void AddFloor()
    {
        GameObject toAdd = levels[levels.Count - 1];
        levels.Add(toAdd);
        elevation += floorH;
        Instantiate(toAdd, new Vector3(0f, elevation, 0f), Quaternion.identity);
        //Debug.Log(elevation);
    }

    //Remove tower floors method
    void RemoveFloor()
    {
        GameObject toDestroy = levels[levels.Count - 1];
        levels.Remove(toDestroy);
        Destroy(toDestroy);
        elevation -= floorH;
        //Debug.Log(elevation);
    }

    //Rotate tower floors


    //Scale tower floors
}

All the videos I watched over weekend, usually have two scripts. Spawner and Destroyer.
Should I do the same thing? It doesn’t seems logical in my case.

I have updated my scrip, it is bit more organised. But Still have a problem with

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TowerConstructor : MonoBehaviour
{
    // PUBLIC VARIABLES
    public GameObject prefab; // reference GameObject in Unity Inspector


    //PRIVATE VARIABLES
    private List<GameObject> levels = new List<GameObject>(); // List of floors
    private float floorH = 1.5f; // to be controlled by Slider at some point
    private float elevation;


    //STATIC VARIABLES
    private const float BASE_ELEVATION = 0; //Base elevation for the tower first floor plate



    // Start is called before the first frame update
    void Start()
    {
        AddGf();
    }



    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            AddFloor();
            Debug.Log(levels.Count);
        }

        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            if (levels.Count-1 > 1)
            {
                RemoveFloor();
                Debug.Log(levels.Count);
            }

        }
    }



    // Add ground floor
    void AddGf()
    {
        levels.Add(prefab);
        GameObject groundFloor = Instantiate(prefab, new Vector3(0f, BASE_ELEVATION, 0f), Quaternion.identity);
    }


    // Add tower floors method
    void AddFloor()
    {
        GameObject toAdd = levels[levels.Count-1];
        levels.Add(toAdd);
        elevation += floorH;
        Instantiate(toAdd, new Vector3(0f, elevation, 0f), Quaternion.identity);
    }


    //Remove tower floors method
    void RemoveFloor()
    {
        GameObject toDestroy = levels[levels.Count-1];
        levels.Remove(toDestroy);
        Destroy(toDestroy);
        elevation -= floorH;
    }


    //Rotate tower floors


    //Scale tower floors
}

Hi Artpen,

Can make sense in some cases, but you dont have to, and i personally would not in your case.

The “AddGF” method in your first (newly posted) script looks correct. What problem is there with it?
The thing is, if you do something like Add(prefab), then you try to add the prefab itself to the list. When you call Destroy on that, then you are telling Unity to destroy the prefab itself, which you can be glad Unity is preventing from happening :wink:
You always want to work with instances of some prefab. That way nothing is lost when you destroy it. Destroying your prefab would be similar to permanently deleting some 3d model, script or something else from your project.

Hope this helps clearing your misunderstanding about prefabs. What exactly is it you are trying to do when adding the prefab? Again the AddGF method you first posted looked fine:

    // Add ground floor
    void AddGF()
    {
        GameObject groundFloor = Instantiate(prefab, new Vector3(0f, BASE_ELEVATION, 0f), Quaternion.identity);
        levels.Add(groundFloor);
    }

Did you fix the problem that some cube at the top remained when you tried to delete the top-most cube? What’s the remaining problem you are fighting right now?

Edit: I did not look at your code as much as i probably should have, so let me just quickly say:
It already looks much cleaner than before! Good job :slight_smile:

1 Like