Unity 3D spawning a prefab from a List - c#

I made a script where it loads the prefabs from my Resources/ai folder and loop up to five times and generate a random index number. It successfully loaded my prefabs as GameObjects and does generate random index based on the count of the List.
However, I am having trouble upon spawning my prefab to my scene since Instatiate command does return an error while I'm coding.
I have tried methods to grab the name of the prefab through list[index].name or even list[index].toString() but it wont work. (Note: list stands for a variable of a list index is just an index to grab the game object on the list. This is not the actual code).
How to spawn a loaded GameObject prefab to my scene with a list?
My current code:
public List<GameObject> ai;
public GameObject[] spawn;
int indexAI;
int indexSpawn;
private void Start()
{
var res = Resources.LoadAll<GameObject>("ai/");
foreach (GameObject obj in res)
{
ai.Add(obj);
}
for (int x=0; x<5; x++)
{
indexAI = UnityEngine.Random.Range(0,ai.Count);
indexSpawn = UnityEngine.Random.Range(0, spawn.Length);
string name = ai[indexAI].name;
Debug.Log(name);
//I am currently using this kind of format since this is what I know for now.
Instantiate(name,spawn[indexSpawn].transform.position,spawn[indexSpawn].transform.rotation);
}
The error looks like this
Thanks!

You are attempting to instantiate a string - you need to instantiate the gameobject in your ai list instead.
Instantiate(ai[indexAI] ,spawn[indexSpawn].transform.position, spawn[indexSpawn].transform.rotation);

Related

How do I access a prefab and all of its instantiated clones in c# and unity?

I need a little help again please. I'm currently working on powerups and it's setup so that the powerup affectsthe prefab thats put into its prefab slot, so this is means it is currently only affecting one object (in my case, enemy). When the game starts I spawn about 20 enemies and if i pickup the powerup I want it to affect all those spawned enemies and not only the one that is dragged onto the script, does anyone have a solution or alternate method to do this please?
When instantiate them store them in a list and then iterate through all and update their value
In your case e.g.
// if enemies have a certain Component rather use that type here instead of GameObject
public GameObject enemyPrefab;
// and use the same type here instead of GameObject
private List<GameObject> enemyInstances = new List<GameObject> enemyInstances;
private void Start()
{
for(int i = 0; i < 20; i++)
{
var newEnemy = Instantiate(enemyPrefab);
enemyInstances.Add(newEnemy);
}
}
and then later
public void UpdateEnemies()
{
foreach(var enemy in enemyInstances)
{
// skip already destroyed objects
if(!enemy) continue;
// whatever to be done to the enemies
}
}
To get all game objects to an array you can use the Tag.
Just add an 'Enemy' tag to the prefab and use the code below:
public GameObject[] enemys;
void Start()
{
respawns = GameObject.FindGameObjectsWithTag("Enemy");
}
Full Unity documentation HERE

Dynamically add a Component from a list of components

I will break it down to give an understanding.
I have a parent class Item.
I have child classes for each type of Item (Weapon, Armor, etc).
I have a list of all the items.
I'm trying to randomly grab an item from that list, and then add it as a component.
My items each randomly generate their own names, stats, etc, but they are all specific to the type of item they are and so I needed them to be generated in their own classes and not just generating "Item".
So here is what I have so far.
public Item RandomList(List<Item> list)
{
return list[random.Next(list.Count)];
}
This returns a random item from my list.
public void GenerateLoot(GameObject gameObject, List<Item> list)
{
gameObject.AddComponent(randomManager.RandomList(list));
}
This is where I'm running into problems. It says : "cannot conver from Item to System.Type". I've tried various things such as using typeof, etc.
This is something I'm fairly new to.
AddComponent seems to not support parameters other than type of a script, and if I understood you correctly, your items are all customizable in many aspects that should be applied when you add component in runtime. I advise adding method AddToGameobject in class Item:
abstract class Item : Monobehaviour {
public abstract void AddToGameObject (GameObject obj);
}
class Weapon : Item {
int damage;
int cooldown;
public override void AddToGameObject (GameObject obj) {
var copy = obj.AddComponent <Weapon> ();
copy.damage = damage;
copy.cooldown = cooldown;
}
}
class Armor : Item {
int protection;
public override void AddToGameObject (GameObject obj) {
var copy = obj.AddComponent <Armor> ();
copy.protection = protection;
}
}
And your loot generation function will become
public void GenerateLoot (GameObject gameObject, List <Item> list) {
randomManager.RandomList (list).AddToGameObject (gameObject);
}
If you want to attach existing component itself instead of a copy, you can't (explanations: 1, 2).
As MSL says, you need to give it a Type argument to AddComponent, as the documentation suggest:
https://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html
So my suggestion is to do it that way:
1.First you call and add the component (similar as you do)
gameObject.AddComponent(typeof(Item)) as Item;
or my prefered way:
gameObject.AddComponent<Item>();
2.Then add the item values and properties:
gameObject.GetComponent<Item>().value = item.value;
You are trying to set a GameObject Component of type Item. If you try to Add Component Item in the Inspector of any GameObject, there is none (unless your script is called Item, but that won't do what you're trying to do).
I'm assuming in your example, you may want to generate a random item upon opening a chest. There is a couple of steps to create a fully functional item, so I can't really give it all to you, so I'll give you an idea of how you'd do it.
The following will randomly change the image of the GameObject by Adding Component SpriteRenderer and randomly change the Sprite value of this Component.
public class RandomSpawn : MonoBehaviour {
GameObject gameObject;
public Sprite[] weapons; // assign an array of weapon sprites in Inspector
void Start() {
gameObject = new GameObject();
SpriteRenderer gameObjectSR = // add SpriteRenderer component
gameObject.AddComponent(typeof(SpriteRenderer)) as SpriteRenderer;
int randomIndex = Random.Range(0, weapons.Length); // random index within weapons
gameObjectSR.sprite = weapons[randomIndex]; // assign Sprite from weapons
// instantiate object
Instantiate(gameObject, transform.position, transform.rotation);
}
}
So you begin with Sprite[] weapons, an array of weapon images (basically). In the Start() function, create a new GameObject, and add a SpriteRenderer (this doesn't have to be random because you want to change what the item looks like). The random part begins with obtaining a random int (the index which is inside the length of the weapons). Then assigning a random weapons Sprite onto the GameObject, and instantiating it. This would work as compared to your attempt because SpriteRenderer is a Unity GameObject Component.
For this GameObject, you may want to attach your Weapon script (since it's derived from the Item class) and assuming you have a Weapon function SetDamage, like this:
Weapon w = gameObject.AddComponent<Weapon>();
w.SetDamage(Random.Range(5,10)); // set weapon damage between 5 and 10
This will then change the value of the Weapon damage, which can be accessed by script for when you are damaging an enemy, for example.
Note: There is probably a way to create your own components, if the normal library of components doesn't satisfy your needs.

Using foreach loop to find GameObjects that are children of something else (Unity3d)

I am making a video game, and while I have an existing code in generic C#, I now have to move it to Unity. I have some basic knowledge with generic C#, but I just started to learn the Unity way of coding.
For starters, I want to write a code that positions all game areas to correct positions, then turn them invisible. Yes, don't be surprised, they need to be all in same places.
Areas can have three size options, I called them Small, Medium and Large. Small and large areas have to be written manually.
List <GameObject> SmallAreas = new List<GameObject>();
void DefineSmallAreas()
{
SmallAreas.Add(areaConfirmLoad);
SmallAreas.Add(areaConfirmQuit);
SmallAreas.Add(areaConfirmSave);
SmallAreas.Add(areaGameSaved);
SmallAreas.Add(areaSave);
SmallAreas.Add(areaLoad);
}
Same with large areas.
Now, all other areas, are medium, and there is a large number of them.
So, I want to go through all game objects that are children of "areaContainer", check if their names start with "area", and if they do, I want to add them to MediumAreas list.
That's how I tried it:
void DefineMediumAreas()
{
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (GameObject thisObject in areaContainer)
{
char[] a = thisObject.Name.ToCharArray();
if (a.Length >= 4)
{
char[] b = { a[0], a[1], a[2], a[3] };
string thisObjectType = new string(b);
(if (thisObjectType == "area")&&(!(SmallAreas.Contains(thisObject))
&&(!(LargeAreas.Contains(thisObject)))
{
MediumAreas.Add(thisObject);
}
}
}
This however shows an error, that "areaContainer" can't be used that way, I don't have access to Unity now, so can't copy exact message. I think that it's something like "Gameobject doesn't have IEnumerator".
I did try to google for the better approach, and found something called "transform".
foreach(Transform child in transform)
{
Something(child.gameObject);
}
What I don't understand, is how to use this "transform" in my specific situation.
Please don't get angry at me if this question is silly, I am very new to Unity, and have to learn it from scratch.
And a small second question. Will this work of turning object invisible work:
foreach(GameObject thisObject in MediumAreas)
{
thisObject.position = MediumVector;
thisObject.GetComponent<Renderer>().enabled = false;
}
MediumVector is location where the object must be moved to, and it seems to be working.
You can do this: foreach(Transform child in transform)
because the Transform class implements IEnumerable and have some mechanism that enables you to access the child GameObjects with the foreach loop.
Unfortunately, you can't do this: foreach (GameObject thisObject in areaContainer)
because areaContainer is a GameObject and this implementation is not done for the GameObject class. That's why you are getting this error:
foreach statement cannot operate on variables of type
'UnityEngine.GameObject' because 'UnityEngine.GameObject' does not
contain a public definition for 'GetEnumerator'
To fix it, change your loop to use Transform after finding the GameObject:
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (Transform thisObject in areaContainer.transform){}
The complete code:
List<GameObject> MediumAreas = new List<GameObject>();
void DefineMediumAreas()
{
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (Transform thisObject in areaContainer.transform)
{
//Check if it contains area
if (thisObject.name.StartsWith("area"))
{
//Add to MediumAreas List
MediumAreas.Add(thisObject.gameObject);
}
}
}
There is multiple approaches to your problem. One of them is using Tags. Simply mark your MediumArea prefab with some Tag and then you can find all tagged GameObjects with FindGameObjectsWithTag(string) (Unity Docs). Then you can populate your collection like that:
MediumAreas.AddRange(FindGameObjectsWithTag("MediumArea"));
Second approach could be finding all objects with same attached script FindObjectsOfType<T>() (Unity Docs). This is usefull when you are searching for instances of same type, like Medium Area.
Lets say that you have an Area script
public class Area : MonoBehaviour {
public AreaSize Size; // AreaSize is Enum
}
Then you can simply find your areas like:
var allAreas = FindGameObjectsOfType<Area>();
var mediumAreas = allAreas.Where(e => e.Size == AreaSize.Medium); // using System.Linq;
I created a project to answer your question, the final result will be like this :
As you can see I have created a game object called "areaContainer" and added 3 children with respective names : "area01", "area02" and "anotherObject".
The script that manage to get all "areaContainer" children that start with "area" looks like :
public GameObject areaContainer;
public List<GameObject> MediumAreas = new List<GameObject>();
private void Start()
{
DefineMediumAreas();
}
void DefineMediumAreas()
{
for (int i = 0; i < areaContainer.transform.childCount; i++)
{
var childGameObject = areaContainer.transform.GetChild(i).gameObject;
if (childGameObject.name.StartsWith("area"))
MediumAreas.Add(childGameObject);
}
}
1- I ended up referencing the areaContainer object in a script rather than using GameObject.Find because it's more performant.
2- To get a child of a game object you need to access to its transform and call GetChild(index). So by iterating through the parent container which is "areaContainer" we are getting its childCount.
3- To check if the name start with "area", simply use .StartsWith("area") which return true or false.
For your second question, you can hide object disabling the Renderer or by deactivating it (thisObject.SetActive(false);
I hope this help you; Happy coding!
You want to access all game objects that are children of "areaContainer"
In void DefineMediumAreas() function, you need to Transform[] to get an array of childeren. Use this:
Transform[] areaContainer = GameObject.Find("areaContainer").GetComponentsInChildren<Transform>();
foreach(Transform thisTransform in areaContainer)
{
...
}
I hope it helps you

Accessing instantiated resources from another script C#

For context, I have instantiated multiple GameObjects over time which represent the score in my game. I know how to change the position where they are instantiated.
string scoreText = score.ToString ();
for (int i = 0; i < scoreText.Length; i++)
{
var go = (GameObject)Instantiate(Resources.Load(scoreText[i].ToString()));
go.transform.localPosition = new Vector3(0.02F + i * 0.01F, 0.13F, 0);
}
What I don't know how to do is change the position in another script. Again, for context, on Game Over in the other script, I want to change the position of these objects I instantiated.
I have tried multiple variations of GetComponent and GameObject. I can't pin down one GameObject that I want to access, because it is multiple, ever changing GameObjects. I feel like I am not looking at this right. Does anyone know how to do this?
Well one way to go would be to make your GameObject variable go a class variable so that you can access it in other methods. But if you spawn many objects you can make an array of GameObjects and then iterate through them with to find one you need. You can make the array a public variable so that other scripts can look at it.
The other scripts can access the data of your script that is public and static, so the first step is to create a variable that will hold your score in the main script:
public static GameObject[] Score;
During the initialization, use this variable to save the score:
string scoreText = score.ToString ();
Score = new GameObject[scoreText.Length];
for (int i = 0; i < scoreText.Length; i++)
{
Score[i] = (GameObject)Instantiate(Resources.Load(scoreText[i].ToString()));
Score[i].transform.localPosition = new Vector3(0.02F + i * 0.01F, 0.13F, 0);
}
At this point, inside the game over script, you should be able to use the score data from the main script (if this doesn't work, make sure you are using the right namespaces and references, but if the two scripts are in the same project there shouldn't be any problems):
MainScript.Score //MainScript is the name of your main script
If you want to iterate over the score in the game over script:
for (int i = 0; i < MainScript.Score.Length; i++)
{
MainScript.Score[i] = do something here...
}

Enabling Game objects

So I've been trying to access game objects in my scene (which are disabled), to enable them. But I'm getting an error: "Object reference not set to an instance of an object"
private List<Character> characters = new List<Character>();
private List<GameObject> characterGameObjects = new List<GameObject> ();
public void loadSceneCharacters(){
if (characters != null) {
for(int i = 0; i < characters.Count; i++){
characterGameObjects.Add(GameObject.Find(characters[i].CharacterName));
characterGameObjects[i].SetActive(true);
}
}
}
You can't find disabled gameobjects.
A solution is to either reference them in inspector or find them all first when they are enabled, then disable those you don't need.
I think your characters list is empty.
If you don't Instantiate GameObjects you can fill characters list with drag and drop so you can change code like this.
public List<Character> characters = new List<Character>(); ///Thanks to public
//you can see list in Unity Editor
private List<GameObject> characterGameObjects = new List<GameObject> ();
public void loadSceneCharacters(){
if (characters != null) {
for(int i = 0; i < characters.Count; i++){
characterGameObjects.Add(characters[i])); //get and add gameobject
characterGameObjects[i].SetActive(true);
}
}
}
If you have dynamically created GameObjects you can fill the list with GameObject.Find("CharacterName");
However, i dont suggest that find every gameobject with name.Instead of that, During the Instantiate state you can add new gameObject instance to your character list.
Even if the character list would be empty this code would not throw an exception.
The problem is probably that you can not find disabled gameobject using the find methods (at least that was my experience, correct me if i am wrong guys).
What i usually do as a workaround instead of searching is to add the gameobjects via drag and drop. If this is not possible you can either, search for the gameobjects in Awake or Start, add them to your list and disable them. Or do some sort of adding when you instanciate them... Basicly you have to somehow get the reference to the gameobjects before you disable them.
Hope this helps.
Something that can help you is to create an empty object, then put all your characters inside the empty object.. then by code do something like:
foreach(Character c in MyEmptyObject.GetComponentsInChildren(Character, true))//the true in this
//line indicates that it should search for inactive objects
{
c.gameObject.SetActive(true);
}
this assuming that your characters have an script called "Character"

Categories

Resources