Is it possible to call a prefab in Unity using a variable? - c#

I'm making an App that instantiates a certain model when the camera is pointed to a QR.
The app then comunicates to a website and shows the data of a place related to the model (example: botanic garden, it shows some flowers and a text about ecology)
Thing is, while I can use text from a website using JSon, I can't seem to find a way to do the same.
I also have a code that makes a JSon, this one returns the name of the model.
My question is, is there a way to use that name to search the prefab in my assets and thus instantiate the prefab?

yes, it is possible, create a dictionary (string, GameObject) and a list for the inspector with its prefabs, use the list to create a new dictionary
Private void Awake(){
dictionary = new Dictionary (string,GameObject); // <- (don't use parentheses)
foreach ( GameObject go in yourList ) {
dictionary.Add(go, go.name);
}
Private void Search(string name){
if(dictionary.ContainsKey ( name )){
Instantiate(dictionary[name]);
}
}

Workaround, assign prefabs in editor, in your script have references to different prefabs:
public GameObject prefab1;
public GameObject prefab2;
When you want to instantiate your prefab
switch (JSONstring) {
case "Garden":
Instantiate(prefab1, new Vector3(0, 0, 0), Quaternion.identity);
break;
}

Related

assign class objects instead of the actual script class to unity game objects in unity c#

I want to create a game in Unity C# in which the player will add as much as drones he needs as game objects to the game world. Therefore, I made a drone prefab and later in runtime I ask the number of drones the player wants to have (e.x. n numbers) and instantiate the prefab n times.
The script which is attached to the drone prefab is called drone_script.
Therefore, I am having a drone_script class as a general class. This class has an attribute (let's call it subscriber) which should have different unique values for each drone. So, it is created as a null reference in drone_script general class and later in runtime, I will initialize it.
During runtime, I create n (the same numbers as the drone game objects) objects from this class and assign their subscriber attribute different values.
This is how it looks:
some_class
{
for (int i = 0; i < number_of_Drones; ++i)
{
drones_script[i] = new drone_script();
\\ here I am creating n objects of my general drone_script class
drones_script[i].drone_subscriber = a unique value;
\\ here I am assigning to each of the drone objects' drone_subscriber attribute a different value.
drco = new Vector3((float)0, (float)1, (float)0);
drones[i] = Instantiate(Resources.Load("drone"), drco,
Quaternion.Euler(-90, 0, 90)) as GameObject;
\\I instatiate the drone prefab in unity game world here.
}
}
drone_script
{
public ROSBridgeSubscriber drone_subscriber;
void Start() { \\some code }
void FixedUpdate () { \\some code }
}
My problem is when the drone game objects are created in unity, the general class drone_script is attached to them and because of this their subscriber attribute is null. However, I need the drone_script[i] object to be assigned to them as their script, not the general drone_script itself. Because only then each drone game object has its corresponding subscriber attribute value from before even the start function is being called.
Am I thinking correctly?
If yes, how can I do it?
First of all, I suggest you to assign dronePrefab using editor or in Start method of "some_class" because Resources.Load("drone") might be resource consuming, since you load a drone resource "n" times.
Now your specific problem, you don't need to create new instances of "drone_script" because as you mentioned yourself, each drone that you create has his own "drone_script". So basically what we need to do is:
Instantiate drone and save his reference (as GameObject)
Get that drone's "drone_script" (using GetComponent<>(); )
Save that script reference in our array so we can use it later
Adjust some variables of that script now or later in the game
Something like that should work. In code everything should be something like this (didn't test, might be some miss types)
some_class
{
public GameObject dronePrefab;
for (int i = 0; i < number_of_Drones; ++i)
{
// Step 1.
drco = new Vector3((float)0, (float)1, (float)0);
drones[i] = Instantiate(dronePrefab, drco,
Quaternion.Euler(-90, 0, 90)) as GameObject;
// Steps 2 & 3
drones_script[i] = drones[i].GetComponent<drone_script>();
// Step 4
drones_script[i].drone_subscriber = a unique value;
}
}
EDIT:
Just to point it out: drone_script has to be attached to drone prefab

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

How to access the properties of any created Game Object from another script Unity C # 2D

I have instantiated multiple game objects like this part of the code:
GameObject newCard = Instantiate(CardPrefab, new Vector3(x, y, z), transform.rotation);
x,y and z being parameter so I could spawn the objects in different positions.
My question is how could I access the name of the Game objects or the properties from another script. Each of my game objects has a property which gives it a value.
In another script I want to add the values of all objects but I am not sure how to access the objects from another script to get their name and values.
Thanks for the support.
If you are trying to access the GameObjects name, with the way you have done it, it is as simple as saying:
newCard.name = "The new name!";
if you are trying to access the properties of a script that you are assigning to this specific GameObject it would be best to do something like this:
public class card : MonoBehavior {
public int faceValue;
public int suitValue;
// or whatever else you need in your card...
}
In your spawner Object:
public class Spawner : MonoBehaviour {
public Card cardPrefab; // Instantiate will spawn this correctly and return the attached component to you.
// Less prone to error for this specific script since we are expecting a prefab
// with a card script attached.
// Spawn Method
void SpawnCard(float x, float y, float z, int value, int suitValue)
{
Card newCard = instantiate(cardPrefab, new Vector3(x, y, z), transform.rotation);
newCard.gameObject.name = "someName";
newCard.faceValue = value;
newCard.suitValue = suitValue;
}
}
To access all gameobject with this script you can either create some form of manager that tracks them all as you create them(Preferred method),
or...
You could use GameObject.FindObjectsOfType<Card>() Which is incredibly slow as you get bigger scenes, if you have a small scene then it is fine.
One thing you could do is to use the GameObject.Find({String gameobjectname}) method, as documented here. This will search through all of the gameobjects and return the gameobject reference to the object with that same name. The issue with this is it only returns one.
Another thing is you could use GameObject.FindGameObjectsWithTag({String tag}). For this one you need to ensure each of the objects have a tag. Based on your object name being card I would set the tag to be Cards. Then you can use the array you get back and do stuff you want with it. That's documented here

Unity - C# Trying to change a tag of an item in one list when an object in another list is destroyed

Recently took an online course on beginner c# w/ unity and unfortunately the instructor ended the course with a broken and unfinished code. Having about 3 weeks of experience under my belt, I decided to learn, research, and make a viable build before moving on.
I've got two lists and a variable:
private List<Widget> WidgetList = new List<Widget>();
private List<Collider2D> ItemList = new List<Collider2D>();
private Collider2D itemSpot;
I then have an if statement inside the update method that, upon clicking the left mouse button the code changes the tag of the item clicked, adds that item to a ItemList, and generates a new Widget in that location.
if (Input.GetMouseButtonDown (0)) {
Vector2 worldPoint = Camera.main.ScreenToWorldPoint (Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast (worldPoint, Vector2.zero);
if (hit.collider.tag == "BlankItem") {
itemSpot = hit.collider;
itemSpot.tag = "FullItem";
RegisterItemSpot (itemSpot);
MakeWidget (hit);
}
}
This currently works fine, albeit I get the impression it was not the most ideal code (was not a well made course). The call to the RegisterItemSpot and MakeWidget methods work flawlessly, each properly uses the .Add to their respective lists. At the end of the game, I've got a method that also clears both lists without a problem.
Here is my hurdle: If I destroy a single instance of a Widget, I would like the itemSpot that was associated with it to change its tag to "BlankItem" again. I used a similar function to the previous to destroy the Widget:
void Update (){
if (Input.GetMouseButtonDown (0)) {
Vector2 worldPointt = Camera.main.ScreenToWorldPoint (Input.mousePosition);
RaycastHit2D hitt = Physics2D.Raycast (worldPointt, Vector2.zero);
if (hitt.collider.tag == "Tower" && destroyMode != false)
Destroy(hitt.transform.gameObject);
}
}
From there in the OnDestroy of the Widget class i have it remove itself from the list, and that works fine too.
In my head I imagine a couple lines of code that, within the OnDestroy of the Widget, would rename the itemSpot that was initially associated with that specific destroyed Widget. I don't know how to associate the new Widget with the specific itemSpot. Something along the lines of "if this object is destroyed, then the object associated with THAT collider gets retagged". The few ideas I have would require rebuilding this code from the ground up (maybe making arrays instead of lists?)and I feel like my novice brain would be better served just moving on to my next course instead of taking all that time. Maybe not? I just need some advice from more experienced folks. If i need to provide more information, please let me know (10 classes of hundreds of code seemed like too much to just throw on here).
EDIT: Ive included the MakeWidget() method and the RegisterItemSpot() and RegisterWidget() methods. I should have from the get-go, very sorry!
public void MakeWidget (RaycastHit2D hit){
if (!EventSystem.current.IsPointerOverGameObject) {
Tower newWidget = Instantiate (widgetBtnPressed.WidgetObject);
newWidget.transform.position = hit.transform.position;
RegisterWidget (newWidget);
widgetBtnPressed = null;}
}
public void RegisterItemSpot (Collider2D itemSpot){
ItemList.Add(itemSpot);
}
public void RegisterWidget (Widget widget)
{
WidgetList.Add(widget);
}
I think your best approach is creating a new containerclass, and then declaring a new list of said class. Here is an example
public class WidgetContainer()
{
public Widget widget;
public Collider2D collider;
}
Then replace your two lists with this one
public List<WidgetContainer> widgetContainers = new List<WidgetContainer>();
Then merge your makewidget and registeritemspot method into one method and create and add a new WidgetContainer to the list. Now you will always have a connection stored between these two variables.
var newContainer = new WidgetContainer
{
widget = newWidget,
collider = itemSpot
}
Finally in your OnDestroy() of the said widget as you describe, you can do this:
widgetContainers.First(x => x == this).collider.tag = "your tag here";
"this" references to instance of the class. In your case this would be your Widgetscript.
Do not that you will need to add this using statement at the top of your script for Linq.
using System.Linq;

Selecting random positions from array without duplicates

In my game, there are many enemies and have this script on it, where I am set their navmesh destination by randomly selecting an element from an array of positions. However, sometimes all enemies go at same position, so I am trying to skip previously generated array indices and find a new index. Any suggestions for this? I don't want to use the goto statement.
Here is code I have done so far:
void move_on_fired_positions()
{
navmesh.Resume ();
again:
new_position = Random.Range (0, firedpoints.Length);
if (prev_num != new_position) {
navmesh.SetDestination (firedpoints [new_position].position);
prev_num = new_position;
} else
{
goto again;
}
}
One thing you can try is keeping a dynamic list of available positions, rather than an array. (Don't worry - they're both serialized and modified the same way in the Unity editor interface.) This way, you can remove positions from the list as they are assigned to enemies, ensuring you never assign duplicates. Here's an idea of how that might look:
public List<Transform> firedpoints;
// Returns available firing position, if any
public Transform GetRandomFiringPosition()
{
if (firedpoints.Count > 0)
{
// Get reference to transform, then remove it from list
int newPositionIndex = Random.Range (0, firedpoints.Length);
Transform returnedPosition = firedpoints[newPositionIndex];
firedpoints.RemoveAt(newPositionIndex);
return returnedPosition;
}
else
{
// Or you can specify some default transform
return null;
}
}
// Makes firing position available for use
public void RegisterFiringPosition(Transform newPosition)
{
firedpoints.Add(newPosition);
}
This code should be in a script on a single object in the scene, which the enemies should have a reference to (or, you can change the code a little and make the class into a singleton, so its methods can be called without a direct reference). Whenever an enemy needs a new position, it can call GetRandomFiringPosition() and store the returned Transform in a variable for future reference.
You haven't determined what conditions make a position available for use again, but when you have, you can call RegisterFiringPosition() to get it back into the list. As a nice side effect, this also makes it possible to assign brand new positions to enemies, for example in response to player-triggered events.
Hope this helps! Let me know if you have any questions.

Categories

Resources