Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Loading new MonoBehaviours at runtime for assets in an AssetBundle?

Discussion in 'Editor & General Support' started by bhfjrskghkksrjlgb, Dec 4, 2017.

  1. bhfjrskghkksrjlgb

    bhfjrskghkksrjlgb

    Joined:
    Sep 10, 2017
    Posts:
    4
    I'm trying to do something that is, perhaps, a little strange, but it looks like I'm not the only one to try it.

    At runtime, I want to instantiate a Prefab that includes new MonoBehaviours from an AssetBundle. I've been trying to get around the fact that AssetBundles don't include any code (compiled or otherwise) by building a separate DLL containing all the MonoBehvaiours the Prefab needs. With the AssetBundle, and DLL in hand, I do something along the lines of:

    Code (CSharp):
    1. System.Reflection.Assembly.LoadFile("/path/to/monobehaviours.dll");
    2. AssetBundle bundle = AssetBundle.LoadFromFile("/path/to/assetbundle");
    3. Instantiate(bundle.LoadAsset("assets/path/to/foo.prefab"));
    Unfortunately, it looks like Unity doesn't automagically recognize the new MonoBehaviours that (I think) should've been loaded into the current context, since I get a whole bunch of errors (one for each bit of the prefab, each reading: The referenced script on this Behaviour (Game Object 'foo') is missing!.

    What should I do to get this working? Is there some way to tell Unity about the (classes in the) newly-loaded assembly?
     
  2. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,052
    Loading new code via assetbundles isn't supported, and expressly prohibited by several platforms.

    Here is some more info if you are using 5.5. That info isn't included in the 2017 docs, so it may not work.
     
  3. bhfjrskghkksrjlgb

    bhfjrskghkksrjlgb

    Joined:
    Sep 10, 2017
    Posts:
    4
    I'm trying to load code in addition to an AssetBundle and don't mind if these shenanigans only work on PC. I definitely realize this sort of thing won't work if the target platform is using AOT compilation, or prohibits this sort of dynamic code execution.

    Is there a link that got eaten there? Was it this one? https://docs.unity3d.com/550/Documentation/Manual/scriptsinassetbundles.html
    If so, that specific solution requires programatically creating a large chunk of the new object, rather than just including it in the assetbundle, which unfortunately means that it doesn't work for my particular use-case. :(

    Based on the error messages it seems like the AssetBundle includes some sort of reference to the scripts and some experiments, which involved merging the contents of the separately built DLL with my already-built Assembly-CSharp.dll, even make it look like the AssetBundle includes enough information for Unity to match the code up with the relevant pieces when they exist in a less dynamically loaded assembly.

    Since the the DLL was built without the editor I assume that Unity is doing some reflection and using the names of the classes to resolve the references (rather than the Type GUIDs or asset GUIDs); I was sort of hoping there'd be a way (potentially involving black magic and ritual sacrifices) to convince the Unity player to re-do that reflection, or even manually inform it about each of the new classes
     
  4. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,052
    Yea, sorry that was the link.

    If a class is attached to object in asset bundle, that class must be available when the asset bundle is loaded. (It is only a reference and the serialized data, not the actual class). Asset bundles are for serialzed data/assets not code. I don’t think you’ll be able to achieve this with assetbundles alone. @superpig may have more useful information on this.
     
  5. bhfjrskghkksrjlgb

    bhfjrskghkksrjlgb

    Joined:
    Sep 10, 2017
    Posts:
    4
    That's sort of what I'd figured, which is why I'm trying to load the code from a separate DLL, rather than something inside the AssetBundle. Any idea what makes a class "available"? Calling System.Reflection.Assembly.LoadFromFile before loading the AssetBundle itself definitely doesn't seem to be sufficient but I'm not sure where else to poke.
     
  6. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,660
    What I guess is happening is that the MonoScript object instances are not being packed into your bundle. MonoBehaviours all reference MonoScript instances, which contain the assembly name + namespace + class name of the class to use. But I'm not sure if that is the only problem.

    Having loaded the assembly using LoadFromFile, are you able to create instances of the MonoBehaviours from code, have them receive Update() calls, etc?
     
  7. bhfjrskghkksrjlgb

    bhfjrskghkksrjlgb

    Joined:
    Sep 10, 2017
    Posts:
    4
    Yep, that works without any trouble. Doing something like this:
    Code (CSharp):
    1. Assembly simpleasm = Assembly.LoadFile(Application.dataPath + "/simple.dll");
    2. GameObject go = new GameObject();
    3. System.Type testBehaviour = simpleasm.GetType ("SimpleBehaviour.SimpleBehaviour");
    4. go.AddComponent(testBehaviour);
    5.  
    creates a MonoBehaviour without any issues. The new MonoBehaviour receives Awake() and Update() calls as expected.

    In fact, if I manually merge the contents of my separate DLL into Assembly-CSharp.dll, I'm able to instantiate a prefab with associated MonoBehaviours from an AssetBundle without any issues.

    From your description of the contents of a MonoScript, and my franken-assembly experiments, I think the MonoScript instances are getting packed into the bundle, but are pointing at the wrong assembly. Is there any way to change what assembly the MonoScript instances point to?
     
    Last edited: Dec 4, 2017
  8. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,660
    You _might_ be able to hack something using SerializedObject/SerializedProperty as applied to the MonoScript object. Note that the MonoScript object is generated when importing your scripts, so whenever you reimport a script the MonoScript object will reset.

    Please note that you are very much in unsupported territory here, though - this kind of dynamic behaviour loading is not something we have ever tried to make work.
     
  9. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,058
  10. kai_lehmann

    kai_lehmann

    Joined:
    Apr 14, 2020
    Posts:
    4
    Just stumbled upon this because I have the same issue, running on Unity 2019.4.9f1. Basically I also have my assetbundle with prefabs and referenced custom MonoBehaviours. Then I have the same Monobevaviour classes compiled into my dll and import both assetbundle and dll like this:

    Code (CSharp):
    1. var scriptDll = Path.Combine(Application.streamingAssetsPath, "script.dll");
    2. var assembly = Assembly.Load(AssemblyName.GetAssemblyName(scriptDll));
    3. var myLoadedAssetBundle = AssetBundle.LoadFromFile(path);
    4. var prefab = myLoadedAssetBundle.LoadAsset<GameObject>(assetBundleName);
    5. Instantiate(prefab);
    I also tried
    Assembly.LoadFrom
    and
    Assembly.LoadFile
    - all without luck.

    The monobehavious from the dll do not get picked up properly, I get the error `
    The referenced script on this Behaviour (Game Object '...') is missing!
    `

    However, If I just drag the dll into my assets folder and attach my script from there manually everything works fine. So, somehow imported dll and imported assetbundle do not get linked properly.....