Wie kompiliere ich ein C # -Skript zur Laufzeit und hänge es als Komponente an ein Spielobjekt an

8

Die Idee dabei ist, dass Benutzer das Verhalten eines Agenten so programmieren können, dass es an das Spiel "Screeps" erinnert, in dem Sie Code entwickeln können, der später ausgeführt wird und das Verhalten Ihrer Einheiten bestimmt.

Meine Idee ist es, Benutzer das Verhalten oder ihre Einheiten mit C # -Code entwickeln zu lassen, diesen Code zur Laufzeit zu kompilieren und ihn als MonoBehavior an ein vorhandenes Spielobjekt anzuhängen.

Wie kann ich das erreichen?

Oscar Guillamon
quelle
3
Sie können auch eine vorhandene Skriptsprache verwenden. Zum Beispiel gibt es JavaScript.net , mit dem Sie JavaScripts aus einem C # -Programm interpretieren und ausführen können. Pro: Minimaler Implementierungsaufwand. Sie können Benutzer auf vorhandenes JS-Lernmaterial verweisen. Con: Könnte für Ihren eingeschränkten Anwendungsfall zu leistungsfähig und zu verwirrend sein.
Philipp
1
Eine andere Möglichkeit wäre, einen visuellen Editor zu erstellen, in dem Benutzer Anweisungsblöcke ziehen und ablegen können. Pro: Sieht aus und fühlt sich eher wie ein Spiel an, leichter zu unterrichten und für Nicht-Programmierer leichter zu lernen. Contra: Die Entwicklung braucht Zeit, kann für erfahrene Programmierer (und diejenigen, die es durch das Spielen des Spiels werden) zu einschränkend sein.
Philipp
Eine meiner Ideen, bei der genau Anweisungsblöcke, die ihre Vor- und Nachteile haben und es Benutzern ermöglichen, ihre eigenen Blöcke zu erstellen, von beiden Ideen profitieren würden, die @Philipp zur Verfügung gestellt hat, habe ich Recht oder ich vermisse etwas, das diese Aussage macht?
Oscar Guillamon
"Wie man das erreicht" ist eine äußerst breite Frage. Dies hat mehrere unabhängige Aspekte, wie das Entwerfen der Benutzeroberfläche, das Entscheiden, welche Blöcke Sie benötigen, wie sie als Datenstrukturen dargestellt werden und wie diese Datenstrukturen interpretiert werden. Sie müssen angeben, mit welchem ​​dieser Aspekte Sie ein Problem haben und um welches Problem es sich handelt.
Philipp

Antworten:

12

Ich habe das nicht selbst gemacht, daher basiert diese Antwort auf einem Artikel von exodrifter ( ich habe gerade "Unity Runtime Compile" gegoogelt und ganz oben 4 Antworten gefunden, also versäume nicht, dich selbst zu suchen!)

Was Sie beschreiben, ist möglich, aber nicht ohne Fallstricke:

  • Verwenden Sie die .NET 2.0-Kompatibilitätsstufe. Andernfalls fehlen möglicherweise einige von Ihnen benötigte Namespaces.

  • Nicht auf jeder Plattform können Sie zur Laufzeit neues C # aus Zeichenfolgen kompilieren - insbesondere Android und iOS verbieten dies.

  • Standardmäßig funktioniert dies unter Windows, jedoch nicht unter Macs / Linux, es sei denn, der Benutzer hat das Mono SDK installiert. Sie können den mcs-ICodeCompiler von aeroson einbinden , um diese Funktionalität als Plugin in Ihr Spiel zu integrieren.

Beispielcode des Exodrifters (geändert, um eine Komponente hinzuzufügen)

(Ausführliche Informationen finden Sie im Artikel. Ich möchte hier nur so viel aufnehmen, dass diese Antwort ein nützlicher Wegweiser ist, auch wenn der verlinkte Artikel ausfällt.)

using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using UnityEngine;

public class RuntimeCompileTest : MonoBehaviour
{
    void Start()
    {
        var assembly = Compile(@"
        using UnityEngine;

        public class RuntimeCompiled : MonoBehaviour
        {
            public static RuntimeCompiled AddYourselfTo(GameObject host)
            {
                return host.AddComponent<RuntimeCompiled>();
            }

            void Start()
            {
                Debug.Log(""The runtime compiled component was successfully attached to"" + gameObject.name);
            }
        }");    

        var runtimeType = assembly.GetType("RuntimeCompiled");
        var method = runtimeType.GetMethod("AddYourselfTo");
        var del = (Func<GameObject, MonoBehaviour>)
                      Delegate.CreateDelegate(
                          typeof(Func<GameObject, MonoBehaviour>), 
                          method
                  );

        // We ask the compiled method to add its component to this.gameObject
        var addedComponent = del.Invoke(gameObject);

        // The delegate pre-bakes the reflection, so repeated calls don't
        // cost us every time, as long as we keep re-using the delegate.
    }

    public static Assembly Compile(string source)
    {
        // Replace this Compiler.CSharpCodeProvider wth aeroson's version
        // if you're targeting non-Windows platforms:
        var provider = new CSharpCodeProvider();
        var param = new CompilerParameters();

        // Add ALL of the assembly references
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            param.ReferencedAssemblies.Add(assembly.Location);
        }

        // Or, uncomment just the assemblies you need...

        // System namespace for common types like collections.
        //param.ReferencedAssemblies.Add("System.dll");

        // This contains methods from the Unity namespaces:
        //param.ReferencedAssemblies.Add("UnityEngines.dll");

        // This assembly contains runtime C# code from your Assets folders:
        // (If you're using editor scripts, they may be in another assembly)
        //param.ReferencedAssemblies.Add("CSharp.dll");


        // Generate a dll in memory
        param.GenerateExecutable = false;
        param.GenerateInMemory = true;

        // Compile the source
        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 the assembly
        return result.CompiledAssembly;
    }
}

Warnung

Seien Sie sehr vorsichtig, wenn Sie Ihr Spiel Skripte kompilieren und ausführen lassen, die Sie nicht selbst geschrieben haben. Diese Skripte können fast alles tun, wozu das Benutzerkonto des Players berechtigt ist - Benutzerdateien löschen oder beschädigen, Malware herunterladen und ausführen, persönliche Informationen abrufen, all das.

So sehr wir auch versuchen mögen, diese Fälle zu blockieren und zu erkennen, ich wäre nie besonders sicher, dass wir sie alle abgefangen haben - es besteht immer die Möglichkeit, dass eine C # -Sprachenfunktion, von der ich nichts wusste, ihnen den Zugriff auf Funktionen ermöglicht, von denen ich dachte, dass sie abgeschnitten sind .

Wenn das Skript nur vom lokalen Spieler erstellt wird, können sie das Spiel auf willkürliche Weise unterbrechen, aber nichts mit ihrem Computer tun, für das sie ohnehin keine Berechtigung hatten. Wenn Spieler jedoch Skripte freigeben, von einer Netzwerkressource herunterladen oder dazu verleitet werden können, ein nicht geschriebenes Skript zu kopieren und einzufügen, wird dies zu einem Angriffsvektor, der ausgenutzt werden kann.

Wenn Sie die Programmierung durch den Player zulassen möchten, ohne diese Art von Sicherheitsanfälligkeit zu öffnen, sollten Sie eine eigene virtuelle Maschine schreiben, um die Skripte des Players in einer eingeschränkteren Umgebung zu analysieren, zu interpretieren und auszuführen, die Sie vollständig steuern.

DMGregory
quelle
Vielen Dank für die Eingabe, insbesondere in Bezug auf die Warnungen, die Sie mir gegeben haben, ich hatte eine Idee von ihnen, aber nicht so präzise, ​​wie Sie sagten
Oscar Guillamon