【问题标题】:Animating sprites in sfml from a sprite sheet从精灵表中以 sfml 动画精灵
【发布时间】:2019-03-10 08:30:16
【问题描述】:

我正在尝试为 sfml 中的精灵制作动画。目前,我可以移动精灵并在朝不同方向移动时更改其图像,但我想在它移动时为其设置动画。我在想可能有一种方法可以使用 sf::Clock 来实现这一点,或者可能有更好的方法。所有精灵都在同一个精灵表上,所以我只需要找到一种方法来根据时间更改 textureRect 的 X 和 Y 坐标,同时在一个方向上移动。如果我遗漏了什么或您有任何问题,我将尽我所能回答。

main.cpp

#include <iostream>
#include <SFML/Graphics.hpp>
#include "Character.hpp"

int main() {
    sf::RenderWindow window(sf::VideoMode(5000, 5000), "Awesome Game" );
    Character Boi("SpritesBoi.png", 0, 0, 5, 100);
    sf::Sprite BoiSprite = Boi.getSprite();
    Boi.SheetX = 0;
    Boi.SheetY = 48;

    while (window.isOpen()){

        // check all the window's events that were triggered since the last iteration of the loop
        sf::Event event;
        while (window.pollEvent(event)){
            // "close requested" event: we close the window
            if (event.type == sf::Event::Closed){
                window.close();
            }
        }

        Boi.Move();
        BoiSprite.setTextureRect(sf::IntRect(Boi.SheetX, Boi.SheetY, 110, 150));
        BoiSprite.setPosition(Boi.x_pos, Boi.y_pos);
        window.clear(sf::Color(255, 255, 255));
        window.draw(BoiSprite);
        window.display();
    }

}

字符.hpp

#ifndef Character_hpp
#define Character_hpp

#include <stdio.h>
#include <SFML/Graphics.hpp>
#endif /* Character_hpp */

class Character{
public:
    int health;
    int speed;
    int x_pos;
    int y_pos;
    int SheetX;
    int SheetY;
    sf::Texture texture;
    sf::Sprite sprite;

    Character(std::string image, int xlocation, int ylocation, int s, int h){
        health = h;
        speed = s;
        x_pos = xlocation;
        y_pos = ylocation;
        texture.loadFromFile(image);
    }
    sf::Sprite getSprite() {
        sprite.setTexture(texture);
        sprite.setPosition(x_pos, y_pos);
        sprite.setTextureRect(sf::IntRect(SheetX, SheetY, 110, 150));
        return sprite;
    }

    void Move();

};

字符.cpp

#include "Character.hpp"
#include <iostream>
void Character::Move(){



    //Up
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)){
        SheetX = 0;
        SheetY = 192;
        y_pos = y_pos - 1;
        Up = true;

    }
    //Down
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)){
        SheetX = 0;
        SheetY = 48;
        y_pos = y_pos + 1;
        Down = false;

    }
    //Left
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)){
        SheetX = 0;
        SheetY = 480;
        x_pos = x_pos - 1;
        Left = true;

    }
    //Right
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
        SheetX = 0;
        SheetY = 339;
        x_pos = x_pos + 1;
        Right = true;

    }
    //Up Right
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up) and sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
        SheetX = 334;
        SheetY = 490;

    }
    //Up Left
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up) and sf::Keyboard::isKeyPressed(sf::Keyboard::Left)){
        SheetX = 333;
        SheetY = 340;
    }
    //Down Right
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down) and sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
        SheetX = 334;
        SheetY = 48;
    }
    //Down Left
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down) and sf::Keyboard::isKeyPressed(sf::Keyboard::Left)){
        SheetX = 334;
        SheetY = 191;
    }

}

【问题讨论】:

  • Maverick 的回答是正确的,但如果您不想推出自己的动画类,您可以使用在 SFML 之上构建的 Thor 库:bromeon.ch/libraries/thor
  • 请记住,Thor 非常有限,并且已知会崩溃。维护者不在附近修复它们。
  • 酷,如果 Mavericks 的答案不适合我,我会检查一下,但我相信它会的,谢谢。
  • @DapperDaniel 嘿,我不知道这是否是正确的询问方式或地点,但我正在尝试完成您在发布之前已经完成的工作(在更改图片)。我试图通过你的代码,但我不明白你是怎么做到的。有什么办法解释吗?我应该发个帖子或其他东西然后在这里链接还是?

标签: c++ animation sprite sfml sprite-sheet


【解决方案1】:

您需要跟踪动画中的帧(sf::IntRects 列表)。并且在两者之间有某种延迟。更新时,只需在框架中移动并应用矩形。

struct Frame {
   sf::IntRect rect;
   double duration; // in seconds
};

class Animation {
   std::vector<Frame> frames;
   double totalLength;
   double totalProgress;
   sf::Sprite *target;
   public:
     Animation(sf::Sprite& target) { 
       this->target = &target;
       totalProgress = 0.0;
     }

     void addFrame(Frame&& frame) {
       frames.push_back(std::move(frame)); 
       totalLength += frame.duration; 
     }

     void update(double elapsed) {
        totalProgress += elapsed;
        double progress = totalProgress;
        for(auto frame : frames) {
           progress -= (*frame).duration;  

          if (progress <= 0.0 || &(*frame) == &frames.back())
          {
               target->setTextureRect((*frame).rect);  
               break; // we found our frame
          }
     }
};

你可以这样使用:

sf::Sprite myCharacter;
// Load the image...
Animation animation(myCharacter);
animation.addFrame({sf::IntRect(x,y,w,h), 0.1});
// do this for as many frames as you need

// In your main loop:
animation.update(elapsed);
window.draw(myCharacter);

【讨论】:

  • 谢谢,我会尽快实现。
  • 要在我当前的代码中实现这一点,我是否只需在 if(sf::Keyboard::isKeyPressed) 下添加我需要的帧,这样当我向不同方向移动时它会播放不同的动画?而且animation.update还是会进入主循环或者键盘输入之后?
  • 在场景开始时不添加帧。如果您需要不同的动画,请使用不同的动画对象,并且只在您需要的那个上调用.update()
  • 在更新参数中,经过的时间是帧之间的时间还是动画开始的时间,我需要在那里使用 sf::Clock 吗?
  • elapsed 是从主循环中的 sf::Clock 返回的秒数