SFML 2D 游戏——流畅的飞船运动 [英] SFML 2D game – Smooth spaceship movement

查看:87
本文介绍了SFML 2D 游戏——流畅的飞船运动的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过使用 xcode 作为编译器和 SFML 作为库来使用 C++ 制作一个简单的游戏.到目前为止,我已经创建了一个 GUI、一个背景和一个精灵(用于宇宙飞船).我还添加了箭头键检测,以便能够移动对象,但问题是当我移动对象时,它移动不顺畅,您可以看到它有点跳跃".

I am trying to make a simple game in C++ by using xcode as compiler and SFML as library. So far I have created a GUI, a background and a sprite (for the spaceship). I have also added arrow keys detection in order to be able to move the object, but the problem is that when i move the object it doesn't move smoothly and you can see it is kind of "jumping".

#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include "Spaceship.hpp"
#include <vector>

// Here is a small helper for you! Have a look.
#include "ResourcePath.hpp"

int main(int, char const**)
{

    // Create the main window
    sf::RenderWindow window(sf::VideoMode(800, 600), "SpaceShuttle");
    window.setFramerateLimit(30);
    // Call to non-static member function without an object argument
    // Set the Icon
    sf::Image icon;
    if (!icon.loadFromFile(resourcePath() + "space-shuttle.png")) {
        return EXIT_FAILURE;
    }
    window.setIcon(icon.getSize().x, icon.getSize().y, icon.getPixelsPtr());

    // Load a sprite to display
    sf::Texture texture;
    if (!texture.loadFromFile(resourcePath() + "bg.png")) {
        return EXIT_FAILURE;
    }
    sf::Sprite sprite(texture);

    // Create a graphical text to display
    sf::Font font;
    if (!font.loadFromFile(resourcePath() + "sansation.ttf")) {
        return EXIT_FAILURE;
    }
    sf::Text text("SpaceShuttle K1LLM33K", font, 50);
    text.setFillColor(sf::Color::White);
    text.setPosition(100.0, 130.0);


    // Load a music to play
   /* sf::Music music; if (!music.openFromFile(resourcePath() + "nice_music.ogg")) { return EXIT_FAILURE; } 
    // Play the music
    music.play();
    */

    Spaceship spaceship(window);
    sf::Clock sf_clock;


    // Start the game loop
    while (window.isOpen()) {

        // Process events
        sf::Event event;
        while (window.pollEvent(event)) {
            // Close window: exit
            if (event.type == sf::Event::Closed) {
                window.close();
            }

            // Escape pressed: exit
            if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
                window.close();
            }
            // Move Spaceship
            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { spaceship.moveship('l'); }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { spaceship.moveship('r'); }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { spaceship.moveship('u'); }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { spaceship.moveship('d'); }

        }
        // Clear screen
        window.clear();

        // Draw the sprite(s)
        window.draw(sprite);
        spaceship.drawsprite(window);

        // Draw the string(s)
        window.draw(text);

        // Update the window
        window.display();
    }

    return EXIT_SUCCESS;
}

Spaceship.cpp

#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include "ResourcePath.hpp"
#include "Spaceship.hpp"

Spaceship::Spaceship(sf::RenderWindow& game_window){
    auto surface = game_window.getSize();
    ss_x = surface.x/2;
    ss_y = surface.y/2;
    ss_speed = 5;
    ss_width = 128;
    ss_height = 128;
    ss_radius = ss_width/2;

}
void Spaceship::drawsprite(sf::RenderWindow& game_window){
    sf::Texture ship;
    if (!ship.loadFromFile(resourcePath() + "space-shuttle-64.png")) {
        return EXIT_FAILURE;
    }
    sf::Sprite ss_sprite(ship);
    ss_sprite.setPosition(ss_x - ss_sprite.getGlobalBounds().width/2, ss_y - ss_sprite.getGlobalBounds().height/2);
    game_window.draw(ss_sprite);
}

void Spaceship::moveship(char move){
    if(move == 'l'){ ss_x -= ss_speed;  }
    else if(move == 'r'){ ss_x += ss_speed; }
    else if(move == 'u'){ ss_y -= ss_speed; }
    else if(move == 'd'){ ss_y += ss_speed; }
}

Spaceship::~Spaceship(){}

Spaceship.hpp

#ifndef Spaceship_hpp
#define Spaceship_hpp
#include <iostream>
#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include <stdio.h>

using namespace std;

class Spaceship {
public:
    Spaceship();
    Spaceship(sf::RenderWindow&);
    ~Spaceship();
    void moveship(char);
    void drawsprite(sf::RenderWindow&);
private:
    signed int ss_x, ss_y;
    unsigned int ss_speed;
    int ss_width, ss_height, ss_radius;

};

#endif /* Spaceship_hpp */

推荐答案

正如评论中所建议的那样,这里的问题是您没有考虑计算中两帧之间经过的时间.因此,发生的情况是您在任何帧上都添加了固定量的速度,而忽略了两个连续帧可能需要非常不同的时间来完成这一事实.

As it has been suggested in the comments, the problem here is that you are not taking into account the elapsed time between two frames in your computations. Hence what happens is that you are adding a fixed amount of velocity at any frame, ignoring the fact that two consecutive frames can need a very different time to finish.

还有另一个问题(这似乎是您的问题的主要来源):您正在检查是否在事件循环中内部按下了按键.这是不正确的,只有在按下您的键并且在循环过程中发生了其他事件时才会如此.你需要在每一帧都检查一下.

There is another problem (which appears to be the main source of your issues): you are checking if keys are pressed inside the event loop. This is not correct, this will be true only if your key is pressed AND there have been other events during the loop. You need to check this out at every frame.

解决最后一个问题的另一种方法是将布尔标志设置为 true/false 当您检测到 press/release 用于您要测试的密钥.如果您查看 isKeyPressed 方法,您还会发现第二种方法比第一种方法更有效.

Another way to tackle the last issue is to have boolean flags that you set to true / false when you detect a press / release for the keys you want to test. If you have a look at the isKeyPressed method, you will also find out that this second method is ways more efficient than the first one.

更改主循环以获取每一帧经过的时间的最简单方法,根据文档 是这样的:

The simplest way to change your main loop to get the elapsed time at every frame, according to the docs is something like this:

sf::Clock sf_clock;

// Start the game loop
while (window.isOpen()) {
    // Get time elapsed since last frame
    float dt = clock.restart().asSeconds();

    // Process events
    sf::Event event;
    while (window.pollEvent(event)) {
        // Close window: exit
    }

    // Move Spaceship, this must be done outside of the pollEvent loop !
         if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) spaceship.moveship(dt, 'l');
    else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) spaceship.moveship(dt, 'r'); 
         if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) spaceship.moveship(dt, 'u');
    else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) spaceship.moveship(dt, 'd');

    }
    // Draw, etc..
}

那么你必须在moveship方法中考虑dt(我在switch case<中改变了你的if/else if/code>,我觉得在这种情况下更干净,应该更有效):

Then you have to take dt into account in moveship method (I changed your if / else if in switch case, which I find cleaner in this case and should be more efficient):

void Spaceship::moveship(float dt, char move) {
    switch (move) {
        case 'l': ss_x -= dt * ss_speed_x; break;
        case 'r': ss_x += dt * ss_speed_x; break;
        case 'u': ss_y -= dt * ss_speed_y; break;
        case 'd': ss_y += dt * ss_speed_y; break;
    }
}

按照 Jesper Juhl 的建议,我在这里做了一些整合(和你真的应该看看文章).

As suggested by Jesper Juhl, I did some integration here (and you should really have a look at the article).

顺便说一句,我建议对您的代码进行一些修改:

BTW, I would suggest some modifications in your code:

// First of all, you should use floating-point values that you convert 
// in screen space at render time for your coordinates
// You could also think about using vectors instead, I won't here
class Spaceship {
private:
    float ss_x, ss_y;
    float ss_speed_x, ss_speed_y;

    // You should also store your sprite instead of creating it over
    // and over again
    sf::Sprite ss_sprite;
};


Spaceship::Spaceship(sf::RenderWindow& game_window) {
    // You can then take those modifications into account
    // in your constructor:
    auto surface = game_window.getSize();
    ss_x = ss_y = 0.5f;
    ss_speed_x = 5.f / surface.x;
    ss_speed_y = 5.f / surface.y;
    ss_width = 128;
    ss_height = 128;
    ss_radius = ss_width/2;

    sf::Texture ship;
    if (!ship.loadFromFile(resourcePath() + "space-shuttle-64.png")) {
        // This is really an awful way to handle an error, but I won't
        // go into details here. A better way would be to have an Init()
        // method that returns an error code on failure for example.
        exit(EXIT_FAILURE);
    }

    ss_sprite = sf::Sprite(ship);
    // http://www.sfml-dev.org/documentation/2.4.1/classsf_1_1Transformable.php#a56c67bd80aae8418d13fb96c034d25ec
    ss_sprite.setOrigin(ss_width / 2, ss_height / 2);
}

// Finally, you reflect those modifications in your draw code
void Spaceship::drawsprite(sf::RenderWindow& game_window){
    auto size = game_window.getSize();
    ss_sprite.setPosition(ss_x * size.x, ss_y * size.y);
    game_window.draw(ss_sprite);
}

这篇关于SFML 2D 游戏——流畅的飞船运动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆