Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Como utilizar el timer correctamente en los juegos al pausarlos

Iniciado por XÑA, 24 de Octubre de 2012, 10:39:23 AM

« anterior - próximo »

XÑA

Buff, vaya título. Pero bueno, los que trabajais en esto sabéis que es importante gestionar correctamente el modo pausa. Yo me he encontrado con este problemilla hace poco, y aquí os explico la forma que he tenido de resolverlo.

Vamos a plantear un jueguecito pequeño, y veamos el problema:
Tengo un jugador, que a cogido super-energía. Pero la energía dura sólo 3 segundos, justo después se vuelve a transformar en un pobre y esmirriao jugador.
¿Cómo programamos esto? Bueno, ahí va.
<code>
if  then
   tiempoParaVolverASerYoMismo=currentTime+3000;
   soySuperHombre =true;
end
</code>
El update sería algo así como
<code>
if soySuperHombre AND currentTime&gt;=tiempoParaVolverASerYoMismo then
   soySuperHombre =false;
   [Activar Animacion de convertires de nuevo al estado normal]
end
</code>

Bien, esto va perfecto! Pero ahora, vamos a pensar lo que ocurriría si nos hicieran Pause justo después de que me convirtiera a Super-Hombre...
¡El timer sigue avanzando! Entonces al regresar de la pausa, no esperará esos 3 segunditos que yo quería que esperara!!!

Así que he escrito un pequeño programa en Corona, para que podáis probarlo y ver cómo se haría correctamente. Pensad que en el mundo del móvil, la pausa puede ocurrir porqué te llaman, así que HAY que gestionarla!!!

En el ejemplo hay 2 rectángulos que se mueven de izquierda a derecha. Ambos se desplazan de forma que tarden 10s en llegar a la derecha de la pantalla. Ambos tienen programados que a los 5s cambien de color. Si no hacemos nada, veremos que cambian a los 5s y a los 10 llegan al final. Perfecto.
Pero si pausamos antes de que lleguen a los 5s, veremos que uno de ellos cambiará de color aunque estemos en pausa. El otro habrá tardado exactamente 5s DE JUEGO EFECTIVO, en cambiar de color, que es la forma correcta.

http://javiermaura.wordpress.com/2012/10/24/como-utilizar-el-timer-correctamente-en-los-juegos-al-pausarlos/

bnl

Lo mas sencillo supongo q es sumarle a tiempoParaVolverASerYoMismo el tiempo que haya estado pausado el juego (currentTime de cuando se reanudo el juego menos el currentTime de cuando se pauso)

El bloque de codigo del final del post se lee fatal. Hay plugins para wordpress para formatear y colorear codigo en diferentes lenguajes
Mi web: http://www.brausoft.com/
No sabían que era imposible, así que lo hicieron.

XÑA

Sí, es una solución, pero...¿Cómo lo añades a TODAS las variables que usan timer?

La forma que indico aquí es general, y funciona siempre. Sólo hay que tener en cuenta que NO se puede usar DateTime.Now ( en el caso de C#)

Y lo de formatear código..bueno, tendré que mirarlo!!!

bnl

Te podrias basar para hacer las comprobaciones en vez de en currentTime en un currentTimeJugado que fuera currentTime menos el tiempo pausado.
Mi web: http://www.brausoft.com/
No sabían que era imposible, así que lo hicieron.

[EX3]

Uff... no habras editado el post en modo visual para insertar codigo fuente, no? A mi en mi blog si lo hago asi me descojona todo el formato y contenido de los bloques de codigo fuente. Hazlo en el modo Texto, que es más seguro y mirate esto para mostrar codigo de forma más amena :)

http://en.support.wordpress.com/code/posting-source-code/

Esto creo que viene ya por defecto con Wordpress cuando lo instalas (yo es que uso Wordpress publico, por eso no estoy seguro).

Sobre el post en si, aunque ya me dijisteis en su día que no veíais correcto mi forma de gestionar los tiempos en un juego (para esto soy un poco cabezota, no lo niego en absoluto :P),  de todas formas ahí va el código del cronometro que suelo usar en XNA y que tiene función de pausa:
Código (csharp) [Seleccionar]
using System;

/// <summary>
/// Cronometro de tiempo para calculos de intervalos.
/// </summary>
public class Timer
{
#region Miembros privados
private int delta;
private int pauseDelta;
private bool isPaused = false;
#endregion

#region Propiedades
/// <summary>
/// Indica si el cronometro esta en pausa.
/// </summary>
public bool IsPaused
{
get { return isPaused; }
}

/// <summary>
/// Devuelve los milisegundos transcurridos desde el ultimo reinicio o desde que se creo el cronometro.
/// </summary>
public int Value
{
get
{
return (isPaused ? pauseDelta : System.Environment.TickCount) - delta;
}
}
#endregion

#region Constructores
/// <summary>
/// Crea una instancia de un cronometro.
/// </summary>
public Timer()
{
this.Reset();
}
#endregion

#region Metodos y funciones
/// <summary>
/// Reinicia el cronometro a 0.
/// </summary>
public void Reset()
{
isPaused = false;
delta = System.Environment.TickCount;
}

/// <summary>
/// Pausa o reactiva el cronometro.
/// </summary>
public void Pause()
{
isPaused = !isPaused;
if (isPaused)
pauseDelta = System.Environment.TickCount;
else
delta += System.Environment.TickCount - pauseDelta;
}
#endregion
}


Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

bnl

Exactamente asi lo he montado yo en los ultimos juegos que he hecho, con una clase Cronometro


public long ObtenerMilisegundosTranscurridos()
   {
      long tiempoTranscurrido;

      if (pausado)
      {
         tiempoTranscurrido = tiempoInicioPausa - tiempoInicial;
      }
      else
      {
         tiempoTranscurrido = System.currentTimeMillis() - tiempoInicial;
      }

      return tiempoTranscurrido;
   }
Mi web: http://www.brausoft.com/
No sabían que era imposible, así que lo hicieron.

blau

Allá voy yo... con un TimeManager que se actualiza sólo si el juego está en modo Playing... aunque tb se le podria asignar a cada timer a que estados responde... ;)


Código (csharp) [Seleccionar]


public class Timer
{
   public Action Trigger;
   public float Interval;
   float Elapsed;

   Timer() {}

   public void Update(float Seconds)
   {
       Elapsed+= Seconds;
       if (Elapsed>= Interval)
       {
            Trigger.Invoke();
            Destroy();
       }
   }

   public void Destroy()
   {
       TimerManager.Remove(this);
   }

   public static void Create(float Interval, Action Trigger)
   {
       Timer Timer = new Timer() { Interval = Interval, Trigger = Trigger }
       TimerManager.Add(this);
   }
}


public class TimerManager : GameComponent
{
    List<Timer> ToRemove = new List();
    List<Timer> Timers = new List();

    public static TimerManager Instance;

    public static void Add(Timer Timer) { Instance.Timers.Add( Timer ); }
    public static void Remove(Timer Timer) { Instance.ToRemove.Add(Timer); }

    public void Update(GameTime gametime)
    {
        if (Game.State == GameState.Playing)
        {
           foreach (Timer timer in Timers) timer.Update( (float) gametime.Elapsed.Totalseconds);
           foreach (Timer timer in ToRemove) Timers.Remove(timer);
           ToRemove.Clear();
        }
    }
}


public class Game
{
    public void Initialize() { Components.Add(new TimerManager(this);}
    public Update()
    {
         if (GotPowerUp(out PowerUp))
         {
             Timer.Create(3, () => powerUp.Destroy());
         }
    }
}  


XÑA

Muy chula la clase. Una forma elegante de resolver el problema del control del tiempo, pero con timers.

El único problema con la progrmación orientada a eventos es que 'pierdes' la visión general. Para mi es más fácil ver un Update así:

Update()
{
  if(time>timeToJump) ;
if(time>timeToFlash) ;
if(time>timeToSleep);
}


que tener eventos en métodos que se llaman. Pierdo el 'hilo', pero es una cuestión de costumbres. Otro pequeño problema que tienes es el de tener que controlar si hay que destruir el timer por algún otro motivo antes de que se produzca.
Pero vamos, que yo he usado tu técnica ( de hecho la uso en Corona) y me parece muy elegante!!

blau

Cada situación requiere su propia solución...

para un control de varias estados por tiempo de ese tipo ni se me ocurriria usarla....

pero para un bonus que tiene una duración determinada..
o un final de nivel por tiempo..
esto va genial... ;)

y respecto a lo de destruir el Timer a voluntad... esta clase es solo un ejemplo... trabajandola un poquito más es fácil de implementar..  8)

chan

Yo optaría por utilizar estados en el juego, eso te mantiene el código bastante desacoplado y no creas dependencias que quizás a la larga te den problemas.

Como ejemplo, simplificando mucho, piensa que tu juego pueda tener los estados: inGame, Paused, GameOver, en tu manager de estados es donde le dirás que el juego ejecute toda la lógica cuando esté inGame, algo así como:


function Status Update(float ticks)
{
switch(gameState)
{
  case(Status.inGame):
     GameManager.Logic.Update(ticks);
     GameManager.PasiveLogic.Update(ticks);
     GameManager.Physics.Update(ticks);
     GameManager.Input.Update(ticks);
     ....
   break;

case(Status.Paused):
   GameManager.GUIMan.Update(ticks);
   ....
  break;
}
}


En el Logic.Update(ticks) es donde tendrías que esta actualizando la lógica activa del juego (IA, estado del personaje, etc.), por lo tanto todo lo que le tengas aplicado a tu personajes, enemigos, etc sólo se actualizarán cuando el estado del juego sea el correcto.


XÑA

Ya, pero esto falla si usas el sistema de coger DateTime.Now para saber cuando ha terminado un evento, porqué el reloj sigue corriendo. Yo lo uso mucho, la verdad...

chan

Eso no debe ser un problema si gestionas bien la entrada y salida de los estados, en donde se tendría que actualizar la varible que te controle el tiempo de la última actualización de cada subsistema

XÑA

Bueno, sí, sería otra opción. Yo he puesto la que a mi me ha parecido mejor y la que utilizo en mi jueguecito en corona....

Vicente

Cita de: blau en 27 de Octubre de 2012, 02:21:44 PM
Allá voy yo... con un TimeManager que se actualiza sólo si el juego está en modo Playing... aunque tb se le podria asignar a cada timer a que estados responde... ;)

Muy muy bonito :)







Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.