ssannandeji / Zenject-2019

Dependency Injection Framework for Unity3D
MIT License
2.53k stars 363 forks source link

[Question] How to inject into ScriptableObject #593

Closed Heeeeeeju closed 5 years ago

Heeeeeeju commented 5 years ago
class DIInstaller : MonoInstaller
{
   Container.Bind<IPlayer>().To<Player>().AsSingle();
}

class Player : IPlayer
{
}

class Item : ScriptableObject
{
   IPlayer player;

   [Inject]
   public void Construct(IPlayer player)
   {
      this.player = player;  // I want inject like this, but Construct() not called when class is inherited ScriptableObject
   }
}

Above code not entered construction. How to inject binded things into class inherited ScriptableObject?

schodemeiss commented 5 years ago

You need to use a Factory for this to work.

https://github.com/svermeulen/Zenject/blob/master/Documentation/Factories.md

This is similar to the way you can't inject Monobehaviours, those most be created via the prefab factories.

svermeulen commented 5 years ago

Any objects that are not created via zenject (except those objects that are in the initial scene) will not be injected. So you could either do as @schodemeiss suggests and use a factory or if you have access to the scriptable object instance during startup you could use Container.QueueForInject

Heeeeeeju commented 5 years ago

@schodemeiss @svermeulen Thanks for answer my question. I use Container.QueueForInject

cspeer504 commented 4 years ago

This is an old topic, but I'm hoping I can help anyone else who finds this.

You can use Unity's AssetDatabase and Resources APIs to find all script object instances then iterate through the list to apply QueueForInject. Example:

// search for all ScriptableObject that reside in the 'Resources' folder
string[] guids = AssetDatabase.FindAssets("t:ScriptableObject");
foreach (string guid in guids)
{
    // retrieve the path string to the asset on disk relative to the Unity project folder
    string assetPath = AssetDatabase.GUIDToAssetPath(guid);

    // retrieve the type of the asset (i.e. the name of the class, which is whatever derives from ScriptableObject)
    System.Type myType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);

    //Find the relative path of the asset. 
    string resourcesDirectoryName = $"/Resources/";
    int indexOfLastResourceDirectory = assetPath.LastIndexOf(resourcesDirectoryName) + resourcesDirectoryName.Length;
    int indexOfExtensionPeriod = assetPath.LastIndexOf(".");
    string assetPathRelative = assetPath.Substring(indexOfLastResourceDirectory, indexOfExtensionPeriod - indexOfLastResourceDirectory);

    //Grab the instance of the ScriptableObject.
    ScriptableObject scriptObject = Resources.Load(assetPathRelative, myType) as ScriptableObject;

    if (scriptObject == null)
    {
        Debug.LogWarning(
            "ScriptableObject asset found, but it is not in a 'Resources' folder. Current folder = " +
            assetPath);
        continue;
    }
    else
    {
        Container.QueueForInject(scriptObject);  
    }
}

This is a great way to automate ScriptableObjects that exist outside of Play Mode. However, you do have to remember to place them all in a parent directory called "Resources". It doesn't have to be in the root of that directory though. For example, this is fine: Assets/Resources/Data/ScriptableObjectData.asset