martes, 1 de abril de 2014

Elementos en pantalla ACS - ZDoom, Parte 3: Práctica y tutorial - Indicador HUD parpadeante

Bien, llegó en momento de poner en práctica lo que había explicado en las dos entradas anteriores.

Si en general los comandos claves se han explicado en la primer parte de éste artículo, hoy incluiremos otros específicos que no se han visto en aquella lista.


Por supuesto, cada quien tiene su forma de realizar un script, de ordenarlo y, más aún, de explicar como realizarlo.

En mi caso, al no haber recibido ningún tipo de formación o experiencia en el área de programación, desarrollé un estilo un tanto intrincado, pero de cierta manera intuitivo.

Paso a explicar, entonces, como lograr un indicador con un efecto parpadeante sencillo, ajustable y vistoso. Cabe aclarar que éste tutorial incluye un wad a modo de ejemplo, para que puedan observar el funcionamiento en tiempo y forma y, en caso de ser necesario, aprovechar los iconos de ahí para realizar sus prácticas.

Aclaración muy importante: Debido a que la inclusión de la función ALPHA en HudMessage es propia de la versión 2.7.1 de ZDoom, el tutorial subsecuente solo funcionará con dicha versión o versiones posteriores del port mencionado.

En caso de introducir la función de parpadeo suavizado que se explica a continuación en alguno de tus proyectos, recuerda aclarar que no funcionará en versiones anteriores a ZDoom 2.7.1.


Apéndice:

Inclusión de gráficos.
Scripting (Paso 1: Imprimir un elemento HUD en pantalla, de manera correctamente posicionada).
Scripting (Paso 2: Aplicar un efecto de intermitencia a la imagen).
Scripting (Paso 3: Generar las condiciones por las cuales se efectúa la ejecución del script con el indicador intermitente).

                                          

Inclusión de los gráficos.

Supongamos que queremos un indicador que avise al jugador que se está quedando sin munición o sin vida. Lógicamente lo primero que necesitamos son los gráficos que vamos a utilizar como iconos. En éste caso, hice muy rápidamente éstos dos, que son los que voy a utilizar a lo largo del tutorial y los que están dentro del WAD de ejemplo que adjunto.

Indicador de baja cantidad de munición: 

Indicador de baja salud:

Una vez que tenemos los gráficos que queremos usar, debemos incluirlos dentro del wad. Si se está utilizando el formato PK3, es posible importarlo tanto en el directorio TEXTURES como en el directorio GRAPHICS, aunque el orden es un factor muy importante a la hora de encontrar las cosas en caso de que necesitemos editar algo, por lo que es recomendable incluir todos los iconos y elementos del hud dentro de la carpeta GRAPHICS, ya que ésta se encuentra orientada a elementos del HUD. El formato tampoco influye, aunque para iconos con transparencia vamos a requerir utilizar una imagen con formato PNG.

Una vez que tenemos los gráficos preparados, solo nos queda comenzar a desarrollar el script.



                                          

Scripting.

Como dije al principio, cada quién enfoca un futuro script desde la perspectiva que más cómoda le resulte. En éste caso, voy a explicarlo en el siguiente orden:

Paso 1) Imprimir una imagen en pantalla de manera posicionada.

Paso 2) Lograr un efecto de intermitencia suavizada (Fade in-out).

Paso 3) Programar las condiciones bajo las cuales ese indicador va a aparecer con el efecto anteriormente mencionado.

Hay que tener en cuenta que no hay una sola manera de lograr algo, por lo que voy a explicar dos métodos diferentes: Uno para la salud y otro para la munición.

Por otro lado, voy a aclarar que, por una cuestión de orden y agilidad, el script que active/desactive los indicadores va a estar separado de los scripts que muestran y le dan el efecto apropiado a cada icono (Uno por imagen, lógicamente).


Paso 1): Imprimir un elemento HUD en pantalla, de manera correctamente posicionada.

Anteriormente expliqué como imprimir una imagen en pantalla con el comando HudMessage. Ahora voy a explicar como posicionar dicha imagen en nuestra pantalla.

Algo que tenemos que tener en cuenta al modificar el HUD es que no todos los jugadores usan la misma resolución. Tampoco es factible forzar a que quienes jueguen nuestro mod lo hagan a una resolución determinada, puesto que éste factor en especial determina el rendimiento que cada jugador puede permitirse.

Entonces, lo que debemos hacer con algo tan importante, es adecuar nuestro trabajo a la resolución de todos los potenciales jugadores. Ésto es posible y de una manera muy sencilla: Obteniendo la resolución del jugador y, mediante unas pequeñas operaciones matemáticas dignas de un escolar primario, posicionar el o los elementos respecto del ancho y alto de la pantalla en cuestión.

Pero primero, antes de llegar a ese punto, debemos saber como se comporta el HudMessage:

Tanto con posiciones hechas a porcentaje, como posiciones hechas en pixeles (Que se requiere luego de configurar el tamaño del HUD con el comando SetHudSize) se manejan del mismo punto de referencia: La esquina superior izquierda.

Ésto significa que darle un valor de 0.0 a un elemento en HudMessage equivale a dejarlo exactamente arriba a la izquierda. 

Si hablamos de posicionarlo mediante porcentaje, 0.5 naturalmente nos va a centrar el elemento, ya sea vertical u horizontalmente, dependiendo de en donde se haya configurado ese valor. Ésto es porque, el valor en porcentajes no se mide en enteros si no en números decimales. Un valor de 1.0, entonces, equivale a 100%, es decir, al tope de la pantalla. Un valor de 0.5 equivale a 50%, es decir, a la mitad de la pantalla. Y así con el resto de los valores.

Si bien ésto es relativo a la resolución del jugador, no sirve para nuestro propósito, puesto que el porcentaje que un elemento está en relación a la pantalla no es relativo al espacio disponible ni al tamaño del elemento.

Entonces, si bien en ambas resoluciones se está mostrando a un, por ejemplo, 75% de la pantalla, se va a ver diferente. Algo como ésto:

Elemento en una resolución de 640x384.

Elemento en una resolución de 1280x800.

Si bien como se puede observar en las imágenes el elemento está virtualmente en la misma posición, tanto el resto de elementos a su alrededor como el espacio disponible cambió entre una resolución y otra, aún con los mismos valores.

Por ésta razón, es inviable realizar nuestro HUD con éste tipo de posicionado. Entonces lo que vamos a hacer es manejar la posición de nuestros elementos por medio de pixeles. Pero a su vez hay que hacerlo de una manera particular.

Como especifiqué anteriormente, hay que tomar en cuenta que no todos los jugadores van a utilizar la misma resolución. Por ésta razón, tampoco podemos excedernos a la hora de configurar nuestro HUD de ésta manera.

Teniendo en cuenta que la resolución más baja del juego (O bien, del port) es de 320x200, es lógico que cualquier valor que asignemos debe estar por debajo de esos parámetros (320 pixeles horizontalmente, 200 pixeles verticalmente). Si superamos éste valor, jugadores que pueden utilizar ésta resolución no van a ver nuestro elemento, o lo van a ver recortado. De igual manera, si especificamos valores verticales mayores a, por ejemplo, 100 pixeles, el icono que un desarrollador, utilizando una resolución de 800x600, ve a una sexta parte desde arriba hacia abajo, quien esté utilizando una resolución de 320x200 lo va a notar a la mitad. Similar al ejemplo que mostré más arriba con los porcentajes y las diferentes resoluciones.

Entonces ¿Como nos vamos a manejar? Sencillo: De manera inversa. Le vamos a asignar al hud el mismo valor que tiene la resolución del jugador, y a ese valor le vamos a restar el espacio que existirá entre el borde de la pantalla y nuestro icono.

Por supuesto que, si nuestro icono está en la parte superior izquierda, no va a haber que restar, si no que deberemos sumar, ya que nos estamos guiando por el punto fijo principal (0.0).

Pasemos entonces a la práctica de todo ésto:

Primero, como siempre, declaramos la librería que utilizaremos:

#include "zcommon.acs"

Luego creamos un script, yo por mi parte lo voy a llamar "Health_Alert", pero cada quién le puede dar el nombre que se le ocurra. Como primero tenemos que ir probando valores hasta tener una posición que nos guste, le vamos a indicar que se abra junto con la apertura del mapa.

Script "Health_Alert" ENTER

{

}

Luego, dentro del script, debemos primero que nada declarar las variables que vamos a utilizar, que son 3:

1) La que utilizaremos para manejarnos horizontalmente (Posición X), y le asignamos el valor que tenga la resolución horizontal, de la manera en la que lo expliqué anteriormente. Yo la llamaré pos_x.

2) La que utilizaremos para manejarnos verticalmente (Posición Y), y le asignamos el valor que tenga la resolución vertical, de la manera en la que lo expliqué anteriormente. Yo la llamaré pos_y.

3) La cantidad de transparencia que vamos a darle al icono. Ésto es MUY importante, puesto que no solo nos va a ahorrar lineas y lineas de código innecesario, si no que además nos permite tener un control dinámico de nuestro efecto. Yo la voy a llamar msg_alpha.

Script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

}

¿Recuerdan la cuestión de los números fijos y los números enteros? Bueno, como acá es muy importante manejar solo números fijos, y GetCVar nos entrega números enteros, debemos convertirlos sin alterar el valor que estamos recibiendo.

Ésto se hace de una manera muy simple, y es dividiendo el número entero como si fuera un número fijo.

Para cada tipo de número se utiliza un comando de división diferente. Para dividir números fijos se utiliza un comando llamado FixedDiv, que funciona así:

FixedDiv ( Numero a dividir, Numero por el que divide)

Entonces, para convertir el valor que recibimos anteriormente de la resolución, solo debemos escribir lo siguiente:

Script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

}

Como ya habíamos declarado las variables al principio, lo único que tenemos que hacer luego es redefinirlas, por lo que no es necesario agregar el INT al principio. Si no recuerdas ésto, puedes repasar la sección sobre variables en la primer parte del artículo.

Una vez que tenemos todo preparado, lo único que hace falta es buscar la posición adecuada. Para eso, vamos a hacer lo siguiente:

Script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - cantidad de pixeles que separaran el elemento del borde derecho;
pos_y = pos_y - cantidad de pixeles que separaran el elemento del borde inferior;

}

Hay que tener muy en claro dos cosas: 

1) Si el elemento se ubica en el costado izquierdo de la pantalla o en la parte superior, no es necesario ninguno de los pasos anteriores. Simplemente se declara la variable y se le da el valor en pixeles que se desee, siempre siguiendo la pauta del segundo punto:

2) Siempre que se le practica una suma o una resta a un numero fijo hay que utilizar decimales. Esto significa que si a pos_x se le quiere restar, por ejemplo, 15 pixeles, se debe hacer de la siguiente manera:

pos_x = pos_x - 15.0

De no hacer ésto, la operación va a incurrir en un error, con resultados inesperados en la posición involucrada.

Otra cosa a tener en cuenta  es que luego de utilizar SetHudSize, las coordenadas aún utilizan el decimal para guiarse el punto desde donde se manipula el elemento. Generalmente yo utilizo el decimal .2, puesto que éste manipula el elemento desde su vértice inferior derecho, y me genera más comodidad a la hora de posicionar elementos en la zona inferior derecha de la pantalla. Pero eso va de a acuerdo a gustos.

En caso de querer manejarse de la misma manera, hay dos formas de dejar el resultado con un decimal de .2:

Una nueva suma, antes o después del valor que vamos a configurar: 

Script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x + 0.2;

pos_x = pos_x - cantidad de pixeles que separaran el elemento del borde derecho;
pos_y = pos_y - cantidad de pixeles que separaran el elemento del borde inferior;

pos_y = pos_y + 0.2;

}

El ejemplo de arriba muestra que se puede hacer tanto antes como después de asignarle los píxeles que deben separar el elemento del borde (Solo por eso están separados entre sí), pero nunca antes de efectuar la conversión con FixedDiv.

La otra forma es hacerlo a la hora de configurar los pixeles que separan al elemento del borde, con un decimal de 8 (Voy a utilizar los valores que están incluidos en el ejemplo, para que sea más gráfico):

Script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

}

Por supuesto que, si el lector quiere utilizar un valor de 15, deberá escribir 15.8, si quiere utilizar un valor de 20, deberá escribir 20.8, y así con cualquier valor que escoja.

Por último, debemos indicarle el tamaño del HUD. Sin embargo ésto no lo haremos con variables, si no directamente con el comando GetCVar dentro del comando SetHudSize:

Script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);

}

Entonces, sin necesidad de crear variables de más, directamente le pedimos a SetHudSize que tome el valor desde la resolución del jugador, mediante el comando GetCVar.


De manera que nos debe quedar algo así:

script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);

SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);

}

De ésta manera, completamos el código para imprimir una imagen en pantalla. Aunque por supuesto, no va a suceder puesto que al principio le dimos un valor de 0.0 a msg_alpha, por lo que el mensaje es transparente. 
Para probar el éxito del anterior paso y para ir posicionando nuestro elemento, simplemente es necesario darle un valor de 1.0 a msg_alpha. Sin embargo, una vez que el elemento quede bien posicionado hay que acordarse de volver a configurar msg_alpha a 0.0, puesto que de otra manera hará interferencia con el efecto que le daremos a continuación.


Paso 2): Aplicar un efecto de intermitencia a la imagen.


Ahora lo que haremos será crear dos condicionales separados: Uno para el fade-in y otro para el fade-out. Ambos se ejecutaran solo hasta que el alpha del mensaje (O lo que es lo mismo, la variable msg_alpha) alcance determinado valor. Ésto nos va a dar la pauta de qué transparencia debe alcanzar el mensaje acorde a su estado.

Si va a aparecer hasta ser totalmente opaco o si va a tener una transparencia en su estado máximo de opacidad (Indicado en el primer while, que será el del fade-in) y si desaparecerá por completo o solo se atenuará hasta cierto punto (Indicado en el segundo while, que será el del fade-out).

Entonces, escribiremos el primer fade con la comparación de inferioridad, un valor máximo (Recuerden que 1.0 es 100%. Voy a utilizar el valor que está en el WAD de ejemplo), y movemos el comando HudMessage dentro de sus llaves:

script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
}

}

Ese while no se va a ejecutar, porque no tiene un Delay antes de cerrar su llave. Pero además, aunque lo tuviera, el mensaje no aparecería, porque el Delay en efecto se ejecutaría siempre, porque msg_alpha siempre será menor a 0.8, y eso es precisamente porque vale 0.0, y por esa razón tampoco lo vamos a ver.

Entonces tenemos que agregarle una suma. Ésto va a determinar la velocidad con la que la imagen aparece. Otra vez, voy a utilizar los valores del WAD de ejemplo: 0.05. Ésto significa que, mientras msg_alpha sea menor a 0.8 (Es decir, mientras tenga menos de 80% de opacidad), se le va a sumar 0.05 (5%) por cada vez que el while se repita. Y ésto se hace a una velocidad considerablemente rápida. 

También agregamos un Delay para que el while no crashee (Dé error).

Quedaría algo como ésto:

script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

}

Ahora nuestra imagen aparecerá, pero se quedará estática una vez que aparezca, entonces debemos hacer que desaparezca. Ésta es la parte más sencilla de todas: Solamente es necesario copiar el primer while, y pegarlo debajo.

script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}


while ( msg_alpha > 0.05 )

{
msg_alpha = msg_alpha - 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

}

Pero como éste es para que la imagen desaparezca, en vez de sumarle le vamos a restar, y en vez de que el while se ejecute mientras la imagen sea menos que 80% opaca (0.8), que se ejecute mientras la imagen sea más que 5% (0.05) opaca. 

Ahora, para que la secuencia se repita todo el tiempo, agregamos un Delay de 1 tic y un Restart, que es el comando que reinicia el script indefinidamente:

script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}


while ( msg_alpha > 0.05 )

{
msg_alpha = msg_alpha - 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay(1);
Restart;

}

Si queremos evitar que la imagen desaparezca inmediatamente después de aparecer, podemos agregar un Delay entre While y While, de aproximadamente 15 tics, de manera que la imagen una vez que aparezca se quede un tiempo determinado antes de desaparecer otra vez:


script "Health_Alert" ENTER

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay (15);


while ( msg_alpha > 0.05 )

{
msg_alpha = msg_alpha - 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay(1);
Restart;

}

Ahora bien, con el script anterior nuestro elemento va a aparecer y desaparecer frenéticamente. La razón de ser es muy sencilla: Restart es un comando que no funciona de manera correcta con un script tipo ENTER.

De todas maneras éste es un script especial que solo se ejecutará, recordemos, una vez que el jugador tenga menos de 25 puntos de vida. Entonces no hay necesidad de indicar un script tipo ENTER.

Lo que haremos entonces es cambiar el tipo de script a (Void). Una vez hecho ésto, vamos a copiar y pegar todo el script, y le vamos a cambiar el nombre a "Ammo_Alert" (Repito, éste nombre puede ser cualquiera, yo solo estoy utilizando los mismos del ejemplo que creé, para que la guía quede un poco más ilustrada).
También vamos a cambiar el ID del HudMessage dentro de "Ammo_Alert", para que se ambos iconos puedan ser mostrados simultáneamente.

Entonces hasta ahora, con los cambios nombrados anteriormente, debemos llevar ésto:

#include "zcommon.acs"

script "Health_Alert" (void)

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay (15);


while ( msg_alpha > 0.05 )

{
msg_alpha = msg_alpha - 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay(1);
Restart;

}

script "Ammo_Alert" (void)

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 2, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay (15);


while ( msg_alpha > 0.05 )

{
msg_alpha = msg_alpha - 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 2, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay(1);
Restart;

}

Por supuesto que aca faltan dos cosas: Hay que cambiar el tipo de fuente en el segundo script (Indicando el archivo que contiene el icono de munición) y programar un activador, puesto que ahora los scripts son tipo (void) y no se ejecutarán a menos que alguien lo llame. Y para eso seguimos con el paso 3.


Paso 3): Generar las condiciones por las cuales se efectúa la ejecución del script con el indicador intermitente.

¡Ya casi terminamos! El último paso para tener un indicador intermitente totalmente funcional en nuestro HUD.

Para eso, primero vamos a crear un nuevo script tipo ENTER, con cualquier nombre o número (Si es el primer script numerado, será el 1, de otra forma será el número que corresponda). Recomiendo escribir éste script al principio, por encima de los dos anteriores, puesto que de ésta manera es más fácil notar que es un script que se ejecuta al entrar al mapa.

Con el nombre de fuente cambiado y el nuevo script comenzado por encima de los otros dos, llevaríamos algo como ésto:


#include "zcommon.acs"

script 1 ENTER

{


}

script "Health_Alert" (void)

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay (15);


while ( msg_alpha > 0.05 )

{
msg_alpha = msg_alpha - 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay(1);
Restart;

}

script "Ammo_Alert" (void)

{

int pos_x = GetCvar("vid_defwidth");
int pos_y = GetCvar("vid_defheight");
int msg_alpha = 0.0;

pos_x = FixedDiv(pos_x,1);
pos_y = FixedDiv(pos_y,1);

pos_x = pos_x - 10.8;
pos_y = pos_y - 45.8;

SetHudSize (GetCvar("vid_defwidth"), GetCvar("vid_defheight"), 0);


while ( msg_alpha < 0.8 )

{
msg_alpha = msg_alpha + 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay (15);


while ( msg_alpha > 0.05 )

{
msg_alpha = msg_alpha - 0.05;
SetFont ("H_Alert");
HudMessage (s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, pos_x, pos_y, 0, msg_alpha);
Delay (1);
}

Delay(1);
Restart;

}


Ahora lo que tenemos que hacer es detectar la cantidad de vida que tiene el jugador. Para eso utilizamos un comando llamado "GetActorProperty" que, entre otras propiedades, nos dice la vida que tiene un Actor (En éste caso, el jugador).

Se utiliza de ésta manera:

GetActorProperty (TID, Propiedad);

Para obtener la vida de un actor se utiliza la propiedad denominada APROP_HEALTH. Sin embargo, para obtener ésta del jugador no utilizamos un Thing ID en el primer parámetro o argumento, si no que directamente lo escribiremos de ésta manera:

GetActorProperty (PlayerNumber(), APROP_HEALTH);

Nota: Personalmente tengo una especie de confusión respecto a como se comporta PlayerNumber() en puertos multiplayer, por lo que dejo advertido que éste ejemplo solo está totalmente comprobado en Singleplayer.

Lo que debemos hacer entonces es almacenar el resultado de ese comando en una variable, como ya está explicado en la segunda parte de éste artículo, y como lo hicimos más arriba con la resolución de video.

Entonces haremos lo siguiente: Escribiremos una variable con un nombre que nos quede cómodo (Yo la llamaré Health_Amount) y le asignamos el comando de la misma manera que con GetCVar, solo que sin comillas:

int Health_Amount = GetActorProperty (PlayerNumber(), APROP_HEALTH);

Ahora sencillamente le agregamos el condicional IF, y comprobamos si Health_Amount vale menos o igual a 25 (Como se explicó en la sección Condicionales, de la primer parte de la guía).
En caso de ser correcto, se ejecutará el comando "ACS_NamedExecute" que a su vez ejecuta, como ya se estarán imaginando, el script del icono intermitente llamado Health_Alert. Hasta ahora, deberíamos llevar ésto en el script 1:


Script 1 ENTER

{

int Health_Amount = GetActorProperty (PlayerNumber(), APROP_HEALTH);

if ( Health_Amount <= 25 )

{

ACS_NamedExecute ("Health_Alert", 1);

}

}

Por supuesto que éste script solo se ejecutará una vez, por lo que no generará una comprobación constante. Pero para eso sencillamente le agregamos un Delay y un Restart antes de completar el script. Todavía no debemos hacerlo, o si no lograremos que los comandos que se encuentren debajo del Restart no se ejecuten y la función no tenga lugar.

Ahora bien, si nosotros le agregamos el Delay y el Restart a ese Script 1 para hacer la prueba, veremos que en efecto, cuando tengamos 25 o menos puntos de vida, el icono empezará a parpadear. Pero cuando aumentamos de nuevo nuestra salud y salimos de la zona de "Peligro", el icono sigue parpadeando.

Ésto es porque el Script del icono ("Health_Alert") nunca se detendrá, porque recordemos que le ingresamos un Restart que lo reinicia una vez que llega al final. Entonces lo que debemos hacer es terminar el script.

Quizás pensaras que para evitar eso hay que agregar un simple ACS_NamedTerminate debajo de la llave que cierra el IF, pero ésto es erróneo, porque eso significaría que el Script se ejecuta y casi al instante se termina, sin dar lugar a nada. Para evitar ésto, lo que se hace es añadir un "ELSE", que es una extensión del IF, y traducido literalmente significaría:

Si ( se cumple ésta condición )

{

Se ejecuta ésto

}

Si no

{

Se ejecuta ésto

}

Entonces deberíamos ingresar el ACS_NamedTerminate entre las llaves de ELSE.

Pero eso no es todo, porque una vez que terminamos el Script, el icono va a mostrarse con el alpha que le quedó antes de terminar el script.

Entonces se hace algo muy simple: Justo debajo de ese ACS_NamedTerminate, dentro de las llaves del ELSE, se crea un nuevo HudMessage vacío con el ID del icono a borrar.

Entonces, el Script 1 quedaría algo así:

Script 1 ENTER

{

int Health_Amount = GetActorProperty (PlayerNumber(), APROP_HEALTH);

if ( Health_Amount <= 25 )

{

ACS_NamedExecute ("Health_Alert", 1);

}

else

{

ACS_NamedTerminate ("Health_Alert", 1);
HudMessage (s:""; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, 0.0, 0.0, 0, 0.0);

}

Con eso nuestro indicador de vida ya debería estar funcionando. Ahora pasemos al de munición.

En vez de utilizar el comando GetActorProperty, deberemos utilizar dos comandos diferentes, llamados CheckWeapon y CheckInventory.

El primero, como su nombre lo indica, chequea si tenemos seleccionada determinada arma. El segundo se fija si tenemos cierto ítem de inventorio, entre los que se encuentra la munición.

Entonces, con esos dos comandos, revisamos si el jugador tiene seleccionada el arma que corresponde con la munición que estamos comprobando, y si la munición es menor o igual a 25.

Ésto lo vamos a hacer directamente, sin variables, de la siguiente manera:

if ( CheckWeapon("SuperShotgun") && CheckInventory("Shell") <= 25 )

{

ACS_NamedExecute ("Ammo_Alert", 1);

}

else

{

ACS_NamedTerminate ("Ammo_Alert", 1);
HudMessage (s:""; HUDMSG_PLAIN | HUDMSG_ALPHA, 2, 0, 0.0, 0.0, 0, 0.0);

}

Como verán, arriba se utilizó el comando CheckWeapon directamente dentro del IF. También se combinaron ambos comandos. Ésto se logro mediante dos símbolos de & (And), que sirve para agregar dos o más condiciones a la comparación. 

CheckWeapon, al ser un comando que devuelve un resultado negativo o positivo (No tiene el arma seleccionada / Sí tiene el arma seleccionada) no requiere comparación, lo que en IF se traduce en que si la respuesta es positiva, lo que esté dentro de las llaves del IF se ejecutará.

Entonces, traduciendo literalmente lo de arriba, quedaría algo como:

si ( SuperShotgun está seleccionada y Hay perdigones (Shells) con una cantidad menor o igual a 25 )

{

Ejecutar ésto

}

Si no, de lo contrario

{

Ejecutar ésto

}

Si a CheckInventory("Shell") no le agregamos el <= 25, lo único que va a hacer IF es comprobar si tenemos munición, lo cual no tiene mucho sentido puesto que cuando no hay munición para una determinada arma ésta se deselecciona sola.

Al igual que &, existe un operador que nos sirve para agregar otra condición. Ésto significa que puede cumplirse tanto la primera como la segunda. Éste operador es la barra ( | ) y al igual que & se debe incluir dos veces.

Entonces, para checkear tanto la SSG y los perdigones como la Pistola y las balas para ésta, deberíamos poner algo como ésto:

if (CheckWeapon("SuperShotgun") && CheckInventory("Shell") <= 25 || CheckWeapon("Chaingun") && CheckInventory("Clip") <= 25 )

Y así con el resto de armas que tengamos en nuestro mod. También podemos asignarle valores diferentes a cada munición, de manera que el indicador puede aparecer cuando hay 25 o menos perdigones, pero cuando hay 50 o menos balas de pistola. Así podemos manejar valores individuales acorde a la cantidad de munición máxima que maneja cada arma.

El resto es lo mismo que con la salud, un ACS_NamedExecute con el nombre del indicador de munición en dentro de las llaves de IF y un ACS_NamedTerminate con el nombre del indicador de munición seguido de un HudMessage vacío con el ID del HudMessage en el script del indicador de munición.

Para finalizar, el Script 1 debería de quedar algo así:

Script 1 ENTER

{

int Health_Amount = GetActorProperty (PlayerNumber(), APROP_HEALTH);

if ( Health_Amount <= 25 )

{

ACS_NamedExecute ("Health_Alert", 1);

}

else

{

ACS_NamedTerminate ("Health_Alert", 1);
HudMessage (s:""; HUDMSG_PLAIN | HUDMSG_ALPHA, 1, 0, 0.0, 0.0, 0, 0.0);

}


if (CheckWeapon("SuperShotgun") && CheckInventory("Shell") <= 25 || CheckWeapon("Shotgun") && CheckInventory("Shell") <= 25 || CheckWeapon("Chaingun") && CheckInventory("Clip") <= 25 || CheckWeapon("RocketLauncher") && CheckInventory("RocketAmmo") <= 25 || CheckWeapon("PlasmaRifle") && CheckInventory("Cell") <= 25 || CheckWeapon("BFG9000") && CheckInventory("Cell") <= 25 )

{

ACS_NamedExecute ("Ammo_Alert", 1);

}

else

{

ACS_NamedTerminate ("Ammo_Alert", 1);
HudMessage (s:""; HUDMSG_PLAIN | HUDMSG_ALPHA, 2, 0, 0.0, 0.0, 0, 0.0);

}

Delay (1);
Restart;

}

Nótese que incluí todos los tipos de armas encontradas en el Doom II original.

                                                 

Ésta es la manera, entonces, de crear un elemento HUD personalizado, prolijo, vistoso y funcional. Sé que seguramente a muchos les habrá resultado muy pesado todo ésto (Si es que alguien se lo leyó todo), pero les puedo asegurar que, si les gusta tener total control creativo de sus mods, les va a servir mucho en el futuro.

Con éste tipo de cosas y con algo de imaginación se pueden llegar a lograr funciones innovadoras, que pueden llegar a tener un impacto notable en tu próximo proyecto.

De hecho, gracias a la práctica que estuve adquiriendo haciendo cosas como las que expliqué a lo largo de la guía he podido lograr funciones muy interesantes que se pueden esperar en la primer versión Singleplayer de HESK.

Aunque hablando desde un punto de vista meramente personal, todo lo vertido en ésta guía ha sido pura y exclusiva deducción propia. Nunca leí un tutorial de ACS ni supe para que funcionaban los comandos que hasta recién estuve explicando. Recién hace un par de semanas empecé a leer en la wiki de ZDoom los artículos individuales de cada comando en ACS que necesitaba para ajustar cierta función a mi gusto. Y estoy intentando adentrarme en ZDoom desde hace casi 6 años.

Por eso, el hecho de que hoy no sepan más que escribir un mensaje o crear una niebla en ACS, no significa que el día de mañana puedan llegar a hacer efectos interesantes, que se acomoden a sus preferencias y les permitan plasmar con plenitud el potencial creativo que puedan llegar a tener.

Espero que les haya servido de algo ésta guía. Voy a dejar el link con el WAD de ejemplo, para que puedan observar un ejemplo funcional y a su vez, si lo necesitan, extraer los gráficos para hacer sus propias pruebas.

Link de descarga: hud.zip (12,5kb)

Saludos.

No hay comentarios:

Publicar un comentario