Cómo codificar las barras HUD
Spanish (Español) translation by Elías Nicolás (you can also view the original English article)
Las barras HUD son elementos visuales en el juego que no pertenecen necesariamente al mundo del juego, pero son una indicación para el jugador de un período de tiempo durante el cual puede o no puede realizar una acción.
En juegos de rol, escenarios de batalla en línea para jugadores múltiples (como League of Legends o DOTA), o incluso juegos de estrategia en tiempo real, estos indicadores son a menudo críticos para el jugador.
En este tutorial, veremos cómo implementar esas barras de enfriamiento, cualquiera que sea su lenguaje de codificación o herramienta, confiando en el pseudocódigo y desglosando la mecánica utilizada. El tutorial no pretende mostrar la mejor o la única forma de implementar barras de enfriamiento, sino que simplemente analiza y muestra una forma práctica y práctica de hacerlo.
Todas las demostraciones se hicieron con la versión estable Construct 2 R168, y se pueden abrir y ejecutar en su versión gratuita. Se comenta todo el código fuente de los ejemplos.
Vamos a profundizarlo.



El mecánico básico de enfriamiento
Una barra de enfriamiento es principalmente una retroalimentación visual para la mecánica de enfriamiento, y la mecánica de enfriamiento es básicamente un temporizador. Considera una acción que realiza un personaje. En el momento en que se realiza la acción, se inicia un temporizador, y mientras este temporizador cuenta regresivamente, el personaje no puede realizar la acción nuevamente. Ese es el descanso.
Desde
la perspectiva del código, para evitar la acción que se realiza durante
el período de enfriamiento, una variable booleana se establece en
true cuando se realiza la acción. Al intentar realizar
su acción, el código verifica que el valor de la variable booleana sea
false, y no permite que la acción se realice de otro modo. Al final del
temporizador, la variable booleana vuelve a establecerse en false, permitiendo que la acción se realice nuevamente.
Considere el siguiente ejemplo como una ilustración de este concepto:
Una vez que hace clic en el botón Action, el personaje realiza una acción y el botón Action se desactiva. Durante el tiempo de reutilización, a intervalos regulares se muestra el tiempo restante en el HUD. Al
final del HUD, el personaje está listo para
realizar la acción de nuevo, por lo que el botón Action se vuelve a
habilitar.
Echemos un vistazo al pseudo-código:
1 |
//An object Character has a boolean variable "Cooldown", a numeric variable "cTimer" and another numeric variable "cEndTime"
|
2 |
|
3 |
On clicked Button |
4 |
txtHistory.text = txtHistory.text & newline & "The character performs an action" |
5 |
Character.cTimer = 0 |
6 |
Character.cEndTime = txtTimer.text //The text object "Cooldown duration" |
7 |
Character.Cooldown = True |
8 |
Button.Enabled = False |
9 |
|
10 |
If Character.Cooldown = True |
11 |
Character.cTimer = Character.cTimer + dt |
12 |
If Character.cTimer >= Character.cEndTime |
13 |
txtHistory.text = txtHistory.text & newline & "The character is ready to perform an action" |
14 |
Character.cTimer = -1 |
15 |
Character.Cooldown = False |
16 |
Button.Enabled = True |
17 |
Else & Every 0.5 seconds |
18 |
txtHistory.text = txtHistory.text & newline & "The action is on cooldown. " & Character.cEndTime - Character.cTimer & " seconds of cooldown remaining." |
19 |
|
20 |
La
función On clicked Button solo se puede ejecutar si el botón está
habilitado, y su código solo se ejecuta una vez después del clic. Esa es la acción del usuario que inicia el temporizador y establece que
Cooldown sea true.
Cuando se hace clic en el botón, la variable
Character.cEndTime variable es el momento en que el período de enfriamiento
finalizará; este valor se establece en función del valor en el objeto de
texto txtTimer, que se ve al lado del botón Action.
También
en este punto, el valor de cTimer se "restablece" a 0 ya que es un
temporizador "nuevo", y Cooldown se establece en true para permitir
que la segunda parte del código se active. Finalmente, desactivamos el
botón. (Podríamos
simplemente dejarlo habilitado y agregar otra condición a nuestra
función de botón On clicked Button para verificar si el valor de
Cooldown es false antes de continuar, pero deshabilitarlo parece
una mejor respuesta).
Nota:
en el ejemplo, y este pseudo-código, no hay errores de captura para
evitar que ingrese algo que no sea un número en ese objeto de texto. Al
ingresar cualquier otra cosa, efectivamente se configurará que cEndTime
sea 0, por lo que, básicamente, no se producirá ningún HUD.
La línea 10 del pseudocódigo anterior se ejecuta cada tic;
comprueba
si el valor de Character.Cooldown es true y, si es así, agrega el
valor de dt al valor actual de Character.cTimer.
dt,
abreviatura de "tiempo delta", es una variable de sistema que devuelve
el tiempo que ha transcurrido entre la representación del fotograma
anterior y la representación del fotograma actual. Esto
significa que, por muy poderosa que sea la computadora del jugador, y
cualquiera que sea la frecuencia de muestreo en el que se ejecute, podemos
asegurarnos de que el período de enfriamiento dura la misma cantidad de
tiempo. (Si
definimos el período de enfriamiento en términos de un número de tics o
marcos que transcurrieron, la duración del período en segundos podría
variar en diferentes máquinas.) El nombre de esta variable dt o su uso
puede diferir de acuerdo con su motor de codificación.
Si el enfriamiento
continúa activo, comprobamos si el valor actual de cTimer es mayor o
igual que cEndTime; si es así, debemos haber llegado al final del
período. Mostramos
un poco de retroalimentación y establecemos Character.Cooldown en false para que esta sección de código no se ejecute nuevamente hasta que el
usuario haga clic en el botón Action. También volvemos a habilitar el
botón Action.
Si el tiempo de reutilización sigue activo, mostramos comentarios que lo explican. Este fragmento específico de comentarios es en realidad nuestra "barra de enfriamiento" para este ejemplo basado en texto.
Entonces esa es una mecánica básica de enfriamiento basada en un temporizador básico. Para el resto de este artículo, centrémonos en las barras de enfriamiento, y veamos cómo implementarlas.



Una barra de enfriamiento básico
Una barra de enfriamiento es en realidad un objeto que cambia de tamaño o de visibilidad con el tiempo, durante un período de enfriamiento. Es una mecánica de retroalimentación para el jugador para hacerle saber cuándo podrá realizar la acción nuevamente.
Considere el siguiente ejemplo:
Haga clic en cualquier lugar de la pantalla para realizar la acción. Tenga
en cuenta que la barra verde se vuelve roja durante el tiempo de
reutilización y crece desde un ancho de 0 hasta su ancho original. Como en el ejemplo textual básico, puede establecer la duración de
enfriamiento.
En este ejemplo, la barra (llamada CooldownBar) no es más
que un sprite de color que se extiende hasta un ancho de 100 píxeles. Cuando se realiza la acción, su ancho se establece en 0 píxeles. Entonces, cada tic que la variable Cooldown es true, el
ancho se establece en función del valor actual de cTimer.
Echemos un vistazo al pseudo-código:
1 |
//Using the same "Character" object as in the basic example, this time the object is visible on screen
|
2 |
|
3 |
On any mouse click & Character.Cooldown = False |
4 |
Character.cTimer = 0 |
5 |
Character.cEndTime = txtEndTimer.text |
6 |
Character.Cooldown = True |
7 |
CooldownBar.Width = 0 |
8 |
CooldownBar.AnimationFrame = 1 |
9 |
|
10 |
If Character.Cooldown = True |
11 |
Character.cTimer = Character.cTimer + dt |
12 |
CooldownBar.Width = (CooldownBar.MaxWidth / Character.cEndTime) * Character.cTimer |
13 |
|
14 |
If Character.cTimer >= Character.cEndTime |
15 |
Character.cTimer = -1 |
16 |
Character.Cooldown = False |
17 |
CooldownBar.Width = CooldownBar.MaxWidth |
18 |
CooldownBar.AnimationFrame = 0 |
La mecánica de enfriamiento es bastante similar a la descrita en el ejemplo anterior, así que centrémonos en CooldownBar. El objeto tiene dos marcos de animación: un cuadrado verde 32x32px y un
cuadrado rojo 32x32px. También tiene una variable de instancia numérica
MaxWidth que se establece en 100 (píxeles), el ancho máximo real de la
barra.
Cada tic, si Cooldown es true, CooldownBar.width se establece
en una fracción de CooldownBar.MaxWidth. Determinamos
esta fracción dividiendo el ancho máximo de la barra por el tiempo de
finalización de enfriamiento, y luego multiplicando este resultado por
el tiempo de cTimer actual.
Al
comienzo y al final del período de enfriamiento, también nos aseguramos
de cambiar el cuadro de la animación en consecuencia: a rojo, cuando
comienza el tiempo de reutilización, y luego a verde una vez que se haya
completado el tiempo de reutilización.
Mejorando el Diseño
Podríamos ir
un poco más allá en el aspecto visual. Aquí hay algunos pensamientos:
El
punto de origen de CooldownBar se encuentra en su centro, lo que le da
la sensación de un crecimiento general. Si
lo prefiere, puede establecer este punto de origen en el borde
izquierdo o derecho del objeto, para darle una sensación más
"lineal".
Este ejemplo es muy básico; la barra en sí solo está compuesta por dos marcos de diferentes colores para reforzar el aspecto de enfriamiento. Siempre que la barra esté roja, el usuario entiende que la acción no se puede realizar, ya que el rojo generalmente es un color que se usa para indicar "detente". ¡Pero no olvides a tus jugadores daltónicos! Asegúrese de usar colores que sean realmente diferentes para que incluso ellos puedan notar la diferencia, o use otro método de comentarios visuales además del cambio de color.
También podría agregar un contorno a la barra, un rectángulo fijo que le permitiría al usuario estimar mejor el tiempo restante en el período de enfriamiento.
Aquí hay una ilustración rápida de los puntos anteriores:
Sigue siendo una barra de recarga muy básica, y aún hay más que podría agregar a lo visual. La barra puede consistir en una animación en lugar de solo dos marcos de colores. El sprite debajo de la barra se puede estilizar de tal forma que su fondo parezca transparente, o incluso se puede agregar otro elemento sobre él para dar una impresión de "vidrio". Hay muchas formas de estilizar la barra de enfriamiento para que se adapte mejor al diseño de tu juego.



Botones de habilidades
En algunos juegos, el jugador tiene botones de habilidad a su disposición. Estos botones no solo muestran el tiempo de reutilización o la disponibilidad de la habilidad; también son una interfaz para que el usuario realice la habilidad indicada (a menudo además de algunos atajos de teclado).
Considere este ejemplo, en el que al hacer clic en uno de los botones se arrojará el arma apropiada y también se mostrará el tiempo de reutilización restante:
Como puede
ver, cada botón de habilidad mostrará una barra negra que "no se llena" y
un temporizador durante el tiempo de enfriamiento. La
barra negra es solo un sprite de color negro colocado sobre el botón de
habilidad con una opacidad del 45% y el temporizador es un objeto de
texto. Cada instancia del botón de habilidad tiene su propia instancia
de estos objetos SkillCover y txtSkillTimer.
Esta vez, las variables
Cooldown, sTime y sEndTime están "conectadas" a cada instancia de
SkillButton. Además, el punto de origen del sprite negro SkillCover está
en su borde inferior.
Considera este pseudo-código:
1 |
//Object "SkillButton" with variables "Cooldown" (boolean), "sTime" (numeric), "sEndTime" (numeric), and a specific animation frame to know which instance is being clicked/selected.
|
2 |
//Object "SkillCover" with a variable "Skill" set accordingly to the animation frame of the SkillButton they are related to.
|
3 |
//Object "txtSkillTimer" with a variable "Skill" for the same purpose as above.
|
4 |
|
5 |
On SkillButton clicked & SkillButton.Cooldown = False |
6 |
SkillButton.sTime = 0 |
7 |
SkillButton.Cooldown = True |
8 |
Create Proj & ProjImage objects |
9 |
ProjImage.AnimationFrame = SkillButton.AnimationFrame //To either throw a dagger or a ninja star |
10 |
|
11 |
txtSkillTimer.Skill = SkillButton.AnimationFrame |
12 |
&
|
13 |
SkillCover.Skill = SkillButton.AnimationFrame |
14 |
Set txtSkillTimer's position to the bottom of SkillButton |
15 |
Set SkillCover's position to the bottom of SkillButton |
16 |
Set txtSkillTimer in Front of SkillButton |
17 |
Set SkillCover behind txtSkillTimer //Still in front of SkillButton |
18 |
txtSkillTimer.Visible = True |
19 |
SkillCover.Visible = True |
20 |
SkillCover.Height = SkillButton.Height |
21 |
|
22 |
For each SkillButton & SkillButton.Cooldown = True |
23 |
SkillButton.sTime = SkillButton.sTime + dt |
24 |
|
25 |
txtSkillTimer.Skill = SkillButton.AnimationFrame |
26 |
&
|
27 |
SkillCover.Skill = SkillButton.AnimationFrame |
28 |
txtSkillTimer.text = SkillButton.sEndTime - SkillButton.sTime |
29 |
SkillCover.height = SkillButton.Height - ((SkillButton.Height / SkillButton.sEndTime) * SkillButton.sTime) |
30 |
|
31 |
If SkillButton.sTime >= SkillButton.sEndTime |
32 |
SkillButton.sTime = -1 |
33 |
SkillButton.Cooldown = False |
34 |
|
35 |
txtSkillTimer.Skill = SkillButton.AnimationFrame |
36 |
&
|
37 |
SkillCover.Skill = SkillButton.AnimationFrame |
38 |
txtSkillTimer.Visible = False |
39 |
SkillCover.Visible = False |
40 |
|
41 |
Aquí, seleccionamos la instancia correcta de txtSkillTimer y SkillCover. (Esta
es una técnica de Construct 2: determinamos las instancias correctas de
acuerdo con el valor de Skill, que debe corresponderse con el cuadro de
animación del SkillButton actual que se hizo clic o se seleccionó en el
ciclo For each).
A
diferencia de nuestras barras de enfriamiento anteriores, SkillCover
comienza "lleno", cubriendo toda la altura del SkillButton, y luego,
durante el enfriamiento, disminuye lentamente, revelando la imagen del
botón.
Para
hacer esto, damos a SkillCover una altura que coincida con la de
SkillButton para comenzar, y luego, cada cuadro, restamos
(SkillButton.Height / SkillButton.sEndTime) * SkillButton.sTime de esta
altura completa. Básicamente
es la misma fórmula que utilizamos antes para calcular la fracción del
período de enfriamiento que ha transcurrido, pero a la inversa.
Algunos
juegos formatearán el tiempo de manera diferente y le permitirán al
jugador configurar la pantalla que prefiera. Por ejemplo, un minuto y
cuarenta segundos podrían mostrarse como 1:40 o como 100 segundos. Para
aplicar esto en su propio juego, ejecute una verificación If, antes de
mostrar el texto de txtSkillTimer, para ver qué formato ha seleccionado
el jugador, y formatee el texto en consecuencia. Algunos juegos o
jugadores incluso prefieren no mostrar la hora como texto en
absoluto.
Hay más que puedes hacer con SkillCover. Podría jugar con su
opacidad para oscurecer el botón más o menos del 45%. Dado
que es un elemento que cubre otra imagen, también puede jugar con su
modo de combinación, por ejemplo, para desaturar la imagen subyacente y
revelar sus colores cada vez más a medida que pasa el enfriamiento. Incluso podría considerar agregar efectos webGL o de sombreado para que
se ajusten al resto de su UI o diseño. En cuanto a la barra de
enfriamiento, depende de tu imaginación y dirección
artística.



Conclusión
Hemos visto que una barra de enfriamiento es una retroalimentación visual para una mecánica simple que evita que una acción se realice durante un cierto período de tiempo.
Puede usar sprites simples y estirar su ancho o alto con el tiempo para indicarle a su usuario que se está produciendo un tiempo de reutilización. Puede visualizar el tiempo restante en texto sin formato, formateándolo en minutos o en segundos. Incluso puede aplicar cualquier tipo de animación, modo de mezcla o efecto de sombreado que se adapte a la dirección artística de su juego.
Si lo piensas por un segundo, también puedes usar este método para controlar la velocidad de disparo de un arma: mientras el arma esté en enfriamiento, no disparará a otro proyectil, incluso si la tecla Fire está abajo; en el momento en que se complete el enfriamiento, se puede disparar otro proyectil. No se requiere una barra de enfriamiento en este caso.
Puedes descargar todos los ejemplos y abrirlos en Construct 2
free edition R168 o posterior.
Espero que hayas encontrado este artículo interesante, y estoy deseando ver tus propios diseños de barras de enfriamiento.
Referencias
- Fondos de batalla de Trent Gamblin - http://opengameart.org/content/12-battle-backgrounds-240x110
- Sprites de personajes de Antifareas - http://opengameart.org/content/antifareas-rpg-sprite-set-1-enlarged-w-transparent-background-fixed
- Pixel Art Icons para juegos de rol de 7SoulDesign - http://7soul1.deviantart.com/art/420-Pixel-Art-Icons-for-RPG-129892453
- Barras de UI de salud o maná de Mumu - http://opengameart.org/content/health-or-mana-ui-bars
- Barras Salud de Enemigos de Paul Wortmann - http://opengameart.org/content/enemy-health-bars
- [LPC] ¡LifeBars! por
Nushio - http://opengameart.org/content/lpc-lifebars



