Wednesday, September 22, 2010

Czas goni nas (1)

Załóżmy, że czas wyrenderowania ramki to 100ms na sprzęcie A. Na sprzęcie B ta sama ramka renderowana jest w ciągu 20ms. Jak łatwo policzyć na sprzęcie A będzie renderowanych 10 klatek na sekundę. Na sprzęcie B będzie to już 50 klatek na sekundę. Jeśli teraz będziemy aktualizować pozycję obiektu o 10 pikseli w każdej ramce to po sekundzie obiekt przebędzie drogę:
  • na sprzęcie A: 10 * 10 pikseli = 100 pikseli
  • na sprzęcie B: 50 * 10 pikseli = 500 pikseli
Jak widać na szybszej maszynie B obiekt przebył znacznie większą drogę niż na maszynie A. Aby wyeliminować to zjawisko animacje obiektów należy opierać właśnie o czas. Wtedy prędkość poruszania się obiektu może być wyrażona w pikselach / sekundę (a nie jak w przykładzie piksele / klatkę), zatem:
  • na sprzęcie A: 1 sekunda * 10 pikseli / sekunda = 10 pikseli / sekunda
  • na sprzęcie B: 1 sekunda * 10 pikseli / sekunda = 10 pikseli / sekunda
Każdy poważny framework (a takim przecież ma być kasuroid :P) powinien posiadać możliwość zarządzania czasem. W kasuroid za czas odpowiedzialna jest klasa Timer. Udostępnia ona podstawowy interfejs do sterowania czasem:
public class Timer
{
    public int start() {}

    public int stop() {}

    public int pause() {}

    public int resume() {}

    public int update() {}
}
Wszelkie obliczenia odbywają się w metodzie update():
public int update()
{
    if ((isStarted() == true) &&
        (isPaused() == false))
    {
        mLastFrame = mCurrentFrame;
        mCurrentFrame = System.currentTimeMillis();
        mDelta = mCurrentFrame - mLastFrame;
        mTimeElapsed += mDelta;
        mDeltaSecs = (float)mDelta / mTimerFrameLength;
    }

    return RetCode.SUCCESS;
}
Pomiar czasu opiera się na różnicy czasów dwóch kolejnych ramek, więc potrzebne jest zainicjalizowanie mLastFrame oraz mCurrentFrame podczas startowania timera:
public int start()
{
    if (isStarted() == false)
    {
        mCurrentFrame = mLastFrame = System.currentTimeMillis();
        mTimeElapsed = 0;
        mDelta = 0;
        mDeltaSecs = 0.0f;

        Debug.inf(getClass().getName(), "Timer with id: " + mId + " started.");
        return RetCode.SUCCESS;
    }
    else
    {
        Debug.warn(getClass().getName(), "Timer already started!");
        return RetCode.SUCCESS;
    }
}
Warto zauważyć, że w update() obliczam od razu liczbę sekund trwania jednej ramki. Doszedłem do wniosku, że dużo obiektów będzie bazowało swoje animacje właśnie na podstawie tej wartości. Dlatego też wystarczy policzyć ją raz per ramka. Każdy obiekt może następnie skorzystać z poniższej metody do pobrania czasu (w sekundach):
public float getTimeDeltaSecs()
{
    return mDeltaSecs;
}
.. i obliczyć na jego podstawie kolejny krok animacji.

Timer liczy także czas jaki upłynął od czasu wywołania metody start() (zmienna mTimeElapsed).

I to chyba tyle. W następnej części przedstawie TimerManagera.

No comments:

Post a Comment