I know that you can reference specific .dll files in Unity, but I'm wondering how I can create a plugin loader for ANY plugin, similar to how Kerbal Space Program works. My goal is to make our game have just about endless possibilities for add ons.
It is pretty easy. 1. You have to have a contract for all the plugins in a form of an interface or some abstract class. For example Code (CSharp): public interface IPlugin { string Name { get; } void Initialize(); } Each plugin must contain a public class that inherits from it. For example Code (CSharp): public class PluginA : IPlugin { public string Name { get { return "Plugin A"; } } public void Initialize() { } } public class PluginB : IPlugin { public string Name { get { return "Plugin B"; } } public void Initialize() { } } 2. The only thing left is to load the plugins: Code (CSharp): private List<IPlugin> LoadPlugins(string directory) { var plugins = new List<IPlugin>(); var files = Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories); foreach (var file in files) { // load the plugin assembly into memory var assembly = Assembly.LoadFrom(file); // scan through all its public types foreach (var exportedType in assembly.GetExportedTypes()) { // filter those that inherit from IPlugin if (typeof(IPlugin).IsAssignableFrom(exportedType)) { // instantiate those IPlugin implementations var plugin = (IPlugin)Activator.CreateInstance(exportedType); plugins.Add(plugin); } } } return plugins; } 3. That's it. Now you can start to use the loaded plugins. For example Code (CSharp): var plugins = LoadPlugins("./Plugins"); foreach (var plugin in plugins) { Debug.Log(plugin.Name); plugin.Initialize(); }
Well, there's an error in LoadPlugins method. There must be Assembly.LoadFrom instead of Assembly.Load. I've updated the example. The real code must definitely have some error handling. There are several ways how LoadPlugins could crash. 1. Assembly.LoadFrom may throw an exception if the dll is not a managed dll, or is a managed dll but compiled for a different version of CLR. 2. Activator.CreateInstance may fail to create an instance of the type if the type lacks a parameterless constructor. 3. A plugin may have classes that implement IPlugin interface, but are abstract. You'll have to ignore them Code (CSharp): // filter those that inherit from IPlugin and not abstract if (exportedType.IsAbstract == false && typeof(IPlugin).IsAssignableFrom(exportedType)) { } 4. All the calls to plugin methods and properties should happen only inside try/catch blocks since you have no idea of how well they are written and what they do.
This is probably a stupid question, but where do I add this part: Code (CSharp): private List<IPlugin> LoadPlugins(string directory) { var plugins = new List<IPlugin>(); var files = Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories); foreach (var file in files) { // load the plugin assembly into memory var assembly = Assembly.LoadFrom(file); // scan through all its public types foreach (var exportedType in assembly.GetExportedTypes()) { // filter those that inherit from IPlugin if (typeof(IPlugin).IsAssignableFrom(exportedType)) { // instantiate those IPlugin implementations var plugin = (IPlugin)Activator.CreateInstance(exportedType); plugins.Add(plugin); } } } return plugins; }
Wherever you want, just before the point when you actually start to use the plugins. KSP does it right at the startup (when you see the loading screen and the progress bar at the bottom). I guess I'll make an example project. I did plugin support a couple of times in the past, but haven't done it in Unity yet. There shouldn't be any difference though.
Should I put it in a void, or somewhere else? It throws errors when just in the class, and it gives a couple when in void Start()
Great post alexzzzz! Some added things you may want to do. 1) the plugin will need a reference to the library that contains IPlugin. This could be done by referencing the 'Assembly***.dll' file in the 'Managed' folder of the built project. Or you could have a separate project that has the plugin framework built in VisualStudio/MonoDevelop. 2) This will only work on Windows/Linux/OSX. Things like the iPhone, and the webgl player, don't use mono in the end and instead compile AOT. This dynamic loading of mono/.net dll's won't work. 3) You could create a hook into the main game as an object that gets passed into the plugin so it has a reference to get a hold of your game specifics more easily: Code (csharp): public class Game { //include methods and properties relavent to your game //examples: public Player GetPlayer(int index) {} } public interface IPlugin { string Name { get; } void Initialize(Game game); }
It should only be called once, and it should be called at a point in your game where it makes sense. Such as when the game first starts. Usually my games start on an empty scene that just loads the basic guts of the game and then loads the next scene. This will have a 'GameStartup' script that only ever exists once in this empty scene. What are the errors you get? The code alexzzzz isn't simple cut/paste example that you can toss into any ol' script.
Unexpected symbol 'private' when it is inside a void. Everything imaginable when it's just inside the class. (Mainly unexpected symbol) Outside the class, it gives me unexpected symbol 'list' though I don't think it should be outside the class.
If you are asking about a complete source file to start with, that can be compiled with no errors, here it is Code (CSharp): using System; using System.Collections.Generic; using System.IO; using System.Reflection; public interface IPlugin { string Name { get; } void Initialize(); } public static class PluginManager { public static List<IPlugin> Plugins { get; private set; } public static void LoadPlugins() { var pluginsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Plugins"); Plugins = LoadPlugins(pluginsDirectory); } private static List<IPlugin> LoadPlugins(string directory) { var plugins = new List<IPlugin>(); var files = Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories); foreach (var file in files) { // load the plugin assembly into memory var assembly = Assembly.LoadFrom(file); // scan through all its public types foreach (var exportedType in assembly.GetExportedTypes()) { // filter those that inherit from IPlugin if (typeof(IPlugin).IsAssignableFrom(exportedType)) { // instantiate those IPlugin implementations var plugin = (IPlugin)Activator.CreateInstance(exportedType); plugins.Add(plugin); } } } return plugins; } } Call PluginManager.LoadPlugins() and it will load the available plugins and put them in PluginManager.Plugins list.
Sounds like you just copy pasted all of that code right into a file. Not going to work quite exactly. Post what you did here. edit: already answered...
You probably want to tackle the beginner scripting tutorials before attempting this. While dynamically loading code is not rocket science, it's also not something to attempt when you are still puzzling over methods and access modifiers.
I already do all this stuff, but I've never actually loaded files. I know, I do come off as sounding stupid in this.