Mostrando entradas con la etiqueta PROYECTOS. Mostrar todas las entradas
Mostrando entradas con la etiqueta PROYECTOS. Mostrar todas las entradas

miércoles, 25 de diciembre de 2013

Volviendo a dar de comer a la Quimera. :P

music: CREAMING JESUS -"Celebrity Cannibalism"-

Ya es oficial: regreso al proyecto Quimera Engine.


Hasta otra. :P


martes, 9 de octubre de 2012

Refactoring, refactoring...

music: DEVO -"Beautiful World"-

...y más, y más refactoring.

Y vuelta a empezar.

Pero al fín el proyecto tiene una estructura de directorios cohesiva (y coherente). Aparte, ha quedado actualizado a DirectX SDK (june 2010).

Y ahora tocará... limpieza y más limpieza de código. En fín...

Hasta otra. :P

martes, 28 de agosto de 2012

Bye, bye, warning C4921...!!!. :D

music: MASTODON -"Seabeast"-

La perseverancia a veces da sus frutos, ¡pardiez...!.

De nuevo, otro tema ha sido arreglado. No era un problema grave pero era algo que me estaba tocando la moral desde el principio (y de hecho, todavía me cuesta hablar en términos de pasado, como algo que ha desaparecido, que ya se ha solucionado). :P

Se trataba de una serie de warnings que aparecían durante la compilación del juego, cuyo código era C4291. Se limitaban a "aparecer", a estar ahí, dando un mensaje que hasta ahora me resultaba ilegible, cada vez que reservabas dinámicamente memoria para crear un objeto teniendo en funcionamiento el sistema de control de memory leaks. Y varias veces a lo largo de todo este tiempo me había puesto a intentar ver por qué se producían y qué hacer para que no salieran... sin éxito. Hasta ahora. : )

Y no, la solución no ha venido añadiendo al código  #pragma warning ( disable : 4291 )  :P

El asunto es: cuando activamos el sistema de control de memory leaks en el engine se realiza una sobrecarga de los operadores new y new[ ]. En concreto la sobrecarga 'Placement Form' de éstos, adoptando una cabecera así:

void* operator new      ( uint uSize, char* pszFile, uint uLine );
void* operator new[]    ( uint uSize, char* pszFile, uint uLine );

Pues bien, dado que estos son los operadores new y new[ ] que se sobrecargan, hay que hacer también las sobrecargas de delete y delete[ ] con esta misma estructura, es decir, así:

void  operator delete   ( void* p, char* pszFile, uint uLine );
void  operator delete[] ( void* p, char* pszFile, uint uLine );

La razón argumentada es que, si se crea dinámicamente un objeto y en el constructor de éste salta una excepción, se pueda liberar la memoria que había sido reservada para el objeto empleando dichas sobrecargas de delete y delete[]. Pero las únicas sobrecargas que había definidas hasta ahora eran:

void  operator delete   ( void* p );
void  operator delete[] ( void* p );

Pues... por eso daba los cansinos warnings. :P Al añadirle las otras sobrecargas de delete y delete[ ] a éstas últimas al fín han desaparecido, ¡joas, joas...!. :D

Mas información aquí.

En mi caso lo anterior nunca se va a producir pues, dada la arquitectura del engine, en los constructores de las clases no se hace nada "raro" que pueda provocar el lanzamiento de una excepción, no se hace nada más allá que inicializar a NULL, false ó 0 algunos atributos de la clase... dejando la "auténtica" inicialización del objeto en el método Init, llamado inmediatamente tras la creación del objeto, que todas las clases poseen. Por ello las nuevas sobrecargas quedan como una mera formalidad, pues no son empleadas dentro del engine (no se necesitan, y se siguen empleando las que ya había). Pero como digo siempre: El problema es no saber, y ahora que sé por qué se producía el tema, no me apetece quitarlas para no olvidarlo. XD

Hasta otra. :P

miércoles, 16 de mayo de 2012

Terrenos y Mapas de Alturas

music: L7 -"Pretend We're Dead"-

( NOTA: es probable que cambie las partes en las que digo píxel por téxel, dado que, aunque lo que voy a llamar Mapa de Alturas no es una textura propiamente dicha -no se mapea en ningún modelo 3D-, el concepto de píxel parece estar semánticamente más asociado con el monitor, mientras que el de téxel lo está más con una unidad atómica, indivisible, de una imagen. Aún no estoy seguro, de momento lo dejo como TO-DO ).

En los últimos días he estado desempolvando uno de los que fueron mis programas favoritos del Máster. Con él se genera la representación visual de un terreno a partir de un Mapa de Alturas, esto es, una imagen de escala de grises (o como es mi caso, una imagen RGB a la que hemos "desatudado" el color) cuyos valores de gris (color) equivalen a una determinada "altura" en un mundo 3D; esto es, a un determinado valor positivo a la coordenada Y de cada uno de los vértices que conforman el terreno.

Así, como valores extremos del rango:

-Un pixel en el mapa de alturas que tenga un color Negro (R:0, G:0, B:0) otorgará un 0.0 como valor a la coordenada Y del vértice correspondiente a ese píxel en el terreno, vamos, en el mundo 3D.

-Y un pixel en el mapa de alturas que tenga un color Blanco (R:255, G:255, B:255) otorgará un valor máximo como valor a la coordenada Y del vértice correspondiente.

Para preparar el Mapa de Alturas se parte de una imagen a la que, mediante Photoshop , hemos quitado el color, retocado a nuestro gusto y por último escalado a un tamaño de 128x128 píxeles. La guardaremos empleando el formato Photoshop RAW (*.RAW), que no debe confundirse con imágenes RAW de cámara digital (so pena de perder un montón de días haciéndote el lío padre como me pasó a mí). :P A todo esto, debo comprobar si el nuevo GIMP 2.8 también tiene soporte para abrir imágenes Photoshop RAW.

No necesitaremos que el fichero posea una cabecera que contenga el ancho y alto de la imagen en píxeles, pero justo por ello sí tendremos que ser nosotros los que tengamos que indicar al "Objeto Terreno" el tamaño del Mapa de Alturas que le pasamos  (que como ya hemos dicho, será 128 píxeles de ancho y de largo).

A partir del Mapa de Alturas tendremos en el mundo 3D una malla poligonal cuadrada de 128x128 = 16384 vértices, dividida en (128 - 1) * (128 - 1) = 16129 sectores cuadrados contiguos, como una rejilla. Pero dado que la unidad atómica que nos interesa para renderizar es el triángulo -y no el cuadrado-, dividimos cada sector en dos triángulos rectángulos que comparten la hipotenusa. El número total de triángulos será el doble que el de sectores, esto es, 32258. : )

Por último, se crea un Vertex Buffer y un Index Buffer, y las coordenadas X,Y y Z son calculadas para cada vértice e introducidas, con la ayuda del Index Buffer, en el Vertex Buffer.

Aquí es donde entra en juego al Mapa de Alturas. Como anteriormente se ha mencionado la coordenada Y del actual vértice (del aquel que hay que generar actualmente las coordenadas, vamos) se toma consultando el valor de color del píxel correspondiente en el Mapa de Alturas; en concreto, se consultará la componente de color G de dicho píxel. Las coordenadas X y Z se generan a partir de un valor de espaciado entre sectores (un valor fijo, actualmente es 16) y de unos Offset en X y en Z: el offset en X es la mitad del ancho del Mapa de Alturas, y el Z la mitad del alto (para el caso, ambos valdrán 64).

Así, si pensamos en una rejilla o matriz 2D a cuyas filas accediéramos mediantes en índice i y a cuyas columnas accediéramos mediante el índice j, las coordenadas X,Y,Z para cada vértice 3D [i][j] se calcularían así:

Vertice3DActual[i][j].X = (i * EspaciadoEntreSectores) - (Offset_X * EspaciadoEntreSectores)
Vertice3DActual[i][j].Y = MapaAlturas[i][j].Componente_G
Vertice3DActual[i][j].Z = (j * EspaciadoEntreSectores) - (Offset_Z * EspaciadoEntreSectores)

Tras haber calculado las coordenadas de todos lo vértices se calculan las normales para los tres vértices de cada triángulo, los índices para el Index Buffer, se sitúa una cámara libre dotada de un movimiento básico de 6 grados de libertad y... estos son los resultados.




La flecha roja indicaría a grandes rasgos la posición y orientación de la cámara. Efectivamente, la última escena es un poquito "marciana". ; )

Hasta otra. :P

viernes, 27 de enero de 2012

Hacia una Broad-Phase Collision Detection

music: INKUBUS SUKKUBUS -"Trinity"-

He terminado de hacer un refactoring bastante duro al Pong que implicaba cambiar la perspectiva que tenía hacia otra más intuitiva y útil. Desde el punto de vista del juego todo se ve igual, pero internamente todo el juego ha sufrido una rotación horizontal de 90º en sentido horario. Gracias a esto, es ahora el eje Z positivo el que apunta hacia dentro de la pantalla, y no el X como hasta ahora. Al estar empleando un sistema de coordenadas Left-handed, ahora el eje X positivo apunta hacia la derecha del jugador, y el eje Y verticalmente hacia arriba.

Con esto, puedo pasar ya a integrar una Broad-Phase Collision Detecion, algo que aún no poseía el sistema de Collision Detection & Response. Entendía que no tenía sentido hacer esto hasta adecuar la perspectiva del juego. Debo hablar de otro libro, pero ya lo haré más adelante.

Por otra parte y, tras haber estado colaborando durante todo el año 2011, he decidido dejar el proyecto Quimera. Guardaré un magnífico recuerdo de esta experiencia y de la gente que he conocido ahí, a quienes ya he explicado las razones de mi baja en el proyecto. ¡Seguid así, chicos!. :D

Hasta otra. :P

sábado, 10 de septiembre de 2011

Primer vídeo

music: SPACE AGE PLAYBOYS -"The Band Gets High"-



Pues... eso. Hasta otra. :P

viernes, 9 de septiembre de 2011

El problema "curioso" (y su resolución)

music: PJ HARVEY -"This Is Love"-

Durante las últimas semanas me he dedicado a solucionar el problema "curioso" del que ya hablaba en la entrada del día 12/05/2011.

Se hacía imprescindible poder dar una solución a esto si pretendía grabar el inplay del juego; como comentaba en la entrada anterior, era imposible hacer un vídeo que se viera decentemente debido al enorme lag que se producía.

Afortunadamente ya dejé hace tiempo las pistas necesarias por donde podía ponerme a empezar a trabajar para solventar esto... y los resultados han sido muy satisfactorios. : )

Al contrario de lo que pensaba el Componente LogicaPelota no era el problema... pero sí la puerta de entrada para ir, poco a poco, "buceando" en el código hasta llegar al núcleo principal de los problemas: los métodos IBounding::GetCollision e IBounding::GetPoints.

La optimización, realizada principalmente en dichos métodos, ha llevado a aumentar el framerate del juego (estando en modo Debug) hasta los... ¡¡¡147-150 FPS!!!. Teniendo en cuenta que, en estas mismas condiciones, las medidas marcaban 46-48 FPS ( hace tiempo hice algunas pequeñas optimizaciones que lo subieron hasta ahí desde la situación de (12/05/2011) ) el empuje ha sido espectacular. : )

Resumo las modificaciones que se han realizado para llevar a cabo la hazaña:

a) Dejar de devolver por valor un std::vector. Una de las cosas que he aprendido gracias a Quimera Engine es que, cuando devuelves por valor el resultado de un método, está sucediendo lo mismo que con los parámetros de entrada por valor: se está creando UNA COPIA del contenedor que alberga el resultado. Si dicho resultado es un std::vector, se tiene que crear UNA COPIA del vector... con lo que se estaba gastando inútilmente un montón de tiempo en cada iteración de GetPoints. Solución: poner un std::vector como un parámetro de salida en GetPoints, y por referencia.

b) Por lo que he podido ver, llamar al método std::vector.clear() en cada iteración de un método puede ser algo ASESINO. Hay que evitar a toda costa llamar a std::vector.clear() en un lugar que tiene que ejecutarse en cada iteración del juego, y con la mayor rapidez posible.

c) Asimismo, realizar múltiples llamadas a std::vector.push_back(elemento) en una misma iteración de un método (en concreto, unas 94 veces), también puede llegar a ser ASESINO. Hay que evitar realizar tantas llamadas a push_back en un lugar que tiene que ejecutarse en cada iteración del juego, y con la mayor rapidez posible.

d) La mezcla de b) y c) había dado un cóctel particularmente letal para el rendimiento. La solución ha sido sencilla:

d.1) Se fija un límite máximo de tamaño para los std::vector (en concreto, para los std::vector de puntos 3D) y se declaran ya con dicho tamaño. Así, ya no es necesario emplear ni clear para vaciarlos en cada iteración, ni push_back para añadir un elemento. Pueden ser los elementos introducidos directamente como si fueran arrays.

d.2) Al tener tamaño fijo ya no es posible emplear el método std::vector.size() para conocer cuántos elementos posee el vector. Por lo que se devolverá también, como parámetro de salida, una variable unsigned int que contendrá la cantidad de elementos que poseerá el vector. Dicho valor podrá ser empleado como centinela para recorrer el vector en un bucle for.

e) Por si fuera poco, había un fallo tonto en IBounding::Draw que estaba haciendo consumir inútilmente un montón de tiempo, y que ya ha sido subsanado.

Aparte... ciertos trucos de más bajo nivel, principalmente declaración de algunos grupos de variables locales a un método como static, que han hecho ganar algunos frames más... pero el gran avance en el rendimiento del juego ha sido gracias a lo explicado anteriormente.

La solución final es terminar de plantear e implementar el sistema de detección y respuesta de colisiones de una forma distinta a la actual, pero por el momento la optimización realizada ha sido suficiente. Al fín se ha podido grabar un vídeo. Ya veremos si continuo optimizando más o me voy a hacer otras cosas más urgentes (como introducir audio, por ejemplo).

Hasta otra. :P

sábado, 20 de agosto de 2011

Reduciendo iteraciones

music: AEROSMITH -"Beyond Beautiful"- / -"She's On Fire"-

Partamos de la siguiente base: el bloque de código más rápido es aquel que nunca se ejecuta. Con esta premisa es evidente que nos interesa lograr, de una forma u otra, reducir la cantidad de veces que ciertos bloques de código se ejecutan para mejorar el rendimiento, si ello es posible. Independientemente de lo mejor o peor implementados (y optimizados) que estén dichos bloques.

Una de las razones por las que no he podido llegar a mucho en términos de optimización es porque concebía la optimización exclusivamente como la "aceleración" de la ejecución de ciertas partes de código particularmente costosas computacionalmente, concentrándome demasiado en este enfoque. No se trata de olvidar esto ni mucho menos, sino de anteponer una política de reducción de iteraciones en ciertos puntos clave. Y en el caso de Super Pong, estos puntos clave son los Componentes.

Existen compomentes que no necesitan ser ejecutados todo el tiempo, en cada tick (la IA o la visión de la raqueta, por ejemplo). Desgraciadamente, el engine no estaba preparado para poder establecer una política así, en la que se establezca que un determinado componente no va a ejecutar su método Update en cada iteración, sino sólamente cada vez que haya transcurrido un determinado espacio de tiempo. Y eso es a lo que me he estado dedicando durante los últimos días.

El punto clave de todo esto es la clase IComponent, de la cual heredan TODAS las demás clases componente. El siguiente resúmen explica los puntos más importantes:

AÑADIDOS en IComponent:

Atributo m_fElapsedTime_Max: El Elapsed Time Establecido para el compoente (en ms, pero expresado en segundos). Representa el intervalo de tiempo que debe transcurrir para dar permiso al componente para ejecutar su método Update_Exec. Se asignará en TODOS los componentes un valor para m_fElapsedTime_Max en el método Init del componente. Asignando 0.0f vamos a hacer hacemos que el componente ejecute Update_Exece en cada tick.

Atributo m_fElapsedTime_Acum: El Elapsed Time (en ms, pero expresado en segundos) actualmente acumulado en el componente. Cuando se alcance el Elapsed Time Establecido, se ejecutará el método Update_Exec del componente.

Método virtual Update_Exec() vacío. Este será, como tal, el antiguo método Update. Todos los componentes heredarán este método vacío, sin código; se sobreescribirá en TODOS los componentes (salvo en aquellos que no tengan elementos que actualizar), y se ejecutará cuando se haya alcanzado el Elapsed Time Establecido para el componente.

MODIFICACIONES en IComponent:

IComponent::Update deja de ser un método virtual vació para pasar a ser no virtual y albergar el bloque de código que comprueba que, si se ha alcanzado el Elapsed Time establecido para ese componente, se ordene ejecutar sú método Update_Exec.

(** TO-DO: ya me acordaré algún día de cómo se tabulaba esto... **)

void IComponent::Update(const float& fDelta)
{
m_fElapsedTime_Acum += fDelta;
if ( m_fElapsedTime_Acum >= m_fElapsedTime_Max )
{
m_fElapsedTime_Acum = 0.0f;
this->Update_Exec(fDelta);
}
}

Este método es heredado por todos los componentes, y cuando la Entidad ordena a sus componentes que se actualicen (manda hacer Update a todos sus componentes, vamos), está llamando a este Update heredado de IComponent.

Tras esto, se puede comenzar a pensar un plan de "presupuestos" (Budget es la palabra) de tiempo para cada componente... y el engine está preparado. Por el momento, tengo establecido que los componentes Vista e IANPC ejecuten el método Update_Exec cada 500 ms (sus m_fElapsedTime_Max tendrán almacenado 0.5f, al estar expresado en segundos).

Hice una prueba loca y asigné 10 segundos a los componentes Vista e IANPC, y el framerate no aumentó respecto del caso anterior. Pero hice una segunda prueba loca asignando también 10 segundos al componente LógicaPelota... ¡¡¡y el framerate aumentó en casi VEINTE FRAMES!!!. El componente LogicaPelota, por lo tanto, está suponiendo un cuello de botella importante (como ya sospechaba). Habrá que optimizarlo acelerando su ejecución... y antes de ello reduciendo el número de iteraciones. ; )

Hasta otra. :P

viernes, 5 de agosto de 2011

Orientación de Entidad y Orientación Visual

music: VENDEMMIAN -"All Is Lost (All Is Gone)"-

Ayer logré otra de las cosas que el juego estaba pidiendo como agua de mayo: hacer que la pelota ruede sobre sí misma mientras se desplaza por el escenario. : )

Para ello se me ha ocurrido un pequeño truco: añadir a las entidades los Ángulos de Orientación Visual, que se unirían a los ángulos que las entidades ya poseían (pasándose éstos últimos a llamarse Ángulos de Orientación de Entidad). Y así, las entidades poseen ahora dos tipos de orientación:
  • DE ENTIDAD: Orientación lógica, real, de la entidad en el mundo.
  • VISUAL: Aquellas entidades visibles en el juego tendrán ahora una orientación de su representación visual en el mundo, la cual NO SIEMPRE coincidirá con la Orientación de Entidad; esto dependerá, en última instancia, del tipo de entidad.
Se asignará el valor de los Ángulos de Orientación de Entidad a los de Orientación Visual al comienzo de la ejecución del método Update de las entidades; de esta forma se asegura que, por defecto, el valor de ambos tipos de ángulos será el mismo al comienzo del método Update.

Y así, dejamos que sea alguno de los componentes de la Entidad quien, en última instancia, determine el valor definitivo de los Ángulos de Orientación Visual por el mero hecho de poseer ó no la Entidad algún componente que los calcule y asigne en la Entidad.

Por otro lado se modifica la llamada al método IRenderContext::DrawMesh (dentro del método Draw del componente Visualization3D), pasando ahora como ángulos de orientación del mesh los Ángulos de Orientación Visual.

Y aquí está la clave: que la pelota (es decir, la Entidad Esfera3D) que va a tener un componente que va a calcular definitivamente los Ángulos de Orientación Visual (y establecerlos en dicha Entidad): el componente LógicaPelota, el cual ya existía (gracias a él la pelota se desplaza y ordena realizar la Collision Detection/Response de ésta)... convenientemente modificado para ir calculando en cada Update unos Ángulos de Orientación Visual que hagan el efecto de rodar sobre sí misma y cambiar el sentido de giro cuando choque contra una raqueta. Simple. :P

Todo esto está muy bién, pero... ¿y por qué no cuelgo un vídeo?. Pues porque estoy desde ayer intentando hacer uno con este programa pero, por mucho tutorial y mucha configuración, me salen con un lag que parece que estuvieran reproduciéndose con un gnomo dando pedales. Así que si logro hacer uno decente ya lo colgaré (** EDIT: ir a entrada de 10/09/2011 **).

Hasta otra. :P

martes, 26 de julio de 2011

Carga Multietapa del Nivel y Movimientos de Cámara

music: ALICE IN CHAINS -"Sea Of Sorrow / Bleed The Freak"-

Estos últimos dos fines de semana han sido de lo más fructíferos. : )


Carga Multietapa del Nivel:

Por una parte, he perfeccionado el sistema de carga de un nivel:

-Sacando la orden de carga fuera del Bucle de Mensajes de la Ventana (la carga comenzará ahora habiendo salido de la iteración actual del Bucle de Mensajes).

-Haciendo que los diversos elementos del nivel se carguen en varias iteraciones o etapas de carga (actualmente, cuatro) para que el programa salga de la sección Update del Bucle Principal del juego y entre en la sección Render de dicho Bucle.

-Estableciendo un nuevo estado (M_LOADING_END), el cual será alcanzado al finalizar toda la carga del nivel; el Bucle Principal detectará que se ha alcanzado dicho estado y entonces se alcanzará el estado "Modo Juego" (M_JUEGO);

CONCLUSIÓN: ya no hay que establecer un tiempo concertado de sincronización como comentada en la anterior entrada, para esperar a que el flujo de programa cargue el nivel y abandone el Bucle de Mensajes de la Ventana... sino que los elementos se sincronizarán con el flujo de programa gracias a los estados M_LOADING y M_LOADING_END.


Movimientos de Cámara:

Y por otra... esto.

Lo había intentado antes varias veces, pero esta vez me dije que del pasado fin de semana no pasaba. Y así, tras un exhaustivo análisis del código y los apuntes, al fin ha logrado comprender el modo de funcionamiento de uno de los puntos negros del engine: la cámara.

Ello me ha permitido hacer los añadidos y modificaciones oportunas al código para poder hacer que, al desplazarse la raqueta, se produzca una leve inclinación horizontal y/o vertical de la escena; más pronunciada ó menos cuanto más se aleja la raqueta de su particular posición de comienzo, al inicio de la partida.

Pongo unos screenshots para mostrar cómo queda el invento a partir de ahora.











Hasta otra. :P

miércoles, 20 de julio de 2011

Loading screen

music: GARY NUMAN -"Cars"-

A veces, la mente funciona de un modo extraño. El caso es que el sábado pasado me levanté inspirado y me vino a la cabeza una idea para solucionar una de las cosas que me parecían más feas del juego.

Es algo que sólo sucedía al comienzo de la primera partida. Cuando seleccionas Jugar, mientras se van creando los elementos del nivel (el mundo, las entidades, los componentes asociados a dichas entidades, etc) se producía una especie de disincronía que hacía que, cuando aparecía la imagen del juego, la pelota ya hubiera avanzado un buen trecho. Resultado: la raqueta controlada por el ordenador no podía alcanzarla y se producía un tanto seguro.

Para solucionarlo, he añadido al motor un nuevo estado de "carga de datos" (M_LOADING) que implica que está cargando datos y, durante este estado, el fDelta que se envía al método principal Update de la aplicación es cero. Al seleccionar la opción Jugar pasamos directamente a este estado y, una vez se hayan cargado los datos y haya pasado un determinado tiempo concertado de sincronización, se pasa automáticamente a "Modo Juego" (M_JUEGO).

Mientras se cargan los datos aparece una pantalla de carga; este es el resultado:


¿Funciona?. Pues sí, funciona. Al entrar en "Modo Juego" todo queda sincronizado y la pelota permanece en el centro de la acción, tal y como puede verse en la captura. ¡¡¡Al fin!!!. :D

Pues nada, veamos si la Musa de la Inspiración me visita en otras ocasiones, ahora que llevaba tiempo dándole un respiro a esto. Si hasta estoy pensando en borrar lo que puse el día 1 y todo. XD

Hasta otra. :P

viernes, 1 de julio de 2011

Punto y aparte

music: COPTIC RAIN playing "Painted Bird [DB Mix]"-

No, no ha abandonado aún este mundo, ni me habían secuestrado. Desde mediados de enero he entrado a formar parte del Grupo de Programación Kinesis, y me he centrado de lleno en el proyecto que se está llevando a cabo, Quimera Engine.

Con lo cual Super Pong ha quedado parado de nuevo. Insisto en que no es un punto y final, sino un punto y aparte. Al estar inmiscuído en Quimera estoy necesariamente dándome de bruces con conceptos que había tratado de abordar en el pasado, sin demasiado éxito alno tener una motivación que superar. Por ejemplo, Cuaterniones y su utilización como un método alternativo a matrices y para efectuar rotaciones en 3D. Cosas que ahora comienzo, al fin, a comprender. Eso sí, los Cuaterniones han traído consigo a los Cuaterniones Duales y ha sido como "¡Venga, va...!". XD

Aunque no haga mucho ruido... por aquí sigo.

Hasta otra. :P

domingo, 9 de enero de 2011

Un poco de todo

music: THE GONE JACKALS -"Legacy"- / -"Trapped"-

Sin novedad en el frente, o mejor... con multitud de pequeñas novedades. Tras el prototipo de dos pantallas he hecho un poco de aquí y otro de allá, centrándome en aumentar el rendimiento general del juego. Al fin puede decirse que funciona a un rendimiento aceptable tanto en Modo Ventana como en Fullscreen.

Por cierto, Las opciones de Menú ya pueden ser seleccionadas, por cierto, a través del ratón. En un futuro incorporaré la posibilidad de manejar el juego a través de un pad (como se indica en el Documento de Especificación de Requisitos). ; )

A la "espera" de estudiar más la sintáctica de UML y ver alguna herramienta de modelado como Rational Rose o eDraw UML Diagram he decidido aparcar por un tiempo el tema de Patrones de Diseño, algo con lo que me interesa mucho ponerme, y centrarme en algo que también es crucial: la incorporación al engine de un Grafo de Escena.

También he estado estudiando algunos capítulos del libro Video Game Optimization pero aún es pronto para hacer una valoración. He varios libros que me llaman la atención pero, por el momento, vamos a aguantar la tentación...

Hasta otra. :P

miércoles, 27 de octubre de 2010

Con dos... monitores. :P

music: FIVE HORSE JOHNSON -"Ten Cent Dynamite"-

Es la 01:00 a.m. y en este momento la inspiración para escribir se ha ido ya a la cama, cosa que debería hacer yo también. He logrado terminar el prototipo en el que estaba trabajando en estos días para probar el funcionamiento del juego con dos monitores.


Básicamente lo he realizado así:
  • Se crean dos ventanas que se mostrarán En Modo Ventana (no fullscreen como hasta ahora) y MAXIMIZADAS, consiguiendo así que los elementos 2D queden automáticamente escalados al tamaño adecuado y posicionados en el lugar correcto.
  • Se captura la resolución del Escritorio (del primer monitor) y, dado que al final se especifica a las ventanas que se maximicen, se logra así tener una resolución independiente en cada monitor.
  • Sólo se crea un único Render Device (no hay replicación de vertex buffers ni doble carga de recursos).
  • Existirán dos Swap Chains. La primera es creada automáticamente al crear el Render Device, y la segunda se crea mediante el método GetAdditionalSwapChain del Render Device. A cada una de ellas se le especificará un Render Target distinto.
  • Se ha creado una cámara adicional que sigue a la raqueta controlada por el ordenador, y cuya escena es mandada a pintar a la segunda Swap Chain.
  • Hay dos pasadas de Render, una para cada Adaptador.

Como prototipo que es no he prestado mucha atención a la calidad de la implementación y ciertos detalles son realmente mejorables. En especial me preocupa el hecho de abandonar el modo fullscreen y el hecho de llamar a los métodos BeginScene / EndScene dos veces, pues es un hecho que va bastante lento. Si a alguien se le ocurre o sabe una forma mejor de hacerlo que lo comente por aquí, soy todo ojos. ; )

Hasta otra. :P

lunes, 11 de octubre de 2010

GAMEFEST 2010

music: COMBICHRIST mixing -"100%"-


El Documento de Especificación de Requisitos del Pong "sigue su cauce normal", así que cambio un rato de tema: hablemos de Gamefest 2010.

La verdad es que ha sido todo un éxito en lo que a asistencia de público se refiere y, al contrario que el supuestamente fallido S2e de 2003, este seguro que repite el año que viene.

Se respiraba en el ambiente un aire de profesionalidad, y mucho jolgorio; principalmente, yo me encontré a caballo entre el stand que Gamelab había instalado, asistiendo a las ponencias, y el montado por RetroMadrid. Por cierto... ¡al fin le eché mano a la recreativa Space Invaders de la gente de AUMAP!.


Y por el camino: volver a encontrarme con gente del sector del videojuego, y conocer alguna que otra persona nueva.

También pude ver (aunque no probar) la versión de Sniper Elite que han hecho para Wii, rifle de francotirador incluído.


Preguntas y respuestas rápidas:

¿Espectacularidad?. A raudales.

¿Novedades?. Aunque escuché algunos comentarios referentes a la escasez de novedades en el evento, no me pareció así; incluso cosas que no me esperaba ver, como la segunda parte de The Conduit y Dead Space.

¿Escena clásica?. Me equivocaba de todas, todas, creyendo que poco debía faltar ya por ver, y la exposición del stand de RetroMadrid me mostró de bruces dicha realidad.

Asimismo, espero que puedan verse el año que viene, en el stand de RetroWorks, los remakes de La Abadía del Crimen y Livingstone Supongo.

¿Kinect/Move?. Anécdotas curiosas, pero no sé si tendrán cabida en la clase de videojuegos que me interesan; supongo que tendré que echarle más imaginación al asunto...

¿S. Balmer?. No lo , no procede. :P

¿El Gamelab stand?. Bien, aunque sería deseable que en la siguiente edición de Gamefest pudiera situarse en un lugar más alejado, o poner mamparas aislantes de ruido, o algo por el estilo... pues el barullo del resto del evento dificultaba mucho la actividad del stand; aparte de la dificultad para seguir con atención las ponencias los micros se acoplaban mucho. Mención especial a la ponencia de motores gráficos del domingo, pero aquellos que teníamos interés en ella... ¡¡¡queríamos MÁÁÁÁÁÁÁÁÁÁS!!!.

¿Castlevania: Lords of Shadow?. Bien, muy chulo; si no le ponen una protección más estúpida de lo normal cuando salga en PC (como sucedió, por ejemplo, con la versión PC de Assasin's Creed II) seguro que me lo compro.

¿Freaks disfrazados?. Los justos y necesarios para darle la vida y el color necesarios que un evento así necesita, sin que se vuelva una parodia carnavalesca.

Y como colofón final, tras una divertida historia... ¡¡¡Runaway en pantalla grande!!!.

Hasta otra. :P

miércoles, 25 de agosto de 2010

Objetos Producer-per-Platform

music: THE PRETTY RECKLESS -"Make Me Wanna Die"-

Durante todo este tiempo que he estado sin actualizar he estado realizando unos cambios a nivel de estructura interna del engine e incoporádole algunas cosillas. Una de ellas es la inclusión de una nueva clase de la que heredarán sucesivas clases hijas con las que se crearán un nuevo tipo de objetos: los Producer-per-Platform.

Un Producer-per-Platform es un objeto servidor, encargado de crear y entregar al objeto cliente objetos dependientes de plataforma. Entiéndase por plataforma una parte del sistema o el engine, normalmente situada en lo que llamaríamos "bajo nivel", que puede variar según nuestras preferencias o disponibilidad (S.O., Bibliotecas de creación de ventanas, de audio, gráficas, etc.). Según la clase a la que pertenezca el objeto Producer-per-Platform, éste creará unos u otros objetos "Product" (ventanas, render devices, audio devices, etc.). Y la clase concreta del Product que creará se decidirá, por directivas de preprocesador, en tiempo de compilación.

Debe aclararse, por tanto, que los objetos Producer-per-Platform (desde ahora, Producer) no son los dependientes de plataforma, sino los objetos product que crearán (desde ahora, Product).

La relación entre el Producer y el objeto cliente (desde ahora, Cliente) será de USO, y normalmente adoptará la siguiente forma:

0) El Cliente poseerá, como atributo privado, un puntero polimórfico de la clase de Product que el Producer debe crear.

1) La generación del Product se producirá dentro de un método del Cliente; en la implementación de dicho método el cliente poseerá, como objeto estático local, el Producer necesario.

2) El producer creará el product dependiendo de la directiva de precompilador actualmente establecida para ese Product (estará establecida en la propia implementación del Producer) y entregará el Product en el puntero polimórfico del cliente como valor de retorno.

3) La implementación del método finalizará, y el Producer quedará destruído. El Cliente tiene comunicación directa con el Product a través del interfaz de éste último.

4) La responsabilidad de ordenar la inicialización del estado del Product recae en el Cliente; éste será quien llamará al método Init del Product.

5) Asimismo, la responsabilidad de finalizar la vida del Product recae también en el cliente; éste será el encargado de destruir el Product, liberando la memoria que ocupaba.

Como puede intuirse un Producer NO ES UN MANAGER; es un concepto mucho más sencillo que el de un objeto Manager que carga recursos en memoria, lleva la cuenta de la cantidad de instancias (referencias) de recurso que hay empleadas en el sistema y, cuando el número de referencias de un determinado recurso llega a 0, elimina realmente dicho recurso de la memoria.

a) El Producer surge de la necesidad de crear objetos (Product) dependientes de plataforma. SOLAMENTE en este contexto tiene sentido el Producer. De saber a ciencia cierta que va a emplearse únicamente una plataforma el Cliente generaría el Product de forma directa, sin Producer.

b) Gracias a los Producer tendremos un mayor control sobre el engine sabiendo dónde hay establecidas directivas de preprocesador que determinan compilación para una u otra plataforma: en los módulos del los Producer.

He creado ya un Producer de ventanas que devuelve al Cliente (que en este caso es el Objeto Aplicación) una ventana creada mediante la biblioteca Win32. Ahora podría crearse una clase ventana creada mediante, por ejemplo, la biblioteca GLUT y el mismo Producer de ventanas creará y devolverá a la Aplicación una ventana creada mediante GLUT con sólo cambiar la directiva de precompilación en el Producer de ventanas (de crear ventanas Win32 a crear ventanas GLUT).

El siguiente paso en el que emplear esta técnica es en el render device. A ello voy.

Hasta otra. :P

miércoles, 12 de mayo de 2010

Super Pong y el Modo Debug

music: WENDY O'WILLIAMS singing -"It's My Life"- / KISS -"Rock and Roll Hell"-

De vuelta al trabajo. Ya estoy dándole duro otra vez al Super Pong; de ahora en adelante voy a intentar actualizar más a menudo, como muy tarde cada dos semanas; aunque será como todo, dependerá de los progresos y cómo me cunda el asunto. : )

Durante la semana anterior he estado arreglando algunas cosas y haciendo que la información de los AABBs quede guarde como un centro y unos "radios" o Halfwidths en X,Y y Z; de esta manera, se guarda la mitad de la longitud (y no la longitud entera) en X,Y y Z del AABB, lo que ahorra tener que dividir entre 2 (o multiplicar por 0.5) en un montón de sitios dentro del juego. : )

He estado marcando objetivos y, entre ellos, está el hecho de crear algún tipo de sombra o proyección de la pelota contra las paredes del recinto, para que el jugador pueda hacerse una mejor idea de lo cerca que está la pelota de la raqueta suya. También voy a meterle algo de musica, aunque aún no he logrado crear el efecto cross-fading que me había planteado.

Sin embargo, sigo arrastrando todavía un problema "curioso": la diferencia brutal de rendimiento entre crear un ejecutable en Release y ejecutarlo separadamente del entorno de programación en Modo Pantalla Completa... y ejecutar el juego en Debug, dentro del entorno de programación(sea en Pantalla Completa ó en modo Ventana). Sigue sucediendo lo mismo: en modo Debug el rendimiento SE VA A PIQUE. Tal es la caida que no se puede llevar una partida con normalidad, lo cual hace muy difícil la depuración del código.
No sería tan importante si, estando en Debug, mantuviera un rendimiento aceptable en Pantalla Completa pues el asunto se resolvería mediante dos monitores (como en el trabajo: uno para tener a la vista el código, otro para ver la ejecución del juego). Pero no, es algo que he estado probando este fin de semana y se ralentiza igual.

El cuello de botella parece producirse en los componentes IA_NPC y LogicaPelota, posiblemente por las llamadas al componente Colision3D; es por ello que deseo modificar el sistema de Collision Detection and Response y hacer algo que sea más eficiente (aparte: solucionar los eventuales problemas con el tunnelling).

En fín, iré compaginando esto con las otras cosas que quiero hacer y veamos en qué acaba el asunto en las próximas semanas. Esta imagen lleva congelada demasiado tiempo.


Hasta otra. :P

jueves, 8 de abril de 2010

Edición Española de RUNAWAY: A Twist of Fate



music: JMM -"My Dear Tula"- / HIS HAIRCUT -"Cultural"-

El pasado 25 de marzo salío a la venta la Edición Española de Runaway: A Twist of Fate, ¡¡¡al fin!!!.

Espero que aquellos que lo jugueis os guste. Los que hemos participado en la creación de este juego hemos dado lo mejor de nosotros para que ello sea así. : )

Por mi parte ya he conseguido ambas ediciones -single y trilogía- y dejo por aquí algunas fotillos que les he hecho, junto con la camiseta de promoción, como hice con la versión alemana; la versión francesa es practicamente igual a la versión single española.

Me gustaría ver las Ediciones Checa y China; la primera, por si tiene un diseño de caja tan... digamos "espectacular"... como en las entregas anteriores de la saga. Y la China... porque tiene que ser el despiporre escuchar a los personajes en chino -no sé si mandarín ó cantonés-. Pero aún no sé nada de dichas versiones.

Y de la edición en habla inglesa... bueno, si nos fiamos de Gamespot parece ser que se pondrá a la venta en mayo. ¡En fín, a esperar un poquito más, jeje...!.

Hasta otra. :P

miércoles, 17 de febrero de 2010

Physics, Collision Response and Runaway

music: AMERICAN DOG -"Dog Will Hunt"-

Veamos... estas últimas semanas han sido muy productivas a nivel de trabajo. He estado analizando el funcionamiento de la demo de los Domuts, especialmente el sistema de detección de colisiones, y la verdad es que me he enterado de bastantes cosas; pero tocar la parte relacionada con la física y el sistema de collision response aún me he parecido delicado con lo que, a riesgo de perderme sin entender ni papa, me he metido primeramente a ver unos artículos sobre Rigid Body Dynamics escritos por C. Hecker (link al website personal) que me están resultando muy, muy útiles y didácticos. Este señor ha logrado resumir en cuatro artículos... diría que entre 6 y 7 temas de física (Cinemática y Dinámica) de un libro de Bachillerato; y con la ayuda de las demos que acompaña, puedes ver perfectamente donde y cómo se emplea cada elemento, cada fórmula... en definitiva, cómo encaja todo. Hoy he terminado de ver los relacionados con 2D y he empezado a ver el de 3D (¡menos mal que avisa que la mayoría de fórmulas de cinemática ya vistas valen también para 3D, jejejeje...!). ¡¡¡RECOMENDADOS!!!.

Respecto de Runaway: A Twist of Fate... he estado bastante tiempo sin comentar nada porque, aparte de estar muy ocupado con estos y otros asuntos, era un tema que estaba copando toda la atención del blog, y tampoco quería eso. Me hice este blog basicamente como un "Dev Blog", para ir comentando mis progresos en programación, en mis proyectos personales, señalar artículos y cosas interesantes relacionadas con el mundo de la programación de videojuegos que fuera utilizando, recomendar libros del tema, etc. Por eso he dejado al Runaway más de lado, aunque han ido apareciendo noticias importantes sobre él (tan importantes como que será publicado en España el próximo 25 de marzo); podeis echar un vistazo a la página de Runaway: A Twist of Fate en español y también a la nueva página de Pendulo Studios, pero vamos, si habeis estado atentos seguro que todo esto ya lo habeis visto.

Hasta otra. :P

miércoles, 25 de noviembre de 2009

Edición alemana de RUNAWAY: A Twist of Fate


music: VISION DIVINE -"Fools Garden / The Fall Of Reason"-

Esta mañana he ido a Correos a recoger mi ejemplar de Runaway: A Twist of Fate (Edición Alemana para PC), que por fín ha llegado.

(**ORIGINAL**)
Sobre qué contiene la Edición Especial "Gina Forever"... no sé nada por el momento. El viernes estuve en la Escuela Oficial de Idiomas, donde una de las profesoras de alemán me tradujo algunas de las noticias, y ayer he estado en el que era mi antiguo instituto, donde uno de mis antiguos profesores de inglés (el cual también sabe alemán) me ha traducido otro tanto. Agradezco enormemente la colaboración de ambos. : ) En cualquier caso, no parecía que ni en las noticias de la página oficial alemana del juego ni en el dossier del juego que puedes descargarte desde allí digan nada al respecto. Habrá que seguir esperando información (aunque sin dormirse en los laureles, pues son sólo 300 copias).(**FIN ORIGINAL**)

(**EDIT: Mirar Edit de entrada anterior **)

Y nada más. Mañana pediré la versión francesa, que es cuando se pone a la venta (aún no sé si la edición normal ó la que contiene los tres juegos, veremos, veremos...). ; )

Asímismo, a través de las plataformas de descarga de pago Metaboli y Games Planet se pondrá también al alcance de todos (pero sinceramente prefiero el formato Caja + Manual + CD/DVD). :P