Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





c# cosas estaticas, reflexión, arboles de comportamiento y más ...

Iniciado por TrOnTxU, 26 de Abril de 2010, 09:31:24 PM

« anterior - próximo »

TrOnTxU

Hola, me encontrao con un problemilla scriptando algunas cosas del editor de arboles de comportamiento de nuestro ultimo juego.
Espero que alguien con más conocimientos de c# que yo me pueda decir si lo que quiero se puedo hacer.

Tengo una jerarquia de clases parecida a esta:
BehaivourTree (arbol generico)
-> Malo1BT ( deriva de BehaivourTree )
-> Malo2BT ( deriva de BehaivourTree )
-> ...

En mi editor por cada nodo me tienen que salir una lista de acciones y condiciones disponibles.
El problema viene cuando cada clase derivada (Malo1BT, Malo2BT, ...) tiene una lista de acciones y condiciones diferente.

En un principio (en teoria) tendria que tener el objeto final (Malo1BT, ...), entonces bastaria con que esta clase tuviera implementado un metodo ObtenAcciones/Condiciones que fuera abstracto (preferiblemente) enla clase genérica BehaivourTree.

Pues bien, el tema es que he implementado que cualquier clase derivada pueda cargar cualquier tipo de arbol. Por tanto en mi editor tengo un BehaivourTree genérico (no puedo hacerlo abstract), luego el arbol se graba como una serie de indices (int) en disco, y se carga en tiempo de ejcución ( ... y si, funciona bastante, bastante bien  ;) )

Pues bien, para que en el editor se sustituyan los indices por los nombres de las acciones y condiciones disponibles he pensao en obtener dos arrays de strings (1 x acciones y otro para condiciones). En el editor (parte superior izq) debe haber una lista con los tipos de arboles (por enemigo), y que indique que arrays de strings tenemos que obtener.

Por tanto para cada TIPO clase (Malo1BT, MaloBT2, ...) tengo dos arrays de strings ( acciones / condiciones ). Que se corresponden con los indices de un enum que tiene cada clase con sus acciones/condiciones.

Como obtenerlos:
1) Clase estatica "BehaivourTreeHelper" que contiene métodos estaticos para listar los tipos de enemigos ( string [] ObtenerTiposEnemigos() ). Y metodos para obtener acciones/condiciones por tipo de clase ( string [] ObtenerAcciones(Type p) o ObtenerAcciones(string nombreClase) ).

2) Mi problema es que defino enums con las acciones dentro de cada clase derivada (Malo1BT por ejemplo).
Y mantener los indices del array de strings en una clase diferente me parece un poco lioso.
Asi que preferiria escribir un método estático en cada clase que obtenga el array. Pero al ser estatico no puedo aplicar ningun tipo de polimorfismo (creo) para decir: todas las clases q derivan de BehaivourTree tiene ue tener un metodo ObtenAcciones, y luego obtener estas acciones dependiendo del tipo.

3) Lo cojonudo del todo seria poder además utilizar Reflexion (pensar que el coste me la suda un poco, porque estoy en el editor, no en el runtime del juego ni en el engine) y obtener las listas de strings a partir de los enums. Pero no tengo mucha idea. Quizás alguien (seguro) ha echo algo parecido y me puede dar una idea de como abordarlo.


Bueno, que opinais al respecto, ¿a alguien se le ocurre una solución?
Pues nada gracias a todos de antemano.

Nos vemos!!!
Vicent: Linked-In  ***  ¡¡Ya tengo blog!!


TrOnTxU

Cita de: Vicente en 26 de Abril de 2010, 09:58:17 PM
Lo he leído dos veces y debo estar super espeso, porque no lo entiendo :(
Pues si tú no lo entiendes mal vamos ... XD

El post en si es espeso. Además no sé si me explicado bien y el tema igual es un poco complejo.
Bueno, voy a intentarme explicar un poco mejor. El código está en el curro, pero pongo algo de ejemplo.
Es como un arbol de comportamiento (aunque más bien es un arbol de decision). Lo que pasa es que he simplificado algunas cosas ya que no dispongo de mucho tiempo para invertir en esto.
Actualmente tengo en el runtime algo parecido a estos scripts de c# (funcionan en Wii bajo la vm mono de Novell):

// Nodos del arbol
class NodoArbolCompo
{
    List<NodoArbolCompo> hijos;

    public void EjecutarNodo(ArbolCompo arbol)
    {
        // Evaluamos condicion
        if (arbol.EvaluarCondicion(this.condicion))
        {
            // Ejecutamos Acción
            arbol.EjecutarAccion(accion);
            // Recorremos hijos ...
            foreach (NodoArbolCompo nodo in hijos)
                nodo.EjecutarNodo(arbol);
        }
    }

    string condicion;
    string accion;
}

// Agente / gestion del conocimiento del agente
class IAAgent
{
    // ... esto es otra historia
};

// Arbol de comportamiento (más bien de decisión)
class ArbolCompo
{
    NodoArbolCompo raiz;
    protected IAAgent iaAgent;

    public ArbolCompo(IAAgent agent)
    {
        iaAgent = agent;
        // ....
    }

    public void EjecutarArbol()
    {
        raiz.EjecutarNodo(this);
    }

    public void CargarArbol(string resourceName)
    {
        // ...
    }

    public void SalvarArbol(string resourceName)
    {
        // ...
    }


    // Para sobreescribir por las clases derivadas
    public virtual void EjecutarAccion(string accion)
    {
        // NOTHING
    }

    public virtual bool EvaluarCondicion(string condicion)
    {
        return false;
    }
}

// Acciones y Condiciones especificas del malo1
class ArbolMalo1 : ArbolCompo
{

    public ArbolMalo1(IAAgent agent) : base(agent)
    {
        // ...
    }

    public override bool EvaluarCondicion(string condicion)
    {
        bool b = false;
        // ...
        return b;
    }

    public override void EjecutarAccion(string accion)
    {
        // ...
    }
}


Se que es un tostón pero he intentado simplificarlo al máximo  ^_^'
El rollo es que cada nodo del arbol tiene dos strings (accion y condicion), la funcion ejecutarNodo y una lista de hijos.
Y ArbolCompo tiene el nodo raiz, una refencia al agente, metodos para cargar/salvar el arbol y dos metodos para sobreescribir por las clases derivadas: EjecutarAccion y EvaluarCondicion. (como he dicho esta muy simplificado).

Bien ahora tengo el editor:


En el editor mantengo una copia de un objeto generico ArbolCompo, y utilizo los metodos de cargar y salvar. Las acciones y condiciones se editan en una textBox.
En runtime cargo el archivo en cualquiera de los derivados de arbolCompo y a funcionar.

Todo esto surgio en una versión de prueba, pero ha funcionado bien y tenemos que iterar y mejorarlo.
La primera cosa que pensé es que cada vez que evaluo el arbol debo comprobar los strings (lo hice asi es su momento para simplificar y de prueba) y es mas caro que comprobar enteros. Además del problema de equivocarte al escribir alguna cadena.
La solución es bastante clara, voy a sustiuir los strings de condicion y accion por un int en las funciones, y a a crear enums dentro de cada clase que derive de el arbol generico para las condiciones y las acciones.

El problema lo tengo en el editor.
Como solución rápida seria sustuir el string por el un id de un numero con la acción a realizar. Pero seria poco informativo ver el árbol de esa forma. Asi que me molaria tener una lista desplegable de opciones para cada acción y condición de cada nodo.
Además tengo que poner otra lista en de seleccion del tipo de arbol que estamos editando (cada uno puede tener unas acciones y condiciones diferentes).
Y de momento la interfaz de mi editor que tengo para llenar ese tipo de lista son un array de strings.

Editor deseado:


Entonces, primera solución para obtener los [] string de tipo de arbol, condicion y accion:
class ArbolCompoHelper
{
    static string[] tiposDeEnemigos = { "Malo1", "Malo2" };
    static string[] ObtenerTiposEnemigos()
    {
        return tiposDeEnemigos;
    }

    static string[] accionesMalo1 = { "Dispara", "SalCorriendo", "Pasea", "UrgarNariz" };
    static string[] condicionesMalo1 = { "VesEnemigo", "EnemigoCerca" }; // ...

    static string[] accionesMalo2 = { "LanzaGranada", "EchateAlSuelo", "Rascate", "DaOrdenes" };
    static string[] condicionesMalo2 = { "EnemigoATiro", "EstasCansado", "PocaVida" };

    static string[] ObtenerAcciones(string tipo)
    {
        switch (tipo)
        {
            case "Malo1": return accionesMalo1;
            case "Malo2": return accionesMalo2;
        }
        return null;
    }

    static string[] ObtenerCondiciones(string tipo)
    {
        switch (tipo)
        {
            case "Malo1": return condicionesMalo1;
            case "Malo2": return condicionesMalo2;
        }
        return null;
    }
}


Y en cada derivada de CompoArbol queria poner estos enums:
enum TipoDeAccion
    {
        Dispara = 0,
        SalCorriendo,
        Pasea,
        UrgarNariz
    }

    enum TipoDeCondicion
    {
        VesEnemigo = 0,
        EnemigoCerca
    }

   // Para poder hacer:

    public override bool EvaluarCondicion(int condicion)
    {
        bool b = false;

        switch ((TipoDeCondicion)condicion)
        {
            case TipoDeCondicion.EnemigoCerca:
                // ... return iaAgent.dist2Player < 1.0f;
                break;
            case TipoDeCondicion.VesEnemigo:
                // ...
                break;
        }

        return b;
    }


El problema es que para añadir una nueva acción (por ejemplo) tengo que añadir el enum y la lógica de EvaluarCondición en la derivada del ArbolCompo, y añadir por otra parte el nombre de la condicion en el array correspondiente de ArbolCompoHelper. (Y ambos estarán en .cs diferentes). Además no me puedo descuidar los indices ya que el nombre del array se tiene que corresponder con el indice que marca el enum correspondiente de cada clase.
Y la cosa es peor para añadir un nuevo enemigo, ya que tengo que definir en el heper sus arrays, llamarlos desde el case, etc.
Con lo que se me antoja complicado de mantener, y muy susceptible a errores (ahora tb lo es, pero se supone que lo tengo que mejorar).

Y después de todo este infierno de post  :-[ tengo solo 4 preguntas para usted señor Vicente  :-[ :-[ :-[ :-[
1) Puedo obtener al abrir el editor (y solo desde él) con reflexión un [] string con el nombre de todas las clases derivadas de ArbolCompo ??? Como?
2) Puedo obtener un array con los nombres de un enum ordenados segun su indice ??? (supongo que si xo ...) Como?
3) Puedo utilizar la info que he sacado del punto 1, para de alguna extraña forma acceder a un desglose (p.2) de un enum de esa clase selccionada? Comoooor?
4) ¿Alguna solución mejor?

Bueno, gracias y perdon por el peñazo, pero ya viene siendo habitual en mi estos post moliticos  0:-)

como dijo el sabio: Buenas noches y buena suerte
voy a dormiiiiiillll por hora
Vicent: Linked-In  ***  ¡¡Ya tengo blog!!

TrOnTxU

Al final me autocensto yo mismo   |:|

Con esto tengo automatizado la lista de strings de las clases derivadas de ArbolCompo y sus acciones y condiciones.
Eso si, los enums se tienen que llamar de una forma determinada (TipoDeAccion, TipoDeCondicion).
El truco esta en hacer el Init solo al crear la ventana del editor, cerrarla para modificar los enums o el numero de clases derivadas de ArbolCompo, y volver a abrir la ventana más tarde (con lo que recarga los cambios por reflexion).

Aqui funciona, lo probaré en el curro con la maquina Mono. Ya digo algo.

La verdad es que ha sido más sencillo de lo que esperaba.

class ArbolCompoHelper
{
    public static void Init()
    {
        Assembly assembly = Assembly.GetExecutingAssembly();

        // Obtenemos todos los types del assembly (igual un poco basto, pero weno)
        Type[] myTypes = assembly.GetTypes();
        List<Type> enemyBTs = new List<Type>();

        // Filtramos por los que sean subclase de ArbolCompo
        foreach (Type t in myTypes)
            if (t.IsSubclassOf(typeof(ArbolCompo)))
                enemyBTs.Add(t);

        // Creamos array de nombres de enemigos:
        int n = enemyBTs.Count;
        tiposDeEnemigos = new string[n];
        acciones = new string[n][];
        condiciones = new string[n][];
        // recorremos los tipos de los enemigos
        for (int i = 0; i < n; i++)
        {
            // seteamos el nombre
            tiposDeEnemigos[i] = enemyBTs[i].Name;
            // Buscamos y listamos los enums
            acciones[i] = ListaEnum(myTypes, enemyBTs[i].FullName + "+TipoDeAccion");
            condiciones[i] = ListaEnum(myTypes, enemyBTs[i].FullName + "+TipoDeCondicion");
        }
    }

    public static string [] ListaEnum(Type [] types, string nombre)
    {
        foreach (Type t in types)
            if (t.FullName == nombre)
                return Enum.GetNames(t);
        return null;
    }


    protected static string[] tiposDeEnemigos = null;
    public static string[] ObtenerTiposEnemigos()
    {
        return tiposDeEnemigos;
    }

    static string[][] acciones = null;
    static string[][] condiciones = null;

    static string[] ObtenerAcciones(int tipo)
    {
        return acciones[tipo];
    }

    static string[] ObtenerCondiciones(int tipo)
    {
        return condiciones[tipo];
    }
}


Espero que alguien le sirva de algo ^^

adew!
Vicent: Linked-In  ***  ¡¡Ya tengo blog!!

Vicente

Me alegro que te hayas autocontestado :)

La lista de tipos (myTypes) puedes filtrarla para que solo tenga enums si quieres:

IEnumerable<Type> enums = myTypes.Where(t => t.IsEnum).Select(t => t);

Eso te debería dar una colección de los tipos que son enums dentro de tu lista de tipos. Por si te vale de algo :) Un saludo!

Vicente

TrOnTxU

Hola,

al final conseguí que rulara en la máquina Mono. Cuando obtenia el assembly con getExecuting solo me devolvia las clases que tenia registradas en el editor, pero al final pude obtenerlo a partir de la clase ArbolCompo y con un par de cambios más funciona bien :)

Por desgracia la maquina mono que utilizo no soporta LINQ y no puedo utilizar esos filtros  :( . Aunque la verdad me molan mucho, y para los editores en .NET la verdad es que es una caña (assetdb, busquedas sobre la jerarquia de escena, etc)  :D

Y ya puestos me molaria hacerte otra pregunta Vicente (si te viene bien claro :)).
Tu que sabes de IA y de C#.
Cuando comencé el diseño de los arboles estos, tenia pensado utilizar un objeto que fuera el Conocimiento del Agente, y que cuando se cambiara algo de él se marcara un flag de modificación, para que el árbol supiera que tenia que reevaluarse.

De forma automática en Lua lo que haria seria utilizar __newindex en la metatabla del objeto para indicar que todo cambio sobre ese objeto, activara el flag de modificado, y cuando acabara de evaluar el arbol deshabilitaria el flag manualmente.

En C# lo único que se me habia ocurrido era utilizar propiedades para cada variable del conocimiento, y cada set modificar el flag.
¿Se te ocurre una forma mejor? (la verdad es que no estoy muy puesto en c#)  ::)

Gracias nuevamente :)

ta luego
Vicent: Linked-In  ***  ¡¡Ya tengo blog!!

Vicente

No, esa es la forma de hacerlo más o menos. Si te miras en .NET hay una interfaz para eso que se llama INotifyPropertyChanged, que es la base de todo el Databinding de WPF. La docu:

http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx

Lo que se hace es en el set levantar un evento con el nombre de la propiedad que ha cambiado. Algo como lo que viene en la MSDN:


        set
        {
            if (value != this.customerNameValue)
            {
                this.customerNameValue = value;
                NotifyPropertyChanged("CustomerName");
            }
        }


Para evitar el lío de los strings, que es un coñazo y propenso a cagadas, mucha gente lo que hace es pasar un Expression<T> (un árbol de expresiones) y sacar el nombre de la propiedad desde ahí.

Un ejemplo:

http://stackoverflow.com/questions/141370/inotifypropertychanged-property-name-hardcode-vs-reflection

Un saludo!

Vicente






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.