2012-01-15 26 views
6

Tôi đang mã hóa một trò chơi roguelike đơn giản trong C++ sử dụng thư viện SDL và tôi gặp một số vấn đề khi di chuyển nhân vật của mình trên màn hình. Mỗi khi một frame cần được render, tôi cập nhật vị trí của sprite bằng cách sử dụng hàm update(), không có gì nếu người chơi đang đứng yên. Để phát hành lệnh chuyển động và do đó bắt đầu hoạt ảnh, tôi sử dụng hàm step() chỉ được gọi một lần cho mỗi chuyển động của người chơi từ ô này sang ô khác. Khi nhận được lệnh "lên", trò chơi hoạt động tốt và nhân vật di chuyển suôn sẻ trong một giây tới vị trí mới. Tuy nhiên, khi lệnh "xuống" được đưa ra, anh ta di chuyển với tốc độ khoảng một nửa, và rõ ràng sau một giây trôi qua, anh ta ngay lập tức "dịch chuyển" đến vị trí cuối cùng, với một cú nhấp nháy đột ngột. Mã cho chuyển động về cơ bản là giống hệt nhau, nhưng thực tế là trong một trường hợp, chuyển động delta được cộng vào vị trí y, trong trường hợp khác được trừ. Có lẽ thực tế là vị trí là một số nguyên và đồng bằng là một đôi là gây ra vấn đề? Liệu tổng hợp và phép trừ có hoạt động khác nhau (có thể làm tròn khác nhau) không? Đây là mã có liên quan (xin lỗi vì sự chiều dài):Hành vi lạ cập nhật vị trí 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: Tôi đã gỡ bỏ một số biến và duy nhất còn lại thời gian trôi qua, tốc độ và vị trí cuối cùng. Bây giờ nó di chuyển mà không nhấp nháy, nhưng chuyển động xuống và phải là rõ ràng chậm hơn so với những người lên và trái. Vẫn tự hỏi tại sao ...

EDIT 2: Ok, tôi đã tìm ra lý do tại sao điều này xảy ra. Như tôi nghĩ ở vị trí đầu tiên, có một sự làm tròn khác nhau từ đôi đến số nguyên khi nói đến tổng và trừ. Nếu tôi thực hiện một dàn diễn viên như thế này:

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

tốc độ hoạt ảnh giống nhau và vấn đề được giải quyết.

+0

Có thể sai, nhưng trong 'Player :: update', trong trường hợp' WALKING_DOWN', không phải cả hai dòng đều sử dụng '+ =' thay vì '- ='? Tôi chỉ đoán 'WALKING_DOWN' phải ngược lại với' WALKING_UP'. Không chắc chắn nếu điều này có bất cứ điều gì để làm với vấn đề của bạn. –

+0

@KenWayneVanderTìm biến m_yDelta lưu các pixel còn lại để đi tới vị trí, vì vậy trong cả hai trường hợp, nó sẽ bị giảm đi. –

+0

Loại 'm_yPos',' m_yVel' và 'm_animTimer' là gì? Và, tôi nghĩ rằng 'm_animTimer' nên luôn được cập nhật, nếu không bạn sẽ nhận được giá trị không có thật tại cuộc gọi tiếp theo để' cập nhật'. –

Trả lời

3

xem xét như sau:

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

Trong chuyển đổi ngầm của phao để int khi a và b được giao nhiệm vụ, tất cả mọi thứ trong quá khứ dấu thập phân sẽ cắt ngắn và không tròn. Kết quả của chương trình này là:

1 
0 

Bạn đã nói rằng m_yPos là số nguyên và m_yVel là gấp đôi. Xem xét những gì xảy ra trong Player::update nếu kết quả của m_yVel * m_animTimer nhỏ hơn 1. Trong trường hợp UP, kết quả sẽ là sprite của bạn di chuyển xuống một pixel, nhưng trong trường hợp DOWN, sprite của bạn sẽ không di chuyển chút nào, bởi vì nếu bạn thêm ít hơn một đến một số nguyên, không có gì sẽ xảy ra. Hãy thử lưu trữ vị trí của bạn như là tăng gấp đôi và chỉ chuyển đổi chúng thành số nguyên khi bạn cần phải vượt qua chúng để các chức năng vẽ.

Một thủ thuật bạn có thể làm để đảm bảo làm tròn thay vì cắt ngắn trong khi chuyển đổi là luôn thêm 0,5 vào giá trị dấu phẩy động trong khi gán cho một số nguyên.

Ví dụ:

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

Trong trường hợp này, x sẽ trở thành 1, trong khi y sẽ trở thành 2.

+0

Đó là kết luận chính xác mà tôi đã đến chỉ vài giây trước khi câu trả lời của bạn. Tôi sẽ đánh dấu bạn là người được chấp nhận. –

1

Tôi không muốn làm các phép tính cộng dồn.Điều này đơn giản hơn, sẽ cho kết quả chính xác ngay cả khi bạn quay ngược thời gian, không bị mất độ chính xác và sẽ nhanh chóng, nếu không nhanh hơn trên phần cứng hiện đại:

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

Cảm ơn rất nhiều, nó thực sự trông đẹp hơn giải pháp của tôi. Tôi se thử no. –