Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Sistema de script para mi juego.

Iniciado por Oanime, 24 de Octubre de 2007, 01:12:47 PM

« anterior - próximo »

Tei

Gracias a vosotros. Aunque yo soy aficionado, y muchos de vosotros profesionales. No soy yo el que tiene que escribir cosas así, pero bueno, si puedo ayudar, lo intentare :D


Cita de: "HexDump"
Alguna idea más? problema, etc...? Yo lo veo bastante bien.

un pdf sobre integración:
http://www.vjuegos.org/Tutoriales/IntegracionCLUA/IntegracionCLua.pdf


Recuerdo un artículos sobre AI relacionado con NW...
Aunque buscando salen cosas que habría que mirar que quizas sean interesantes.
http://www.cs.unimaas.nl/p.spronck/GameAIPage.htm

En algún sitio tiene que haber una libreria de "Actores" para NW, que creo tenia unas bases muy solidas e interesantes.

Bueno, quizas no te hace falta tanto.   Y siempre puedes preguntar alguna cosa en el foro de AI, si es que necesitas algo.

nota: he corregido el error

kaworu

en serio NwN utiliza lua? hombre, tiene su propio lenguaje de script Nwscript xD yo lo he utilizado a muerte xd es genial.

Oanime

Por si a alguien le interesa, yo he estado viendo cosas y deja el tema del scripting mas claro:

http://nwn.bioware.com/builders/sctutorial.html

Un saludo,
HexDump.

kaworu

hmm soy de los  q piensan q los lenguajes de script son el futuro de la programación xD adios hardcoded xd
PD: otro gran ejemplo es el propio torqueScript, pero le estoy encontrando carencias akojonantes, a pesar de su edad y de su uso :s Tb mirad el ActionScript de Flash!

Tei

Cita de: "kaworu"en serio NwN utiliza lua? hombre, tiene su propio lenguaje de script Nwscript xD yo lo he utilizado a muerte xd es genial.

no idea, creia que si pero ..por lo que dices, no es el caso. no se de donde vendra mi confusion, ??

Oanime

Never winters nights no usa Lua, estuve hablando ayer con el que curraba en la IA del juego en el canal de ogre y la verdad es que el tio estaba bastante quemado por que no usaron un lenguaje pre-hecho.

Un saludo,
HexDump.

ethernet

En el juego de "tempo" de art futura han usado lua, tal vez pueda comentar algo el programador. Muy a lo novarama xml + lua, aunque creo aphex dijo que habían dejado lua. (python rocks)

gdl

Hola de nuevo. Siento no haber respondido antes HexDump, hay cosas de la vida real que interfieren mi presencia virtual (y alegre) en la red.

Te explico cómo lo haría yo. Para empezar te aviso que hace mucho que leí sobre el Lua y puede que diga alguna sarta de chorradas, pero las ideas deben quedarte claras (espero).

Lo primero que voy a hacer va a ser darle nombres a las cosas.

1) El código de tu script vamos a llamarlo el comportamiento.
2) La representación de ese script en tu programa C++ (será probablemente un objeto derivado del binding de Lua en C++) la vamos a llamar género (la llamaría tipo o clase, pero están pilladas ya).
3) Lo más fácil. Un bicho lo vamos a llamar ejemplar (traducción correcta de la palabra "instance").

Ahora a lo práctico.

Lo primero que hay que hacer es recorrer el (o los) directorios con los scripts cargando los comportamientos en los distintos géneros. Creo el género (con new) y uso lo que tenga Lua para cargar el script y dejarlo dentro del género ya compiladito (o lo que haga el Lua al cargar). Tendría los géneros bien ordenaditos, cada uno con un nombre o algo para poder identificarlos bien. Si un script da problemas de compilación, pues tiras el género correspondiente (delete, vamos). Cuando cierres tu aplicación deberás borrar los géneros después de borrar los ejemplares (esos deletes en el destructor o en la función miembro de finalización). Esto es básico, ya lo sabrás  :wink:

Cada género debe tener una forma de poder ejecutar una función Lua. Así podría llamar directamente al género olvidándote de que realmente son scripts. De esa forma encapsulamos. Así las funciones miembro de la clase que implemente el género puede tener nombres como think() onAttack() onHit() que llamarían a las funciones correspondientes en el comportamiento Lua.

Hasta ahora podemos hacer llamadas del programa de C++ hasta el Lua, pero es interesante también hacer lo contrario. Sé que Lua tiene tipos definibles por usuarios y simula una especie de orientación a objetos (por lo menos cuando lo leí). La forma más sencilla que hay de hacer las llamadas del Lua al C++ (las llamadas callbacks) creo yo que es mediantes funciones globales.

Si mal no recuerdo, es relativamente fácil declarar funciones globales en Lua que, cuando se llaman desde el script, ejecutan una función en C++. Perfecto. Esas funciones globales pueden usarse para que tu script acceda a los datos del ejemplar en concreto mediante un truco muy simple. Antes de llamar al género (y al comportamiento en Lua a través de él) pones un puntero de tu motor de scripting apuntando al ejemplar que tiene que recibir el evento. Luego las callbacks usarán ese puntero de forma que para el comportamiento (el script) es transparente a qué ejemplar se está refiriendo.

Los objetivos interesantes que conseguimos son:
1) El script nunca sabe sobre qué ejemplar está tratando. No tiene referencias, punteros o identificadores del ejemplar (puede ser bueno o malo, pero independiza el script de la implementación). Siempre que llames en Lua a la función global  getPos()  te va a dar la posición del ejemplar adecuado (gracias al punterito).
2) Un género puede tener varios ejemplares (lo cual está bien porque queremos muchos orcos pululando por ahí). Podríamos calentarnos el coco para permitir scripts particularizados a los ejemplares (como hace el Daimonin) pero no merece la pena en una primera fase.
3) Independizas el comportamiento del género (y del resto del programa). Suponte que quisieras cambiar el script del Lua al Ruby o que cambia la versión del Lua de forma algo "incompatible". Sólo deberías cambiar la(s) clase(s) que implementen el género. El resto se queda igual y, adicionalmente, si falla la compilación no tienes que abortar tu programa. Basta con no crear el género correspondiente (y emite un warning en algún fichero de log porque si no te puedes volver loco intentando averiguar porqué los dragones pardos moteados no salen en el juego con lo bien programados que estaban).
4) Al usar funciones globales, y no el sistema de objetos o tablas que tiene el Lua, evitas tener que usar y acceder a la forma que tiene el Lua de almacenar sus variables. Esto son varias ventajas: independizas tu código del script y guardas tú tus datos. Muy interesante esto último si quieres serializar (grabar partidas, pasar datos por internet, etc.)


También es verdad que tienes contras: tienes que asegurar la consistencia de los datos, pierdes el azucarillo sintáctico de la orientación a objetos, tienes que usar el gasto computacional extra de las funciones globales cuando quieras leer una variable miembro (aka atributo), etc.


Espero haberte servido de alguna ayuda.

gdl

PD: Cuando termines con el script del NwN, no dejes de echarle un vistazo a los del Unreal Engine. Tiene cosas muy interesantes si quieres programar una aplicación distribuida por internet. Por ejemplo, cuando se modifica una variable, se modifica en todos los clientes remotos también (con ciertos trucos para la eficiencia y el dead reckoning).

PPD: Ha sido el post más largo que he escrito en mi vida. Siento la longitud.

Oanime

No pasa nada gdl, bastante es que te dejas el tiempo esribiendo tal cantidad de palabras :D.

Lo que escribes es muy interesante, ya vamos sacando cosas en claro. Veo que al final no hay muchas formas de hacerlo y todos más o menos llegamos a una conclusión parecida pero con sus peculiaridades. Dejame repensar todo y postearé un diseño final (que pensaba que ya tenía :D).

Por otra parte, el tema de los punteros no me gusta nada tenerlos repartidos por ahi, ni siquiere siendo un sharedptr, así que usare ID's, pero ya te digo he de verlo.

Unas cosas que he visto y quiero dejar claras sobre lo que expones, según expones, los ejemplares se mantienen en C++, o eso creo entender de tú texto. Otra cosilla más cuando hablas de que se cargan los comportamientos desde disco, imagino que no serán solo comportamientos, sino también las distintas cualidades del bicho, como por ejemplo: energia, etc... Te lo digo porque cada tipo de bicho no tiene porque tener las mismas cualidades. Sobre lo demás creo que apuntaba algo parecido en el ultimo post que escribi, de tener una clase "proxy" en C++ que solo contenga eventos que llamen a los de lua y sus datos esten en lua (que es lo que cargamos junto con la lógica del bicho al parsear los directorios).

Me he perdido algo? estamos de acuerdo?.

Gracias por adelantado,
Un saludo.

Tei

Cita de: "gdl"Por ejemplo, cuando se modifica una variable, se modifica en todos los clientes remotos también (con ciertos trucos para la eficiencia y el dead reckoning)

Esto puede que sea el metodo "netcode por reflexion" que me comentaron una vez.

En lugar de enviar el estado y las modificaciones de todos los elementos que forman un tanque, o un robot.  Se envia una descripcion abstracta.  "tanque 2, rotado 10 grados, con torreta rotada 10, con cañon rotado 1"   o  "lanzamisiles apuntando a 10", o "flor procerual genreada por semill 33" .

Asi para describir un conjunto de objetos que entre todos forman un ente (como un tanque, con su torreta y su cañon) que seria un trafico de quizas 20 enteros y 12 floats, con esta abstraccion se envian quizas solo 3 numeros enteros  ..y ya describen el objeto.
El lado cliente ha de saber coger esos 3 enteros, y convertirlos otra vez en el objeto complejo.  Por tanto el lado cliente necesita scripting ejecutandose despues del netcode y antes del renderizado.

Esto ademas desde el script es muy bonito.  Se puede hacer asi:
marcas unas variables  miembro del objeto como "significativas desde un punto de vista netcode", y el engine hace el resto.  
Esta variable, por ejemplo, puede indicar la animación que se esta reproduciendo.  
De modo que al lado cliente le llega un único byte, que le indica "esta realizando la accion saludar", y el ya sabe que frames tiene que mostrar, efectos de sonidos y de partículas, asociados a esa animación.

En los netcodes "normales" que no tienen esto, cuando una acción modifica un atributo importante a lo largo del tiempo (una mano que saluda) esto crea un pequeño trafico continuo todo el tiempo que dura el temilla. (actualización de angulos del codo, la mano, el hombro, etc.. en cada frame del servidor).  Si hubiera 30 bichos saludando, eso podria "flodear" el canal con datos, para algo muy simple, que se ve que con otra filosofia se puede solucionar con un solo byte.

Y aun me quedaria explicar como hace quake lo de las explosiones, que pegaria explicarlo a continuación, pero no tengo tiempo.

gdl

Exacto Tei. Leí hace mucho tiempo el artículo aquél (sobre el 2000), pero la idea era que como el script sabía qué hacer con sus variables mucho mejor que el engine, era más conveniente propagar las variables que el engine se dedicase a transmitir acciones y cosas así. No sabía que se llamaba netcode... siempre se aprende algo.


Sobre el puntero de HexDump... pensaba en la alternativa como tipo-usuario y eso te puede causar algunos problemas. Si mal no recuerdo había dos tipos-usuario el recolectado por el GC de Lua y el no recolectado.

En el primer caso tendrás el problema de tener que mantener el conjunto raíz del GC para que no te borre tus ejemplares. No sería conveniente que te borrara un ejemplar porque el Lua no tiene más referencias a él (tu aplicación puede tenerlas). Esto implica que has de decirle al Lua que ciertos punteros están siendo utilizados, lo cual pues también lo tendrías que hacer si no usaras el GC.

En el segundo caso tienes el problema de tener que averiguar si el script está guardando referencias a tu ejemplar antes para saber si puedes borrarlo. No sería agradable que por lo que fueras quitaras a tu orco del mundo, pero hubiera una variable del Lua que apuntara a él e intentase usarlo una vez borrado. Tendrías la opción de recorrer todas las variables almacenadas en Lua (o al menos las relevantes) para ver si hay un uso de tu ejemplar.

Estas dos alternativas, desde mi punto de vista, son bastante chungas. Por eso te decía que usaras otro sistema que mantiene un único puntero el cual puedes controlar bien. Sobre todo, ninguna variable de Lua podrá tener una referencia (ya sea puntero, identificador, etc.) a un ejemplar sin que pase por tu código C++ para controlarla.

Que me voy por las ramas.

Hombre, si me apuras... preferiría, antes de un puntero, tener un número para cada ejemplar, de forma que si referencias a un ejemplar que no existe puedas detectarlo e ignorarlo sin causar un estropicio. Ahí estamos de acuerdo que usar IDs es más conveniente.

Queda por ver si las variables has de tenerlas en el lado del Lua o en el lado del C++.

Tei

mi uso de la palabra netcode es muy chungo. llamo asi al codigo que envia datos por red y los recibe,   y en algun caso he llamado netcode al producto de ese codigo (esos datos) pero esto es pasarse.

ademas de determinar que datos enviar, y enviarlos en un orden que sea descifrable por el otro lado, los datos que viajan por red pueden ser comprimidos ( por ejemplo con hoffman o algo asi se llama una especie de compresion de bits, que se usaba mucho en discos duros y otros dispositivos fisicos, pero pienso que cualquier tipo de compresion que pueda trabajar con streams vale )  y cifrado  (para evitar que sea comprendido y abusado).
el netcode muchas veces tiene que lidiar con la incertidumbre del envio de datos por red.  Los datos pueden llegar corruptos, en desorden, manipulados,  incompletos, etc ..     una version muy popular de un router que instala una ISP determinada puede tener un bug particularmente vicioso que solo muestra sus letales caracteristicas con un juego en particular.  y ese bug del router mostrarse solamente cuando el router esta caliente.
asi que mejor que el netcode sea buen codigo, confiable, que pueda lidiar con los errores, y consiga enviar la minima cantidad de informacion posible, porque es mas rapido, y porque a mayor "superficie", mas facil que le ocurran desgracias a la informacion :D

una fuente inagotable de diversion para la gente que escribe codigos de red, es intentar que una maquina detras de un router NAT haga de servidor. Afortunadamente yo no trabajo en eso :P

Oanime

Bueno gdl aquí tenemos 2 cosas, donde mantener las plantillas (q tu llamas generos(yo creo q clarisimamente en LUA) y despues las instancias o como tu las llamas (ejemplares).

Una cosilla importante me gustaría que cada vez que digamos de hacer algo, comentar en que cara se hace C++ o LUA porque a veces me pierdo :D.

Está bien lo de escanear los directorios y cargar los generos desde C++. Pero hay algo que no veo claro y es lo que dices de instanciarlos con new y guardarlos. Mi forma de verlo es que tras cargar todos los generos (q son scripts de lua), estos ya estan registrados. Así despues le digo a mi gestor de entidades (en C++) CreateEntity("Ogre"), y el gestor de entidades  instancia una clase en C++ Actor super generica, que a su vez dispara un OnCreate del tipo Ogre::OnCreate(id_del_objeto_en_c++), o si quierse con funciones globales Ogre_OnCreate(id_del_objeto_en_c++) que lo que hace es instanciarme la parte de datos del genero Ogre, osea un Ejemplar en LUA. Tras esto se añade a una lista interna de lua, que hace como de gestor de esos cachos de datos, y devuelve a C++ el id de esa tabla en el gestor por si C++ quiere hacer algo con ello. El id que se le pasa en el oncreate desde C++ al evento de lua es para ir en sentido contrario, es decir, hacer desde lua algo con un objeto de C++, algo como, LookAt(id_del_ogro_tonto, id_del_player), en este caso LookAt se ha publicado desde C++ a LUA.


Despues a la hora de las destrucciones ( o desactivaciones si se quieren usar pools, etc...):

Si en Lua se dispara un OnHit de un objeto y hay que matarlo porque se le gasto la energia, pues tenemos su id a la clase C++ asi que podemos hacer un removeObject() o lo que sea que será una funcion expuesta desde C++ para quitar el objeto (o destruirlo), y despues se quita él mismo de la lista en lua.

Si destruimos una instancia desde C++ tambien tenemos el id de lua asi que podemos hacer tambien remove object en lua para quitarla.

No me extraña que me haya expresado mal en partes ya que he escrito mucho, si teneis cualquier problema avisar!, y gracias por todas las sugerencias,

HexDump.

gdl

He estado releyendo un poco el manual de Lua. Voy a intentar decir poco en cada post para no liarnos.

El problema realmente se basa en cómo hacer si escribes en Lua "GetPos()" que te dé la posición de nuestro orco.

Una opción es pasando un identificador a las funciones del comportamiento. Algo como "function onHit(id)...GetPos(id)...end" en Lua.

Otra opción es tener el parámetro implícito (guardado en el C++ como un puntero o un identificador) de forma que sería algo como "function onHit()...GetPos()...end".


Por otra parte, cuando cargues un script una opción sería (desde C++) algo como "lua_open(...)... lua_load(...)... " cuando cargues un segundo sería "lua_load(...)" otra vez (metiéndolo de nuevo en el mismo estado).

Otra opción sería "lua_open(...)... load_load(...)..." para el primer script, otra vez "lua_open(...)... load_load(...)..." y seguimos haciendo "lua_open()" en cada script. Tenemos un estado de Lua para cada comportamiento y guardamos ese estado en cada género.


En la primera opción todas las cosas Lua están cargadas en el mismo sitio (y puede liarse) mientras que en la segunda opción cada script está compartimentizado.

Mi opinión es que las segundas opciones que te he puesto son mejores que las primeras porque encapsulan y aislan.






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.