Timer

Editado:

He cambiado el Script para corregir un par de cosillas que me han comentado. Lo marco en negrita en la entrada, mas abajo. (Gracias por el Apunte Miguel Lahoz)

Hola a todos,

En el proyecto actual estoy trabajando en un Timer para contabilizar cuando terminan los niveles.

He creado uno mas o menos útil. Puede:

  • Contar hacia arriba hasta el infinito desde una posición inicial.
  • Contar hacia arriba hasta una posición predefinida desde una posición inicial.
  • Contar hacia abajo desde una posición predefinida hasta 0.
  • Contar hacia abajo desde una posición predefinida hasta otra posición final.

Esto cubre casi todas las necesidades de un juego. Tan solo no puede hacer una cosa: Contar en negativo (pero seguro que con un poco de tweaking podría).

 

Ademas tiene un diccionario de Accion (Action<>) donde podéis poner delegados de tipo “Action” y estos se ejecutan en el segundo indicado por el diccionario. Para mas info sobre delegados pincha aquí. Puedes programar que un método o acción ocurra en el segundo 30… o segundo 120…. Ademas tiene una Acción que se ejecuta cuando el Timer acaba.

Os dejo el código aquí, solo una pequeña nota:

Se trata de la clase “Timer” y no hereda de Monobehaviour, por tanto “otra” clase que SI hereda de Monobehaviour (“TimeController”) la crea en su seno y ademas le alimenta los updates mediante su metodo Update. Podéis cambiar esto si queréis, a mi me gusta mas así por si quiero programar otro Timer en otro sitio o incluso 2 Timers dentro de la clase “TimeController”.

También podéis ver el método “UpdateGUI(float)” donde se actualiza un Texto de Unity (para la interfaz), aquí podéis actualizar el texto como queráis!, o borrar eso e incluir vuestra UI personalizada. En mi caso, uso Text y TimeSpan (TimeSpan es completamente opcional).

He modificado la lógica para que use de manera granular segundos como Float directamente dentro de “Timer” y hacer la transformación a minutos + segundos fuera de la clase, en “TimeController” para el GUI.

Ademas, como ejemplo, en el “Awake()” he añadido una ACTION que se ejecuta en el segundo 55.

También quiero anotar que se puede iniciar o parar el temporizador con los métodos “StartTimer()” o “StopTimer()”, Así como poner un limite en “Finish at second”, por si os preocupa tener el contador corriendo indefinidamente.


Edit:

Advanced:

podéis añadir una Action que pare el contador. Se añaden acciones con el método AddAction(int Segundo, Action <metodo>) por ejemplo:

AddAction(60, () => { timer.StopTimer();});

El constructo “() = > { <código> }” es una Acción Anónima (también conocidas como Lambdas). Un trozo de código que se puede almacenar en delegados y Actions y ser almacenado hasta que se ejecute.

Nota: ademas, como el resto de delegados y Actions, se ejecutan en el ÁMBITO del que la definió y no de quien la llama, es decir, pueden funcionar también como callbacks, ES DECIR!, dentro de ese cachito de código puedes usar variables y métodos de la clase que lo declaró en lugar de la clase que invoca la Action, en nuestro caso, podemos usar cosas de “TimeController” en lugar de cosas de “Timer”. Puesto que “timer” (con minúscula) está definido en el ámbito de “TimeController” podemos manejarlo también con una Lambda y programar acciones en el futuro que afecten al propio contador….

El código anterior parará el Timer en el segundo 60. Las Actions aceptan otros métodos (por ejemplo UpdateGUI, ved el código completo mas abajo) ó métodos anónimos del tipo  “() => { <código>}” siempre y cuando tengan los mismos parámetros.

Ejemplo: Una Action del tipo “Action<int, float, string>” aceptará métodos anónimos del tipo “(i,f,s) => { <código>}” ó metodo no-anonimo: “HacerCosas(int cosa, float cosa2, string cosas3)“. En nuestro caso la Action no recibe parámetros, es sencillamente “Action” así que usamos () = { <código>}. (notase el vació dentro del paréntesis).

Si lo deseáis podéis cambiar el diccionario de Actions para que sean delegados mono-cast u otras Actions que acepten parametros. Por ejemplo “Action<float>” (aceptará Actions anonimas del tipo “(f) => {<código, puedo usar f aquí>}” u otros métodos no anónimos como “HacerDaño(float dmg)” que compartan el mismo numero y tipo de parámetros.

Para saber mas de delegados y Acciones ved esto. Para saber mas de acciones y métodos anónimos: aquí.

Otro ejemplo avanzado que se puede implementar con esto es:

void DoDamageEveryTimer(int secs){

//Se retroalimeta 5 segundos despues!

timer.AddAction((int)currentTime + secs, () =>{ DoDamageEveryTimer(secs);});

Player.DoDamageToPlayer(10); //Aquí tu codigo, o acción a repetir

}

void Awake(){

//Añadir esto a Awake:

DoDamageEveryTimer(secs);

}

 

Este código ejecutara “Player.DoDamageToPlayer(10);” cada 5 segundos.

Sencillamente cada vez que se ejecuta el método “DoDamageEveryTimer” este programa un Lambda que se lanza 5 segundos después que ejecuta “DoDamageEveryTimer”, otra vez.. y así sucesivamente. Lógicamente habrá que programar algo para que se pare!, tal vez un sencillo “IF” bastaría. Eso lo dejo a vuestra parte.


Aconsejo encarecidamente copiar esto en monodevelop o Visual Studio para verlo bien!

Ó bien descargarlo: os dejo enlace al archivo!! (LINK)

Aquí tenéis el script del Timer, finalmente:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine.UI;

public class TimeController : MonoBehaviour {
#region Declaración de Variables y Propiedades
[Header(“Timer Settings”)]
[SerializeField]
//Se trata de los minutos por donde empieza el Timer
int minutes;

[SerializeField]
//se trata de los segundos por empieza el Timer
float seconds;

[SerializeField]
//Esto es solo para DEBUG (eliminar en versión final)
//Es para saber en que “Step” va el timer en el inspector
//Tambien habria que eliminar la parte de “out” de timer.UpdateTimer()
#pragma warning disable 0414
int debugStep;
#pragma warning restore 0414

[SerializeField]
//Indica si el tiempo va hacia atras o hacia adelante
bool decrement = true;

[Header(“When to finish (0 = infinite)”)]

[SerializeField]
//Indica cuando debe detenerse el temporizador. Si es 0 es infinito
float finishAtSecond = 0f;

//Mi Timer
public Timer timer;

//Este es el tiempo ACTUAL.
float _currentTime;
public float currentTime
{
get
{
return _currentTime;
}

set
{

if (value != _currentTime)
{
//Al cambiar currentTime, se actualiza el GUI
UpdateGUI(value);
}

_currentTime = value;
}
}

[Header(“GUI”)]
[SerializeField]
Text TimerGUI;

#endregion

#region Metodos
void Update()
{
//FailSafe
if (timer == null) return;

//Sumamos o restamos al temporizado tanto como el tiempo que tarda
//en ejecutarse el tiempo anterior
if (decrement)
{
//menos
currentTime = timer.UpdateTimer(-Time.deltaTime, out debugStep);
}else
{
//mas
currentTime = timer.UpdateTimer(Time.deltaTime, out debugStep);
}
//debug
//Esto es para ver como corre el temporizador en el inspector
//Las dos lineas siguientes son innecesarias.
minutes = Mathf.FloorToInt(currentTime) / 60;
seconds = currentTime % 60f;
}

void Awake()
{
//Llamamos al CONTRUCTOR de la clase Timer y le pasamos los datos
//Entre ellos:
// Tiempo , la acción a ejecutar al terminar el timer, y el momento final de timer
timer = new Timer(seconds,() => { Debug.Log(“Timer Finished”); }, finishAtSecond);
//por defecto el timer esta parado, lo empezamos
timer.AddAction(55, () => { Debug.Log(“Estamos en el segundo 55″); });

timer.StartTimer();
}
/// <summary>
/// Actualiza la GUI (un text)
/// </summary>
/// <param name=”thisTime”></param>
void UpdateGUI(float currentSeconds)
{
//Formatea el Texto y lo presenta.
TimeSpan ts = TimeSpan.FromSeconds(currentSeconds);
//string s = ts.Milliseconds.ToString();
//s = s.Substring(0, 1);
string minutesString = ts.Minutes.ToString();
if (minutesString.Length <= 1) minutesString = “0” + minutesString;
string secondsString = ts.Seconds.ToString();
if (secondsString.Length <= 1) secondsString = “0” + Mathf.FloorToInt(currentTime % 60f);
TimerGUI.text = minutesString + “:” + secondsString;// + “.” + s;

}

#endregion
}

//Aquí empieza la clase TIMER
public class Timer
{
#region Declaración de Variables y Propiedades
bool started; //si es false no corre el tiempo
//Tiempo actual
float currentTime = 1f; //EN SEGUNDOS!
//Esto es el ultimo “int” por que pasamos ocn nuestro float (seconds)
int lastStep = 0;
//esto es el “int” en el que el Timer se acaba
int finishStep = 0;
//La lista de acciones
Dictionary<int, Action> ActionList;
//la accion final
Action finishedAction;
#endregion
#region constructores

public Timer(float second, Action FinishedAction, float finSec = 0f)
{

if (second < 0f) second = 0f;

//iniciamos el diccionaio
ActionList = new Dictionary<int, Action>();

//transformamos minutos y segundos de fin de Timer en Step:
finishStep = (int)finSec;

//Asignamos lo demas
finishedAction = FinishedAction;

currentTime = second;
//lastStep es igual al tiempo en el que estamos transformado en Step
lastStep = (int)currentTime;
}

#endregion

#region Metodos

#region TimerUpdate
/// <summary>
/// Hace pasar el tiempo y devuelve el tiempo actual
/// Si se le pasa 0 o “started” es == false, devuelve el tiempo actual sin cambiar nada
/// </summary>
/// <param name=”deltaTime”>el tiempo que pasa (negativo para bajar, positivo para subir</param>
/// <returns>El tiempo actual</returns>
public float UpdateTimer(float deltaTime, out int debugStep)
{
//Si este Timer está parado no hacemos nada
if (started == true)
{

//Actualizamos los segundos
currentTime += deltaTime;

//Comprobación de si lo segundos llegan a 0
while (currentTime <= 0f)
{
TimerFinished();
currentTime = 0f;
break;
}

//esta parte llama a las acciones registradas en el diccionario
//Calculamos en que “Step” estamos
int thisStep = 0;
// miramos el int mas pequeño
//Es decir, el ultimo “int” por el que pasamos.
thisStep = Mathf.FloorToInt(currentTime);

while (thisStep < lastStep)
{
lastStep -= 1;
if (ActionList.ContainsKey(lastStep)) ActionList[lastStep]();
}
while (thisStep > lastStep)
{
lastStep += 1;
if (ActionList.ContainsKey(lastStep)) ActionList[lastStep]();
}

//Comprueba si ha llegado el final del timer
if (finishStep == lastStep) TimerFinished();

}
//Linea de Debug, para ver luego en el inspector por el step en el que estamos
debugStep = lastStep;

//Generamos el RETURN
return currentTime;
}
#endregion

#region TimerFinished
/// <summary>
/// Tiempo finalizado, se ejecua la Acción “FinishedAction” si exite;
/// </summary>
void TimerFinished()
{
if (!started) return;
if (finishedAction != null) finishedAction();
started = false;
}
#endregion

#region Action Add/Remove
/// <summary>
/// Añade acción.
/// </summary>
/// <param name=”second”>El segundo en el que ejecutar</param>
/// <param name=”action”>La Accción</param>
/// <returns>True si salió bien, False si ya existia</returns>
public bool AddAction(int second, Action action)
{
if (ActionList.ContainsKey(second)) return false;
ActionList.Add(second, action);
return true;
}
/// <summary>
/// Quita una acción.
/// </summary>
/// <param name=”second”>El segundo en el que ejecutar</param>
/// <returns>True si salió bien, False si ya existia</returns>
public bool RemoveAction(int second)
{
if (ActionList.ContainsKey(second)) return false;
ActionList.Remove(second);
return true;
}
#endregion

#region Start/Stop Timer
/// <summary>
/// Empieza el Timer.
/// </summary>
public void StartTimer()
{
started = true;
}
/// <summary>
/// Congela el Timer.
/// </summary>
public void StopTimer()
{
started = false;
}
#endregion
#endregion
}

Os paso enlace al archivo: link

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s