Unity3d, add component to prefab in resource - unity3d

One time I was playing around with Resouce system in Unity I discovered this:
Say if you only load but do not Instantiate the prefab:
GameObject testObj = Resource.Load("testPrefab");
And you have a script called "TestScript", and you add this script to testObj
testObj.AddComponent<TestScript>();
You run it, and close it, this TestScript will still be inside testPrefab inside Resource folder, and if you run it again, the testPrefab in Resource folder will have two TestScript component.
I understand that Resouce.Load() is just point to that prefab in Resource folder, no copy has occured.
But would it be a good idea says, put a CleanPrefab() function in this prefab to make sure it restore to the original configuration first and then add other conponents as needed?

Do not modify a prefab. Prefabs are made to be used as re-usable Objects. There are many reasons you shouldn't try to modify it. One of them is that it will affect instantiated objects references that prefab. With modifications done to your prefab, there is no guarantee your levels will start the way they are supposed to start. This will mess up your game.
You run it, and close it, this TestScript will still be inside
testPrefab inside Resource folder, and if you run it again, the
testPrefab in Resource folder will have two TestScript component.
The behavior you described is only true in the Editor. One you build it, the behavior is totally different. In a standalone version, once you restart the program, the prefabs will automatically reset itself. What you are really modifying is the loaded prefab in the memory not the one stored on the disk. Also note that everything in the Resources folder is read-only. They can't be modified.
If you modify a prefab directly, it stays in the memory even when you reload the scene. The only time that modification will be gone is when you restart the application. Do not modify a prefab.

As mentioned here, Resources.Load doesn't create a new instance of an object because it does not create an Object in the scene; that's what Instantiate does.
To avoid this problem of linking to the original prefab and avoid overriding, use Instantiate() and then add all your components with respect to that variable.
It is important to use Instantiate before apply new components to the object or modify the existing ones.
For instance you have this snippet of code:
void Start()
{
SelectCharacter();
InstantiateObj();
ControllerAssignment();
AddColliderComponent();
AddScriptComponent();
}
private void InstantiateObj()
{
obj = Instantiate(obj_original_prefab); //apply changes to this variable
}
This code is a correct implementation, where you call the Instantiate method before applying all the changes to the object obj, which is a 'deep copy' of your original prefab and not a reference!

Related

Singletons in Unity

I want to keep my player when switching scenes but without getting lots of copies of it , so I found out about singletons.You do something like this:
public class Player : Monobehaviour
{
private static Player instance = null;
void Start()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameobject);
}
else
Destroy(gameobject);
}
}
And it's pretty hard for me to understand.I get how static variables work , they belong to the class not to the instance.So , first my objects is in scene A.I switch to scene B,a new gameobject will be created with the script Player on it.Now , since instance holds the reference to the gameobject in scene A , why won't the else statement get called and a new gameobject will actually be created?Also , if you could explain a little bit more detailed than unitydoc how DontDestroyOnLoad() works , I'd be thankful.
This code runs after the object is created. In fact it runs on a script on the GameObject. The else statement will run, and it will destroy the GameObject this script is attached to, which is what you want because you already had another copy in the world from previously (the one that is referenced by the "instance" variable).
JSchiff is correct that this code will run after an instance of this object is created, storing the current instance in your static variable for unconditional access to "the current instance" or destroying new instances if there is one already. The else branch in your code will run.
To answer your extended question about DontDestroyOnLoad and it's interaction with this static variable: all the objects in the Engine are held in memory by the Engine. A Scene can be thought of as a "collection of objects in memory" by the engine. When a scene is unloaded, all objects in that collection are destroyed, unless the engine has been told DontDestroyOnLoad (as in un-load, although documentation refers to it as a "swap". You can load scenes without unloading scenes, this might have been terminology which just grew outdated but that's conjecture on my part).
These ideas are combined to make your Singleton, a highly accessible (in this case static instance variable), persistent (through DontDestroyOnLoad), one of a kind (through destroying any instance after the first) object. This isn't the only way to apply the Singleton pattern, but it is a common one in Unity.

Why couldn't I use the "Resources.Load" result directly?

The Unity Manual says the Resources.Load returns the requested asset as an Object.I wonder why could't I use the returned Objectdirectly.For example,I have a Text prefab and I want to add it's instance to the Hierarchy,but the Code below won't work
Text prefab;
private void Start()
{
prefab = Resources.Load<Text>("Prefabs/Text");
GameObject canvas = GameObject.Find("Canvas");
prefab.transform.SetParent(canvas.transform);
}
I must Instantiate the return of the Resources.Load first like below
Text prefab;
private void Start()
{
prefab = Resources.Load<Text>("Prefabs/Text");
GameObject canvas = GameObject.Find("Canvas");
Text text = Instantiate(prefab);
text.transform.SetParent(canvas.transform);
}
I don't know what's the difference between the Instantiate result and Resources.Load result,and what the Instantiate do ,so that it's return can be added to Hierarchy.
Forgive my poor English!
To use the Method Instantiate(GameObject) you would write a new component, create a new variable of type GameObject, attach the component to an GameObject, and fill the variable in the inspector.
To use the Method Instantiate(Resource.Load("object path")) you just need the name/path of the Prefab.
this is extremely useful if you have a huge amount of generated parts in your game (so there are no gameobjects placed in the editor), if you'd want to avoid Resource.Load you'd need some "data-holder-gameobject" placed in an nearly empty scene. edited to make my point a bit clearer
it is aswell helpfull if you have large number of different Prefabs and your method knows the name of the object it wants to build, or you just simply dont want to drag and drop all those prefabs into the inspector window
Resource.Load, loads data from your drive. it's possible that your game is played from a Hard drive, which would mean to load the prefabs the hard drive needs to rotate, position the read-head, and so on.
Instantiate is slow itself even without the need of Resource.Load Instantiate is not that fast. if it happens that you need it very often ( multiple times per second) you should consider some kind of object-pool 1

Zenject Mono Installer not called in Scene tests under some circumstances

I have a "Main" scene consisting of a softozor game object which is controlled by the player. It's a flappy dinosaur. As a child of that softozor game object, I have setup another game object, Installer, consisting of a Transform component and a PlayerInstaller (Script) component:
The PlayerInstaller installs everything necessary for my player's logic. Finally, in the softozor game object, I have added a Game Object Context (Script) where I register the PlayerInstaller:
In addition to the softozor game object, I have also defined a SceneContext:
You'll notice that all installers lists are empty in that SceneContext. However, without that SceneContext registering nothing, the PlayerInstaller is not triggered. Playing the game with that setup works perfectly well, i.e. the PlayerInstaller is called and I can control my dinosaur to do whatever I want in my game.
So far, so good. Now, consider the following Scene Test:
public class PlayerTests : SceneTestFixture
{
[Inject]
private IPlayer _player;
[UnityTest]
public IEnumerator TestScene()
{
yield return LoadScene("Main");
_player.Flap();
yield return new WaitForSeconds(0.01f);
[...]
}
}
In that test, the _player member variable is not injected with an object satisfying the IPlayer contract. In fact, the PlayerInstaller.InstallBindings() is not called.
If I, instead, get rid of the Game Object Context (Script) component in my softozor game object, and register the PlayerInstaller in the SceneContext:
then I can play the game too, as before, and my test is running, i.e. the PlayerInstaller.InstallBindings() method is called during my Scene Test.
What is wrong with my first attempt where I register the PlayerInstaller in the softozor game object context?
I am working with
Zenject ver. 7.3.1
Unity 2019.1.8f1 PC, Mac & Linux Standalone
So you have two containers, the SceneContext and a GameObjectContext.
I think what happens here is that they both get installed, but your GameObjectContext is not added to the SceneContext -- it worked until the SceneContext actually needed to know about the GameObjectContext (which was the case in your scene test).
It's difficult to give more precise directions not knowing what is IPlayer and what you expect to be injected there, but it makes sense that it's not injected in your SceneContext, but only in your GameObjectContext.
By putting the PlayerInstaller in the SceneContext's MonoInstallers list, you technically solved that problem, but that's clearly not what you want since it makes the sub-container useless and breaks any kind of separation you were going for.
Instead, you need to interface both contexts using a façade: Sub-containers and Facades: Using GameObjectContexts (the explanation consists of an example, so it doesn't make sense for me to quote parts of it, but it's detailed and helpful)
Thanks to hugo, I'm starting to understand how to make it run. My current game components setup is as follows:
My PlayerInstaller is defined like this:
public class PlayerInstaller : MonoInstaller
{
[SerializeField]
Settings _settings;
public override void InstallBindings()
{
Container.BindInterfacesTo<Softozor>()
.AsSingle()
.WithArguments(_settings.Rigidbody);
Container.BindInterfacesTo<InputController>().AsSingle();
Container.Bind<InputState>().AsSingle();
Container.BindInterfacesTo<PlayerInputHandler>().AsSingle();
Container.BindInterfacesTo<PlayerMoveHandler>().AsSingle();
}
[Serializable]
public class Settings
{
public Rigidbody2D Rigidbody;
}
}
After reading the documentation advised by hugo, I checked which contexts are created during my scene tests. I was able to see that the method PlayerInstaller.InstallBindings was called in my scene tests. In addition, my Softozor object implementing the IPlayer interface was also injected into the PlayerMoveHandler class. That means that the Game Object Context was successfully built during the scene tests. The objects registered in that context are, however, not available from the Scene Context, which is the one I can access to in my Scene Tests. I naively assumed that whatever I would register in my Game Object Context could be retrieved in my Scene Test. It is apparent, however, that I can only access objects registered in the Scene Context.
At that point, if I really want to access the instance implementing IPlayer, I have one alternative: either I create a facade on the softozor game object and make the IPlayer publicly available on it, or I find a way to resolve instances from the Game Object Context. Is the latter possible at all?
In the end, I think I will redesign my scene test in such a way that I don't need access to the instance of type IPlayer, which is totally possible in my case. But I would be curious to know whether or not it's possible to resolve an object from the Game Object Context in a Scene Test. How do I do that?

Is there any way to use AddComponent with a script in Unity if there is no instance of that script in the game yet?

I need a way to add a script to an object that I just Instantiated. Ideally I could just set the script to add in the inspector. The problem is that I can't use
public Component scriptToAdd;
because then I would need to already have an empty object or something with the script on it. While that would work it feels kind of dirty or hackish to make an empty that just stores references to something. Also the point of doing it like this for me is to make it more efficient to swap out different scripts. If I had to swap out a script on an empty and then drag the new one in I'd lose on some of that efficiency I'm after. Thanks for any help.
After you instantiate, you should use the GameObject.AddComponent() Class, to add any component Type you want.
for example, if you want to add a camera to the scene.
void Start () {
GameObject myNewCamera = new GameObject();
//creates an empty game object, you can already attach components to it.
myNewCamera.AddComponent<Camera>();
//adds a component of type Camera
}
You can also use the overloaded constructor
GameObject myNewCamera = new GameObject("my object name", typeof(ScriptToAdd));
GameObject myNewCamera = new GameObject("my object name", typeof(ScriptToAdd), typeof(MoreComponentsToAdd));
Also, you should never be afraid of using an empty GameObject on your scene, for keeping information, references, variable values, saves, or anything really. It doesn't really affect the performance of your game, and you can make your life easy just by having an easy to find tag, such as "Engine".
Then you can access anything just by adding:
GameObject.FindGameObjectWithTag("Engine");
I hope I was able to help you :)
-Noe

How to keep references to UI elements in a prefab, instantiated at runtime

I have a prefab, which has a script component as MonoBehaviour. This script has 3 public fields as text; which are assigned in the inspector, before saving the gameobject and remove it from the scene.
Now, this works fine if I have a UI element, like a panel. Every text field on the panel, defined in the prefab, is still assigned when the prefab is instantiated at runtime.
This sadly does not work on another prefab that I have made; which is not a UI element. In my case it is a meshgameobject with various components on it (navmesh agent, capsule collider, rigidbody, animator and so on)
I believe this is due the fact that with the UI panel, the elements are already in the gameobject hierarchy, while when the reference is on a different gameobject; Unity does not keep track of them.
This means that I have to always add at runtime via code, the elements that I want to reference on each prefab, if they are not part of the game object itself? In this case I would just avoid to have my references public then, since I won't be using the inspector in this case (every gameobject of this type is instantiated at runtime).
Just checking if there is any other solution to work around this.
Use List to save reference for each generated object.
Lets assume that the object you want to add to store the reference is called meshgameobject.
Example:
List<meshgameobject> meshGOB;
initiaize inside Start function
void Start(){
meshGOB = new List <meshgameobject>();
}
Then create and use list.add() to add reference during runtime like
meshGOB.Add((meshgameobject)Instantiate(prefab, Pos, Quaternion.Identity);
OR
meshgameobject tempGOB= (meshgameobject)Instantiate(prefab, Pos, Quaternion.Identity);
//Do something with meshgameobject
tempGOB.someAction......
then add the reference to the List
meshGOB.Add(tempGOB);
Do not make a perfab which references to other gameobjects in the scene but are not in the hierarchy of the perfabI itself. AFAIK, Unity can not track this kind of references.
Even if Unity supported this kind of prefab, you could not use this kind of prefab in other scenes because the specific references, so it is not make much sense to make a perfab like that. If you have to make it a pefab just because the scene and the game object are maintained by different person, you may use Find() to populate the references at runtime.