目录
Preface
如果我们想给人一种物体在自然运动的印象,动画是非常重要的。以campfire 中的sprite 为例。如果只有一个火焰的图像,它看起来就像火没有燃烧。但是,如果您在多个图像之间进行切换,就会产生一种错觉,以为那里发生了什么事情。这就是我们将在本章中讨论的内容。在此之前,我们需要探索基于 time-based 的模拟世界,因为这是我们的animation 正常运行所必需的。
● 在本章中, 我们将介绍以下主题:
- Capturing time
- Animating sprites
- Building an animator
Capturing time
● time 很重要。 即使在计算机中,我们也必须在大多数应用程序中处理它。然而,让我们假设Timmy(我们的一个朋友)不相信time是很重要的,有一天,他坐下来并创建了一款多人赛车游戏,其中汽车在每一帧中只移动一个像素。Timmy对他的机器上的结果很满意,他将程序发送给他的朋友Jimmy, Jimmy刚刚为他的机器购买了最新的Super Ultra X CPU。他们进行了一场比赛,但是,绿灯一亮,Jimmy 就冲到Timmy 的前面,把他甩在了后面,满身尘土,不知所措。后来,Timmy意识到Jimmy的机器执行代码的速度比他自己的机器快得多,因此,他的车速度较慢。Timmy再也没有忽略过帧时间。
尽管前面的故事展示了一个过于简单的例子,但它揭示了在任何模拟中忽略时间的最大缺点——帧在不同的机器上以不同的速度执行,因此模拟看起来也不同。下面是Jimmy 修改之前的代码:
// Car speed= 1 pixel per frame
const float carSpeed = 1.f;
sf::Sprite carSprite;
//Advance the car
carSprite.move(carSpeed, 0);
上述的代码执行每一帧时,因此非常依赖于 CPU和GPU的速度。如果在一台机器上,这段代码以每秒30帧的速度运行,导致30个像素在一秒内移动,那么在另一台机器上,这段代码可以以每秒60帧的速度运行,在相同的时间内,这段代码的移动距离会增加一倍。更不用说,几乎所有情况下,帧在同一台机器上运行的时间都是不同的。
显然,我们有一个问题——游戏逻辑在不同的系统上以不同的速度运行。幸运的是,解决方案非常简单——使用帧之间的 elapsed time 来更新游戏逻辑。下面是前面使用移动的相同示例,这依赖于time :
变化不大,是吗?我们有一个额外的变量( deltaTime ),它保存上一帧的持续时间(duration )(以秒为单位)。当我们将变量传递给Sprite::move ( )方法时,我们实际上是在说“我想在水平方向上以每秒carSpeed像素的速度移动这辆车。”这非常简单,但是非常有效。
然而,我们如何 capture elapsed time ?
sf::Time and sf::Clock
● 在 SFML 中, 有两个类可以很好的与 time 一起工作。time 类保存一个duration 。这意味着它没有告诉我们任何关于一天中的当前时间或程序启动后过去的时间,它只有一个保存time量的变量。它可能是5微秒,也可能是10个月 , 或者是任何代表一段时间的东西。
我们可以使用函数 sf :: seconds(),sf :: milliseconds()和 sf :: microseconds()分别从秒,毫秒和微秒 构造一个 time 对象。一旦我们有了那个对象,我们就可以对其进行算术运算(加,减和比较)。 我们稍后可以通过调用函数 Time :: asSeconds(),Time :: asMilliseconds()和Time :: asMicroseconds()可以将Time对象转换为秒,毫秒和微秒。 以下是Time类如何工作的示例:
sf::Time time = sf::seconds(5) + sf::milliseconds(100);
if (time > sf::seconds(5.09))
std::cout << "It works";
Time类可以方便地存储time,但它并没有为我们提供捕获它的方法。Clock 类提供了一个接口,通过使用操作系统Clock 来检测 elapsed time。
sf::Clock clock;
// Run heavy CPU code
sf::Time timePassed = clock.getElapsedTime();
除了 Clock::getElapsedTime ( ),Clock类还有另一个函数,那就是 Clock::restart ( ),它返回 elapsed time,同时重启clock。
考虑到这一点,如何测量帧时间应该是很清楚的了。我们在游戏循环之外初始化一个clock变量,在帧的开始,我们获取 elapsed time 并重新启动clock。这给了我们上一帧所花的时间,这样我们就可以利用它来推进对象。从最后一帧开始到当前帧开始之间的时间量通常称为deltaTime (简称dt )。代码如下所示:
sf::RenderWindow window(sf::VideoMode(482, 180), "Bad Squares!"); //创建窗口代码
sf::Time deltaTime;
sf::Clock clock;
while (window.isOpen())
{
// Returns the elapsed time and restarts the clock
deltaTime = clock.restart();
float dtAsSeconds = deltaTime.asSeconds(); //Deltatime as seconds,将Time对象装换为秒
//Handle input
//Update frame
//Render frame
}
现在我们知道如何 capture time, 我们应该确保在游戏逻辑需要的任何地方使用它 —— 在任何一个movement, animation, time-based events (to destroy an object after N seconds)这些类型中使用它, 由于我们知道两个帧之间的deltatime, 我们也可以在不同的变量中累加时间来检测帧结构之外的时间。我们希望在打开窗口5秒后将其关闭的示例:
sf::RenderWindow window(sf::VideoMode(482, 180), "Bad Squares!"); //创建窗口代码
sf::Time ela