What is the correct way to copy a component? - c#

This question has been on my mind for a long time because the process of copying it does not seem so difficult. Although Unity can easily copy countless components with instantiate code, why do not I see such a feature in a single component?
public class FreeFly : MonoBehaviour
{
public float wingSpeed = 2f;
public bool canFly = true;
public void CopyComponent()
{
wingSpeed = 10f;
canFly = false;
var _fly = this;
var secondFly = gameObject.AddComponent<FreeFly>();
secondFly = _fly; // The second component did not register the changes.
}
public void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) CopyComponent();
}
}
As you can see, it does not change anything. Interestingly, the Rider IDE also shows it ineffective in code with a yellow underscore.
What is the best solution?
Considering that variable-to-variable copy is a beginner way, I am looking for a solution that can copy any component using the Generic method. Any help is accepted.
public T CopyComponent<T>(T copyFrom)
{
// I want this one..
return copyFrom;
}

secondFly = _fly; tells the computer "from now on, if I say secondFly, I mean the Component (Object) that _fly referenced at time of executing this line." It does not modify the component that was referenced by the variable secondFly, it only changes what secondFly refers to. This is because secondFly is an Object (any type declared as public class ClassName {...}, Component, Rigidbody, etc), not a primitive type (int, float, double, byte, etc.). Variables that are of Object types aren't data themselves, they point to/reference data.
Beginner Way
You can copy the variables of _fly just like this:
secondFly.wingSpeed = _fly.wingSpeed;
secondFly.canFly = _fly.canFly;
Advanced Way
Because of the way Unity's Components work, I don't think there's a simple way to duplicate a Component and attach it to a GameObject, but if you don't want to manually copy the variables of the Component, try adding this function to your code and calling it to duplicate your Component (from https://answers.unity.com/questions/458207/copy-a-component-at-runtime.html)
public static T CopyComponent<T>(T original, GameObject destination) where T : Component
{
var type = original.GetType();
var copy = destination.AddComponent(type);
var fields = type.GetFields();
foreach (var field in fields) field.SetValue(copy, field.GetValue(original));
return copy as T;
}

Related

How can I change the parent of a GameObject instantiated via PrefabUtility.InstantiatePrefab?

Usually you would instantiate a clone of a prefab in a script using
var obj = (GameObject)Instantiate(....);
and than change it's parent using
var obj.transform.SetParent(...);
However I'm writing on a little editor script for instantiating prefabs over the menu entries that should keep their link to the prefab. I do it in a static class like:
public static class EditorMenu
{
// This is a reference that I already have and is not null
privtae static Transform exampleParent;
[MenuItem("Example/Create Clone")]
private static void CreateClone()
{
var prefab = AssetDatabase.LoadAssetAtPath("Example/Path/myObject.prefab", typeof(GameObject));
var obj = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
obj.transform.position = Vector3.zero;
obj.transform.rotation = Quaternion.identity;
Selection.activeGameObject = obj;
}
}
I had to use
(GameObject)PrefabUtility.InstantiatePrefab(prefab);
because Instantiate() doesn't keep the link to the prefab but creates a clone.
So the above code works great so far and the object is inserted to the scene as if I would drag and drop it.
Now I would like to change this newly instantiated objects parent to parent so I added the line
obj.transform.SetParent(exampleParent, true);
I also tried
obj.transform.parent = exampleParent;
But both throw an exception:
Setting the parent of a transform which resides in a prefab is disabled to prevent data corruption.
UnityEngine.Transform:SetParent(Transform)
prefab and thereby obj are the topmost GameObject of the prefab so I thought I am setting the parent of the whole instantiated object not any transform inside of the prefab hierachy.
How can I change the parent of a GameObject instantiated via PrefabUtility.InstantiatePrefab?
Update
A workarround I just tried was to actually use Instantiate and do
var obj = Object.Instantiate(prefab, cancel.transform);
// remove the added "(clone)" suffix
obj.name = prefab.name;
obj.transform.SetParent(cancel.transform);
however as mentioned before this doesn't completely keep the prefb functionality intact .. so it only allows me to Revert but not to Apply changes. As workaround it might be good enough however since in my case I use it as a shortcut for instantiating a prefab which users are not really supposed to change later...
Sorry I just found the issue:
I didn't add it since the error message was confusing/bad formulated.
I was using Resources.FindObjectsOfTypeAll for checking if the exampleParent of type SomeType was in the scene.
Following Unity's example which should exclude prefabs I used
// Validation for the Menu item
[MenuItem("Example/Create Clone", true]
private bool TargetAvailable()
{
foreach (var target in (SomeType[])Resources.FindObjectsOfTypeAll(typeof(SomeType))
{
if (target.hideFlags == HideFlags.NotEditable || target.hideFlags == HideFlags.HideAndDontSave)
continue;
if (!EditorUtility.IsPersistent(target.transform.root.gameObject))
continue;
exampleParent = target.transform;
return true;
}
exampleParent = null;
return false;
}
But this seems actually to be wrong and not working since it allways returned me the SomeType reference from the prefabs! (I already found it a bit strange that they do
!EditorUtility.IsPersistent(target.transform.root.gameObject))
continue;
I'm not sure if that ! maybe is a type in their example code?!
So the error which sounds like setting the parent was not allowed actually means and should say
setting the parent to a Transform which resides in a prefab is not allowed ...
than I would have found the actual issue in the first place.
So again as a workaround until I can figure out that FindObjectsOfTypeAll thing I switched to Object.FindObjectOfType instead assuming my target will always be active in the scene. And using SetParent works now together with PrefabUtitlity.InstantiatePrefab.
How can I change the parent of a GameObject instantiated via PrefabUtility.InstantiatePrefab?
This is right, but make sure cancel is also an instantiated object.
obj.transform.parent = cancel.transform;

Dynamically render GameObject in Unity C#

I am working on a AR project, where the virtual objects will be shown/hide in the scene based on information found in a text file. The text files will be updated from an external service. So I need to read the file on a frequent interval and update the scene. As a result I only have the Camera object and I am rendering the scene in OnPreCull() method.
The text files contain many objects but not all the objects are within the scene at any instance of time. I was looking for a way to render only those objects that are within the scene.
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Yes absolutely ... so would it if you do it in Update or any other repeatedly called method.
Instead you should rather Instantiate objects in Awake and only activate or deactivate them.
Let's say you have 3 objects A, B and C than I would make a kind of controller class that looks like
public class ObjectsController : MonoBehaviour
{
// Define in which intervals the file should be read/ the scene should be updated
public float updateInterval;
// Prefabs or simply objects that are already in the Scene
public GameObject A;
public GameObject B;
public GameObject C;
/* Etc ... */
// Here you map the names from your textile to according object in the scene
private Dictionary<string, GameObject> gameObjects = new Dictionary<string, gameObjects>();
private void Awake ()
{
// if you use Prefabs than instantiate your objects here; otherwise you can skip this step
var a = Instantiate(A);
/* Etc... */
// Fill the dictionary
gameObjects.Add(nameOfAInFile, a);
// OR if you use already instantiated references instead
gameObjects.Add(nameOfAInFile, A);
}
}
private void Start()
{
// Start the file reader
StartCoroutine (ReadFileRepeatedly());
}
// Read file in intervals
private IEnumerator ReadFileRepeatedly ()
{
while(true)
{
//ToDo Here read the file
//Maybe even asynchronous?
// while(!xy.done) yield return null;
// Now it depends how your textile works but you can run through
// the dictionary and decide for each object if you want to show or hide it
foreach(var kvp in gameObjects)
{
bool active = someConditionDependingOnTheFile;
kvp.value.SetActive(active);
// And e.g. position it only if active
if (active)
{
kvp.value.transform.position = positionFromFile;
}
}
// Wait for updateInterval and repeat
yield return new WaitForSeconds (updateInterval);
}
}
If you have multiple instances of the same prefab you also should have a look at Object Pooling
I'd recommend adding each of the game objects to a registry and the switching them on or off (dis/enable SetActive) via the registry class's Update() cycle.
One Update() process to retrieve and handle the server file, another Update() process to dis/enable objects. Might sound oversimplified however it's the fastest way I think of getting the result.
Good Luck!

Driving in Tree Unity 3D C#

Hi guys i'm trying to drive in the my GameObject (The Player) in my Project, I want a script to navigate to a var in a script that is on another GameObject. It's not easy because there is Parents and children... Here is my tree:
>PlayerEntity
>Canvas
Gun_Name_Text
Gun_Ammo_Text
>Player
Sprite
I want a script attached to 'Gun_Name_Text' to fetch a var in a script attached to 'Player', so i didnt manage to do it with :
var ammo1 = GetComponentInParent<GameObject> ().GetComponent<weapon> ().weaponOn.Ammo1;
PS: I prefer not to use GameObject.Find()
Thanks in advance
As I said in the comments, the easiest way to do this would to be to just assign the variable in the inspector. However if you can't do this then you could simply use:
OtherScript otherScript = null;
void Start()
{
otherScript = transform.root.GetComponentInChildren<OtherScript>();
}
Note: This will set otherScript equal to the first instance of the OtherScript that it finds in the child objects though. You will have to use GetComponentsInChildren if you have more than one OtherScript object in the children.
For your specific example you could use:
var ammo1 = transform.root.GetComponentInChildren<weapon>().Ammo1;
If you are calling this often then it would be wise to cache a refrence to the weapon script though. You will also have to do this if you want to modify the Ammo1 variable that is a member of the weapon class as it is passed to the var ammo1 by value and not by reference.
There are cases where my game object does not know everything that interacts with it. An example would be a destructible object. If I wanted to know so what I'm hitting that was destructible I'd have all destructible objects inherit from a base type or interface and search on that type. Here is an example:
private void CheckForDestructables(Collider2D c)
{
this.Print("CheckForDestructables Called");
string attackName = GetCurrentAttackName();
AttackParams currentAttack = GetCurrentAttack(attackName);
D.assert(currentAttack != null);
this.Print("CheckForDestructables Called");
if (currentAttack.IsAttacking && c.gameObject.tag == "Destructable")
{
List<BaseDestructibleScript> s = c.GetComponents<BaseDestructibleScript>().ToList();
D.assert(s.Any(), "Could Not find child of type BaseDestructibleScript");
for (int i = 0; i < s.Count(); i++)
{
s[i].onCollision(Movement.gameObject);
}
}
}

C# (Unity 4.3) Cannot implicitly convert type `ShotScript[]' to `ShotScript'

Thanks all! The issue was I was using GetComponents rather than GetComponent, thanks!
I am fairly new to programming and I am trying to make a 2D game in Unity. I have found a tutorial online which is really good, up until a point (Tut can be found here http://pixelnest.io/tutorials/2d-game-unity/shooting-1/). I've come across an error and can't figure out why. The error occurs when I try and use another script inside my current script (if that makes sense to anyone). The two scripts in question have the name ShotScript and HealthScript and they are below
ShotScript:
using UnityEngine;
using System.Collections;
public class ShotScript : MonoBehaviour {
public int damage = 1;
public bool isEnemyShot = false;
void Start () {
Destroy (gameObject, 20);
}
}
HealthScript:
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : MonoBehaviour {
public int hp = 2;
public bool isEnemy = true;
void OnTriggerEnter2D(Collider2D Collider) {
ShotScript shot = collider.gameObject.GetComponents<ShotScript>();
if (shot != null) {
if (shot.isEnemyShot != isEnemy) {
hp -= shot.damage;
Destroy (shot.gameObject);
if (hp <= 0) {
Destroy(gameObject);
}
}
}
}
}
The error I get is:
"Assets/Scripts/HealthScript.cs(13,36): error CS0029: Cannot implicitly convert type ShotScript[]' toShotScript'"
I'm rather stuck and so if anyone could point me in the right direction, that'll be great =)
P.S I'm new to this asking questions thing, so if you need any extra info, I'll do my best to provide it
Well this is the problem:
ShotScript shot = collider.gameObject.GetComponents<ShotScript>();
It sounds like GetComponents<ShotScript> is returning an array of ShotScript references, i.e. its return type is ShotScript.
Do you want to take the same action for each ShotScript? If so, you probably want to just use a foreach loop... although you probably only want to check for the hp going negative at the end. I would expect you to be able to remove the check for nullity, too, assuming that GetComponents will just return an empty array if there aren't any such components:
ShotScript[] shots = collider.gameObject.GetComponents<ShotScript>();
foreach (ShotScript shot in shots)
{
if (shot.isEnemyShot != isEnemy)
{
hp -= shot.damage;
Destroy(shot.gameObject);
}
}
if (hp <= 0)
{
Destroy(gameObject);
}
(I've reformatted that to be rather more conventional C#, but of course you can use whatever indentation you want.)
You're calling GetComponents (notice the plural) which returns an array (that is ShotScript[]) of all matching components of the type on that particular GameObject.
So this is attempting to assign a ShotScript[] array into a single ShotScript instance which is not possible.
If you wanted to retrieve only one ShotScript (because you intend to have only 1 on an object), use the GetComponent (notice the singular) method instead which will return only one instance or null if none are assigned.
So change the one line to this:
ShotScript shot = collider.gameObject.GetComponent<ShotScript>();
If your intent was to handle many ShotScript instances/components on the same GameObject, use the fix/code supplied in Jon Skeet's answer.
The error message says that line 13 is in error:
ShotScript shot = collider.gameObject.GetComponents<ShotScript>();
It looks like the GetComponents method is returning an array ShotScript[], but you're trying to implicity cast it to just a single ShotScript instance. Try:
ShotScript[] shots = collider.gameObject.GetComponents<ShotScript>();

How to find inactive objects using GameObject.Find(" ") in Unity3D?

I needed to find inactive objects in Unity3D using C#.
I have 64 objects, and whenever I click a button then it activates / inactivates objects for the corresponding button at runtime. How can I find inactive objects at this time?
Since Unity 2020
In the years since this question was asked, Unity put in the exact thing you need. At least, the exact thing I needed. Posting here for future peoples.
To find an object of a certain type whether it's on an active or inactive GameObject, you can use FindObjectsOfType<T>(true)
Objects attached to inactive GameObjects are only included if inactiveObjects is set to true.
Therefore, just use it like you regularly would, but also pass in true.
The following code requires System.Linq:
SpriteRenderer[] onlyActive = GameObject.FindObjectsOfType<SpriteRenderer>();
SpriteRenderer[] activeAndInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true);
// requires "using System.Linq;"
SpriteRenderer[] onlyInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true).Where(sr => !sr.gameObject.activeInHierarchy).ToArray();
The first array includes only SpriteRenderers on active GameObjects, the second includes both those on active and inactive GameObjects, and the third uses System.Linq to only include those on inactive GameObjects.
See this answers for Unity 2020 and higher.
Before Unity 2020
Well, using GameObject.Find(...) will never return any inactive objects. As the documentation states:
This function only returns active gameobjects.
Even if you could, you'd want to keep these costly calls to a minimum.
There are "tricks" to finding inactive GameObjects, such as using a Resources.FindObjectsOfTypeAll(Type type) call (though that should be used with extreme caution).
But your best bet is writing your own management code. This can be a simple class holding a list of objects that you might want to find and use at some point. You can put your object into it on first load. Or perhaps add/remove them on becoming active or inactive. Whatever your particular scenario needs.
If you have parent object (just empty object that plays role of a folder) you can find active and inactive objects like this:
this.playButton = MainMenuItems.transform.Find("PlayButton").gameObject;
MainMenuItems - is your parent object.
Please note that Find() is slow method, so consider using references to objects or organize Dictionary collections with gameobjects you need access very often
Good luck!
For newer Unity versions this answer provides probably a better solution!
First of all
In general any usage of Find or it's variants should be avoided.
Actually they are never really required but only a "hot-fix" used to cover an implementation "laziness".
Usually from the beginning storing and passing on required references is always the better approach.
Especially in your case you seem to have a fix amount of objects so you could probably already reference them all in a certain "manager" component and store them in a list or array (them you can get a reference by index) or even a Dictionary<string, GameObject> (then you can also get the according reference by name - you can find an example below).
Workarounds
There are alternative solutions (FindObjectsWithTag, FindObjectsOfType) but it will always be quite expensive (though most of the Find variants are expensive anyway).
You could e.g. also "manually" iterate through all objects in the scene using Scene.GetRootGameObjects
Returns all the root game objects in the Scene.
And then search through them until you find your object. This way you get also inactive GameObject.
public static GameObject Find(string search)
{
var scene = SceneManager.GetActiveScene();
var sceneRoots = scene.GetRootGameObjects();
GameObject result = null;
foreach(var root in sceneRoots)
{
if(root.name.Equals(search)) return root;
result = FindRecursive(root, search);
if(result) break;
}
return result;
}
private static GameObject FindRecursive(GameObject obj, string search)
{
GameObject result = null;
foreach(Transform child in obj.transform)
{
if(child.name.Equals(search)) return child.gameObject;
result = FindRecursive (child.gameObject, search);
if(result) break;
}
return result;
}
But ofcourse this should be strongly avoided and the usage of such deep searches reduced to a minimum!
What I would do
Another way - in my eyes the best approach here - could be to have a certain component attached to all your objects and actually store all the references once as said before in a dictionary like e.g.
public class FindAble : MonoBehaviour
{
private static readonly Dictionary<string, GameObject> _findAbles = new Dictionary<string, GameObject>();
public static GameObject Find(string search)
{
if(!_findAbles.ContainsKey(search)) return null;
return _findAbles[search];
}
private IEnumerator Start()
{
// Wait one frame
// This makes it possible to spawn this object and
// assign it a different name before it registers
// itself in the dictionary
yield return null;
if(_findAbles.ContainsKey(name))
{
Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
yield break;
}
_findAbles.Add(name, gameObject);
}
private void OnDestroy ()
{
if(_findAbles.ContainsKey(name))
{
_findAbles.Remove(name);
}
// Optionally clean up and remove entries that are invalid
_findAbles = _findAbles.Where(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
}
and then use it like
var obj = FindAble.Find("SomeName");
if(obj)
{
// ...
}
Also for this the component would need to be enabled at least once so Start is called.
Again an alternative would be to have instead a
public void Initialize(string newName)
{
if(_findAbles.ContainsKey(name))
{
Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
return;
}
name = newName;
_findAbles.Add(name, gameObject);
}
which you could call also after e.g. spawning an inactive object.
You can use Predicates.
Just get the gameObjects and check them whith a Predicate as below:
public List<GameObject> FindInactiveGameObjects()
{
GameObject[] all = GameObject.FindObjectsOfType<GameObject> ();//Get all of them in the scene
List<GameObject> objs = new List<GameObject> ();
foreach(GameObject obj in all) //Create a list
{
objs.Add(obj);
}
Predicate inactiveFinder = new Predicate((GameObject go) => {return !go.activeInHierarchy;});//Create the Finder
List<GameObject> results = objs.FindAll (inactiveFinder);//And find inactive ones
return results;
}
and don't forget using System; using System.Collections.Generic;
You can do this at runtime by having your inactive gameobject under an active parent object as previously mentioned; slightly different from what was mentioned, this is an approach I've used for activating/deactivating menus that should be inactive by default:
canvas = GameObject.FindGameObjectWithTag("GameMenu").GetComponentInChildren<Canvas>().gameObject;
Now you can change its activeSelf to toggle it in a method/event listener of your choice:
canvas.SetActive(!canvas.activeSelf);
Even while it is inactive, you can still use the tag property of it and use it for a filter, if getting multiple components of the same type. I haven't tested this using GetComponentsInChildren, but you could probably use a 'Single' linq query, and get the object by tag name which would require creating a tag for every gameobject you want to do this to.
Although its not the correct answer, but this is what I did in my case.
1) Attach a script to (inactive) game objects and instead of setting then inactive keep it active.
2) Position them out of the scene somewhere.
3) Set a flag in the script which says inactive.
4) In Update() check for this inactive flag and skip function calls if false.
5) When needed the object, position it at the proper place and set the flag active.
It will be a bit of a performance issue but that's the only workaround I could think of so far.

Categories