- Home /
In-Game scripting works in Editor (Try it!) - But Not Build
The Idea
want players to be able to interact with the game through C# scripting. Everything works in the editor (go ahead an try it), but not when I "build" the game. This is the error (exception message) I see:**
The Error Message
n exception was thrown by the type initializer for Mono.CSharp.CSharpCodeCompiler System at Microsoft.CSharp.CSharpCodeProvider.CreateCompiler () [0x00000] in :0 at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource (System.CodeDom.Compiler.CompilerParameters options, System.String[] fileNames) [0x00000] in :0 at ScriptEditor.Compile () [0x00000] in :0
The Code
o Run this script in your game:
Create a C# script file "ScriptEditor.cs "
Paste this code.
Attach to any game object
In Build Settings->Player Settings, change ".NET 2.0 Subset" to ".NET 2.0"
using UnityEngine; using System; using System.Text; using System.Collections; using System.Reflection; using System.CodeDom; using System.CodeDom.Compiler; using Microsoft.CSharp;
///
/// ScriptEditor.cs (Attach this to any game object) /// Useage Notes: /// For this to run, you MUST: /// 1. Go to File-->Build Settings... /// 2. Click "Player Settings..." /// 3. "Other Settings" /// 4. "Optimization"-->"API Compatability Level" /// 5. Change from ".NET 2.0 Subset" --> ".NET 2.0" ///public class ScriptEditor : MonoBehaviour {
void Update() { // Dont run our script until it has been COMPILED successfully. if(myScript_Type == null || myScript_Instance == null) return; // Run the script's Update() function myScript_Type.InvokeMember("Update",BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, myScript_Instance, null); // Read the scripts "readme" variable. PropertyInfo readmePropertyInfo = myScript_Type.GetProperty("readme"); // Source: http://www.csharp-examples.net/reflection-examples/ readme = (string)readmePropertyInfo.GetValue(myScript_Instance, null); } private string scriptText = @"using System; using System.Collections.Generic; public class MyScript { public string readme {get;set;} // Our unity program will read this variable. private int counter = 0; public MyScript() // Constructor { readme = """"; } public void Update() // Our unity game will run this function. { counter++; readme = ""Hi! Script is Running! "" + counter + ""\n""; } } "; private string readme = ""; // Displayed on the GUI private string compilerErrorMessages = ""; // Displayed on the GUI void OnGUI() { // ********** Display the script in Unity scriptText = GUI.TextArea(new Rect(10, 10, 700, 500), scriptText); // ********** Compile your script if (GUI.Button(new Rect(720, 10, 300, 30), "Compile and Run")) { Compile (); // Compile the script. Write errors to "compilerErrorMessages", if any. } // ********** Display any compiler errors GUI.TextArea(new Rect(10, 510, 700, 50), compilerErrorMessages); // The console for displaying errors. // ********** Display the Script's Output. GUI.TextArea (new Rect(720, 50, 300, 30), readme); } private Assembly generatedAssembly; // Compiled code is called an "Assembly" private Type myScript_Type = null; // These two variables are used run the compiled code. private object myScript_Instance = null; // These two variables are used run the compiled code. private void Compile() { try { compilerErrorMessages = ""; // clear any previous messages // ********** Create an instance of the C# compiler CSharpCodeProvider codeProvider = new CSharpCodeProvider(); // ********** add compiler parameters CompilerParameters compilerParams = new CompilerParameters(); compilerParams.CompilerOptions = "/target:library /optimize /warn:0"; compilerParams.GenerateExecutable = false; compilerParams.GenerateInMemory = true; compilerParams.IncludeDebugInformation = false; compilerParams.ReferencedAssemblies.Add("System.dll"); compilerParams.ReferencedAssemblies.Add("System.Core.dll"); // ********** actually compile the code ??????? THIS LINE WORKS IN UNITY EDITOR --- BUT NOT IN BUILD ?????????? CompilerResults results = codeProvider.CompileAssemblyFromSource(compilerParams,scriptText); // ********** Do we have any compiler errors if (results.Errors.Count > 0) { foreach (CompilerError error in results.Errors) compilerErrorMessages = compilerErrorMessages + error.ErrorText + '\n'; } else { // ********** get a hold of the actual assembly that was generated generatedAssembly = results.CompiledAssembly; if(generatedAssembly != null) { // get type of class Calculator from just loaded assembly myScript_Type = generatedAssembly.GetType("MyScript"); // create instance of class MyScript myScript_Instance = Activator.CreateInstance(myScript_Type); // Say success! compilerErrorMessages = "Success!"; } } } catch(Exception o) { compilerErrorMessages = ""+o.Message +"\n"+ o.Source +"\n"+ o.StackTrace +"\n"; } } }
Very cool implications here. Have you experimented with sandboxing to render this a safe game modding system?
I am attempting something quite similar to what you list here and I am see the same issue. Have you had any success in the last 6 months with this?
I noticed in the $$anonymous$$ono code you've linked to below that the static constructor is starting with a private property of Environment GacPath and navigating relative to that for mono.exe and gmcs.exe. So I compared the GacPath between my Editor version and Build version and made local copies of those folders. This is an ugly workaround, and strangely only worked when I choose to Build and Run from the Editor, it still didn't run my new code when started it by double clicking the executable in the folder.
In any case you have to ship the Cä compiler with your Unity-game since you can't rely on the fact that the user has a $$anonymous$$ono version installed (or .NET). This page might be helpful but you probably have to dig quite a bit into the $$anonymous$$ono code to get it working ;)
As alternative either search if someone implemented a C# compiler library that can be easily included into a Unity or C# application or use a simpler scripting language like LUA. Just if you haven't seen there's a lua implementation, modified for Unity3d, called UniLUA
That is awesome! I can not wait to try the lua implementation. This would be just fine for my application. Thank you Bunny83
LUA is something different and I love C#. I would be happy if it was possible to add an addon system to my game depending on C# language.
Have you found a solution for including the mono compiler into the game build ?
Answer by Bunny83 · Dec 16, 2012 at 05:18 PM
The answer is quite simple. If you use ILSpy or any other .NET reflector you will see that the Mono.CSharp.CSharpCodeCompiler has a static constructor which searches for the Mono SDK.
I'm not sure why so many implementetions catch exceptions to rethrow them in a cryptical manner. When you remove your try-catch block and run your code in a standalone build, you will see something like that in your output_log.txt:
FileNotFoundException: Windows mono path not found: D:\Unity35\Projects\mono\mono\mini\mono.exe
at Mono.CSharp.CSharpCodeCompiler..cctor () [0x00000] in <filename unknown>:0
Rethrow as TypeInitializationException: An exception was thrown by the type initializer for Mono.CSharp.CSharpCodeCompiler
at Microsoft.CSharp.CSharpCodeProvider.CreateCompiler () [0x00000] in <filename unknown>:0
at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource (System.CodeDom.Compiler.CompilerParameters options, System.String[] fileNames) [0x00000] in <filename unknown>:0
at ScriptEditor.Compile () [0x00000] in <filename unknown>:0
at ScriptEditor.OnGUI () [0x00000] in <filename unknown>:0
This:
D:\Unity35\Projects\
is actually not my project path. It's actually:
D:\Unity35\Projects\TestProject\build
TestProject is the project folder with the asset folder. The build folder contains the whole build.
It's a bit strange how Unity's mono version searches for the path. this is the static constructor:
static CSharpCodeCompiler()
{
if (Path.DirectorySeparatorChar == '\\')
{
PropertyInfo property = typeof(Environment).GetProperty("GacPath", BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo getMethod = property.GetGetMethod(true);
string directoryName = Path.GetDirectoryName((string)getMethod.Invoke(null, null));
CSharpCodeCompiler.windowsMonoPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(directoryName)), "bin\\mono.bat");
if (!File.Exists(CSharpCodeCompiler.windowsMonoPath))
{
CSharpCodeCompiler.windowsMonoPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(directoryName)), "bin\\mono.exe");
}
if (!File.Exists(CSharpCodeCompiler.windowsMonoPath))
{
CSharpCodeCompiler.windowsMonoPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(directoryName))), "mono\\mono\\mini\\mono.exe");
}
if (!File.Exists(CSharpCodeCompiler.windowsMonoPath))
{
throw new FileNotFoundException("Windows mono path not found: " + CSharpCodeCompiler.windowsMonoPath);
}
CSharpCodeCompiler.windowsMcsPath = Path.Combine(directoryName, "2.0\\gmcs.exe");
if (!File.Exists(CSharpCodeCompiler.windowsMcsPath))
{
CSharpCodeCompiler.windowsMcsPath = Path.Combine(Path.GetDirectoryName(directoryName), "lib\\net_2_0\\gmcs.exe");
}
if (!File.Exists(CSharpCodeCompiler.windowsMcsPath))
{
throw new FileNotFoundException("Windows mcs path not found: " + CSharpCodeCompiler.windowsMcsPath);
}
}
}
I guess since Unity brings it's own Mono compiler it's not installed in your system. When you run your game inside the editor it uses different assemblies which are shared with the editor.
Bunny83 Thank you so much for you reply! Very encouraging!
I tried installing two different versions of $$anonymous$$ono.
I tried placing the files I thought it was looking for (indicated in output_log.txt) in "...mono\\mono\\$$anonymous$$i\\" etc.
No luck so far.
How would you recommend I include the "$$anonymous$$ono compiler" in my game build? Is there a "mono.dll" I can place in my assets folder?
What are your thought?
Answer by aeroson · Apr 05, 2015 at 12:18 PM
Solved it.
Installed Mono in default path: C:\Program Files (x86)\Mono (used for editor), then copied the Mono folder next to my standalone .exe as well (used for standalone).
Now i know where Mono is located so i just needed to edit the paths of mono.exe and mcs.exe. So i modified the CSharpCodeCompiler (link provided by Alizarin). And used this modified class to create the assembly.
Here source to the modification and it works both in editor and standalone. Thank you all.
Another solution is to take the $$anonymous$$CS compiler source code which is written in C# and compile it for .NET 3.5
You can try referencing the mcs.exe it self as it is now, but it is compiled with .NET 4.0 so it doesn't work with Unity.
Here is the definitive answer to this cause: https://github.com/aeroson/mcs-ICodeCompiler
Perfect work! Also very good to include info about what they did for Cities: Skylines.
Great work, but the compile from DO$$anonymous$$ method doesn't seem to work. I get a class cast exception on line 120, right when you try to cast this
into an ICodeGenerator. I'm looking into this issue and am trying to find a workaround, but if you have any idea of what's wrong any help is appreciated.
Never$$anonymous$$d, found a solution. I'll post it below.
How can I use only the default solutions (provided only by $$anonymous$$icrosoft and/or $$anonymous$$ono at all) to be able to compile c# codes at runtime? I don't want to use ICodeCompiler you made at all, to understand better the whole thing.
So I get the "Windows mono path not found: F:/mono/mono/$$anonymous$$i/mono.exe" error message, if I want to compile in build. You said you have copied the installed $$anonymous$$ono folder near your game.exe. But where can you find CSharpCodeCompiler? And how can you create an assembly with that?
You need to understand why it doesn't work now by looking into the source code of what is your Unity using (CSharpCodeCompiler class), then fix it. CSharpCodeCompiler is just starting new process ($$anonymous$$ono\lib\mono\4.5\mcs.exe) to compile your code, that means you can create new modified CSharpCodeCompiler to find correct mcs.exe
It seems they took down the source $$anonymous$$ono code. Is there anywhere I can view it?
Answer by Rallymen007 · Feb 23, 2016 at 02:31 PM
Building on aeroson's answer I managed to get Unity to build C# in standalone in a much simpler way, though not as clean and space-efficient as his improved answer that comes with a patched version of mcs. This version is probably more suitable to bigger projects, for which including a 400 MB Mono runtime isn't an issue, if you want to have DLLs generated (to reuse them afterwards for example), or do not want to cope with all the little quirks of using Reflection.Emit instead of a good old compiled assembly. This version also fully works with CodeDOM objects.
I reused aeroson's CSharpCodeCompiler, which I copied into a CustomCSharpCodeCompiler.cs file inside my project. Then I added a slightly modified CSharpCodeGenerator using the Mono source code, which I copied into a CustomCSharpCodeGenerator.cs file inside my project. The main modification to this file was moving it to the Modified.Mono.CSharp package where the CSharpCodeCompiler is, which enabled the generator to use the custom compiler.
And here's the bit of script that uses those classes, it's a simple function that takes a CodeDOM object, a filename (used to determine the name of the assembly to compile), compiles it into a DLL file and loads this assembly in memory so you can use it straight away.
This worked, thanks much.
I think this is actually a cleaner solution. Than the recompiled $$anonymous$$CS (which doesnt work with Visual Studio Integration tools because of conflicts)