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 »

Oanime

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.

gdl

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.

Tei

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.

Oanime

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.

Tei

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 :(

Oanime

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.

Tei

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.

Oanime

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.

Tei


ethernet

Tei, tú has trabajado con el motor del quake podrías comentar un poco más en detalle como lo hace id :)

Tei

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.

ethernet


TiRSO

Muy interesante. Tei, esa información vale su peso en aceite de oliva virgen. Gracias.

jazcks

Tei, excelente "disección" del motor.
Sería interesante si alguien hiciera lo mismo con otros motores :D

Oanime

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.






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.