MonoBehaviour from DLL in AssetBundle Reflection problem

Hello.

I made a project and decided to move its scripts to a .DLL so I can load them later from an AssetBundle. In the project I have the .dll under the plugins folder and I can drag/attach them to GameObjects just fine.

I export the scene into an AssetBundle, in a different project I load the dll with the script files using

m_Assembly = System.Reflection.Assembly.LoadFile(Application.dataPath + "/../" + worldFileName + ".dll"); .

According to log the scripts seems to be loaded and found alright:

System.Type behaviour = m_Assembly.GetType("BirdBehaviour");
 if (behaviour != null)
 {
 UnityEngine.Debug.Log(behaviour.Name); 
 
 }

This returns “BirdBehaviour” in the console.

Then I load my Scene via AssetBundle, the scene is loaded fine but none of the script seems to be working.
The class defined in script file named ‘BirdBehaviour’ does not match the file name!

I could make a script to use
m_Assembly.GetType(“BirdBehaviour”);

and then AddComponent but that will make things complicated and won’t allow me to edit the public variables of scripts in the editor of the Scene I am exporting as a bundle. Is there a solution to have the referenced scripts properly load from the dll I have loaded?

3 Answers

3

[edit(2012-09-28)]

Ok, i finally found the problem. It is possible to have MonoBehaviours in an external DLL and load it dynamically at runtime. The problem was the namespace. Usually when you create a dll it uses it’s own namespace. When you import such a dll into a Unity project the namespace seems to disappear and Unity just grabs all MonoBehaviour classes it can find in the dll.

When you load the dll at runtime Unity has problems to “find” the classes inside the assembly. You can get the System.Type object of your class, but AddComponent will complain it can’t find the class.

The solution is: Just remove the namespace when you build your DLL. put all classes into the global namespace and it will work :wink:

After you’ve loaded the assembly you have to retrieve the System.Type object for your class and use it in AddComponent.

Note: AddComponent with a string parameter won’t work unless you added the component at least one time with the Type-object. After that the MonoBehaviour is known to the system and the string version works as well, but not for the first time.

Since it is possible to directly load MonoBehaviour derived classes from an assembly my old answer becomes almost useless :wink:

[/edit(2012-09-28)]

You can’t have MonoBehaviours in an external dll. MonoBehaviours have to be registered in the AssetDatabase.

It’s fine to load “normal” classes that way. On this basis i developed my own “plugin” interface. Something like that:

public class Plugin
{
    public MonoPlugin container;
    public virtual void Awake() {};
    public virtual void Start() {};
    public virtual void Update() {};
    //[...]
}

This interface should be in a common dll which is used by your plugin dll and your actual project. Now just create a MonoBehaviour in your project that implements all unity function your plugin interface needs and call the interface functions from your plugin object:

public class MonoPlugin : MonoBehaviour
{
    public Plugin plugin;
    void Start()
    {
        plugin.Start();
    }
    void Update()
    {
        plugin.Update();
    }
    //[...]
}

Now you can create an extension method for GameObject and / or Component which allows you to add a MonoPlugin:

public static class MonoPluginExtension
{
    public static Plugin AddPlugin(this GameObject aObj, System.Type aPlugin)
    {
        MonoPlugin MP = aObj.AddComponent<MonoPlugin>();
        Plugin P = (Plugin)Activator.CreateInstance(aPlugin);
        MP.plugin = P;
        P.container = MP;
        // Awake can't be wrapped since it's called within AddComponent
        P.Awake();
        return P;
    }
}

Finally you can derive your plugins from Plugin and put them in an external dll. Hopefully Unity will support using MonoBehaviours directly the other day.

ps: I’ve written that just from scratch. So no syntax checks. It’s just a prove-of-concept :wink:

Hi, that is incorrect - as I said the MonoBehaviour from the dll in fact work in the original project! It is when I export it to a bundle and try to load the dll via reflection in the project which loads the bundle before loading the bundled scene then even though the dll loads and I can use GetType from the assembly and manually AddComponent, the scripts from the bundled scene won't recognize they were loaded via reflection and send debug warning for missing reference as I mentioned above.

Ok, so there is no way for me for example to have the scripts inside Unity so I can set parameters individually? For example what if I have a path finding script for an NPC, or something else specific, that won't be good for it then,m will it? As I need to have it all self initializing in the Start() etc..? Or is there a solution for inputting the variable values somehow?

Actually code is most the time the smallest part of your game. Loading external code is usually only required when you want to provide some kind of update interface. Loading parts of you code from an external source to reduce the game size is pretty much useless. My Test project for UnityAnswers contains around 100 scripts and some are quite complex. The created assembly is just 120KB. As already said, code makes up the smallest part of your game, that's why it's much better to use AssetBundles for assets ;)

I just now did what you tried to do. I have created an assetbundle for the whole scene. All the gameobjects in that scene will not have any scripts attached. Then I built a .DLL for all the scripts which are going to operate on the gameobjects for the scene.
Now in a new project, I have loaded the scene assetbundle and loaded the scene additively. Then using AngryAnt’s technique, I have downloaded the .DLL assetbundle as text asset and loaded the assembly using reflection. Then to do the work of attaching the scripts to the gameobjects in the loaded scene, I called a predefined method of a predefined class from the same assemby which knows which scripts are needed to be attached to which gameobjects and what public data members of the scripts are needed to be set. It worked perfectly for me.

Note that new project doesn’t know anything about the models in the assetbundle and doesn’t know anything about any scripts in the dll either.

If you need a working example, revert me back.

If you have got a better solution please share it.

Well, I have prepared sample apps and tried it for Android, Win32 and WebPlayer. All worked just fine. Let me know if you still like to see the samples.

Sure, samples would be nice ;) I'm mainly interested in Win32 and Webplayer

@Bunny83: I shared the code. pls check it and tell me if it's working with you. If you have done any modifications/improvements, pls share it.

Thank you very much. I will check it as soon as i can. At first glance it seems to be quite similar what i tried myself some time ago, but we'll see ;)

@Bunny83 I'm facing the same problem : i try to create/load AssetBundles with Behaviours script. Do you still have the @appearance 's solution ? (he didn't connect since 2014, so i'm asking you) I would really appreciate your help :)

Here is the working sample. Tested for Win32, Android and WebPlayer (on Unity 3.5.5f2 Pro)
It needs mono to generate the assembly.
Mono can be downloaded from here.

Samples can be downloaded from [Sorry! Please write me, I will surely send the files].

NOTE:

  • Assembly can be created by running
    “DynamicScript\Remote\Assets\Scripts\makedll.bat”.
  • Assumed that mcs is added to PATH environment variable.
  • Update the path in “DynamicScript\Host\Assets\Scripts\SceneLoader.cs”
    before compiling.
  • Build AssetBundles from Remote app from Assets Menu of unity editor

meet the same problem, a working sample will be very helpful. would you please send me one? thanks

Yes, sure, why not. give me your email id.

@appearance my email: 892502@qq.com ,I just can not find a good way to keep the original inspector value. As you know in hydra, script and object will be download separately. when script is attached to the object at run time, the public value is not what I set in the local on the inspector.

I've sent you unity project, "DynamicScript". If you go through the project carefully, you will see that there's a class named 'SetupCube' and it has a public method called 'Setup'. This method will set all the variables to required public values as you would have set in the inspector. Note that in the Host project 'NewBehaviorScript' is loading the assembly and creating an instance of 'SetupCube' class and invoking it's public method called 'Setup'.

@appearance would you please send unity project, "DynamicScript" to my mail address: yeliangqi@gmail.com . I did not receive last time. thanks.