Software engineer with a focus on game development and scalable backend development
In 2014 Double Fine released a game by the name of Hack ’n’ Slash, the premise being that you could access the code running the game and modify it to change how the game works, it was a really cool premise though maybe not that interestingly implemented. The way it worked was by using a scripting language to run the logic of the game actors and thus allowing you to modify their code.
Unity, whilst it can have a scripting language built into it, generally relies on compiled C# code to run the game logic. In Unity C# will either get compiled into a .Net assembly to be run with the game, or in the case of the IL2CPP backend, get compiled to C++ to then be compiled into a native library.
Luckily he C# compiler is actually part of .net, meaning you can make use of it from within code. This gave me the idea to test whether it would be possible to in a very simple form, recompile and replace a monobehaviour.
First thing is to create a text area with a button to compile the code in the text area. This will serve as a place to edit the code live, alternatively you could have a very simple core game that loads all C# code from files at runtime.
Next is having the code required to compile your input C#, this makes use of the CSharpCodeProvider, and requires the UnityEngine.dll as a reference, luckily using reflection it is easy to find the location of this at runtime. Here I am just running this in the main thread which causes lag spikes on compile, if you were going to do this properly you would have it in a coroutine or its own thread.
var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" });
parameters.ReferencedAssemblies.Add (typeof(MonoBehaviour).Assembly.Location);
parameters.GenerateInMemory = true;
CompilerResults results = csc.CompileAssemblyFromSource(parameters, sourceCode);
results.Errors.Cast<CompilerError>().ToList().ForEach(error => Debug.Log(error.ErrorText));
This creates a new CSharpCodeProvider, adds the required .net assemblies as references, then adds UnityEngine.dll as a reference using its runtime location. Finally it compiles the given code and produces an assembly object.
In the example I just have a simple cube gameobject, and a list of all the compiled components I have added onto it. Upon having done a new compile I iterate over the list and remove all of the components from the cube.
foreach(Component c in components)
{
Destroy (Box.gameObject.GetComponent(c.GetType ().Name));
}
components.RemoveRange (0, components.Count);
I then iterate over all types in the new assembly checking for subtypes of monobehaviours, and adding all of these on to the Box.
var types = results.CompiledAssembly.GetTypes();
foreach (Type type in types)
{
if (type.IsSubclassOf (typeof(MonoBehaviour)))
{
Debug.Log (type.Name);
components.Add (Box.gameObject.AddComponent (type));
}
}
The final result is viewable in the video bellow.