Umgang mit skriptbasierten und "nativen" Komponenten in einem komponentenbasierten Entitätssystem

11

Ich versuche derzeit, ein komponentenbasiertes Entitätssystem zu implementieren, bei dem eine Entität im Grunde nur eine ID und einige Hilfsmethoden sind, die eine Reihe von Komponenten zu einem Spielobjekt zusammenbinden. Einige Ziele davon sind:

  1. Komponenten enthalten nur Status (z. B. Position, Gesundheit, Munitionszahl) => Logik geht in "Systeme", die diese Komponenten und ihren Status verarbeiten (z. B. PhysicsSystem, RenderSystem usw.).
  2. Ich möchte Komponenten und Systeme sowohl in reinem C # als auch durch Scripting (Lua) implementieren. Grundsätzlich möchte ich in der Lage sein, völlig neue Komponenten und Systeme direkt in Lua zu definieren, ohne meine C # -Quelle neu kompilieren zu müssen.

Jetzt suche ich nach Ideen, wie ich effizient und konsistent damit umgehen kann, damit ich beispielsweise keine andere Syntax verwenden muss, um auf C # -Komponenten oder Lua-Komponenten zuzugreifen.

Mein aktueller Ansatz wäre die Implementierung von C # -Komponenten mithilfe regulärer öffentlicher Eigenschaften, die wahrscheinlich mit einigen Attributen versehen sind, die dem Editor Informationen zu Standardwerten und anderen Elementen geben. Dann hätte ich eine C # -Klasse "ScriptComponent", die nur eine Lua-Tabelle intern umschließt, wobei diese Tabelle von einem Skript erstellt wird und den gesamten Status dieses bestimmten Komponententyps enthält. Ich möchte von der C # -Seite nicht wirklich auf diesen Status zugreifen, da ich zum Zeitpunkt der Kompilierung nicht wissen würde, welche ScriptComponents mit welchen Eigenschaften mir zur Verfügung stehen würden. Der Editor muss zwar darauf zugreifen, aber eine einfache Oberfläche wie die folgende sollte ausreichen:

public ScriptComponent : IComponent
{
    public T GetProperty<T>(string propertyName) {..}
    public void SetProperty<T>(string propertyName, T value) {..}
}

Dies würde einfach auf die Lua-Tabelle zugreifen und diese Eigenschaften von Lua festlegen und abrufen, und es könnte leicht auch in den reinen C # -Komponenten enthalten sein (aber dann die regulären C # -Eigenschaften durch Reflexion oder so etwas verwenden). Dies wird nur im Editor verwendet, nicht im regulären Spielcode, daher ist die Leistung hier nicht so wichtig. Es würde es notwendig machen, einige Komponentenbeschreibungen zu generieren oder handschriftlich zu schreiben, die dokumentieren, welche Eigenschaften ein bestimmter Komponententyp tatsächlich bietet, aber das wäre kein großes Problem und könnte ausreichend automatisiert werden.

Wie kann man jedoch von der Lua-Seite auf Komponenten zugreifen? Wenn ich so etwas getComponentsFromEntity(ENTITY_ID)anrufe, bekomme ich wahrscheinlich nur ein paar native C # -Komponenten, einschließlich "ScriptComponent", als Benutzerdaten. Der Zugriff auf die Werte aus der umschlossenen Lua-Tabelle würde dazu führen, dass ich die GetProperty<T>(..)Methode aufrufe, anstatt wie bei den anderen C # -Komponenten direkt auf die Eigenschaften zuzugreifen.

Schreiben Sie möglicherweise eine spezielle getComponentsFromEntity()Methode, die nur von Lua aufgerufen werden soll. Sie gibt alle nativen C # -Komponenten als Benutzerdaten zurück, mit Ausnahme von "ScriptComponent", wo stattdessen die umschlossene Tabelle zurückgegeben wird. Es wird aber auch andere komponentenbezogene Methoden geben, und ich möchte nicht alle diese Methoden duplizieren, um sie sowohl aus dem C # -Code als auch aus dem Lua-Skript aufzurufen.

Das ultimative Ziel wäre es, alle Arten von Komponenten gleich zu behandeln, ohne dass eine spezielle Fallsyntax zwischen nativen Komponenten und Lua-Komponenten unterscheidet - insbesondere von der Lua-Seite. Zum Beispiel möchte ich in der Lage sein, ein solches Lua-Skript zu schreiben:

entity = getEntity(1);
nativeComponent = getComponent(entity, "SomeNativeComponent")
scriptComponent = getComponent(entity, "SomeScriptComponent")

nativeComponent.NativeProperty = 5
scriptComponent.ScriptedProperty = 3

Das Skript sollte sich nicht darum kümmern, welche Art von Komponente es tatsächlich hat, und ich möchte dieselben Methoden verwenden, die ich von der C # -Seite zum Abrufen, Hinzufügen oder Entfernen von Komponenten verwenden würde.

Vielleicht gibt es einige Beispielimplementierungen für die Integration von Skripten in solche Entitätssysteme?

Mario
quelle
1
Bleibst du bei Lua? Ich habe ähnliche Skripte für eine C # -Anwendung ausgeführt, wobei andere CLR-Sprachen zur Laufzeit analysiert wurden (in meinem Fall Boo). Da Sie bereits Code auf hoher Ebene in einer verwalteten Umgebung ausführen und alles IL unter der Haube ist, ist es einfach, vorhandene Klassen von der "Skriptseite" zu unterordnen und Instanzen dieser Klassen an die Hostanwendung zurückzugeben. In meinem Fall würde ich sogar eine kompilierte Zusammenstellung von Skripten zwischenspeichern, um die Ladegeschwindigkeit zu erhöhen, und sie einfach neu erstellen, wenn sich die Skripte ändern.
Justinian
Nein, ich stecke nicht mit Lua fest. Persönlich würde ich es sehr gerne wegen seiner Einfachheit, geringen Größe, Geschwindigkeit und Beliebtheit verwenden (daher scheinen die Chancen, dass Leute bereits damit vertraut sind, höher zu sein), aber ich bin offen für Alternativen.
Mario
Darf ich fragen, warum Sie gleiche Definitionsfunktionen zwischen C # und Ihrer Skriptumgebung wünschen? Normales Design ist die Komponentendefinition in C # und dann 'Objektassemblierung' in der Skriptsprache. Ich frage mich, welches Problem aufgetreten ist, für das dies die Lösung ist.
James
1
Ziel ist es, das Komponentensystem von außerhalb des C # -Quellcodes erweiterbar zu machen. Nicht nur durch Festlegen von Eigenschaftswerten in XML- oder Skriptdateien, sondern auch durch Definieren ganz neuer Komponenten mit ganz neuen Eigenschaften und neuer Systeme, die diese verarbeiten. Dies ist weitgehend inspiriert von Scott Bilas 'Beschreibungen des Objektsystems in Dungeon Siege, wo es "native" C ++ - Komponenten sowie eine "GoSkritComponent" gab, die selbst nur ein Wrapper für ein Skript ist: scottbilas.com/games/dungeon -siege
Mario
Seien Sie vorsichtig, wenn Sie in die Falle tappen, eine Skript- oder Plugin-Oberfläche zu erstellen, die so komplex ist, dass sie sich einer Neufassung des ursprünglichen Tools (in diesem Fall C # oder Visual Studio) nähert.
3Dave

Antworten:

6

Eine logische Folge von FXIII Antwort ist , dass man alles entwerfen soll (das wäre modifizierbar sein) in der Skriptsprache zuerst (ein sehr anständiger Teil davon oder zumindest) , um sicherzustellen , dass Ihre Integrationslogik tatsächlich Bestimmungen für modding. Wenn Sie sicher sind, dass Ihre Skriptintegration vielseitig genug ist, schreiben Sie die erforderlichen Bits in C # neu.

Ich persönlich hasse die dynamicFunktion in C # 4.0 (ich bin ein Junkie in statischer Sprache) - aber dies ist ohne Zweifel ein Szenario, in dem sie verwendet werden sollte und in dem sie wirklich glänzt. Ihre C # -Komponenten wären einfach starke / expando-Verhaltensobjekte .

public class Villian : ExpandoComponent
{
    public void Sound() { Console.WriteLine("Cackle!"); }
}

//...

dynamic villian = new Villian();
villian.Position = new Vector2(10, 10); // Add a property.
villian.Sound(); // Use a method that was defined in a strong context.

Ihre Lua-Komponenten wären vollständig dynamisch (der gleiche Artikel oben sollte Ihnen eine Vorstellung davon geben, wie Sie dies implementieren können). Beispielsweise:

// LuaSharp is my weapon of choice.
public class LuaObject : DynamicObject
{
    private LuaTable _table;

    public LuaObject(LuaTable table)
    {
        _table = table;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var methodName = binder.Name;
        var function = (LuaFunction)_table[methodName];
        if (function == null)
        {
            return base.TryInvokeMember(binder, args, out result);
        }
        else
        {
            var resultArray = function.Call(args);
            if (resultArray == null)
                result = null;
            else if (resultArray.Length == 1)
                result = resultArray[0];
            else
                result = resultArray;
            return true;
        }
    }

    // Other methods for properties etc.
}

Jetzt, da Sie verwenden dynamic, können Sie Lua-Objekte so verwenden, als wären sie echte Klassen in C #.

dynamic cSharpVillian = new Villian();
dynamic luaVillian = new LuaObject(_script["teh", "villian"]);
cSharpVillian.Sound();
luaVillian.Sound(); // This will call into the Lua function.
Jonathan Dickinson
quelle
Ich stimme dem ersten Absatz voll und ganz zu, ich mache meine Arbeit oft so. Das Schreiben von Code, von dem Sie wissen, dass Sie ihn möglicherweise in einer niedrigeren Sprache neu implementieren müssen, ist wie ein Darlehen aufzunehmen, in dem Wissen, dass Sie für Zinsen zahlen müssen. IT-Designer nehmen häufig Kredite auf: Dies ist gut, wenn sie wissen, dass sie es getan haben und ihre Schulden unter Kontrolle halten.
FxIII
2

Ich würde lieber das Gegenteil tun: Schreiben Sie alles in einer dynamischen Sprache und suchen Sie dann die Engpässe (falls vorhanden). Sobald Sie eine gefunden haben, können Sie die damit verbundenen Funktionen mithilfe eines C # oder (eher) C / C ++ selektiv neu implementieren.

Ich sage Ihnen dies hauptsächlich, weil Sie versuchen, die Flexibilität Ihres Systems im C # -Teil einzuschränken. Wenn das System komplex wird, sieht Ihre C # "Engine" wie ein dynamischer Interpreter aus, wahrscheinlich nicht der beste. Die Frage ist: Sind Sie bereit, den größten Teil Ihrer Zeit in das Schreiben einer generischen C # -Engine zu investieren oder Ihr Spiel zu erweitern?

Wenn Ihr Ziel das zweite ist, wie ich glaube, werden Sie wahrscheinlich Ihre Zeit am besten nutzen, um sich auf Ihre Spielmechanik zu konzentrieren und einen erfahrenen Interpreter den dynamischen Code ausführen zu lassen.

FxIII
quelle
Ja, es gibt immer noch diese vage Angst vor schlechter Leistung für einige der Kernsysteme, wenn ich alles durch Scripting mache. In diesem Fall müsste ich diese Funktionalität wieder auf C # (oder was auch immer) verschieben ... also brauche ich sowieso eine gute Möglichkeit, von der C # -Seite aus mit dem Komponentensystem zu kommunizieren. Das ist das Kernproblem hier: Erstellen eines Komponentensystems, das ich sowohl in C # als auch im Skript gleichermaßen verwenden kann.
Mario
Ich dachte, dass C # als eine Art "Accelerated Scripting-ähnliche" Umgebung für Dinge wie C ++ oder C verwendet wird? Wenn Sie sich nicht sicher sind, in welcher Sprache Sie landen, versuchen Sie einfach, eine Logik in Lua und C # zu schreiben. Ich wette, am Ende werden Sie in C # die meiste Zeit schneller sein, dank erweiterter Editor-Tools und Debugging-Funktionen.
Imi
4
+1 Ich stimme dem jedoch aus einem anderen Grund zu - wenn Sie möchten, dass Ihr Spiel erweiterbar ist, sollten Sie Ihr eigenes Hundefutter essen: Sie könnten eine Skript-Engine integrieren, nur um herauszufinden, dass Ihre potenzielle Modding-Community eigentlich nichts damit anfangen kann es.
Jonathan Dickinson