I have a gameObject (myObject) and a component that attached somewhere in that gameObject (myComponent).
I duplicate game object:
var duplicate = Instantiate(myObject);
And then I want to have reference to the same component, but on my duplicate gameObject.
Is there a way to get same component on duplicated object?
I tried to get component by index, but is is not work for hierarchy of game objects.
You could make a copy of component but since component can have only 1 parent (gameObject) this means 1 component instance cannot be shared with 2 game objects.
Otherwise if you are OK with having 2 separate instances of component on both game objects, you could create Prefab out of gameObject (with component) and instantiate prefab.
Considering that you might have multiple components of the same type, but with different settings (properties) and you want to find component with the same settings you would have to use GetComponents and loop through results to find a new (duplicated) component with exactly the same settings.
Considering (for simplicity) you look for property called Id:
MyComponent myObjectsComponent = ... // here logic to find it, etc
GameObject duplicate = Instantiate(myObject);
List<MyComponent> myComponents = duplicate.GetComponents<MyComponent>();
// This can be replaced with bellow LINQ
MyComponent foundComponent = null;
foreach(MyComponent c in myComponents) {
if (c.Id=myObjectsComponent.Id) {
foundComponent = c;
break;
}
}
Alternatively you could use LINQ to simplify loop:
MyComponent foundComponent = (from c in myComponents where c.Id=myObjectsComponent.Id select c).FirstDefault<MyComponent>();
Thanks to all of you.
I was needed something like this:
public static T GetSameComponentForDuplicate<T>(T c, GameObject original, GameObject duplicate)
where T : Component
{
// remember hierarchy
Stack<int> path = new Stack<int>();
var g = c.gameObject;
while (!object.ReferenceEquals(g, original))
{
path.Push(g.transform.GetSiblingIndex());
g = g.transform.parent.gameObject;
}
// repeat hierarchy on duplicated object
GameObject sameGO = duplicate;
while (path.Count != 0)
{
sameGO = sameGO.transform.GetChild(path.Pop()).gameObject;
}
// get component index
var cc = c.gameObject.GetComponents<T>();
int componentIndex = -1;
for (int i = 0; i < cc.Length; i++)
{
if (object.ReferenceEquals(c, cc[i]))
{
componentIndex = i;
break;
}
}
// return component with the same index on same gameObject
return sameGO.GetComponents<T>()[componentIndex];
}
Try this:
var duplicate = Instantiate(myObject) as GameObject;
var componentDup = duplicate.GetComponent<__YOUR_COMPONENT__>();
https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html
Alright, I'm going to have a go at trying to understand what you want as well... My interpretation is that you want a reference to a specific myComponent on your duplicateObject. Very well, this is how you could do it:
public class MyObject : MonoBehaviour {
// Create a reference variable for the duplicate to have
public Component theComponent { get; set; }
void Start()
{
// Save the component you want the duplicate to have
theComponent = GetComponent<Anything>();
// Create the duplicate
var duplicate = Instantiate(gameObject);
// Set the component reference to the saved component
duplicate.GetComponent<MyObject>().theComponent = theComponent;
}
}
This way you should have a reference to the same component in all your instances of the object. You could also just create a script that holds a static reference to the specific component, which every script can reach without instantiating anything.
using UnityEngine;
public class DataHolder {
public static Component theComponent;
}
Related
I have just started to learn Unity 2d and I have taken a task at hand where I want to Instantiate a Prefab from Assets folder and then disable some of the child elements in it once the prefab is initiated. Following is my code :
void createPuzzleGame()
{
Puz = Resources.Load("Dog") as GameObject;
Instantiate(Puz, PuzArea.transform);
for (int i = 0; i < Puz.transform.childCount; ++i)
{
Transform currentItem = Puz.transform.GetChild(i);
if (currentItem.name.StartsWith("a") || currentItem.name.StartsWith("og"))
{
currentItem.gameObject.SetActive(false); //this line doesn't work
}
else
{
Debug.Log(currentItem.name);
}
}
}
I want to disable all child images of the prefab Puz that start with letter 'a' or 'og'.
The prefab Dog(clone) gets created on running the code. However the child elements don't seem to disable. Where am I going wrong? Please help.
You are trying to iterate through the children of the original prefab Puz.
You rather need to iterate through the newly created instance
var instance = Instantiate(Puz, PuzArea.transform);
foreach(Transform child in instance.transform)
{
if (child.name.StartsWith("a") || child.name.StartsWith("og"))
{
child.gameObject.SetActive(false);
}
else
{
Debug.Log(child.name);
}
}
Puz is the prefab's reference. Instantiate returns the actual instance you create from Puz. You need to change the instance:
Transform dog = Instantiate(Puz, PuzArea.transform).transform;
for (int i = 0; i < dog.childCount; ++i) { /* ... */ }
or, you can add a script to your prefab, and move this logic there, something like this (in this case, you have to add your references to _images on the prefab, but you can use your search by name logic also):
public class Puzzle : MonoBehaviour
{
[SerializeField] private GameObject[] _images;
public void SetImagesVisibility(bool visible)
{
for (int i = 0; i < _images.Length; i++) _images[i].SetActive(visible);
}
}
and you can use this at instantiating:
Puzzle puz = Instantiate(Puz, PuzArea.transform).GetComponent<Puzzle>();
puz.SetImagesVisibility(false);
I'm making a save system for my game, and have run into a NullReferenceException while loading the keybindings for one of the player objects (player 2). Since I used identical code to load the keybinds for player 1, and that one works fine, I believe that the problem comes from player 2 being inactive when the load script is run.
Here's most of the relevant code, based on Brackey's Save/Load tutorial:
public string[] player1;
public string[] player2;
public GameData(AchievementsManager am, GameObject play2)
{
var p1 = play1.GetComponent<playerMovement>();
var p2 = play2.GetComponent<playerMovement>();
player1 = new string[4] { p1.up, p1.down, p1.left, p1.right};
player2 = new string[4] { p2.up, p2.down, p2.left, p2.right};
}
public class SaveManager : MonoBehaviour
{
public GameObject play2;
void Start()
{
GameData data = SaveSystem.loadGame();
if (data != null)
{
var p1 = play1.GetComponent<playerMovement>();
var p2 = play2.GetComponent<playerMovement>();
string[] p1_controls = data.player1;
string[] p2_controls = data.player2;
p1.up = p1_controls[0]; p2.up = p2_controls[0];
p1.down = p1_controls[1]; p2.down = p2_controls[1];
p1.left = p1_controls[2]; p2.left = p2_controls[2];
p1.right = p1_controls[3]; p2.right = p2_controls[3];
}
}
}
The error is on the line p2.up = p2_controls[0];, but I think that it's because there's a problem with play2.GetComponent<playerMovement>(); related to player 2 being inactive at startup. Is there a good way to fix this without making player 2 active at startup, or at least a way to better understand what exactly is causing the problem?
play2 is a GameObject referenced in the class header of SaveManager. play2 does have a playerMovement component that runs properly. I think that the NullReferenceException comes from the SaveManager not properly loading the information that it contains.
You should use in the GameData() an If(play2 != null) before trying to access to the P2 variable and assign it.
And you can do the same thing in the Start () when you assign the P2 variables
I need your help with things that are going to blow up my mind...(Don't know why but I can't write "Hello everybody!" at the start of the post. Forum hides it:)
I can't figure out why my List <'My Tile Class> is cleared by itself.
I have few prefabs (with box-collider) with no scripts attached to them and one script on the scene. The idea is to take random prefab, attach random value to it and initiate it. After that player clicks on prefab on the scene and gets its value.
public class Generate_Field : MonoBehaviour {
public class Tiles {
public GameObject My_Tile;
public int My_Value;
}
public List<Tiles> My_List;
private GameObject EmptyObj;
// ... other variables
void Start (){
List<Tiles> My_List = new List<Tiles> ();
// ... below some calculation (about 100 rows)
//Instantiate obj and give its reference to Emptyobj
EmptyObj = Instantiate (My_GameObj_To_Inst, new Vector3(My_X, My_Y, 0f), Quaternion.identity);
Tiles Tile1 = new Tiles ();
Tile1.My_Tile = EmptyObj;
Tile1.My_Value = 1; //
My_List.Add (Tile1); // Add Tile1 (with reference to Gameobj and value to List)
// If I put code inside Void Start () it works ok and print all values
foreach (Tiles SS in My_List) {
print (SS.My_Value);
}
The problem is when I put it to Void Update ().
My_List somehow "Suicide" to zero.count although it is public List. In that case I get error:
"NullReferenceException: Object reference not set to an instance of an object..."
void Update ()
{
if (Input.GetKeyDown (KeyCode.Mouse1)) {
print (My_List.Count);
}
}
You declare a new local list in Start and populate that one. If you mean to initialise the class member, remove the type from the line doing the initialisation.
void Start (){
My_List = new List<Tiles> (); // class member, not new variable.
As a usual guideline, it's better to initialise lists at the declaration point. There's usually no reason to replace the list at a later time.
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
I am new to Unity3D and C# and have been struggling to find a convenient way to reuse the same prefab, with unique name & tag, and Add it to a List.
I have the following test code:
public class testScript : MonoBehaviour {
public static List<string> cardDeckBackListNames = new List<string> {
"HAB", "H2B", "H3B", "H4B", "H5B", "H6B", "H7B", "H8B", "H9B", "H10B", "HJB", "HQB", "HKB",};
private GameObject temp_GameObject;
public static List<GameObject> master_GameObject_List = new List<GameObject> ();
// Use this for initialization
void Start () {
temp_GameObject = Resources.Load ("BackSide") as GameObject;
temp_GameObject.name = cardDeckBackListNames [0];
temp_GameObject.tag = cardDeckBackListNames [0];
master_GameObject_List.Add (temp_GameObject);
temp_GameObject = Resources.Load ("BackSide") as GameObject;
temp_GameObject.name = cardDeckBackListNames [1];
temp_GameObject.tag = cardDeckBackListNames [1];
master_GameObject_List.Add (temp_GameObject);
int yy = 0;
foreach (GameObject aGO in master_GameObject_List) {
print ("After loading: " + aGO.tag + " #"+ yy);
yy++;
}
print ("master_GameObject_List.Count: " + master_GameObject_List.Count);
}
}
Get the following result:
After loading: H2B #0
After loading: H2B #1
master_GameObject_List.Count: 2
My conclusion is that I cannot use the same GameObject and "only" modify the name & tag as it seems like when i do that it also modify all objects that i have loaded into the List.
As i am very new at C# i would like to ask what would be the correct way of doing what i am trying to do? 1.) reusing the same prefab with unique name & tag, 2.) load the "unique" objects into a List
ps. I initially used a "foreach" loop but removed that to be able to do a clean and simple test.
Problem
When you use Resources.Load() you loading the prefab, but every time you make a change you are editing the same prefab.
Here is a picture of the editor showing you what you are pointing to:
Solution
Since you want unique GameObjects the first thing you need to do is Instantiate them. After that you can edit each individual GameObject as you like.
void Start ()
{
temp_GameObject = Instantiate(Resources.Load("BackSide")) as GameObject;
temp_GameObject.name = cardDeckBackListNames[0];
temp_GameObject.tag = cardDeckBackListNames[0];
master_GameObject_List.Add(temp_GameObject);
temp_GameObject = Instantiate(Resources.Load("BackSide")) as GameObject;
temp_GameObject.name = cardDeckBackListNames[1];
temp_GameObject.tag = cardDeckBackListNames[1];
master_GameObject_List.Add(temp_GameObject);
}
These are the results: