2012-01-15 12 views
6

Koduję prostą grę roguelike w C++ przy użyciu biblioteki SDL i mam pewne problemy z poruszaniem moją postacią na ekranie. Za każdym razem, gdy ramka wymaga renderowania, aktualizuję pozycję ikonki za pomocą funkcji update(), która nie robi nic, jeśli odtwarzacz stoi w miejscu. Aby wydać komendę ruchu, a tym samym uruchomić animację, używam funkcji step() wywoływanej tylko raz na każdy ruch gracza z jednej płytki na drugą. Po otrzymaniu polecenia "w górę" gra zachowuje się dobrze i postać porusza się płynnie w ciągu jednej sekundy do nowej pozycji. Jednakże, gdy polecenie "w dół" jest podane, porusza się z prędkością około połowy prędkości, i oczywiście po upływie jednej sekundy, jest on natychmiast "teleportowany" do pozycji końcowej, z nagłym migotaniem. Kod ruchu jest w zasadzie identyczny, ale z tego powodu, że w jednym przypadku ruch delta jest sumowany do pozycji y, w drugim przypadku jest odejmowany. Być może fakt, że pozycja jest liczbą całkowitą, a delta jest podwójnym, powoduje problemy? Czy suma i subract zachowują się inaczej (może inne zaokrąglenia)? Oto odpowiedni kod (przepraszam za długość):Dziwne zachowanie aktualizujące pozycję sprite

void Player::step(Player::Direction dir) 
{ 
    if(m_status != STANDING) // no animation while standing 
     return; 

    switch(dir) 
    { 
    case UP: 
     if(m_currMap->tileAt(m_xPos, m_yPos - m_currMap->tileHeight())->m_type == Tile::FLOOR) 
     { 
      // if next tile is not a wall, set up animation 
      m_status = WALKING_UP; 
      m_yDelta = m_currMap->tileHeight(); // sprite have to move by a tile 
      m_yVel = m_currMap->tileHeight()/1000.0f; // in one second 
      m_yNext = m_yPos - m_currMap->tileHeight(); // store final destination 
     } 
     break; 
    case DOWN: 
     if(m_currMap->tileAt(m_xPos, m_yPos + m_currMap->tileHeight())->m_type == Tile::FLOOR) 
     { 
      m_status = WALKING_DOWN; 
      m_yDelta = m_currMap->tileHeight(); 
      m_yVel = m_currMap->tileHeight()/1000.0f; 
      m_yNext = m_yPos + m_currMap->tileHeight(); 
     } 
     break; 

    //... 

    default: 
     break; 
    } 

    m_animTimer = SDL_GetTicks(); 
} 

void Player::update() 
{ 
    m_animTimer = SDL_GetTicks() - m_animTimer; // get the ms passed since last update 

    switch(m_status) 
    { 
    case WALKING_UP: 
     m_yPos -= m_yVel * m_animTimer; // update position 
     m_yDelta -= m_yVel * m_animTimer; // update the remaining space 
     break; 
    case WALKING_DOWN: 
     m_yPos += m_yVel * m_animTimer; 
     m_yDelta -= m_yVel * m_animTimer; 
     break; 

    //... 

    default: 
     break; 
    } 

    if(m_xDelta <= 0 && m_yDelta <= 0) // if i'm done moving 
    { 
     m_xPos = m_xNext; // adjust position 
     m_yPos = m_yNext; 
     m_status = STANDING; // and stop 
    } 
    else 
     m_animTimer = SDL_GetTicks(); // else update timer 
} 

EDIT: usunąłem kilka zmiennych i lewo tylko upływ czasu, prędkości i ostatecznego stanowiska. Teraz porusza się bez migotania, ale ruchy w dół i w prawo są wyraźnie wolniejsze niż w górę i w lewo. Wciąż zastanawiam się, dlaczego ...

EDYTUJ 2: Ok, zorientowałem się, dlaczego tak się dzieje. Tak jak przypuszczałem, po zaokrągleniu od podwójnego do całkowitego istnieje inne zaokrąglenie, jeśli chodzi o sumowanie i odejmowanie. Jeśli wykonam rzut tak:

m_xPos += (int)(m_xVel * m_animTimer); 

prędkość animacji jest taka sama, a problem został rozwiązany.

+0

Może być źle, ale w 'Player :: update', w przypadku' WALKING_DOWN', czy obie linie nie powinny używać '+ =' zamiast '- ='? Zgaduję, że 'WALKING_DOWN' powinno być dokładnym przeciwieństwem' WALKING_UP'. Nie jestem pewien, czy ma to coś wspólnego z twoim problemem. –

+0

@KenWayneVanderLinde Zmienna m_yDelta przechowuje pozostałe piksele do pokonania, aby osiągnąć pozycję, więc w obu przypadkach powinna być zmniejszona. –

+0

Jaki jest typ 'm_yPos',' m_yVel' i 'm_animTimer'? I myślę, że 'm_animTimer' powinien być zawsze aktualizowany, w przeciwnym razie dostaniesz fałszywą wartość przy następnym wywołaniu' update'. –

Odpowiedz

3

Rozważmy następujący:

#include <iostream> 

void main() 
{ 
    int a = 1, b = 1; 
    a += 0.1f; 
    b -= 0.1f; 

    std::cout << a << std::endl; 
    std::cout << b << std::endl; 
} 

Podczas niejawna konwersja float na int gdy aib są przypisane, wszystko przeszłości punktu dziesiętnego będzie obcinane i nie zaokrąglone. Wynikiem tego programu jest:

1 
0 

Powiedziałeś, że m_yPos jest liczbą całkowitą, a m_yVel to liczba podwójna. Zastanówmy się, co stanie się w Player::update, jeśli wynik m_yVel * m_animTimer jest mniejszy niż 1. W przypadku UP wynikiem będzie, że twój duszek przesunie się w dół o jeden piksel, ale w przypadku DOWN twój duszek w ogóle się nie ruszy, ponieważ jeśli dodaj mniej niż jeden do liczby całkowitej, nic się nie stanie. Spróbuj zapisywać swoje pozycje jako liczby podwójne i konwertuj je tylko na liczby całkowite, kiedy musisz je przekazać do funkcji rysowania.

Sztuczka, którą możesz zrobić, aby zapewnić zaokrąglenie zamiast obcinania podczas konwersji, zawsze dodaje 0.5 do wartości zmiennoprzecinkowej podczas przypisywania do liczby całkowitej.

Na przykład:

double d1 = 1.2; 
double d2 = 1.6; 
int x = d1 + 0.5; 
int y = d2 + 0.5; 

W tym przypadku będzie x 1, natomiast y staną 2.

+0

To dokładnie ten sam wniosek, do którego doszedłem kilka sekund przed twoją odpowiedzią. Zaznaczę twoją jako zaakceptowaną. –

1

Wolałbym nie robić przyrostowych obliczeń.Jest to prostsze, daje poprawne wyniki, nawet jeśli cofniesz się w czasie, nie stracisz precyzji i będzie równie szybki, jeśli nie szybszy, na nowoczesnym sprzęcie:

void Player::step(Player::Direction dir) 
{ 
    // ... 
     case UP: 
     if(m_currMap->tileAt(m_xPos, m_yPos - m_currMap->tileHeight())->m_type == Tile::FLOOR) 
     { 
      // if next tile is not a wall, set up animation 
      m_status = WALKING_UP; 
      m_yStart = m_yPos; 
      m_yDelta = -m_currMap->tileHeight(); // sprite have to move by a tile 
      m_tStart = SDL_GetTicks(); // Started now 
      m_tDelta = 1000.0f; // in one second 
     } 
     break; 
    case DOWN: 
     if(m_currMap->tileAt(m_xPos, m_yPos + m_currMap->tileHeight())->m_type == Tile::FLOOR) 
     { 
      m_status = WALKING_DOWN; 
      m_yStart = m_yPos; 
      m_yDelta = m_currMap->tileHeight(); 
      m_tStart = SDL_GetTicks(); // Started now 
      m_tDelta = 1000.0f; // in one second 
     } 
     break; 
    // ... 
} 

void Player::update() 
{ 
    auto tDelta = SDL_GetTicks() - m_tStart; 

    switch(m_status) 
    { 
    case WALKING_UP: 
    case WALKING_DOWN: 
     m_yPos = m_yStart + m_yDelta*tDelta/m_tDelta; // update position 
     break; 

    default: 
     break; 
    } 

    if(tDelta >= m_tDelta) // if i'm done moving 
    { 
     m_xPos = m_xStart + m_xDelta; // adjust position 
     m_yPos = m_yStart + m_yDelta; 
     m_status = STANDING; // and stop 
    } 
} 
+0

Wielkie dzięki, to wygląda ładniej niż moje rozwiązanie. Wypróbuję to. –