Gestion des composants scriptés et «natifs» dans un système d'entités basé sur les composants

11

J'essaie actuellement d'implémenter un système d'entités basé sur des composants, où une entité est essentiellement juste un ID et quelques méthodes d'assistance liant un tas de composants ensemble pour former un objet de jeu. Certains objectifs comprennent:

  1. Les composants ne contiennent que l'état (par exemple position, santé, nombre de munitions) => la logique entre dans les "systèmes", qui traitent ces composants et leur état (par exemple PhysicsSystem, RenderSystem, etc.)
  2. Je veux implémenter des composants et des systèmes à la fois en C # pur et par script (Lua). Fondamentalement, je veux pouvoir définir des composants et des systèmes complètement nouveaux directement dans Lua sans avoir à recompiler ma source C #.

Maintenant, je cherche des idées sur la façon de gérer cela de manière efficace et cohérente, donc je n'ai pas besoin d'utiliser une syntaxe différente pour accéder aux composants C # ou aux composants Lua, par exemple.

Mon approche actuelle serait d'implémenter des composants C # en utilisant des propriétés publiques régulières, probablement décorées avec certains attributs informant l'éditeur des valeurs par défaut et d'autres choses. Ensuite, j'aurais une classe C # "ScriptComponent", qui enveloppe simplement une table Lua en interne, cette table étant créée par un script et contenant tout l'état de ce type de composant particulier. Je ne veux pas vraiment accéder à cet état beaucoup du côté C #, car je ne saurais pas au moment de la compilation, quels ScriptComponents avec quelles propriétés seraient disponibles pour moi. Néanmoins, l'éditeur devra y accéder, mais une interface simple comme celle-ci devrait suffire:

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

Cela accéderait simplement à la table Lua et définirait et récupérerait ces propriétés à partir de Lua et cela pourrait facilement être également inclus dans les composants C # purs (mais utilisez ensuite les propriétés C # normales, par réflexion ou quelque chose). Cela ne serait utilisé que dans l'éditeur, pas par le code de jeu normal, donc les performances ne sont pas aussi importantes ici. Cela nécessiterait de générer ou d'écrire à la main des descriptions de composants documentant les propriétés d'un certain type de composant, mais ce ne serait pas un problème énorme et pourrait être suffisamment automatisé.

Cependant, comment accéder aux composants du côté Lua? Si j'appelle quelque chose comme getComponentsFromEntity(ENTITY_ID)je vais probablement obtenir un tas de composants natifs C #, y compris "ScriptComponent", en tant que données utilisateur. Accéder aux valeurs de la table Lua encapsulée me ferait appeler la GetProperty<T>(..)méthode au lieu d'accéder directement aux propriétés, comme avec les autres composants C #.

Peut-être écrire une getComponentsFromEntity()méthode spéciale uniquement pour être appelée à partir de Lua, qui renvoie tous les composants natifs C # en tant que données utilisateur, à l'exception de "ScriptComponent", où il renverra la table encapsulée à la place. Mais il y aura d'autres méthodes liées aux composants et je ne veux pas vraiment dupliquer toutes ces méthodes pour être appelées à partir du code C # ou du script Lua.

Le but ultime serait de gérer tous les types de composants de la même manière, sans syntaxe de cas particulier faisant la différence entre les composants natifs et les composants Lua - en particulier du côté Lua. Par exemple, j'aimerais pouvoir écrire un script Lua comme ça:

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

nativeComponent.NativeProperty = 5
scriptComponent.ScriptedProperty = 3

Le script ne devrait pas se soucier du type de composant qu'il a réellement obtenu et j'aimerais utiliser les mêmes méthodes que j'utiliserais du côté C # pour récupérer, ajouter ou supprimer des composants.

Peut-être y a-t-il des exemples d'implémentations d'intégration de scripts avec des systèmes d'entités comme ça?

Mario
la source
1
Êtes-vous coincé avec Lua? J'ai fait des choses similaires en scriptant une application C # avec d'autres langages CLR en cours d'analyse au moment de l'exécution (Boo, dans mon cas). Parce que vous exécutez déjà du code de haut niveau dans un environnement géré et que tout est sous le capot, il est facile de sous-classer les classes existantes du côté "scripting" et de renvoyer les instances de ces classes à l'application hôte. Dans mon cas, je mettrais même en cache un assemblage compilé de scripts pour augmenter la vitesse de chargement, et je le reconstruirais simplement lorsque les scripts changeraient.
justinian
Non, je ne suis pas coincé avec Lua. Personnellement, j'aimerais beaucoup l'utiliser en raison de sa simplicité, de sa petite taille, de sa vitesse et de sa popularité (donc les chances que les gens le connaissent déjà semblent plus élevées), mais je suis ouvert à des alternatives.
Mario
Puis-je vous demander pourquoi vous désirez avoir des capacités de définition égales entre C # et votre environnement de script? La conception normale est la définition de composant en C # puis «l'assemblage d'objet» dans le langage de script. Je me demande quel problème est survenu pour lequel c'est la solution?
James
1
Le but est de rendre le système de composants extensible depuis l'extérieur du code source C #. Non seulement en définissant des valeurs de propriété dans des fichiers XML ou de script, mais en définissant de nouveaux composants entiers avec de toutes nouvelles propriétés et de nouveaux systèmes les traitant. Ceci est largement inspiré par les descriptions de Scott Bilas du système d'objets dans Dungeon Siege, où ils avaient des composants C ++ "natifs" ainsi qu'un "GoSkritComponent", qui n'est lui-même qu'un wrapper pour un script: scottbilas.com/games/dungeon -siege
Mario
Méfiez-vous de tomber dans le piège de la construction d'une interface de script ou de plugin si complexe qu'elle approche d'une réécriture de l'outil d'origine (C # ou Visual Studio dans ce cas).
3Dave

Réponses:

6

Un corollaire de la réponse de FXIII est que vous devez concevoir tout (ce serait moddable) dans le langage de script premier (ou au moins une partie très décent de celui - ci) pour vous assurer que votre logique d'intégration des dispositions réellement pour modding. Lorsque vous êtes certain que votre intégration de script est suffisamment polyvalente, réécrivez les bits requis en C #.

Personnellement, je déteste la dynamicfonctionnalité en C # 4.0 (je suis un drogué de langage statique) - mais c'est sans aucun doute un scénario où il devrait être utilisé et où il brille vraiment. Vos composants C # seraient simplement des objets comportementaux forts / expando .

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.

Vos composants Lua seraient complètement dynamiques (le même article ci-dessus devrait vous donner une idée de la façon de l'implémenter). Par exemple:

// 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.
}

Maintenant, parce que vous utilisez, dynamicvous pouvez utiliser des objets Lua comme s'ils étaient de vraies classes en 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
la source
Je suis totalement d'accord avec le premier paragraphe, je fais souvent mon travail de cette façon. Écrire du code que vous savez que vous devrez peut-être réimplémenter dans un langage de niveau inférieur, c'est comme prendre un prêt sachant que vous devrez payer des intérêts. Les concepteurs informatiques contractent souvent des prêts: c'est bien quand ils savent qu'ils l'ont fait et qu'ils gardent leurs dettes sous contrôle.
FxIII
2

Je préfère faire le contraire: écrire tout en utilisant un langage dynamique, puis traquer les embouteillages (le cas échéant). Une fois que vous en avez trouvé, vous pouvez réimplémenter sélectivement les fonctionnalités impliquées en utilisant un C # ou (plutôt) C / C ++.

Je vous le dis principalement parce que ce que vous essayez de faire est de confiner les flexibilités de votre système dans la partie C #; à mesure que le système devient complexe, votre "moteur" C # commencera à ressembler à un interpréteur dynamique, probablement pas le meilleur. La question est: êtes-vous prêt à investir le plus de temps possible dans l'écriture d'un moteur C # générique ou à étendre votre jeu?

Si votre cible est la seconde, comme je le crois, vous utiliserez probablement votre temps au mieux en vous concentrant sur vos mécanismes de jeu, en laissant un interprète chevronné exécuter le code dynamique.

FxIII
la source
Oui, il y a toujours cette vague peur de mauvaises performances pour certains des systèmes de base, si je fais tout par le biais de scripts. Dans ce cas, je devrais déplacer cette fonctionnalité en C # (ou autre) ... donc j'avais besoin d'un bon moyen d'interfacer avec le système de composants du côté C #, de toute façon. C'est le problème principal ici: créer un système de composants que je peux utiliser aussi bien à partir de C # que de script.
Mario
Je pensais que C # est utilisé comme une sorte d'environnement "de type script accéléré" pour des choses comme C ++ ou C? Quoi qu'il en soit, si vous n'êtes pas sûr de la langue dans laquelle vous vous retrouverez, commencez simplement à écrire de la logique en Lua et en C #. Je parie qu'au final, vous finirez par être plus rapide en C # la plupart du temps, en raison des fonctionnalités avancées d'outil d'édition et de débogage.
Imi
4
+1 Je suis d'accord avec cela pour une autre raison cependant - si vous voulez que votre jeu soit extensible, vous devriez manger votre propre nourriture pour chien: vous pourriez atterrir en intégrant un moteur de script uniquement pour découvrir que votre communauté de modding potentielle ne peut rien faire avec il.
Jonathan Dickinson