El Humo y Espejos de las Buenas Cuentas Regresivas, Parte 2
() translation by (you can also view the original English article)



La última vez, le echamos una mirada a las cuentas regresivas en los juegos, cómo se configuran, y qué elementos puedes usar para hacerlos más atractivos. ¡Aun así hay mucho más de lo que se puede cubrir en un sólo artículo!
Esto continuará desde la Parte 1, por lo cual comenzaremos en el número 7.
¿Listo? ¡Vamos!
7: Ten una Velocidad de Tiempo Constante Diferente
Este es un truco simple que ni tan siquiera necesita que le mientas al jugador. Simplemente dices "cuando el tiempo se acabe" y muestra un cronómetro. Lo que no mencionas es la unidad que es mostrada.
Si muestras un "10", el jugador intuirá que son 10 segundos, pero el número puede correr más lento de lo que los segundos lo harían. Si modificas el valor con un molutiplicador de 0.5, por ejemplo, se agotará despues de 20 segundos en lugar de sólo 10.
Puedes ver cómo funciona esto en mi juego Ludum-Dare, Cada Diez Segundos un Gatito se Ahoga.



El tema del aprieto necesitaba que diez segundos fueran usados, pero 10 segundos exactos es una cantidad muy baja de tiempo para lograr algo significativo.
8: Adapta la Velocidad del Reloj Durante el Juego
Esto también funciona mejor si las unidades de tiempo no se mencionan y si al jugador se le da solamente una idea general de "hasta que esto pase".
Si tienes una serie de paneles que se iluminan para mostrar el progreso del reloj, no necesitas activarlos al mismo ritmo. De hecho se volverá más intenso si los primeros se encienden rápido y los otros tienen más tiempo entre ellos. En el calor del juego lleno de acción, el jugador no se dará cuenta de esto y tendrá una experiencia mucho más intensa
Esto no se debe emplear con unidades de tiempo real, pues los jugadores pueden sentirse engañados y que les mintieron. No pierdas la confianza del jugador en tu sistema.
Esto se puede observar en el nivel uno de Starfox 64, donde el jugador tiene que defender una posición contra un barco gigante que se aproxima.



A primera vista, te haces una idea general de cuanto tiempo queda, pero la nave misma parece moverse a diferentes velocidades y no en una línea recta.
En esencia el reloj esta siendo adaptado sobre la marcha, y el proceso está oculto detrás de humo y espejos.
9: Usa Sonidos
¡Las cuentas regresivas no tienen que ser puramente ópticas! Un bip cada cuantos segundos realzará de gran manera la inmersión.
Una vez que entiendas eso, puedes también adaptar la frecuencia cuando una cierta cantidad de tiempo haya pasado. Si el bip ocurre cada cinco segundos, en los últimos 30 segundos del reloj, se le notificará al jugador sin este tener que mirar el número y podrá calcular mentalmente cuanto tiempo queda.
Similarmente, también resulta útil si un personaje comenta sobre el progreso. Que alguien diga, "¡Vamos por la mitad!" te da una pieza de información provechosa que es mucho más fácil de entender que analizarla de una lectura.
10: Usa Gráficos a Escala
Esto funciona muy bien cuando la cuenta se acerca a su final. Cada vez que otro segundo pasa, incrementa el tamaño del reloj por medio segundo.
Además del color y sonido, esto lo hará mucho más jugoso.
11: Haz Algo Cuando Llegue a Cero
Asegúrate que un reloj nunca haga una cuenta regresiva negativa—esto resultará confuso para mucha gente y parecerá un error, ya que los relojes mal creados y con errores tienden a hacer eso.
Hacer que parpadee sería un buen toque, ya que, de nuevo, se enfatiza el punto de que el tiempo se ha agotado.
Si tienes un reloj positivo, es un elemento divertido para dejarlo correr y usar ese elemento como un puntuaje alto. El juego Devil Daggers hace algo similar donde el objetivo principal es "Sobrevive 500 segundos".



Qué No Hacer que Causaría que se Pierda la Confianza del Jugador
Una vez que las reglas de un reloj han sido establecidas, ellos deberían mantenerse aproximandamente consistentes, con mayor margen permitido cuando el reloj sea menos exacto. La pregunta principal debe ser, "¿Cómo es esta regla oculta beneficiosa para la experiencia?"
Ralentizar un reloj en los últimos tres segundos, hará la situación más tensa y le dará al jugador unos cuantos segundos más para cumplir las tareas. Ralentizarlo o acelerarlo aleatoriamente en el medio, sólo logrará perder la confianza del jugador en tus reglas.
Construyamoslo
Esto continuará con el código base del último tutorial y lo empleará como base. Si no lo has visto aún, ¡hazlo ahora! También puedes descargar la fuente terminada en la parte superior derecha de este artículo.
La última vez, nuestro reloj se veía así:



Ahora nuevas adiciones lo harán comportarse de manera más interesante. Continuaremos con el countdown.cs-script
, que debería verse así:
1 |
using UnityEngine; |
2 |
using System.Collections; |
3 |
|
4 |
public class Countdown : MonoBehaviour { |
5 |
|
6 |
float timer = 60f; |
7 |
public AudioClip soundBlip; |
8 |
|
9 |
void Update (){ |
10 |
if (timer > 0f){ |
11 |
timer -= Time.deltaTime; |
12 |
} else { |
13 |
timer = 0f; |
14 |
}
|
15 |
|
16 |
if (timer < 10f) { |
17 |
GetComponent<TextMesh>().color = Color.red; |
18 |
} else if (timer < 20f) { |
19 |
GetComponent<TextMesh>().color = Color.yellow; |
20 |
}
|
21 |
|
22 |
GetComponent<TextMesh>().text = timer.ToString("F2"); |
23 |
}
|
24 |
}
|
Haz que Haga Bip
Hagamos que nuestro reloj haga bip cada segundo, de manera que el jugador sepa que está corriendo sin que tan siquiera lo vea.
Primero, necesitamos un efecto de sonido blip agradable. Puedes encontrar un archivo de audio en los archivos fuente, o crear uno agradable usando la herramienta gratuita BFXR. Cuando tengas una, cópiala en tu folder de recursos.
Agrega un componente AudioSource a tu objeto de cuenta regresiva vía Componente>Audio>AudioSource. Agrega esta variable al libreto de cuenta regresiva:
1 |
public AudioClip soundBlip; |
También necesitas asignar el archivo de sonido a la variable soundBlip
:



Y agrega este bloque a la función Update
:
1 |
if (Mathf.Round(timer * 100) / 100 % 1f == 0) { |
2 |
GetComponent<AudioSource>().PlayOneShot(soundBlip); |
3 |
}
|
Esto se verificará si el reloj está en la marca del segundo completo, y si es así el archivo de sonido sonará.
El funcionamiento de este código es un poco más complicado. Lo que hace es, primero, aproximar el reloj de punto flotante a dos puntos decimales, luego dividirlo por 1, y ver si hay un remanente. Si el remanente es cero, significa que el reloj ha alcanzado un segundo completo, y el sonido del blip puede ser reproducido.
Si no hacemos esto, el sistema podría desencadenarse mucho más frecuentemente, lo cual no será divertido ni favorable para un buen reloj.
Diferente Velocidad
Esto agregará un multiplicador que reducirá o acelerará la velocidad del reloj. Agrega esta variable al inicio:
1 |
float multiplier = 0.6f; |
Ahora encuentra la línea que reduce el tiempo—es esta:
1 |
timer -= Time.deltaTime; |
Y adáptala para que se vea así:
1 |
timer -= Time.deltaTime * multiplier; |
Si el multiplicador está por debajo de 1.0 el reloj correrá más lentamente, y si está sobre 1.0 correrá más rápido. Configurarlo en 1.0 no hará nada.
¡Intenta experimentar para encontrar un buen valor! Siento que una velocidad levemente más lenta, como 0.85f, hará que el jugador sienta el reloj más.
Ve Despacio Cuando Tengas Poco Tiempo
¡Ahora que tenemos un componente multiplicador, podemos cambiarlo durante el juego!
Ve al bloque de cambio de color del código:
1 |
if (timer < 10f) { |
2 |
GetComponent<TextMesh>().color = Color.red; |
3 |
} else if (timer < 20f) { |
4 |
GetComponent<TextMesh>().color = Color.yellow; |
5 |
}
|
Aquí ya tenemos las condiciones donde un cambio en la velocidad del reloj sería apropiado, ¡así que podemos simplemente agregarlo!
1 |
if (timer < 10f) { |
2 |
GetComponent<TextMesh>().color = Color.red; |
3 |
multiplier = 0.6f; |
4 |
} else if (timer < 20f) { |
5 |
GetComponent<TextMesh>().color = Color.yellow; |
6 |
multiplier = 0.8f; |
7 |
} else { |
8 |
multiplier = 1.0f; |
9 |
}
|
Cuando el reloj se ponga amarillo en 20 segundos, ahora hará tick con un 80% de la velocidad. Una vez que se ponga rojo, bajará al 60% de la velocidad regular. De otra manera, será configurado a una velocidad de 100%.
Ampliar Cuando Tengas Poco Tiempo
Otra gran manera de hacer que un reloj que se está quedando sin tiempo sobresalga es ampliarlo con cada segundo que pasa al irse quedando sin tiempo. Puesto que ya tenemos un código que se desencadena cada segundo, podemos adaptarlo aun más.
Primero necesitamos una función que incremente y disminuya el tamaño de nuestro despliegue. Agrega esta función:
1 |
private bool isBlinking = false; |
2 |
private IEnumerator Blink() { |
3 |
isBlinking = true; |
4 |
float startScale = transform.localScale.x; |
5 |
transform.localScale = Vector3.one * startScale * 1.4f; |
6 |
yield return new WaitForSeconds(0.3f); |
7 |
transform.localScale = Vector3.one * startScale; |
8 |
isBlinking = false; |
9 |
}
|
Este es un IEnumerator
, el cual es un tipo de función que puede contener comandos de espera. Necesitamos la variable isBlinking
para asegurarnos que no la habilitemos múltiples veces.
Una vez iniciado, ampliará el tamaño del objeto por el factor de 1.4f, esperará 0.3 segundos, y luego la reducirá de nuevo al tamaño original.
Lo habilitamos usando este código especial:
1 |
if (Mathf.Round(timer * 100) / 100 % 1f == 0) { |
2 |
GetComponent<AudioSource>().PlayOneShot(soundBlip); |
3 |
|
4 |
if (timer < 10f) { |
5 |
if(!isBlinking) { |
6 |
StartCoroutine(Blink()); |
7 |
} |
8 |
} |
9 |
} |
Un IEnumerator
necesita ser iniciado al habilitarlo via StartCoroutine
, de lo contrario no funcionará.
El bloque entero será habilitado cuando pase un segundo, en cuyo punto podemos revisar si el reloj es lo suficientemente bajo para hacerlo parpadear.
Parpadea en Cero
Hagamos algo cuando el reloj se agote. El que solamente este ahí en cero puede ser aburrido, así que hagamos que parpadee.
Primero, necesitaremos otra función IEnumerator
:
1 |
private bool isZeroBlinking = false; |
2 |
private IEnumerator ZeroBlink() { |
3 |
isZeroBlinking = true; |
4 |
GetComponent<Renderer>().enabled = false; |
5 |
yield return new WaitForSeconds(1.5f); |
6 |
GetComponent<Renderer>().enabled = true; |
7 |
yield return new WaitForSeconds(1.5f); |
8 |
isZeroBlinking = false; |
9 |
}
|
Esto encenderá y apagará el reloj en intervalos de 1.5 segundos. Actívalo en el bloque que ya revisa si el reloj está en cero.
1 |
if (timer > 0f){ |
2 |
timer -= Time.deltaTime * multiplier; |
3 |
} else { |
4 |
timer = 0f; |
5 |
if(!isZeroBlinking) { |
6 |
StartCoroutine(ZeroBlink()); |
7 |
}
|
8 |
}
|
Antes que se agote, necesitamos desactivar el bip y el parpadeo en el mismo cero, de otra manera los comportamientos chocarán.
Adapta las condiciones en el bloque para revisar si ha pasado un segundo y también revisa si el tiempo actual es más que cero.
1 |
if (Mathf.Round(timer * 100) / 100 % 1f == 0 && timer > 0f) { |
2 |
GetComponent<AudioSource>().PlayOneShot(soundBlip); |
3 |
|
4 |
if (timer < 10f && timer > 0f) { |
5 |
if(!isBlinking) { |
6 |
StartCoroutine(Blink()); |
7 |
}
|
8 |
}
|
9 |
}
|
Esto asegurará que el parpadeo regular y el parpadeo de cero funcionen sin enterferir el uno con el otro.
El libreto countdown
completo debería verse así:
1 |
using UnityEngine; |
2 |
using System.Collections; |
3 |
|
4 |
public class Countdown : MonoBehaviour { |
5 |
|
6 |
float timer = 5f; |
7 |
float multiplier = 0.6f; |
8 |
public AudioClip soundBlip; |
9 |
|
10 |
void Update (){ |
11 |
if (timer > 0f){ |
12 |
timer -= Time.deltaTime * multiplier; |
13 |
} else { |
14 |
timer = 0f; |
15 |
if(!isZeroBlinking) { |
16 |
StartCoroutine(ZeroBlink()); |
17 |
}
|
18 |
}
|
19 |
|
20 |
if (timer < 10f) { |
21 |
GetComponent<TextMesh>().color = Color.red; |
22 |
multiplier = 0.6f; |
23 |
} else if (timer < 20f) { |
24 |
GetComponent<TextMesh>().color = Color.yellow; |
25 |
multiplier = 0.8f; |
26 |
} else { |
27 |
multiplier = 1.0f; |
28 |
}
|
29 |
|
30 |
if (Mathf.Round(timer * 100) / 100 % 1f == 0 && timer > 0f) { |
31 |
GetComponent<AudioSource>().PlayOneShot(soundBlip); |
32 |
|
33 |
if (timer < 10f && timer > 0f) { |
34 |
if(!isBlinking) { |
35 |
StartCoroutine(Blink()); |
36 |
}
|
37 |
}
|
38 |
}
|
39 |
|
40 |
GetComponent<TextMesh>().text = timer.ToString("F2"); |
41 |
}
|
42 |
|
43 |
private bool isBlinking = false; |
44 |
private IEnumerator Blink() { |
45 |
isBlinking = true; |
46 |
float startScale = transform.localScale.x; |
47 |
transform.localScale = Vector3.one * startScale * 1.4f; |
48 |
yield return new WaitForSeconds(0.3f); |
49 |
transform.localScale = Vector3.one * startScale; |
50 |
isBlinking = false; |
51 |
}
|
52 |
|
53 |
private bool isZeroBlinking = false; |
54 |
private IEnumerator ZeroBlink() { |
55 |
isZeroBlinking = true; |
56 |
GetComponent<Renderer>().enabled = false; |
57 |
yield return new WaitForSeconds(1.5f); |
58 |
GetComponent<Renderer>().enabled = true; |
59 |
yield return new WaitForSeconds(1.5f); |
60 |
isZeroBlinking = false; |
61 |
}
|
62 |
}
|
Esto se encargará de todo. Por supuesto, tú puedes hacerlo más elegante y combinar los comandos en un código más eficiente.
Conclusión
Nuestra pequeña cuenta regresiva estándar se ha vuelto mucho más interesante y atractiva. Si lo construyes una vez, puedes usarlo y conectarlo en cualquier otro proyecto que hagas sin importar cuan pequeño.
¡Ahora ve y mételo en un juego!