- Home /
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?
Answer by Bunny83 · Aug 08, 2012 at 11:39 AM
[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 ;)
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 ;)
[/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 ;)
Hi, that is incorrect - as I said the $$anonymous$$onoBehaviour 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.
@Shanee22: No, you got me wrong. It in fact doesn't work when you load a $$anonymous$$onoBehaviour from an external source. Unity creates $$anonymous$$eta data for each $$anonymous$$onoBehaviour when it's compiled / imported into a project. There it get registrated in the AssetDatabase. This metadata is required so Unity knows which methods the class implements. Without that registration the class doesn't exist from the runtimes point of view.
AssetBundles doesn't support $$anonymous$$onoBehaviours at the moment. See this post. Also take a look at the comments on his blog
$$anonymous$$y method or the one that is described by AngryAnt are the only ways to get external code working in Unity. It even works great in the webplayer.
So in short: Unity can only use $$anonymous$$onoBehaviours that are within the project itself.
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.
$$anonymous$$y Test project for UnityAnswers contains around 100 scripts and some are quite complex. The created assembly is just 120$$anonymous$$B. As already said, code makes up the smallest part of your game, that's why it's much better to use AssetBundles for assets ;)
Oh, it isn't about the size of the code, it is about the project my company is working on.
We have a software which needs to be run most of the time due to real time accelerometer sampling. The system is connected to an elevator and shows virtual worlds in relation to the physical change in position. Now we also publish different content worlds and we want to make world transition as smooth as possible.
Packages and world content updates can be a problem as we want them to be as automated and smooth as possible via our web portal. If the elevator keeps moving as we turn off, patch and reboot the system then the accelerometer samples get out of precision.
Answer by appearance · Sep 03, 2012 at 05:16 PM
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.
I would love to see a working example, because (at least until now) it's not possible to load any $$anonymous$$onoBehaviour script at runtime. How do you attach the scripts? The string version of AddComponent? I've already tried all possible ways and none worked because the class isn't registered in the AssetDatabase of the project.
AngryAnt doesn't have a $$anonymous$$onoBehaviour in his external DLL. He just executes either a static function or use normal managed classes within the DLL which of course work fine, even in the webplayer (as long as you respect the security sandbox).
The point is that you can't load "scripts" (aka $$anonymous$$onoBehaviours) from an external DLL.
All the scripts which I have compiled to an assembly (DLL) are derived from $$anonymous$$onoBehaviour only (check the command AngryAnt used to compile) I have written code to attach the scripts to the relative gameobjects and included this class also in the Assembly (DLL).
Now the loader application will load the DLL and call this method which will do the job of attaching the scripts to gameobjects. But make sure you have loaded the assetbundle of models before you call this method coz, it will find the gameobjects in the current scene.
I have done this only for android and win32 standalone executable. Both are working fine. I will try with WebPlayer too.
I have done this for a proprietory project of the firm I am working with.
I will prepare a project and upload later. pls give me some time.
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.
Answer by appearance · Sep 06, 2012 at 07:38 AM
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
@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'.
hi,it's glad to find this answer finally ,I met the same problem and have no idea how to update my scripts dynamic.an sample would be very helpful.and thank you so much for sharing this .could you please send me a copy ? my mail is :lchen36@gmail.com. thanks again
@appearance would you please send unity project, "DynamicScript" to my mail address: yeliangqi@gmail.com . I did not receive last time. thanks.
Your answer
Follow this Question
Related Questions
Load a DLL only when needed ? 1 Answer
How to link to scripts in the Package Manager when building a dll. 0 Answers
Can a plugin-like system be implemented using AssetBundles? 1 Answer
Accessing UnityEngine properties from standalone dll - running in a Unity game 1 Answer
"DllNotFoundException: htmlTexture" How to let Unity find the DLL? 0 Answers