Manejo de componentes con script y "nativos" en un sistema de entidad basado en componentes

11

Actualmente estoy tratando de implementar un sistema de entidad basado en componentes, donde una entidad es básicamente una ID y algunos métodos auxiliares que unen un conjunto de componentes para formar un objeto de juego. Algunos objetivos de eso incluyen:

  1. Los componentes solo contienen estado (por ejemplo, posición, estado, recuento de munición) => la lógica entra en "sistemas", que procesan estos componentes y su estado (por ejemplo, PhysicsSystem, RenderSystem, etc.)
  2. Quiero implementar componentes y sistemas tanto en C # puro como a través de scripting (Lua). Básicamente quiero poder definir componentes y sistemas completamente nuevos directamente en Lua sin tener que recompilar mi fuente C #.

Ahora estoy buscando ideas sobre cómo manejar esto de manera eficiente y consistente, por lo que no necesito usar una sintaxis diferente para acceder a componentes de C # o componentes de Lua, por ejemplo.

Mi enfoque actual sería implementar componentes de C # usando propiedades públicas regulares, probablemente decoradas con algunos atributos que le informan al editor sobre valores predeterminados y otras cosas. Entonces tendría una clase C # "ScriptComponent", que simplemente envuelve una tabla Lua internamente con esta tabla creada por un script y que contiene todo el estado de ese tipo de componente en particular. Realmente no quiero acceder a ese estado desde el lado de C #, ya que no sabría en el momento de la compilación qué componentes de script con qué propiedades estarían disponibles para mí. Aún así, el editor tendrá que acceder a eso, pero una interfaz simple como la siguiente debería ser suficiente:

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

Esto simplemente accedería a la tabla Lua y establecería y recuperaría esas propiedades de Lua y también podría incluirse fácilmente en los componentes puros de C # (pero use las propiedades normales de C # entonces, mediante reflexión o algo así). Esto solo se usaría en el editor, no por el código de juego regular, por lo que el rendimiento no es tan importante aquí. Haría necesario generar o escribir a mano algunas descripciones de componentes que documenten qué propiedades ofrece un determinado tipo de componente, pero eso no sería un gran problema y podría automatizarse lo suficiente.

Sin embargo, ¿cómo acceder a los componentes desde el lado de Lua? Si hago una llamada a algo así getComponentsFromEntity(ENTITY_ID), probablemente obtendré un montón de componentes nativos de C #, incluido "ScriptComponent", como datos de usuario. Acceder a los valores de la tabla Lua envuelta resultaría en que llame al GetProperty<T>(..)método en lugar de acceder a las propiedades directamente, como con los otros componentes de C #.

Tal vez escriba un getComponentsFromEntity()método especial solo para ser llamado desde Lua, que devuelve todos los componentes nativos de C # como datos de usuario, a excepción de "ScriptComponent", donde devolverá la tabla envuelta. Pero habrá otros métodos relacionados con los componentes y realmente no quiero duplicar todos estos métodos tanto para ser llamados desde el código C # o desde el script Lua.

El objetivo final sería manejar todos los tipos de componentes de la misma manera, sin una sintaxis de caso especial que diferencie entre los componentes nativos y los componentes Lua, especialmente desde el lado Lua. Por ejemplo, me gustaría poder escribir un script Lua así:

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

nativeComponent.NativeProperty = 5
scriptComponent.ScriptedProperty = 3

El script no debería importar qué tipo de componente tiene realmente y me gustaría usar los mismos métodos que usaría desde el lado C # para recuperar, agregar o eliminar componentes.

¿Quizás hay algunas implementaciones de muestra de integración de scripts con sistemas de entidades como ese?

Mario
fuente
1
¿Estás atrapado en el uso de Lua? He hecho cosas similares al escribir una aplicación C # con otros lenguajes CLR que se analizan en tiempo de ejecución (Boo, en mi caso). Debido a que ya está ejecutando código de alto nivel en un entorno administrado y todo está bajo el capó, es fácil subclasificar las clases existentes desde el lado de "secuencias de comandos" y pasar instancias de esas clases a la aplicación host. En mi caso, incluso almacenaría en caché un conjunto compilado de scripts para aumentar la velocidad de carga, y simplemente lo reconstruiría cuando los scripts cambiaran.
Justiniano
No, no estoy atrapado usando Lua. Personalmente, me gustaría mucho usarlo debido a su simplicidad, tamaño pequeño, velocidad y popularidad (por lo que las posibilidades de que las personas ya estén familiarizadas con él parezcan mayores), pero estoy abierto a alternativas.
Mario
¿Puedo preguntarle por qué desea tener capacidades de definición iguales entre C # y su entorno de script? El diseño normal es la definición de componentes en C # y luego 'ensamblaje de objetos' en el lenguaje de secuencias de comandos. Me pregunto qué problema ha surgido para el que esta es la solución.
James
1
El objetivo es hacer que el sistema de componentes sea extensible desde fuera del código fuente de C #. No solo mediante el establecimiento de valores de propiedad en XML o archivos de script, sino mediante la definición de componentes completamente nuevos con propiedades completamente nuevas y nuevos sistemas que los procesen. Esto está inspirado en gran medida por las descripciones del sistema de objetos de Scott Bilas en Dungeon Siege, donde tenían componentes "nativos" de C ++, así como un "GoSkritComponent", que es en sí mismo un contenedor para un script: scottbilas.com/games/dungeon -siege
Mario
Tenga cuidado de caer en la trampa de crear una interfaz de scripting o plugin tan compleja que se acerque a una reescritura de la herramienta original (C # o Visual Studio en este caso).
3Dave

Respuestas:

6

Un corolario de la respuesta de FXIII es que se debe diseñar todo (que sería modificable) en el lenguaje de script primera (o al menos una porción muy decente de la misma) para asegurar que su lógica de integración en realidad disposiciones para modding. Cuando esté seguro de que su integración de scripts es lo suficientemente versátil, vuelva a escribir los bits necesarios en C #.

Personalmente odio la dynamicfunción en C # 4.0 (soy un adicto al lenguaje estático), pero este es sin duda un escenario en el que debería usarse y es donde realmente brilla. Sus componentes de C # serían simplemente objetos fuertes / expansivos de comportamiento .

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.

Sus componentes Lua serían completamente dinámicos (el mismo artículo anterior debería darle una idea de cómo implementar esto). Por ejemplo:

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

Ahora, como lo está usando dynamic, puede usar objetos Lua como si fueran clases reales 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
fuente
Estoy totalmente de acuerdo con el primer párrafo, a menudo hago mi trabajo de esta manera. Escribir un código que sabe que puede tener que volver a implementar en un lenguaje de nivel inferior, es como pedir un préstamo SABIENDO que tendrá que pagar los intereses. Los diseñadores de TI a menudo toman préstamos: esto es bueno cuando saben que lo hicieron y mantienen sus deudas bajo control.
FxIII
2

Prefiero hacer lo contrario: escribir todo usando un lenguaje dinámico y luego rastrear los cuellos de botella (si los hay). Una vez que encuentre uno, puede volver a implementar selectivamente las funcionalidades involucradas usando un C # o (más bien) C / C ++.

Te digo esto principalmente porque lo que estás tratando de hacer es limitar las flexibilidades de tu sistema en la parte C #; A medida que el sistema se vuelve complejo, su "motor" de C # comenzará a parecerse a un intérprete dinámico, probablemente no el mejor. La pregunta es: ¿estás dispuesto a invertir la mayor parte de tu tiempo en escribir un motor genérico C # o extender tu juego?

Si su objetivo es el segundo, como creo, probablemente usará su tiempo mejor enfocándose en la mecánica de su juego, permitiendo que un intérprete experimentado ejecute el código dinámico.

FxIII
fuente
Sí, todavía existe este vago temor al mal rendimiento de algunos de los sistemas centrales, si hago todo a través de scripts. En ese caso, tendría que mover esa funcionalidad de nuevo a C # (o lo que sea) ... así que de todos modos necesitaría una buena forma de interactuar con el sistema de componentes desde el lado de C #. Ese es el problema central aquí: crear un sistema de componentes que pueda usar igualmente bien desde C # y script.
Mario
¿Pensé que C # se usa como una especie de entorno "similar a secuencias de comandos aceleradas" para cosas como C ++ o C? De todos modos, si no está seguro de en qué idioma terminará, simplemente comience a intentar escribir algo de lógica en Lua y C #. Apuesto a que al final terminarás siendo más rápido en C # la mayor parte del tiempo, debido a las herramientas avanzadas de edición y las características de depuración.
Imi
44
+1 Estoy de acuerdo con esto por otra razón: si quieres que tu juego sea extensible, deberías comer tu propia comida para perros: podrías terminar integrando un motor de script solo para descubrir que tu comunidad de modding potencial no puede hacer nada con eso.
Jonathan Dickinson