2

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:

image

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:

image

In addition to the softozor game object, I have also defined a SceneContext:

image

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:

image

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
Laurent Michel
  • 1,069
  • 3
  • 14
  • 29

2 Answers2

2

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)

hugo
  • 3,067
  • 2
  • 12
  • 22
0

Thanks to hugo, I'm starting to understand how to make it run. My current game components setup is as follows:

UML diagram

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?

Laurent Michel
  • 1,069
  • 3
  • 14
  • 29