我正在使用SDL库在C ++中编写一个简单的roguelike游戏,我在屏幕上移动我的角色时遇到了一些问题。每次需要渲染帧时,我都会使用update()函数更新精灵的位置,如果玩家静止不动,则不会执行任何操作。为了发出移动命令,从而开始动画,我使用每个玩家从一个瓦片移动到另一个瓦片时只调用一次的step()函数。收到" up"命令,游戏表现良好,角色在一秒内平稳移动到新位置。然而,当" down"命令被给出,他以大约一半的速度移动,显然在一秒钟过后,他立刻被传送到了#34;到了最后的位置,突然闪烁。移动的代码基本相同,但是在一种情况下,delta移动被加到y位置,在另一种情况下被减去。也许这个位置是一个整数而delta是一个双倍的事实会导致问题? 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:好的,我弄清楚为什么会这样。正如我所假设的那样,当涉及到求和减法时,有一个从double到integer的不同舍入。如果我执行这样的演员:
m_xPos += (int)(m_xVel * m_animTimer);
动画速度相同,问题解决了。
答案 0 :(得分: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;
}
在分配a和b时,将float隐式转换为int期间,超过小数点的所有内容都将截断而不是舍入。该计划的结果是:
1
0
你说m_yPos是一个整数,m_yVel是一个整数。如果Player::update
的结果小于1,请考虑m_yVel * m_animTimer
中发生的情况。在UP
情况下,结果将是您的精灵向下移动一个像素,但在{{1}例如,你的精灵根本不会移动,因为如果你给一个整数添加少于一个,那么什么都不会发生。尝试将您的位置存储为双精度,只有在需要将它们传递给绘图函数时才将它们转换为整数。
在转换过程中,您可以采取的确保舍入而不是截断的技巧是在赋值给整数期间始终将浮点值加0.5。
例如:
DOWN
在这种情况下,x将变为1,而y将变为2.
答案 1 :(得分: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
}
}