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?
Related
I have a system that does initialization logic in Awake() and has an appropriate [DefaultExecutionOrder(-100)] attribute to guarantee its Awake() be called before each other "playable" game objects Awake(). Now I need the reverse logic, i need to call a function after each game object being destroyed.
I tried to traverse over root objects in main component's OnDestroy(), check if there's an object that is still alive (i.e. cast to bool gives true), and "pass the baton" of deinitialization to it (adding a CleanUp component on it). Then when this object's OnDestroy() is called it does the same logic, checks if there is at least one object alive, etc... The problem is that it doesn't work as i expected.
Also tried to make OnDestroy() async and check if there's no alive objects in the scene and wait for 100ms in a loop. The result is that playmode exits but the loop still goes on valid objects outside the playmode
Another approach was to instantiate an object with cleanup component in main component's OnDestroy() but Unity does not allow to instantiate in OnDestroy() event
Also tried to implement this logic in component's destructor but it was weirdly been called even before the Awake()
The other possible approach would be to use ISubsystem interface Unity provides but there's no clear documentation on how to use it.
I don't want to use any kind of "global factories" to instantiate and control objects
A possible way to workaround is to use other components' dependent logic in OnApplicationQuit() instead of OnDestroy() but it is the most fallback solution for me
What is the best way for component to be initialized before each other and outlive everyone?
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.
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!
I would like to populate the UI when I load a scene, with the correct data, instead of placeholders.
When I call "LoadSceneAsync", what would be the first object that is called, so I can fill the UI label with the correct data? I know that there is a scene GameObject, but I am not sure if that would fit my needs.
I am looking for some sort of constructor, called when a new scene object is loaded; to plug in my setup function.
You say
Indeed I did use "onlevelwasloaded" but the UI element may not be there, ready to go, when I invoke it, which lead to errors
That would be an incredibly sever bug in Unity! :)
Could it be that you are mixing-up Awake and Start somewhere?
One way to think of it is once you call Start, you know all the Awake have already run.
When I call "LoadSceneAsync", what would be the first object that is called, so I can fill the UI label with the correct data
You are still within the same frame.
Once you see LoadSceneAsync you can be absolutely assured everything is Awake 'd.
Or indeed once you use Start you can be absolutely assured everything is Awake 'd.
1) could it be that in some of your UI elements (or whatever) you are doing something in Start which you should do in Awake?
2) if (for some reason) you want to "wait until the next frame", perhaps just during development - then do that, wait a frame. You'll see a flicker, but if that's what you want to do (for some reason) do that.
3) note that if you mean you want to go to the net to get something, well of course you have to wait frames (use Update/coroutine) until the information comes back from the net, obviously. (How else could it be?)
Note that in practice, one should be using UnityEngine.Events.UnityEvent everywhere.
Maybe this is what you are looking for http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnLevelWasLoaded.html
Relying on Unity internal functioning is not always the way to go. Particularly when dealing with RESTApi (which is somehow what you are dealing with here).
You cannot assume one object will be ready before another except if you control it.
Add a Controller script that uses Awake. In the Awake, call all the methods you are needing and use some callback to generate secondary code when primary is ready.
public class MyController: MonoBehaviour{
private ServerRequestController serverCtrl = null;
private UIController uiCtrl = null;
private void Awake(){
serverCtrl = this.gameObject.AddComponent<ServerRequestController>();
uiCtrl =this.gameObject.AddComponent<UIController>();
serverCtrl.GetData(uiCtrl.SetUI);
}
}
public class UIController:MonoBehaviour{
public void SetUI(Data data)
{
SetTopImage(data.topImage);
SetBottomImage(data.bottomImage);
// And so on
}
}
public class ServerRequestController:MonoBehaviour{
public void GetData(Action onCompletion){
// This may be a coroutine if you fetch from server
Data data = GetDataFromSomewhere();
// At this point, your data is ready
onCompletion(data);
}
}
Thanks to this, you are now able to know exactly when a piece of code is ready.
I'm designing a 3D Tetris game. I'm new to unity and just started to learn some basics in this site
My plan is not to create a regular tetris board, but to create a 3D surface, with width,height and depth so the user will need to build a surface in order to destroy the objects.
So in my design I know that I need an object generator and to create an object (Tetris cube) in they that I'm calling the object generator in the update method with some timer.
Also the idea is to make a 3d matrix that represent the game board so I could check in the game logic if there is a "surface" in that matrix.
This is a part of the game logic script.. and my problem is that i don't know where to put this script.. I mean in the game logic I need to create a random cube, and to check if there is a surface that can be destroyed.. but where should I put the game logic script?
I always used IDE's like visual studio or eclipse so there you can have a main class with the main method that start your program.. and now in unity i'm confused..
Edit:
Thanks for your replying.. I forgot to mention that I have something like 3 scenes (levels) in the game.. so for each level (scene) should I create an empty game object?
make an empty gameobject and attach your object that you want to spawn(as child) to it and attach your spawn code to that empty object(parent)
Create an empty gameobject like mentioned and put a script on it that saves it from being destroyed on scene-change (some sort of singleton).
Some basic way to do it:
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
private static GameController instance
public static GameController Instance
{
get { return instance; }
}
private void Awake()
{
instance = this;
DontDestroyOnLoad(instance);
}
}
The DontDestroyOnLoad will hold the whole gameobject with everything attached to stay loaded on scene-change.