Estaba pensando en guardar replays, para ello he pensado en capturar las pulsaciones de teclado en cada frame y reproducirlas después. El problema que me viene a la cabeza es que el tiempo de frame puede que no sea idéntico en el momento de la grabación que en el de la reproducción y exista un offset de teclas (XD). Cómo se os ocurre a vosotros?
Yo era como siempre he pensado que se hacía, guardando pares de .
Luego al reproducirlos, si inicialmente tu sistema de input va por eventos que se llaman en cualquier momento que se presione una tecla, en vez de usar un buffer y procesarlos luego todos, sí que es posible que tengas algún problema, pero supongo que todo depende de si al reproducirlo estás en una máquina muy lenta y ese offset es tan significativo como para que sea visible, no?
EDIT: Mírate el PopCap Framework que tiene un sistema de grabación de demos. Aunque más que replays, simplementa graba todo todo, para reproducir fielmente algunos bugs y tal. Pasándole nosequé parámetro al ejecutable guardaba todo lo que hacías en un fichero, y con otro parámetro lo reproducía.
Cita de: "CoLSoN2"Yo era como siempre he pensado que se hacía, guardando pares de .
Luego al reproducirlos, si inicialmente tu sistema de input va por eventos que se llaman en cualquier momento que se presione una tecla, en vez de usar un buffer y procesarlos luego todos, sí que es posible que tengas algún problema, pero supongo que todo depende de si al reproducirlo estás en una máquina muy lenta y ese offset es tan significativo como para que sea visible, no?
No lo he probado, pero ahora mismo lo pruebo. Dame 10 minutos :)
Funcioan asombrosamente bien, eso sí, en la misma máquina, habría que verlo con deltas diferentes.
class KeyBoardController:
def __init__(self,keys = [K_UP,K_DOWN,K_LEFT,K_RIGHT],save_keys= 0):
self._keys = keys;
self._keys_saved = [];
self._save_keys = save_keys;
#reproduccion
self._active_key = 0;
self._replay = 0;
def SaveKeys(self,file):
packer.save(self._keys_saved,file);
def Replay(self,file):
self._keys_saved = packer.load(file);
self._replay = 1;
def Tick(self,car,delta = 0):
global Gkeys;
if(self._save_keys):
k = [Gkeys[self._keys[0]],Gkeys[self._keys[1]],Gkeys[self._keys[2]],Gkeys[self._keys[3]]];
self._keys_saved.append(k);
if(self._replay):
Gkeys[self._keys[0]],Gkeys[self._keys[1]],Gkeys[self._keys[2]],Gkeys[self._keys[3]] = self._keys_saved[self._active_key];
self._active_key+=1;
# mover el muñeco con las teclas pulsadas :)
La solución que uso yo en mis juegos es la siguiente:
Coloco un timer con intervalo de 20ms. En cada evento del timer provoco un keydown de la tecla "Impr Pant" ("Imprescindible Pantalones", para el que no lo sepa), con lo que tengo una captura de la pantalla en el portapapeles de Windows. Entonces guardo los datos del portapapeles a disco, usando un formato gráfico propio (".jpg", es posible que lo hayáis visto alguna vez por internet). Cuando ha terminado la captura de todos los frames individuales, llega el momento de unirlos en un solo archivo de vídeo. Para ello, he encapsulado una DLL de edición de vídeo (también creada por mi, "premiere.dll") en el ".frm" principal del juego (es importante que sea en el principal). Haciendo una llamada a la función "Arrejúntalos()" de dicha DLL, pasándole como parámetro una matriz con las rutas de los archivos ".jpg", consigo un vídeo en formato ".wmv". Cuando quieras replayear la escena, simplemente usas DirectVideo para reproducir el archivo ".wmv" y ya está. Todo ésto es transparente al usuario.
PS: si te interesa, te puedo pasar código comentado de todo lo que te digo.
PS2: lo he testeado sobre Windows y Linux y funciona. En Mac no lo he probado, pero no hay motivos para que no funcione. Después de todo, está programado con java, html y mdb, todo es estandar, vamos.
Nota: alguno puede pensar "en Linux no existe el portapapeles de Windows". Efectivamente, así es. Para solucionarlo, lo que hace mi código es, si comprueba que el SO no es Windows, abrir el puerto 6661 y enviar a través de él los datos de la captura al servidor de "www.microsoft.com\tewe76" (gracias Guillermo por cederme espacio en tu web, saludos a tu mujer). Todo ésto sigue siendo transparente para el usuario, por supuesto.
tewe, no me refiero a guardar el replay en video, me refiero a guardar los datos imprescindibles para que el motor reproduzca lo que hizo el user. No obstante tu técnica es curiosa de cojones XDD.
Citarno me refiero a guardar el replay en video, me refiero a guardar los datos imprescindibles para que el motor reproduzca lo que hizo el user
Ya, ya lo sé. Te he dicho que es transparente al usuario.
Dado que los frames y los tiempos de ejecución no se repiten exactamente igual (ni en la misma maquina), y cualquier diferencia de tiempo por mínima que sea en la pulsación puede modificar completamente todo el desarrollo (suponiendo que se trate de un juego), la verdad me parece imposible que se pueda hacer así.
Creo que deberías hacer una grabación larga cuando lo consigas para asegurarte.
Apostaría por que la IA sea independiente del framerate:
Jare y su Fixed Time Stamp LoopEn Gamasutra apareció un artículo sobre Replays en juegos, échale un ojo.
No tienes unicamente el problema del tiempo cualquier uso de una funcion random lo echaria abajo. Creo que la mejor forma es ir guardando cada cierto tiempo la posicion y velocidad/aceleracion (o las propiedades que hagan falta) de cada objeto.
El tema de los rands no es problema alguno, lo que haces es usar en tu juego una función propia de random (aunque sea una tabla circular precalculada) y simplemente guardar en la demo el estado actual de la semilla.
El par (evento+frame_number) y simulación por pasos fijos debería ser lo mas fiable. Durante el juego normal no proceses los eventos de teclado cuando te los manda el Sistema Operativo, sino que los encolas y que sean procesados en el siguiente paso de simulación.
La cosa es elegir bien los pasos de simulación por segundo, creo que DOOM1 usaba 30Hz y DOOM3.... ¿ 60 ?
NOTA: Donde pongo numero de frame se entiende frame de simulación, no de rendering...
Cita de: "Grugnorr"Apostaría por que la IA sea independiente del framerate:
Jare y su Fixed Time Stamp Loop
En Gamasutra apareció un artículo sobre Replays en juegos, échale un ojo.
Venía pensando en ese artículo en el autobús :)
Cita de: "senior wapo"El tema de los rands no es problema alguno, lo que haces es usar en tu juego una función propia de random (aunque sea una tabla circular precalculada) y simplemente guardar en la demo el estado actual de la semilla.
Basta con usar la función standard guardando su semilla, o me pierdo algo? :huh:
Cita de: "Grugnorr"Cita de: "senior wapo"El tema de los rands no es problema alguno, lo que haces es usar en tu juego una función propia de random (aunque sea una tabla circular precalculada) y simplemente guardar en la demo el estado actual de la semilla.
Basta con usar la función standard guardando su semilla, o me pierdo algo? :huh:
Ricemos el rizo (mas que nada por putear :P) y si entre version y version del juego cambias la funcion rand? Los relplays no serian compatibles.
Cita de: "samsaga2"Ricemos el rizo (mas que nada por putear :P) y si entre version y version del juego cambias la funcion rand? Los relplays no serian compatibles.
¿Qué función rand? Se supone que usas la de la librería estándar de C, y sólo guardas la semilla.
Aunque lo de que en diferentes versiones dejen de funcionar las replays antiguas no es nada nuevo, en según qué parches de WarCraft3 pasa esto, no estoy seguro si en otros juegos como AoE 2 o StarCraft también. Aunque en el SC lo que sí pasa es que a veces las replays se "estropean" o algo, y no muestran lo que realmente pasó en la partida, suelen acabar en el tiempo que toca, pero las unidades no hacen lo mismo. Y hablo de reproducir replays que se grabaron en el mismo PC. Raro, eh? XD
Cita de: "CoLSoN2"Cita de: "samsaga2"Ricemos el rizo (mas que nada por putear :P) y si entre version y version del juego cambias la funcion rand? Los relplays no serian compatibles.
¿Qué función rand? Se supone que usas la de la librería estándar de C, y sólo guardas la semilla.
Aunque lo de que en diferentes versiones dejen de funcionar las replays antiguas no es nada nuevo, en según qué parches de WarCraft3 pasa esto, no estoy seguro si en otros juegos como AoE 2 o StarCraft también.
¿Y si el juego es multiplataforma y usa el rand de dos implementaciones libc diferentes? xD
Sí, es cierto que el 90% de los parches de StarCraft y Warcraft III invalidaban los replays de las partidas anteriores, lo cual era una putada porque ya no podias ver las repeticiones de jugadores coreanos "pros" que te habias bajado la semana anterior.. :P
En realidad para guardar una demo lo que se hace es tener encapsulado el estado del juego como tambien el estado de los sprites, de tal manera que los guardas en un array circular donde guardes los ultimos 30 segundos por ejemplo ..., esto funciona realmente bien para timesteps fijos, para timesteps variables debes ademas guardar el delta_tiempo y crear una funcion que interpole los estados para la reproducción. Tambien se aplica a fisica con los estados fisicos y es compatible para la transmicion por red, las funciones interpolar_estados es algo muy usado y el grabar demos es otra aplicación mas.
Saludos
Creo que no te entiendo bien Pogacha. Sugieres que guardemos el estado del juego cada 30 segundos y luego al reproducir la partida, interpoles entre esos "keyframes" que están a 30 segundos de distancia? Porque eso es totalmente ridículo.
Si no he entendido mal, pogacha quiere decir que se guarden 30 segundos de partida siempre. Cuando se alcanza el límite de tiempo se vuelve a grabar desde el principio. De ahí la lista circular de la que habla...
El problema que yo le veo a eso es que como no se limpie la lista al finalizar el tiempo, se mezclarán repeticiones entre tramos de tiempo. :ph34r:
Cita de: "Topper"Si no he entendido mal, pogacha quiere decir que se guarden 30 segundos de partida siempre. Cuando se alcanza el límite de tiempo se vuelve a grabar desde el principio. De ahí la lista circular de la que habla...
El problema que yo le veo a eso es que como no se limpie la lista al finalizar el tiempo, se mezclarán repeticiones entre tramos de tiempo. :ph34r:
En esa lista circular siempre tienes un puntero al principio, no habrá problemas por ello.
Yo sigo pensando que usando fixed timestep (por ejemplo, 100 frames por segundo de tus llamadas a Update() o lo que sea, como hace el PopCap Framework) no debes tener problemas luego para reproducir los eventos que leas.
Alguien me ha mentado? :)
Las dos formas habituales de guardar replays son:
- guardando pares (tiempo, evento del jugador), y teniendo un motor de juego completamente determinista, que si recibe esa misma serie, generará unos resultados IDENTICOS a cada paso. Este método es típico de los RTS (aunque el Total Annihilation usaba el segundo sistema).
- guardando las posiciones de los bichos, y los eventos que se producen en el juego (disparo, daño, muerte). Esto es lo que suelen hacer los FPS (aunque el Doom original usaba el método anterior).
El método elegido suele tener mucho que ver con el sistema de red. Ya que todos los jugadores de una partida en red tienen que ver la misma partida, el problema es similar al de reproducir la partida posteriormente.
El segundo método es en principio más sencillo y flexible, pero potencialmente genera más tráfico, todo depende de la cantidad de entidades móviles. El primero genera unos problemas del carajo, porque conseguir que el motor sea 100% determinista es un horror. 99.999% no sirve, en el momento en que la reproducción se desvíe del original, el juego empezará a tomar decisiones diferentes, y la reproducción se degenera rápidamente, el conocido "efecto mariposa".
- Hay que tener muy controladitos los generadores de números aleatorios. Se suelen usar dos: uno para la lógica y cualquier cosa que pueda afectar al estado del juego, y uno para los efectos visuales y cosas que jamás afecten a la lógica. De esa forma, los rand() que hagas para las partículas (que pueden estar desactivadas o con niveles de detalle) no afectan a las decisiones de la IA.
- Hay que tener cuidado con las operaciones en coma flotante, si reproduces la partida en una máquina con una CPU diferente (Mac vs. PC por ejemplo) los decimales de las operaciones pueden diferir, y lo harán. Al carajo la reproducción!
- Hay que cuantizar el tiempo de ejecución de la lógica a intervalos de tiempo fijos, o al menos almacenar los tiempos entre ticks de lógica, y asegurarse de que la reproducción los sigue fielmente. No puedes hacer ninguna virguería para ajustar el tiempo de cálculo de la lógica en función del framerate, y por supuesto esparcir la lógica en varios thread es imposible excepto en operaciones muy concretas (para las que no suele merecer la pena sacar un thread aparte).
- Cualquier chorrada, cualquier bug, cualquier valor no inicializado, cualquier cambio en los valores o algoritmos (típico al sacar parches), cambiará los resultados de la lógica y destruirá la reproducción. Necesitas incluir trazas para depurar estos problemas, normalmente grabas el valor de la semilla de rand() de la lógica y lo compruebas constantemente para detectar si se ha producido una desincronización.
- La reproducción tiene que realizarse en orden y desde el principio: no puedes arrancar a mitad, saltarte unos minutos, o ir hacia atrás. Aunque quieras dar esas capacidades al juego, por dentro tendrás que reproducir cada tick hasta el punto que quieres mostrar.
Muchos muchos problemas, pero las ventajas también son potentes:
- Tienes la herramienta perfecta para depurar un crash: saca el replay para reproducir exactamente la situación que provocó el casque, mucho mejor que el tester te cuente "pues llevé a la tropa cerca del pueblo y luego lancé un ataque contra uno de los defensores".
- El tamaño de las replays es muy pequeño.
- Cuando se usa este sistema para implementar la red, resulta casi imposible hacer muchas trampas en red, como las que hacen los trainers típicos, de darte vida o pasta o matar a un enemigo porque sí. Lo más que puedes hacer es mostrar
El tema da para un libro entero, pero como decía al principio, lo mejor es leer artículos sobre sistemas de red - una vez que tienes el juego corriendo en red, incluir un sistema de replay es casi trivial.
http://www.gamasutra.com/features/20010322.../terrano_01.htmhttp://www.gamasutra.com/features/20050823...itkine_02.shtml
Cita de: "Jare"- La reproducción tiene que realizarse en orden y desde el principio: no puedes arrancar a mitad, saltarte unos minutos, o ir hacia atrás. Aunque quieras dar esas capacidades al juego, por dentro tendrás que reproducir cada tick hasta el punto que quieres mostrar.
Bueno, yo me refería a algo menos avanzado, algo así como un replay de lo que pasó pero sin volver a calcular la lógica. Por ejemplo, si tenemos la simulación de una pelota botando, hay dos opciones:
- cualquiera de las que has dicho tú
- guardar pares (tiempo, estado_pelota) para después reproducirlo sin recurrir a la lógica del juego símplemente interpolando los valores de estado pelota para el instante de tiempo especificado. Vamos, saltarse la lógica del juego y usar la ya calculada.
En el segundo caso se podría reproducir a partir de cierto instante, rebobinar, etc.
El tema que planteaba era más parecido a el replay de un juego de fútbol en el que te repiten los goles, das para alante, atrás, etc, que a un sistema de reproducción exacta de la partida. No sé si es un sistema viable para sistemas con muchas entidades, pero para un juego con, como mucho, 15 ó 20 entidades sin muchas florituras creo que puede funcionar bien.
La verdad es que echando un ojo a los artículos que has puesto me asombra que el código que he usado yo reproduzca tan bien lo que guardé XD, tendré que probarlo en otra máquina.
Ah vale, para las repeticiones de momentos puntuales sin duda el segundo método, guardar el estado visual de los objetos en cada frame (or cada X frames), e interpolar. Como sugerían, un buffer circular es perfecto para eso. Es exactamente lo que hicimos en el NBA Inside Drive (que para todo lo demás usaba ticks fijos y motor determinista). Allí además teníamos otros buffers en donde copiabamos ese buffer circular y así contar con una selección de "las mejores jugadas" al final del partido.
Supongo que los juegos deportivos son los que mayor uso dan a los replays.
Jare, enrollate y cuentanos como funciona los savestates en el napoleon :P
A mí me impresionó la capacidad de "rebobinar" en el tiempo en los últimos Prince of Persia :D.
Me pareció algo original y que afectaba positivamente la jugabilidad... ahora es cuando me decís que el juego 'X' ya hacía lo mismo hace 5 años :ph34r:
Eso ya lo hacian juegos anteriores. :P
Pero si, en el prince of persia quedaba espectacular, y ademas parecia guardar intacta toda la partida. Seguro que usaban el sistema que comenta jare, teniendo en cuenta que la marcha atras estaba limitada a un tiempo maximo (se supone en el juego por la cantidad de arena).
Si tienes la función guardar partida (), la idea seria guardarla en cada frame en un arreglo de memoria ... en definitiva yo te recomendaria usar un metodo visual de demos, que consiste en guardar unicamente los estados de los graficos y sonidos tan solo (todo en un arreglo circular), los sonidos puedes manejarlos como eventos y los graficos como sprites donde pones que frame de animación tiene, que posicion y a que sprite del timestep anterior corresponde y listo, luego tener el motor preparado para que haga los replays. Puedes ir mas allá y hacer que las entidades hereden de las clases sprites las cuales sean dibujadas por el motor y así tienes un motor con solo una linea de render, a fin de cuentas puedes usar fixed time steps y con solo agregar una funcion "interpolar estados" puedes tener time steps variables ( de aca lo de guardar tambien en la info de estado del sprite un puntero al estado anterior).
Saludos.