更新精灵位置的奇怪行为

Strange behaviour updating sprite position

本文关键字:精灵 位置 更新      更新时间:2023-10-16

我正在使用SDL库用C++编写一个简单的roguelike游戏,在屏幕上移动角色时遇到了一些问题。每次需要渲染帧时,我都会使用update()函数更新精灵的位置,如果玩家静止不动,该函数将不起任何作用。为了发出移动命令,从而启动动画,我使用了step()函数,每次玩家从一个瓦片移动到另一个瓦片时只调用一次。收到"向上"命令后,游戏表现良好,角色在一秒钟内顺利移动到新位置。然而,当发出"向下"命令时,他以大约一半的速度移动,显然在一秒钟后,他立即"传送"到最终位置,并突然闪烁。移动的代码基本上是相同的,但在一种情况下,德尔塔移动被加和到y位置,在另一种情况中被减去。也许这个位置是一个整数,而delta是一个二重这一事实造成了问题?sum和subact的表现是否不同(可能取整不同)?以下是相关代码(抱歉长度过长):

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);

动画的速度是相同的,并且问题得到了解决。

考虑以下内容:

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

当a和b被赋值时,在浮点到int的隐式转换过程中,超过小数点的所有内容都将被截断,而不是四舍五入。这个程序的结果是:

1
0

你说过m_yPos是一个整数,m_yVel是一个二重。考虑一下如果m_yVel * m_animTimer的结果小于1,在Player::update中会发生什么。在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。

我宁愿不做增量计算。这更简单,即使你回到过去,也会给出正确的结果,不会失去精度,而且在现代硬件上也会同样快,甚至更快:

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