《随心记二》SFML中的 “ Animating Sprites ”

小编 2026-07-01 阅读:807 评论:0
  前言 ● 在本章中, 我们将介绍以下主题:  Capturing time  Animating sprites Building an animator   Capturing ti...

 


前言


● 在本章中, 我们将介绍以下主题:

  •  Capturing time
  •  Animating sprites
  • Building an animator

 


Capturing time


●  time 很重要。 即使在计算机中,我们也必须在大多数应用程序中处理它。先看一个示例代码:

 // Car speedd= 1 pixel per frame
    const float carSpeed = 1.f;

    sf::Sprite carSprite;
    //Advance the car
    carSprite.move(carSpeed, 0);

前面的代码执行每一帧时,因此非常依赖于 CPU和GPU的速度。如果在一台机器上,该代码以每秒30帧的速度运行,导致30个像素以每秒30帧的速度运行,那么在另一台机器上,该代码可以每秒60帧的速度运行,使同样时间内的运行距离加倍。更不用说,几乎总是,帧在同一台机器上运行需要不同的时间。
 


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 来测量经过的时间。

    sf::Clock clock;

    // Run heavy CPU code
    sf::Time timePassed = clock.getElapsedTime();

除了 Clock::getElapsedTime ( ),Clock类还有另一个函数,那就是 Clock::restart ( )它返回经过的时间,同时重启clock。

 

考虑到这一点,如何测量帧时间应该是显而易见的。我们在游戏循环之外初始化一个clock变量,在帧的开始,我们得到经过的时间并重新启动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

        //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 elapsedTime;
    sf::Clock clock;
    while (window.isOpen())
    {
       
       sf::Time deltaTime = clock.restart();

       // Accumulate time with each time
       elapsedTime += deltaTime;
       if (elapsedTime > sf::seconds(5))
           window.close();
       
    }

因为我们不在循环中的任何地方调用Window::pollEvent ( ),所以该窗口不会处理来自用户的任何事件 ( focus,  move, resize, and so on). 然而 即使窗口关闭后 逻辑还会继续执行一段时间 。

 


Sprites in action


●  Animation 以多种形式存在。传统的动画制作方法是绘制一系列彼此略有不同的图像,并在屏幕上依次显示。虽然这种方法仍然被广泛使用,但还有更优雅的选择。 例如,绘制(或用3D建模)一个角色的四肢,然后对它们如何相对于时间移动进行动画是一种为艺术家节省大量时间的技术。它还可以创建更平滑的结果,因为不必重新绘制动画的每个帧。 在本书中,我们将仅探索传统方法,因为它是程序员更简单的解决方案,在许多情况下,它足以为任何Sprites 带来生命。


The setup


●  正如之前所说的,传统方法涉及到一组需要随时间变化的图像。对于我们的例子,我们将使用一个围绕其中心旋转的晶体。通常,动画保存在单个文件(a sprite sheet)中,动画的每一帧都存储在其中,在大多数情况下,每一帧都是相同的大小——对象的大小。 在我们的例子中,sprite是32×32像素,有八帧,播放一秒钟。下面是sprite sheet的样子:
 

\"\"

 

下面的屏幕截图显示了我们在代码中的动画设置:

    sf::Vector2i spriteSize(32, 32);
    sf::Sprite sprite(AssetManager::GetTexture(\"spriteSheet.png\"));

    //将sprite图像设置为动画的第一帧
    sprite.setTextureRect(sf::IntRect(0, 0, spriteSize.x, spriteSize.y));

    int frameNum = 8; //Animation consists of 8 frames
    float animationDuration = 1; // 1 seconds 

首先,请注意我们使用AssetManager类(from Chapter 2, Loading and using textures)来加载我们的Sprites sheet. 下一行设置Sprites 的textures 矩形,目标是 sprite sheet中的第一幅图像。这是 sprite sheet textures 的含义:

\"\"

接下来,我们将偶尔移动此textures 矩形以模拟旋转晶体。在前面的代码中,我们将帧数设置为8 (与sprite sheet中的数量一样多),并将动画时间设置为总共一秒,这意味着每帧持续大约0.125秒(动画持续时间除以帧数 = 1/8)。下面看代码示例:

int main()
{
    sf::RenderWindow window(sf::VideoMode(482, 180), \"Bad Squares!\");  //创建窗口代码
  // sf::sleep(sf::seconds(3)); //这样我们就可以创建窗口之后,运行代码可以看到窗口,否则不可以

    sf::Vector2i spriteSize(32, 32);
    sf::Sprite sprite(AssetManager::GetTexture(\"spriteSheet.png\"));

    int frameNum = 8; //Animation consists of 8 frames
    float animationDuration = 1; // 1 seconds
   
    sf::Time deltaTime;
    sf::Time elapsedTime;
    sf::Clock clock;
   
    while (window.isOpen())
    {
        // Returns the elapsed time and restarts the clock
        deltaTime = clock.restart();
       
        //Handle input

        //Accumulate time with each time
        elapsedTime += deltaTime;
        float timeAsSeconds = elapsedTime.asSeconds();

        //Get the current animation frame
        int animFrame = static_cast<int>((timeAsSeconds / animationDuration)*frameNum) % frameNum;

        // Set the texture rectangle, depending on the frame
        sprite.setTextureRect(sf::IntRect(animFrame *spriteSize.x, 0, spriteSize.x, spriteSize.y));

        // Render frame
    }
    system(\"pause\");
    return 0;
}

 

在代码中 我们首先纪录上一帧到当前所流逝的时间记为 delta time,然后累加到accumulated time中。 代码的最后两行实际上完成了所有的工作。乍一看,第一个看起来很吓人,但是通过基于流逝时间和动画长度来选择正确的帧是一个比较简单的方法。

公式 timeAsSeconds / animationDuration 为我们提供了相对于动画持续时间的时间。 因此,假设已经过了0.4秒,我们的动画持续时间为1秒。这给我们留下了0.4秒的局部动画时间。将这个0.4秒乘以帧数,我们会得到以下结果:

0.4 * 8 = 3.2 

这就给出了我们现在应该处于什么状态,我们在那里呆了多久。 当前帧索引是3.2的整数部分,分数部分(0.2)是我们在该帧上的时间长度。 在这种情况下,我们只对当前帧感兴趣,因此我们将通过将整个表达式转换为int来实现。

2.3 * 8 = 18.4

 

我们没有要显示的第19帧,因此我们显示的帧与我们的本地比例[0 ... 7]相对应。 在这种情况下:

18 / 8 = 2 (and 2 remainder)

由于%运算符采用除法的余数,我们剩下来显示索引为2的帧,即第3帧。(作为程序员,我们从零开始计数,记得吗?) )

 

代码的最后一行将texture 矩形设置为当前帧。这个过程非常简单 - 因为我们只在x轴上有帧,我们不需要担心矩形的y坐标,所以我们将它设置为零。 x由animFrame * spriteSize.x计算,它将当前帧乘以帧的宽度。 在这种情况下,当前帧是2,帧的宽度是32,所以我们得到:

2 * 32 = 64

 

texture 矩形的外观如下:

\"\"

 

我们需要做的最后一件事是在渲染帧内渲染sprite,然后我们就完成了。如果一切顺利,我们应该在屏幕上有一个八帧的旋转晶体。使用这种技术,我们可以动画各种类型的sprite,不管它们有多少帧或者动画有多长。虽然当前的方法存在问题 - 代码看起来很混乱,但它只对单个动画有用。如果我们想要一个sprite有多个动画(也在垂直方向上旋转水晶),并且我们希望能够在它们之间切换,会怎么样?目前,我们必须复制每个动画和每个动画sprite的所有代码。在下一节中,我们将讨论如何通过构建一个功能齐全、要求尽可能少重复代码的动画系统来避免这些问题。


Building an animator


在我们开始做之前,准确地知道我们正在做什么是很重要的,所以让我们列出Animator类的具体说明:

  • Animator类需要支持通过一个对象或者多个纹理对象来使sprite动起来
  • 动画师需要从单个对象或多个texture对象动画sprite。
  • 我们的Animator类应该拥有多个动画
  • 它需要能够在动画之间切换
  • 每个sprite都应该有自己的动画对象
  • 它应该易于使用 
  • 我们的Animator类需要能够自动生成texture矩形

因为我们想为每个Animator类执行多个动画,所以我们必须创建一个结构动画来保存每个动画属性。我们使用的是struct,而不是具有适当封装的class来减少代码大小。然而,在这两种情况下,每个动画都应该有持续时间、帧列表、纹理(由动画使用)、循环信息(它循环吗?),和一个名称句柄(用于引用动画)。基于这个设计,我们的结构应该是这样的:

struct Animution 
{
    std::string m_Name;
    std::string m_TextureName;
    std::vector<sf::IntRect> m_Frames;
    sf::Time m_Duration;
    bool m_Looping;

    Animution(std::string const &name, std::string const &textureName,
        sf::Time const & duration, bool looping) :m_Name(name), m_TextureName(textureName),
        m_Duration(duration), m_Looping(looping)
    {}
    // Adds frames horizontally
    void AddFrames(sf::Vector2i const &startFrom, sf::Vector2i const &frameSize, unsigned int frames)
    {
        sf::Vector2i current = startFrom;
        for (unsigned int i = 0; i < frames; ++i)
        {
            // Add current frame from position and frame size
            m_Frames.push_back(sf::IntRect(current.x, current.y, frameSize.x, frameSize.y));

            //Advance current frame horizontally
            current.x += frameSize.x;
        }

    }
};

Animation::AddFrames()该方法在我们创建该类的实例时,可以简化我们的工作。它接受一个位置、大小和多个帧,并且它从那个位置水平迭代以添加帧到m_Frames向量中。

 

此外,由于动画与时间有关的,它需要有一个Animator :: Update()方法,该方法在每一帧都用deltaTime调用。最后但同样重要的是,它需要一种在动画之间切换的方式。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
标签列表