Compiling c# code at runtime(not working)

So i was trying to do a game where player need to write scripts to make their units move or do something else like in “Screeps”. And i find some information about that topic on internet

I tried to use System.CodeDom.Compiler to compile user code at runtime. But i get an error:

The invoked member is not supported in a dynamic module.

Code:

static string filePath = Path.Combine(Application.streamingAssetsPath, “main.cs”);
public static void Start()
{
    var assembly = CompileFromFile(filePath);
    var method = assembly.GetType("main").GetMethod("Code");
    //var del = (Action)Delegate.CreateDelegate(typeof(Action), method);
    method.Invoke(null, null);
}

public static Assembly CompileFromFile(string filePath)
{
    string sourceCode = File.ReadAllText(filePath);
    return Compile(sourceCode);
}

public static Assembly Compile(string source)
{
    var provider = new CSharpCodeProvider();
    var param = new CompilerParameters();

    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
        param.ReferencedAssemblies.Add(assembly.Location);
    }

    param.ReferencedAssemblies.Add("UnityEngine.dll");

    param.GenerateExecutable = false;
    param.GenerateInMemory = true;

    var result = provider.CompileAssemblyFromSource(param, source);

    if (result.Errors.Count > 0)
    {
        var msg = new StringBuilder();
        foreach (CompilerError error in result.Errors)
        {
            msg.AppendFormat("Error ({0}): {1}\n",
                error.ErrorNumber, error.ErrorText);
        }
        throw new Exception(msg.ToString());
    }

    return result.CompiledAssembly;
}

I think iets easier to use something that compiles line per line and on the fly like python

Allowing players to compile arbitrary code in a game that supports the full C# language is generally a bad idea. Not only is it extremely difficult to implement, since even minor compile-time errors can prevent the program from running without providing enough information to explain why, and simple runtime mistakes can cause the program to crash, but it also poses serious security risks. Giving players unrestricted execution privileges could allow scripts ranging from harmless experiments to destructive commands that wipe a hard drive or install malware. Players will reasonably expect your game to have safeguards that prevent such operations.

A far safer and simpler approach is to support a subset of an interpreted language, such as Lua, or even a custom language designed specifically for your game. You can find many ready-made solutions for this on GitHub or in the asset store.

  1. I don’t care about security for now because i need this to work.
  2. I want to make possible to make scripts on c# not a Lua.
  3. In this topic i need help in making this code working.

As far as I know, Unity itself has no functionality for this. The methods you are using there would not be functional once you build the game.

This is most likely the best maintained 3rd party solution for that, especially if you need something that’s not fiddly:

It’s not free but the one time cost is bearable. It also does have various security features to isolate the user provided code from the rest and even sanitize input by supressing “dangerous” functionality of the language (disk access etc.).

Let’s not reinvent the wheel.

Programming-games with their own language or a rarely used language just… suck.

Instead of that, rather invest the time in a really good autocomplete and live syntax checker that describes the problem for the newcomer. Programming languages are just syntax. Hence why a programmer with solid experience can learn modern languages within months or even weeks.

Therefore the newcomer will also spend most time understanding how programming works. Not how the syntax works. If someone can write sentences, they can also write code once they’ve understood programming.

So let’s teach them a language they can then actually use on their own and isn’t tied to a programming learning game.

Because what you are asking for is quite complicated and doesn’t have a simple answer. You’re essentially asking someone to do work for you. If it’s something quick and straightforward, people might help if they have the time and energy, but if it’s not then payment is usually expected, nobody is going to do significant work for free.

This is a fairly complex piece of software. If you don’t want to pay for something like it, you can try building it yourself. Asking for help is different from expecting people to solve complex problems for you without compensation, especially when you write things like:

Please edit your post so that it doesn’t include insults or disrespectful remarks.

^^^^

For obvious reasons. :slight_smile:

Seriously, creating and compiling C# code at runtime is an arduous, error-prone task that requires intimate knowledge of how the C# compiler and language (IL) works. Integrating Lua or any other scripting language is in comparison downright straightforward.

You really want to look into Roslyn. You don’t need a paid asset to use it but nevertheless read through its description so you get an idea how involved the whole concept of runtime compilation is, especially within Unity.

Analogy time… I kinda like this one:

  • making a game is like using electric power from the power company: done
  • making a game with lua is like installing solar and sharing power back to the grid: simple, complete.
  • making a game that compiles C# is like building a power station at your house and installing several giant steam turbines burning coal to produce power to share back to the grid: complex, open-ended, noisy, expensive, requires constant maintenance and upkeep, breaks down constantly, explodes occasionally without reason, catches fire on hot days, etc.

Allowing users to write scripts is , frought with issues, allowing users to write modules, you can read in, and perhaps run/load stuff from? maybe

What if you just pay someone 30 bucks to do it for you? :stuck_out_tongue:

Maybe @scottyboy805 would like to write a word here regarding the concerns :slight_smile:

… not to mention dealing with safety regulations and inspections, pollution and public health concerns, environmental laws, structural integrity.

I thought I would offer what can about the topic of compiling C# in Unity. Just for full disclosure, I am the publisher of Roslyn C# - Runtime Compiler asset mentioned previously in the thread.

First thing I will say is that you can certainly compile C# in Unity without a paid asset, but it will take some research and learning on the subject as it is by no means trivial.

You mention you are trying to use the Code Dom in your initial post. From experience there are a few reasons why I would not use it, and unless things have changed in .Net Standard 2.1/Unity 6, then the Code Dom is using the Mono MCS compiler under the hood. The problems with that are:

  1. The MCS compiler is not available as part of the runtime on most platforms. It means you will need to ship the compiler packaged with your game in order for things to work, but even then due to the way it is implemented, the Code Dom implementation provided by Mono is using a fixed path where it is trying to locate the compiler binaries. For that reason it wont work as a standalone on most platforms because most users will not have the Mono compiler installed. It can be solved in other ways but it is a lot of pain and testing for not much gain.
  2. Another reason is that the MCS compiler does not support the latest C# language features. It may have changed a bit by now, but last I checked it was essentially the same as .Net 3.5 compatibility level from Unity 5 days or similar (Even simple things like the in keyword were not yet supported just as an example). May have been updated since, but it will always lag behind the official C# standard by some margin because Unity cannot keep the runtime updated at a reasonable pace since the C# language is evolving so fast.
  3. I believe CodeDom (Or at least the compiler aspect) is depreciated in newer .net.

This is coming from previous experience because many years ago I created another paid asset named Dynamic C# which used MCS compiler internally. I found out all of these issues along the way (I originally started with the CodeDom just like you), and in the end I had to find a way to ship the MCS compiler as part of the game which required extensive modification.

With that said, the modern and much more portable option is to use Microsoft.CodeAnalysis known as Roslyn. It provides a C# API for analyzing, generating and compiling C# code, and since it is consumed as a library, then the dependencies get packaged automatically as part of your game so it can run on all platforms without extra installation steps. It is the official compiler for the C# language, so of course it supports all the latest language features, and internally this is what the paid asset is built on top of, but the paid asset just aims to make things as easy as possible while providing many other features like code and execution security, limited IL2CPP support via extension and more.

You will find that CodeAnalysis is free and open source under the MIT license, so can be used without restrictions. It will just need to be installed manually along with dependencies, which essentially means dropping the .Net Standard 2.1 version of the binaries into your project. Since nuget packages are essentially zip files, you can just download the latest version and all dependencies from the above link and extract to access those binaries (Make sure you include all dependencies recursively or Unity will complain about missing assemblies).

Once you have it installed in your Unity project, you will then need to read up on CodeAnalysis in relation to compilation. Unfortunately it is quite a bit more involved than the Code Dom to get things working since CodeAnalysis is quite a verbose/low-ish level API to work with. The documentation will be helpful there, and also it ChatGPT or similar can give good code examples to get you started if you ask how to compile C# code using Roslyn for example. Essentially there are 2 main steps to get this working which are parsing (Load a C# source file or string and convert it to a syntax tree) and emitting (provide a syntax tree, reference assemblies and further metadata so that the compiler can produce the output assembly or executable), so will be worth searching around with those keywords to get more info.

Hopefully that will get you started on a better path than MCS, which will no doubt cause you more issues down the line when you come to realize that it does not work at runtime for the most part without extensive modification.

For now the only limitation with Microsoft.CodeAnalysis(Or more a limitation of IL2CPP) is that it it will not be possible to execute the compiled code on any AOT platforms. Essentially it means you are limited to using the Mono backend, which equates to Windows, Linux and Mac. The paid asset can support IL2CPP with limitations using a free MIT add-on, but I will assume using Mono backend will be fine for your needs up to now.

I hope that can provide some help to you. As other users have mentioned, it is an advanced topic and will require research and leg work to get things setup and working, but no doubt it is possible for anyone paid or free thanks to Roslyn compiler platform. I have not covered the security aspect since you mention you do not care about it, but just be aware that executing external code on the user device particularly from an unknown/third party source (Modding or similar) can be dangerous and place the user device at risk. If you know the origin of the source code 100% (Content delivery or similar) then it is not so much of an issue but should always be a consideration.

Now this is helpful. Also i tried free Roslyn that you mentioned and it works in editor but not in build.

Happy I could help.
CodeAnalysis will work fine on all Unity platforms as we have tested it extensively, so I suspect the issue here is probably coming down to referencing if I had to take a guess, as other than installation issues it is the other main thing that can cause problems.
Correct me if I am wrong but I will assume you are using file paths to provide reference assemblies for things like netstandard? If so it will not work on all platforms since the assemblies may be at different locations or simply missing in a build. My Roslyn C# asset uses a feature called assembly reference assets to solve this problem and support very restrictive platforms like WebGL and Android with no issues. Essentially you will need to store those reference assemblies somewhere as part of your game files (TextAsset or maybe streaming asset or similar could work), and then you can load a metadata reference from that text asset instead of using an assembly path which is very brittle.
After that everything should work as you would hope if there are no further issues, but if not let me know and I will try to help, and if you are getting any errors in the logs that will be a useful indicator of what the problem might be.

I can send my code here so you can see.

Also run time compiling doesn’t work. Like i did a script then start the game everything works, but if i change some values in that script nothing changed at scene.

Best to show the code you are trying to use then because it could be many things causing it not to work.

using UnityEngine;
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using System;
using System.Linq;
using System.Reflection;

public class Retranslator : MonoBehaviour
{
    string filePath = Path.Combine(Application.streamingAssetsPath, "Scripts", "main.cs");
    private object scriptInstance;
    Canvas canvas;

    async void Start()
    {
        /*filePath = Path.Combine(Application.dataPath, "StreamingAssets", "Scripts", "main.cs");
        #if !UNITY_EDITOR
            filePath = Path.Combine(Application.dataPath, "StreamingAssets", "Scripts", "main.cs");
        #endif*/

        await RunScript(filePath);
    }

    async Task RunScript(string path)
    {

        if (!File.Exists(path))
        {
            Debug.LogError("Script file not found: " + path);
            return;
        }

        string code = File.ReadAllText(path);

        try
        {
            var assemblies = new[]
            {
                typeof(MonoBehaviour).Assembly,
                typeof(UnityEngine.AI.NavMeshAgent).Assembly,
                typeof(Debug).Assembly,
                typeof(TMPro.TextMeshProUGUI).Assembly,
                typeof(Canvas).Assembly,
                Assembly.GetExecutingAssembly()
            };

            var scriptOptions = ScriptOptions.Default
                .WithReferences(assemblies)
                .WithImports("UnityEngine", "UnityEngine.AI", "TMPro");

            var result = await CSharpScript.RunAsync(code, scriptOptions);

            var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes());

            var mainType = types.FirstOrDefault(t => t.Name == "main");
            scriptInstance = Activator.CreateInstance(mainType);
            var methods = scriptInstance.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            foreach (var method in methods)
            {
                try
                {
                    method.Invoke(scriptInstance, null);
                }
                catch (Exception e)
                {
                    Debug.LogError($"Ошибка при вызове метода {method.Name}: {e.Message}");
                    print("Not workin 1");
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError("Script error: " + e.Message);
            print("Not working 2");
        }
    }

    public class Globals
    {
        public GameObject UnitObject { get; set; }
    }

    public async void Compile()
    {
        await RunScript(filePath);
    }
}

Oh ok, you are using the scripting aspect of Roslyn. In any case the code looks fine to me for the most part.
Only issue I can see is that you are searching for the first type with the name main. You have to understand that each compile will load a new type named main into the app domain (as part of a new assembly normally, but I am not too sure when using scripting API). It means for the second compile you will end up with 2 types named main, and FirstOrDefault will always find the first script you compiled which would explain why you don’t see the changes.
I am not sure if the order of assemblies in memory is in a guaranteed order, but you might like searching for the LastOrDefault instead to see if it makes a difference, since it is more likely that newer compiled scripts would be added to the back of the assemblies collection. Probably not a good thing to rely on that ordering though unless you can find information online to suggest it is ordered in a deterministic/predetermined way.
If that works then it just means you were finding the wrong script previously, otherwise not too sure what the problem could be without debugging it.