Foros - Stratos

Programadores => General Programadores => Mensaje iniciado por: Oanime en 24 de Octubre de 2007, 01:12:47 PM

Título: Sistema de script para mi juego.
Publicado por: Oanime en 24 de Octubre de 2007, 01:12:47 PM
Hola,

Estoy diseñando en este momento el sistema de scripting para mi juego y la verdad veo tantas posibilidades que no acabo de concentrarme en un diseño decente para este.

Me gustaría que la gente que ya ha hecho previamente algo de esto me aconsejara, por cierto me he decantado al final por LUA + toLua++ por la comunidad tan arraigada que tiene (python no me interesa).

Aquí dejo unas divagaciones:

En principio el script lo quiero montar simplemente para configurar acotres en mi juego, algo que podría haber hecho tambien con un simple xml, pero ya que voy a usar el scripting para IA tambien lo uso para esto.

Tenía pensado tener un fichero .lua por actor y tener 2  funciones dentro, una de inicializacion que pasandole un ID hace una petición a la cara c++ y "setea" el objeto con sus valores por defecto, como, malla, energia, etc... La otra función sería una de IA que se llamaría desde el DoLogic de cada actor en C++ para ejecutar la IA del objeto.

Sería algo como esto:

en el fichero Orc.lua:

function Orc_Init(OrcInstance)
{
//Inicializar valores del orco
}

function Orc_DoLogic(OrcID )
{
//logica del orco
}

In c++:

en orc.cpp file:

Orc()
{
//Cargamos script

Script::Orc_Init(this);

//Lanzamos lo necesario para cargar recursos, com por ejemplo
//la carga de la malla que ha asigando el script a este objeto

}

void Orc::DoLogic()
{
Script::Orc_DoLogic(this);

}

Que pensais? alguna forma mejor de hacerlo?

Un saludo,
HexDump.
Título: Sistema de script para mi juego.
Publicado por: gdl en 24 de Octubre de 2007, 01:28:09 PM
Para hacerlo bonito, bonito, en el código C++ no debería aparecer la palabra "Orc" por ningún lado ya que ¿vas a recompilar cada vez que añadas un bicho nuevo? Lo suyo es hacerlo todo dinámicamente. Recorres el directorio de los scripts y los vas leyendo conforme te los encuentres. ¿Que es un orco? Bienvenido sea. ¿Que es un zombie? Pues me da igual, lo cargo con el mismo procedimiento.
Título: Re: Sistema de script para mi juego.
Publicado por: Tei en 24 de Octubre de 2007, 01:34:50 PM
Una parte muy maltratada por los videojuegos son los menus.

Al usuario le aporta muchisimo el que los menus sean bonitos y claros, y que tenga las opciones que necesita.

Pero para el desarrollador es un coñazo aburrido el programar menus. Que o bien es una cosa para la que encuentras un patron de diseño, o bien los programas con codigo espaguetti.

El principio de la solucion puede ser que utilices scripting tambien para los menus.

Utilizar scripting para las reglas del juego ya depende de ti mismo. Pero si lo haces asi, habres la puerta a que exista el modding.

De todos modos, puede ser interesante que en tu arquitectura de juego parte de la gamelogica venga con los "mapas". De una manera que sea la mano creativa que crea el mapa, la que tambien modifique un poco las reglas del juego.   Esto concentra potencia ahi donde la potencia puede ser aprovechada: por los maperos y sus amigos (mas que nada por los amigos de los maperos, porque los maperos no saben programar, pero siempre tienen amigos).

Si vas a usar una instancia global para el scripting. Cuanto antes sea instanciada, y mas cosas relevantes contenga, mas potencia puede tener luego.   Salvo que se produzca alguna clase de problema, o tenga algun tema de cheating en juegos multijugador (como que varios jugadores desactiven la niebla, porque sea posible).

Otra opcion es crear el contexto de scripting con cada mapa, o asi. De este modo si el contexto tiene leaks de memoria, esa memria perdida se recuperaria cada vez que se cambia de mapa, porque el contexto se destruiria y construiria otra vez.   Pero para juegos pequeños esto no creo que sea un factor, porque habra leaks, seran culpa tuya (del lado del engine, y no del lado del script), y aun asi siempre sobrara memoria porque la gente hoy en dia tiene capacidades de memoria absurdas como 1 GB y tal.

Al menos asi es como yo lo veo. Pero esta es una opinion mas, y de aficionado, no de profesional de los videojuegos.
Título: Sistema de script para mi juego.
Publicado por: Oanime en 24 de Octubre de 2007, 02:22:15 PM
Gracias por las respuestas, y buen apunte el de Gdl. Por otra parte, Gdl, segun tu metodo, la creacion de actores estaría en lua, es decir tu desde lua vas cargando los actores que automaticamente se registran en c++ (o eso es lo que entiendo de lo que me has comentado). El tema es, donde deberian de estar las instancias de los actores en la cara de C++ o script?. Y nadie, ha comentado nada sobre el tema de tener esas 2 funcioncillas por actor.

Gracias por adelantado,
HexDump.
Título: Sistema de script para mi juego.
Publicado por: Tei en 24 de Octubre de 2007, 02:41:26 PM
Cita de: "HexDump"Gracias por las respuestas, y buen apunte el de Gdl. Por otra parte, Gdl, segun tu metodo, la creacion de actores estaría en lua, es decir tu desde lua vas cargando los actores que automaticamente se registran en c++ (o eso es lo que entiendo de lo que me has comentado). El tema es, donde deberian de estar las instancias de los actores en la cara de C++ o script?. Y nadie, ha comentado nada sobre el tema de tener esas 2 funcioncillas por actor.

Gracias por adelantado,
HexDump.

en Quake1, no se como se hara en otros juegos, la presencia de una entidad en un mapa fuerza la instanciacion de esa entidad por la gamelogica. Pero es la gamelogica la que hace practicamente todo (excepto un = new something, que eso se lo regalan de regaliz).

como te ha dicho el compañero, si creas una dependencia muy fuerte entre tu codigo C++ y las clases de mostruos que tienes,  no obtienes muchas ventajas del scripting porque tendras que escribir mas C++ para añdir una nueva clase de mostruos (por ejemplo un orco, pero mas feo, y que lanza bolas de fuego).

todo lo posible deberia estar en gamelogica, y yo he sido tan absolutamente brutal, que te he sugerido tener en gamelogica, hasta los menus y la inicializacion grafica.
para conseguir algo asi tendras que crear una expecie de "API" expuesta al lenguaje de scripting que llame a funciones del juego.  

entre esa API, yo tendria algo como "spawn" para invocar un nuevo mostruo, y luego darle propiedades  como .life con los puntos de vida, etc..   aunque esto ya es una cosa muy concreta de hacer las cosas.

Y bueno, creo que a base de insistir creo que en algun momento dare con un consejo que es util para ti. ¡Es cuestion de estadistica :D !. Lo siento si hasta ahora no te soy muy util :(
Título: Sistema de script para mi juego.
Publicado por: Oanime en 24 de Octubre de 2007, 03:43:39 PM
Tei claro que has ayudado hombre, además me das distintos puntos de vista sobre esto, que yo no tengo.

Solo hay una cosa que no veo clara. Las clases de actores se definen complemtamente en script? y la factoria de esta tambien en script? Te lo digo porque si no es así siempre habrás de tocar C++ para añdir una entidad.

Entonces para resumir, lo q sugeris seríai algo así?:

1) Se escribe la clases de la entidad en script (declaración de clase y lógica).
2) Se tiene algo parecido a un factory de actores en script que se encarga de parsear un directorio de entidades y registrarlas.
3) Desde C++ se llama a createActor (una funcion en script) que a partir de esa factoría te instancia el objeto que quieres. y te devuelve su ID a C++ para poder usarlo en un getActor o algo asi (q no se i le va a hacer falta alguna vez ya que toda la logica se hace en script según esto). En este caso, estoy suponiendo que la gestion de entidades se hace en el script, que no se si es el mejor lugar para hacerlas vivir.
4) Si todo esto es correcot solo me queda como duda el tema de las colisiones/físicas. Yo quiero meter en las entidades desde escript cosas como OnHit(), OnDead(), etc... Que se tendrán que llamar desde un dispatcher, por ejemplo, yo uso newton como motor de fisicas, cuando se detecta que 2 se pegan se llama al OnHit() en script de los objetos.

Siento si me he liado y te estoy dando la paliza en demasía pero es algo que parece que tú tienes muy claro y yo lo llevo colgandillo :D.


Gracias por adelantado,
HexDump.
Título: Sistema de script para mi juego.
Publicado por: Tei en 24 de Octubre de 2007, 04:32:08 PM
Cita de: "HexDump"Tei claro que has ayudado hombre, además me das distintos puntos de vista sobre esto, que yo no tengo.
gracias :D ,  happyness + 1

Citar
Solo hay una cosa que no veo clara. Las clases de actores se definen complemtamente en script? y la factoria de esta tambien en script? Te lo digo porque si no es así siempre habrás de tocar C++ para añdir una entidad.

si, yo los tendria todos en el script, incluso vas a tener objetos que no se renderizan en nada, ni afectan en nada al motor. Y por tanto este no tiene porque saber que existe.

he visto como se embeben un par de lenguajes de script, y me ha parecido que hay que hacer mucho trabajo de conversion desde el formato interno del script al exterior.   Asi que quizas te venga bien que los objetos que tengan una representacion renderizada, tengan un objeto gemelo tangible en el lado C++, creado por el script, y que fuera de este de donde se leyeran cosas como la textura y posicion, en lugar de preguntar al objeto dentro del lenguaje de scripting, que podria ser lento.


1) Se escribe la clases de la entidad en script (declaración de clase y lógica).

Sinceramente, creo que es lo mas potente. Y corta de raiz esto que ponias en el primer post, que parece limitante.

2) Se tiene algo parecido a un factory de actores en script que se encarga de parsear un directorio de entidades y registrarlas.

Por ejemplo. osea, si.

3) Desde C++ se llama a createActor (una funcion en script) que a partir de esa factoría te instancia el objeto que quieres. y te devuelve su ID a C++ para poder usarlo en un getActor o algo asi (q no se i le va a hacer falta alguna vez ya que toda la logica se hace en script según esto). En este caso, estoy suponiendo que la gestion de entidades se hace en el script, que no se si es el mejor lugar para hacerlas vivir.

Estas pensando en funcion de "el motor llama al script", pero no solamente se puede hacer esto. De echo, puedes reducir un motor a un monton de apis (iba a decir a pulpa)  y que sea el script quien "mande" y dirija todo el cotarro. Bueno.  No hace falta hacer eso. Pero  tampoco irse al otro extremo y que todo funcione como comentas. Es mucho mas practico que haya mecanismos como "callbacks".   El script inicia una accion, por ejemplo, cargar mapa,  y el motor necesitar hacer sus cositas (parsear un xml o lo que sea), y cuando hace falta, invoca al script a ciertos callbacks (como "crearActor"), el cual a su vez llama a las funciones del engine que necesite.  Los callbacks son los mecanismos mas utiles para meter en tu lenguaje de script automagia. Que porque una funcion se llama "asi", sea llamada automaticamente en ciertos momentos. Por ejemplo, tener una funcion llamada  OnMouseClick.  Sin callbacks, tendrias que estar corriendo esta funcion en cada frame, haciendo pooling al engine, para ver si hay clicks pulsados... ineficiente. Es mejor que si hay un click pulsado, el engine vea si el objeto tiene la funcion OnMouseClick. Esto es un callback, convierte lo que serian poolling aburridisimos de programar e ineficientes, en automagia.

IMHO, que tampoco se mucho del tema :D

4) Si todo esto es correcot solo me queda como duda el tema de las colisiones/físicas. Yo quiero meter en las entidades desde escript cosas como OnHit(), OnDead(), etc... Que se tendrán que llamar desde un dispatcher, por ejemplo, yo uso newton como motor de fisicas, cuando se detecta que 2 se pegan se llama al OnHit() en script de los objetos.

Yo creo que un motor de simulacion fisica es un tipo de animal muy fiero y peligroso. Y aun no he tenido la suerte de pelearme con uno.
¿Que pasa si tu motor ha de representar una moneda, y esta cae entre dos objetos, y empeza a rebotar rapidamente entre estos dos objetos?. Se podrian producir muchisimos eventos rapidamente.  Cuando hay muchos eventos por frame, igual es lo inteligente "sumarlos" en un solo evento.  En lugar "tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon","tio te ha dado un perdigon",...  pues un solo evento "te acaban de disparar en la cara con una escopeta de cañones recortados".
Esperaria al final del frame, antes de reportar eventos. Sin perderlos. Quizas haciendo una cola, o algo.
Luego, como me parece un animal fiero. Quizas te interese documentarte a base de bien.  Lo que te escribo es como lo haria yo. Pero igual hay "papers" por ahi, tratando el tema de una manera cientifica.  O en el peor de los casos, ejemplos de motores libres que utilicen algo como ODE.

A los motores comerciales 3D que utilizan simuladores de fisica, les cuesta salir sin ningun error.  Sobretodo es facil que salgan con el tipico problema de "creacion de energia". Que hace que los objetos alcancen velocidades infinitas.

Una cosa que comentaban los desarrolladores del juego "Portal", es que la simulacion fisica entorno a los portales, corre a una frecuencia de frames inferior.  Osea, estos tios, corriendo una gamelogica en C++ compilado. Han tenido que bajar la frecuencia de ejecucion del simulador de fisica. Seguramente porque no habia forma de evitarlo.

Enfin.

Comentarte que solo son aficionado al tema. Y que no te tomes demasiado en serio mis comentarios. Solo pueden servirte para plantearte cosas, no es bueno que les des mas confianza.
Título: Sistema de script para mi juego.
Publicado por: Oanime en 24 de Octubre de 2007, 05:55:14 PM
He, Tei, dices que no sabes mucho del tema y te sueltas unas parrafadas de la leche y además con sentido :D, que bueno.

A ver si alguien más se pasa y da nuevos puntos de vista, aunqeu ya te digo, el tuyo me parece muy logico en muchos aspectos.

HexDump.
Título: Sistema de script para mi juego.
Publicado por: Tei en 25 de Octubre de 2007, 11:16:23 AM
bump!
Título: Sistema de script para mi juego.
Publicado por: ethernet en 25 de Octubre de 2007, 12:36:35 PM
Tei, tú has trabajado con el motor del quake podrías comentar un poco más en detalle como lo hace id :)
Título: Sistema de script para mi juego.
Publicado por: Tei en 25 de Octubre de 2007, 01:13:48 PM
Cita de: "ethernet"Tei, tú has trabajado con el motor del quake podrías comentar un poco más en detalle como lo hace id :)

vale.

quake tiene una maquina virtual de un lenguaje estilo C, orientado a objetos (QuakeC, o QC).  El lenguaje es muy malo, pero se adapta como un guante al tema.

El proceso es el siguiente.
El motor arranca, inicializa memoria, y se queda a la espera que el usuario inicie cualquier cosa.

Entonces el usuario empieza un juego singleplayer. Y el motor comienza la carga del fichero del mapa.  Cuando le llega el turno de las entidades del mapa (que estan guardadas como una especie de fichero CSS) las recorre una a una, y busca una funcion del script que tenga el mismo nombre que la entidad.
Si existe esta funcion. El engine reserva memoria para una estructura de un tamaño maximo conocido, popula los datos de la estructura con los datos fijados en el mapa (de modo que si es un mob, desde el mapa se puede ajustar los puntos de vida), y arranca esa funcion, que hace las inicializaciones tipicas de una funcion constructora en la programacion orientada a objetos.  Cuando QC accede a un campo de ese objeto (como en this.health = this.health - 10)  esta  leyendo y modificando esta estructura que existe en el engine.

Ademas hay una serie de funciones, que se pueden poblar, como ondeath, ontouch, think,  que actuan como callbacks, que los llamara automaticamente el engine cuando sea necesario.  La mas usada es think.   Todos los bichos del juego funcionan con think.  Think al correr ajusta el frame del modelo a una etapa determinada dentro de la animacion,  hace cualquier otra accion (como disparar un misil, o emitir un sonido) , y ajusta el valor de nextthink a time + 0.1. El motor es quien se encarga de llamar a las funciones apuntadas por think cuando caduque ese time + 0.1.  El resultado es que estas funciones se llaman cada 0.1 segundos.

Cuando lanzas un misil, se carga nextthing con time + 6, y se carga touch.
La funcion que se invocara al pasar los 6 segundos hara desaparecer el misil.  Pero si antes de los seis segundos toca algo, correra la funcion apuntada por touch (el motor llamara a la funcion de touch, del objeto, pasandole como parametro el objeto tocado) y asi posiblemente explote :D, posiblemente mate a esa cosa con la que toque, aunque esa cosa que toque puede ser el objeto "world" que es quien representa al mundo (el mapa).

Hasta aqui, todo lo relevante al problema actual.
Desde aqui, un poco de informacion extra:

Extra 1: netcode
Como es un juego de red. Lo que hace el engine tras cada frame. Es recorrer la coleccion de objetos (que es un array, porque cada objeto tiene el mismo tamaño), y si el objeto tiene un modelo (como misil.mdl), entonces lo va enviar por red. Si el objeto no tiene modelo, lo ignora.
Antes de enviarlo por red, o para ver que informacion debe enviar. Tiene una especie de "cache" del ultimo frame, y lo compara. Y solo envia los campos que hayan modificado desde este frame cacheado.  Para que al otro lado sepan que informacion va a llegar o no, se activa un flag a 1 para indicar que ese campo se envia. Hay un word con 1 bit por cada campo que se puede actualizar (x, y,z, modelo, frame, etc..)

Extra 1.1: demos
Un demo no es mas que todos los datos de netcode salvados en raw a un fichero. Reproducir una demo supone que el engine se conecte con un servidor "fake" que no es mas que el netcode reproduciendose.

Extra 2: savegames
Un savegame es una serializacion del estado del juego. Todo en quake es serializado a un fichero. Los punteros a funcion se serializan con el nombre de la funcion. Cuando se restaura, se desserializa todo, sustituye ese nombre, por el entero que identifica a la funcion.

Extra 3: camara
Para dar soporte a la camara, el servidor identifica que entidad lleva la camara, y el cliente renderiza desde ella. Los angulos de vision son manejados completamente por el lado cliente. Aunque se puede enviar un mensaje de reseteo. Tambien hay un mensaje para cambiar la entidad desde la que mirar, y esto se usa facilmente para poner camaras en cohetes, y cosas asi.

Extra 4: render
Para renderizar. El motor toma las entidades que el netcode le ha propocionado, que son las que tiene modelo. Y las envia a una funcion de dibujado que es sensible al tipo de modelo ( bsp, mdl, sprite). Como hay una separacion servidor/cliente, y es el cliente el que renderiza no hay ninguna relacion en absoluto con el scripting en esta fase.

Extra 5: problemas de quakec
Tener un lenguaje especial para un scripting es un poco coñazo, porque no se puede reutilizar conocimientos, ademas quakec solo permite un tipo de objeto, y este siempre tendra el mismo tamaño. Algo asi como si solo se pudiera instanciar un prototipo de Object(), y que si se creasen campos en Object, se vieran esos atributos en todas las instancias. No algo asi, sino exactamente esto es lo que pasa en QuakeC.
Por otra parte, la programacion de AI para QuakeC es trivial copiando y pegando la que hay para los mostruos, que es codigo trivial de entender, y funciona muy bien.  No es de extrañar que los primeros bots de FPS fueran de quake.   Lo unico que provee el motor que hacerlo desde codigo sencillo de scripting seria mareante, es una funcion que mueve el muñeco desde un punto A a un punto B, cambiando la orientacion del muñeco si hace falta.   Pero esta funcion no tiene ningun tipo de pathfinding decente*. Y aun asi ha sido posible escribir bots que ganan a cualquier humano y que recorren *todo* el mapa.  Simplemente utilizando los botes de vida, y .las armas de waypoints, y viajando de unos a otros.


nota: entrar en una region convexa, que cubra al bot, seria una trampa mortal para este pathfinding, porque no sabe "retroceder" para "evitar un optimo local". no tiene memoria, solo sabe ir hacia adelante, evitando pequeños obstaculos, pero con pequeños giros de angulo. Si tubiera que llegar a un sitio, en el que parte del camino *hay* que hacer un giro de 90 grados, creo que no llegarian. Aunque como digo, es suficiente para que un bot recorra todo el mapa.
Título: Sistema de script para mi juego.
Publicado por: ethernet en 25 de Octubre de 2007, 01:45:37 PM
gracias tei :)
Título: Sistema de script para mi juego.
Publicado por: TiRSO en 25 de Octubre de 2007, 01:59:11 PM
Muy interesante. Tei, esa información vale su peso en aceite de oliva virgen. Gracias.
Título: Sistema de script para mi juego.
Publicado por: jazcks en 25 de Octubre de 2007, 02:41:26 PM
Tei, excelente "disección" del motor.
Sería interesante si alguien hiciera lo mismo con otros motores :D
Título: Sistema de script para mi juego.
Publicado por: Oanime en 25 de Octubre de 2007, 03:39:13 PM
Jejejej, pues nada acabo de entrar ahora y me he quedado bastante sorprendido al ver que más o menos mis conclusiones finales se parecen bastante a lo que comenta tei.

Por una parte he decidio partir las distintas clases de entidades en 2 partes, una en C++ y otra en lua. En lua se van a encontrar los datos de la entidad como vida, velocidad, etc... Mientras que en C++ tendré una clase muy genérica, llamemosla actor. La clase de C++ será la que defina una serie de callbacks a los que llamará el engine cuando pase algo, como puede ser OnHit, OnDeath, etc... y estos llamaran a los que tiene cada entidad en script.

Durante el juego, el script o codigo de c++ podrán crear actores, al crearse se creara la clase de c++ que pedira que se cargue su parte de datos en lua, manteniendo ambas partes un ID para poder hablar una con la otra.

Alguna idea más? problema, etc...? Yo lo veo bastante bien.

Un saludo,
HexDump.
Título: Sistema de script para mi juego.
Publicado por: Tei en 25 de Octubre de 2007, 04:06:08 PM
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
Título: Sistema de script para mi juego.
Publicado por: kaworu en 25 de Octubre de 2007, 04:22:22 PM
en serio NwN utiliza lua? hombre, tiene su propio lenguaje de script Nwscript xD yo lo he utilizado a muerte xd es genial.
Título: Sistema de script para mi juego.
Publicado por: Oanime en 25 de Octubre de 2007, 04:23:53 PM
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.
Título: Sistema de script para mi juego.
Publicado por: kaworu en 25 de Octubre de 2007, 04:44:32 PM
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!
Título: Sistema de script para mi juego.
Publicado por: Tei en 25 de Octubre de 2007, 05:07:34 PM
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, ??
Título: Sistema de script para mi juego.
Publicado por: Oanime en 25 de Octubre de 2007, 05:22:35 PM
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.
Título: Sistema de script para mi juego.
Publicado por: ethernet en 25 de Octubre de 2007, 06:27:56 PM
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)
Título: Sistema de script para mi juego.
Publicado por: gdl en 26 de Octubre de 2007, 12:15:02 AM
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.
Título: Sistema de script para mi juego.
Publicado por: gdl en 26 de Octubre de 2007, 12:18:29 AM
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.
Título: Sistema de script para mi juego.
Publicado por: Oanime en 26 de Octubre de 2007, 09:17:04 AM
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.
Título: Sistema de script para mi juego.
Publicado por: Tei en 26 de Octubre de 2007, 09:57:54 AM
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.
Título: Sistema de script para mi juego.
Publicado por: gdl en 26 de Octubre de 2007, 01:29:10 PM
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++.
Título: Sistema de script para mi juego.
Publicado por: Tei en 26 de Octubre de 2007, 01:56:46 PM
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
Título: Sistema de script para mi juego.
Publicado por: Oanime en 26 de Octubre de 2007, 02:23:23 PM
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.
Título: Sistema de script para mi juego.
Publicado por: gdl en 26 de Octubre de 2007, 09:56:32 PM
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.
Título: Sistema de script para mi juego.
Publicado por: Oanime en 28 de Octubre de 2007, 03:03:27 PM
Sobre lo primero que comentas, la segunda opcion no la entiendo. NO querrás haber querido decir tener en LUA por objeto un puntero al objeto de C++ o algo asi?.

Yo estaba llevando lo segundo que comentas 1 paso más allá y era crear un estado por ejemplar (o instancia). Según he estado leyendo se puede tener un estado maestro, donde residirian todos los generos (se cargarian todos en ese estado), y despues a la hora de crear un objeto, este se crearia en un subestado (creo que lo llaman coroutines) q puede acceder a ese estado maestro, pero su instancia vive en otro estado aparte. Así tienes la ventaja de tener una relación 1:1 entre el CActor en C++ y su instancia de datos en LUA. He de investigar algo más.


Muchas gracias por los apuntes,
HexDump.
Título: Sistema de script para mi juego.
Publicado por: gdl en 28 de Octubre de 2007, 08:28:54 PM
No sabía lo de las corrutinas, pero es lo que te quería decir en la segunda parte de mi anterior post. Sobre la primera parte y el puntero díscolo, intentaré explicarte.

Este es el pseudocódigo de la función C++ que llama a Lua cuando atacan a un ejemplar.


void Instance::onHit(INSTANCE_ID attacker)
{
 Llamada_a_Lua("onHit", this->GetID(), attacker);
}


Y este el pseudocódigo de la función C++ que implementa la función "GetPos()" del Lua.


void implement_GetPos(Lua_loquesea estado)
{
 INSTANCE_ID id_instance=estado.get("id");//O como se haga en Lua
 estado.return( engine.GetInstance(id_instance)->GetPos() );
 //Vale, engine es una fea variable global con todos los ejemplares :-(
}


Esta es la forma explícita de hacerlo. Hemos de obtener de Lua el id del bicho.

La forma implícita es la siguiente.


void Instance::onHit(INSTANCE_ID attacker)
{
 engine.current_instance=this;
 Llamada_a_Lua("onHit", attacker);
 engine.current_instance=NULL;
}



void implement_GetPos(Lua_loquesea estado)
{
 //current_instance se inicializó antes de llamar siquiera al Lua
 estado.return( engine.current_instance->GetPos() );
}



Espero que veas la diferencia. Como siempre, tendrás los pros y los contras.
Título: Sistema de script para mi juego.
Publicado por: Oanime en 29 de Octubre de 2007, 09:31:13 AM
Bueno, primero de todo perdón por mi anterior post porque vaya tela con mi expresión escrita :D, es que tenía mucha prisa y escribí muy rápido.

Tranquilo, te entiendo perfectamente. Y bueno, sobre eso no se, la sintaxis de acceso queda más limpia pero lo de tener que inicializar la variable esa cada vez como obligación no se si me gusta. De todas formas esto son cosas ya de implementación.

Esta noche intentaré leerme bien lo de las corutinas y ya por fin escribir el sistema entero, a ver que sale.

Un saludo,
HexDump
Título: Sistema de script para mi juego.
Publicado por: gdl en 29 de Octubre de 2007, 10:53:29 AM
¡Suerte! A ver si das con la tecla  :wink:
Título: Sistema de script para mi juego.
Publicado por: Oanime en 29 de Octubre de 2007, 12:55:06 PM
Pondré está semana mi solución final despues de que haya funcionado y visto que es un diseño decente/bueno.

Por otro lado, hay algo que me martillea la cabeza y es que si nuestra clase generia Actor en C++ tiene todos los eventos que cualquier genero puedo recibir, como hacer para que no se llamen a eventos que ese genero no soporta, porque no me veo usando reflexion en cada llamada de evento.

Un saludo,
HexDump.