Gestione dei componenti con script e "nativi" in un sistema di entità basato su componenti


11

Attualmente sto cercando di implementare un sistema di entità basato su componenti, in cui un'entità è fondamentalmente solo un ID e alcuni metodi di supporto che uniscono un gruppo di componenti per formare un oggetto di gioco. Alcuni obiettivi includono:

  1. I componenti contengono solo stato (ad es. Posizione, salute, numero di munizioni) => la logica va in "sistemi", che elaborano questi componenti e il loro stato (ad es. PhysicsSystem, RenderSystem, ecc.)
  2. Voglio implementare componenti e sistemi sia in puro C # che tramite scripting (Lua). Fondamentalmente voglio essere in grado di definire componenti e sistemi completamente nuovi direttamente in Lua senza dover ricompilare il mio sorgente C #.

Ora sto cercando idee su come gestirlo in modo efficiente e coerente, quindi non ho bisogno di usare una sintassi diversa per accedere ai componenti C # o ai componenti Lua, ad esempio.

Il mio approccio attuale sarebbe quello di implementare componenti C # usando proprietà pubbliche regolari, probabilmente decorate con alcuni attributi che dicevano all'editore di valori e cose predefiniti. Quindi avrei una classe C # "ScriptComponent", che avvolge semplicemente una tabella Lua internamente con questa tabella creata da uno script e contenente tutto lo stato di quel particolare tipo di componente. Non voglio davvero accedere a quello stato dal lato C #, come non saprei al momento della compilazione, quali ScriptComponents con quali proprietà sarebbero disponibili per me. Tuttavia, l'editor dovrà accedervi, ma una semplice interfaccia come la seguente dovrebbe essere sufficiente:

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

Questo semplicemente accede alla tabella Lua e imposta e recupera quelle proprietà da Lua e potrebbe essere facilmente incluso anche nei componenti C # puri (ma usa le normali proprietà C #, attraverso la riflessione o qualcosa del genere). Questo sarebbe usato solo nell'editor, non dal normale codice di gioco, quindi le prestazioni non sono così importanti qui. Sarebbe necessario generare o scrivere a mano alcune descrizioni dei componenti documentando quali proprietà offre effettivamente un determinato tipo di componente, ma questo non sarebbe un grosso problema e potrebbe essere sufficientemente automatizzato.

Tuttavia, come accedere ai componenti dal lato Lua? Se faccio una chiamata a qualcosa del genere getComponentsFromEntity(ENTITY_ID)probabilmente riceverò solo un mucchio di componenti C # nativi, incluso "ScriptComponent", come userdata. L'accesso ai valori dalla tabella Lua racchiusa mi farebbe chiamare il GetProperty<T>(..)metodo invece di accedere direttamente alle proprietà, come con gli altri componenti C #.

Forse scrivere un getComponentsFromEntity()metodo speciale solo per essere chiamato da Lua, che restituisce tutti i componenti nativi di C # come userdata, ad eccezione di "ScriptComponent", dove restituirà invece la tabella spostata. Ma ci saranno altri metodi relativi ai componenti e non voglio davvero duplicare tutti questi metodi sia per essere chiamati dal codice C # sia dallo script Lua.

L'obiettivo finale sarebbe quello di gestire tutti i tipi di componenti allo stesso modo, senza che la sintassi del caso speciale distingua tra componenti nativi e componenti Lua, specialmente dal lato Lua. Ad esempio, vorrei poter scrivere una sceneggiatura di Lua in questo modo:

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

nativeComponent.NativeProperty = 5
scriptComponent.ScriptedProperty = 3

Lo script non dovrebbe interessarsi del tipo di componente effettivamente ottenuto e mi piacerebbe usare gli stessi metodi che userei dal lato C # per recuperare, aggiungere o rimuovere componenti.

Forse ci sono alcune implementazioni di esempio di integrazione di script con sistemi di entità del genere?


1
Sei bloccato su usando Lua? Ho fatto cose simili copiando un'applicazione C # con altri linguaggi CLR che venivano analizzati in fase di esecuzione (Boo, nel mio caso). Poiché stai già eseguendo codice di alto livello in un ambiente gestito ed è tutto IL nascosto, è facile sottoclassare le classi esistenti dal lato "scripting" e restituire istanze di tali classi all'applicazione host. Nel mio caso, avrei persino memorizzato nella cache un assembly compilato di script per una maggiore velocità di caricamento, e lo ricostruivo solo quando gli script cambiano.
Giustiniano,

No, non sono bloccato con Lua. Personalmente mi piacerebbe molto usarlo a causa della sua semplicità, dimensioni ridotte, velocità e popolarità (quindi le probabilità che le persone che già lo conoscano sembrino più alte), ma sono aperto alle alternative.
Mario,

Potrei chiederti perché desideri avere pari capacità di definizione tra C # e il tuo ambiente di script? Il design normale è la definizione dei componenti in C # e quindi "assembly object" nel linguaggio di scripting. Mi chiedo per quale problema sia sorto che questa è la soluzione?
James,

1
L'obiettivo è rendere il sistema componente estensibile dall'esterno del codice sorgente C #. Non solo impostando i valori delle proprietà in XML o file di script, ma definendo componenti completamente nuovi con proprietà completamente nuove e nuovi sistemi che li elaborano. Ciò è in gran parte ispirato dalle descrizioni di Scott Bilas del sistema di oggetti in Dungeon Siege, dove avevano componenti C ++ "nativi" e un "GoSkritComponent", che è esso stesso un wrapper per uno script: scottbilas.com/games/dungeon -siege
Mario,

Attenzione a cadere nella trappola della creazione di un'interfaccia di scripting o plug-in così complessa da avvicinarsi a una riscrittura dello strumento originale (in questo caso C # o Visual Studio).
3Daveva il

Risposte:


6

Un corollario di risposta di FXIII è che si dovrebbe progettare tutto ciò (che sarebbe moddable) nel linguaggio di scripting prima (o almeno una parte molto dignitoso di esso) per garantire che la logica di integrazione effettivamente fondi per modding. Quando si è certi che l'integrazione degli script sia abbastanza versatile, riscrivere i bit richiesti in C #.

Personalmente odio la dynamicfunzionalità in C # 4.0 (Sono un drogato di linguaggio statico) - ma questo è senza dubbio uno scenario in cui dovrebbe essere usato ed è dove brilla davvero. I componenti di C # sarebbero semplicemente oggetti di comportamento forti / esplicativi .

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.

I tuoi componenti Lua sarebbero completamente dinamici (lo stesso articolo sopra dovrebbe darti un'idea di come implementarlo). Per esempio:

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

Ora, poiché stai usando dynamic, puoi usare gli oggetti Lua come se fossero vere classi in C #.

dynamic cSharpVillian = new Villian();
dynamic luaVillian = new LuaObject(_script["teh", "villian"]);
cSharpVillian.Sound();
luaVillian.Sound(); // This will call into the Lua function.

Sono totalmente d'accordo con il primo paragrafo, spesso faccio il mio lavoro in questo modo. Scrivere codice che sai che potresti doverlo reimplementare in una lingua di livello inferiore, è come prendere un prestito CONOSCENDO che dovrai pagare per gli interessi. I progettisti IT spesso prendono prestiti: questo è positivo quando sanno di averlo fatto e tengono sotto controllo i propri debiti.
FxIII,

2

Preferirei fare il contrario: scrivere tutto usando un linguaggio dinamico e quindi rintracciare i bottleneks (se presenti). Una volta trovato, è possibile reimpostare selettivamente le funzionalità coinvolte utilizzando un C # o (piuttosto) C / C ++.

Te lo dico principalmente perché ciò che stanno cercando di fare è confinare la flessibilità del tuo sistema nella parte C #; man mano che il sistema diventa complesso, il tuo "motore" C # inizierà a sembrare un interprete dinamico, probabilmente non il migliore. La domanda è: sei disposto a investire la maggior parte del tuo tempo a scrivere un motore C # generico o ad estendere il tuo gioco?

Se il tuo obiettivo è il secondo, come credo, probabilmente utilizzerai meglio il tuo tempo concentrandoti sulle tue meccaniche di gioco, lasciando che un interprete esperto esegua il codice dinamico.


Sì, c'è ancora questa vaga paura di cattive prestazioni per alcuni dei sistemi di base, se farò tutto attraverso lo scripting. In quel caso dovrei riportare quella funzionalità su C # (o qualsiasi altra cosa) ... quindi avrei bisogno di un buon modo per interfacciarmi con il sistema di componenti dal lato C #, comunque. Questo è il problema principale qui: la creazione di un sistema componente che posso usare ugualmente bene sia da C # che da script.
Mario,

Pensavo che C # fosse usato come una sorta di ambiente "accelerato simile allo scripting" per cose come C ++ o C? Ad ogni modo, se non sei sicuro in quale lingua finirai, inizia a provare a scrivere un po 'di logica in Lua e C #. Scommetto che alla fine finirai per essere più veloce in C # la maggior parte delle volte, grazie alle funzionalità avanzate di editor e di debug.
Imi,

4
+1 Sono d'accordo con questo per un altro motivo: se vuoi che il tuo gioco sia estensibile dovresti mangiare il tuo cibo per cani: potresti finire integrando un motore di script solo per scoprire che la tua potenziale comunità di modding non può davvero fare nulla con esso.
Jonathan Dickinson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.