2012-01-15 25 views
6

我正在用C++編寫一個簡單的roguelike遊戲,使用SDL庫,我在屏幕上移動我的角色時遇到了一些問題。每次渲染框架時,我都會使用update()函數更新精靈的位置,如果玩家靜止不動,它將不會執行任何操作。爲了發出移動命令,從而開始動畫,我使用step()函數,每個玩家從一個貼片到另一個貼片移動一次。在收到「向上」命令後,遊戲表現良好,角色在一秒鐘內平穩移動到新位置。然而,當發出「下行」命令時,他以大約一半的速度移動,顯然在一秒鐘後,他立即被「傳送」到最終位置,突然閃爍。運動的代碼基本上是相同的,但事實上,在一種情況下,三角形運動被加總到y位置,而在另一種情況下,則被減去。也許事實上,位置是一個整數,三角洲是一個雙倍是導致問題? sum和subract行爲有不同(也許不同舍入)?下面是相關的代碼(遺憾的長度):奇怪的行爲更新精靈位置

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 
} 

編輯:我刪除了一些變量,只留下一個經過的時間,速度和最終位置。現在它沒有閃爍地移動,但向下和向右移動明顯慢於向上和向左移動。仍然想知道爲什麼......

編輯2:好吧,我想出了爲什麼會發生這種情況。正如我首先想到的那樣,當涉及到加法和減法時,從雙倍到整數的舍入是不同的。如果我這樣表演:

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

動畫速度是一樣的,問題就解決了。

+0

可能是錯的,但在'Player :: update'中,在'WALKING_DOWN'情況下,它們不應該使用'+ ='而不是' - ='嗎?我只是猜測'WALKING_DOWN'應該與'WALKING_UP'完全相反。不知道這是否與你的問題有關。 – 2012-01-15 21:18:00

+0

@KenWayneVanderLinde m_yDelta變量存儲要走過的剩餘像素以達到該位置,因此在這兩種情況下都應該遞減。 – 2012-01-15 21:20:00

+0

「m_yPos」,「m_yVel」和「m_animTimer」的類型是什麼?而且,我認爲應該總是更新'm_animTimer',否則在下次調用'update'時會得到假值。 – 2012-01-15 21:26:32

回答

3

考慮以下幾點:

#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; 
} 

在浮動的隱式轉換爲int a和b被分配的時候,一切都會過去的小數點會截斷而不是四捨五入。這個方案的結果是:

1 
0 

你說m_yPos是一個整數,m_yVel是雙。考慮Player::update如果m_yVel * m_animTimer的結果小於1,會發生什麼情況。在UP的情況下,結果將是您的精靈向下移動一個像素,但在DOWN的情況下,您的精靈根本不會移動,因爲如果您添加少於一個整數,什麼都不會發生。嘗試將您的位置存儲爲雙精度,並且只有在需要將它們傳遞給繪圖函數時纔將它們轉換爲整數。

在轉換過程中,您可以確保四捨五入而不是截斷的技巧是在分配給整數時總是將0.5加到浮點值上。

例如:

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

在這種情況下,x將成爲1,而在Y將成爲2.

+0

這與我剛纔在答案前幾秒發現的結論完全相同。我會將你的標記標記爲已接受的標記。 – 2012-01-15 21:58:48

1

我寧願不要做增量計算。這是簡單的,即使你回去的時候,不會丟失的精度受到影響,並會一樣快,如果不是更快,在現代硬件上會給出正確的結果:

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

非常感謝,它實際上看起來比我的解決方案更好。我會嘗試一下。 – 2012-01-15 22:10:50