Loading new MonoBehaviours at runtime for assets in an AssetBundle?

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:

System.Reflection.Assembly.LoadFile("/path/to/monobehaviours.dll");
AssetBundle bundle = AssetBundle.LoadFromFile("/path/to/assetbundle");
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?

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.

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? Unity - Manual: Including scripts in AssetBundles
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. :frowning:

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

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.

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.

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?

Yep, that works without any trouble. Doing something like this:

Assembly simpleasm = Assembly.LoadFile(Application.dataPath + "/simple.dll");
GameObject go = new GameObject();
System.Type testBehaviour = simpleasm.GetType ("SimpleBehaviour.SimpleBehaviour");
go.AddComponent(testBehaviour);

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?

2 Likes

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.

It’s fixed in 2019.1 beta (I have tested it myself):

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:

var scriptDll = Path.Combine(Application.streamingAssetsPath, "script.dll");
var assembly = Assembly.Load(AssemblyName.GetAssemblyName(scriptDll));
var myLoadedAssetBundle = AssetBundle.LoadFromFile(path);
var prefab = myLoadedAssetBundle.LoadAsset<GameObject>(assetBundleName);
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…