关于C#:SFML和将对象移动到位置

SFML and moving object to position

我的SFML棋盘游戏有问题。我得到了在木板上移动的令牌,木板有40个字段,每个字段都定义了positionX和positionY。但是有时候,这完全是随机的,我的代币错过了他的目标位置,在局上进行了额外的回合,然后停下来。

1
2
3
4
5
6
7
class Field {
protected:  
    int m_positionX;
    int m_positionY;
public:
    //getters and setters
};

每个玩家都有PositionID目标位置坐标和CircleShape,这是会引起麻烦的令牌

1
2
3
4
5
6
7
8
class Player {
    sf::CircleShape m_token;
    int m_positionID = 0;      
    int m_targetPositionX;
    int m_targetPositionY;
public:
    //getters and setters
}

然后在我的GameEngine类中,我得到了setInMotion()函数,该函数为播放器设置了几个变量,我猜这里所谓的函数代码无关紧要,函数名称说明了一切

1
2
3
4
5
6
void GameEngine::setInMotion(int number) {  
        m_players[m_activePlayer].startMoving();
        m_players[m_activePlayer].incrementPositionID(number);                          
        m_players[m_activePlayer].setTargetPositionX(m_gameBoard.getField(m_players[m_activePlayer].getPositionID()).getPositionX());
        m_players[m_activePlayer].setTargetPositionY(m_gameBoard.getField(m_players[m_activePlayer].getPositionID()).getPositionY());
}

然后是最重要的功能,它实际上在板上移动令牌。
#定义TOKEN_SPEED 1000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Player::moveForward(sf::Time dt) {
    if ((int)m_token.getPosition().x >= 240 && (int)m_token.getPosition().y >= 600) {
        this->m_token.move(-TOKEN_SPEED * dt.asSeconds(), 0);      
    }

    if ((int)m_token.getPosition().x <= 240 && (int)m_token.getPosition().y >= 40) {
        this->m_token.move(0, -TOKEN_SPEED * dt.asSeconds());    
    }

    if ((int)m_token.getPosition().x <= 800) && (int)m_token.getPosition().y <= 40) {
        this->m_token.move(TOKEN_SPEED * dt.asSeconds(), 0);    
    }

    if ((int)m_token.getPosition().x >= 800) && (int)m_token.getPosition().y <= 600) {
        this->m_token.move(0, TOKEN_SPEED * dt.asSeconds());        
    }

    if ((int)m_token.getPosition().x >= getTargetPositionX() - 1 && (int)m_token.getPosition().x <= getTargetPositionX() + 1 &&
        (int)m_token.getPosition().y >= getTargetPositionY() - 1 && (int)m_token.getPosition().y <= getTargetPositionY() + 1) {
        this->stopMoving();
    }
}

令牌在4个方向上移动,方向取决于当前位置(提醒,它是棋盘游戏,这会更容易理解我在这里所做的事情)
最后,一旦当前位置等于目标位置,令牌便停止移动。现在这是主要问题,我的令牌有时会错过目标位置,在板上绕行,然后停在他错过的目标位置。它完全随机发生。就像有时应用程序没有足够的时间检查应该停止令牌的if语句。放慢TOKEN_SPEED会有所帮助,但随后它会变得太慢。这就是为什么我的最后一个if语句看起来像这样的另一个原因,如果我仅检查(int)m_token.getPosition().x == getTargetPositionX(),它几乎总是会丢失。不将浮点位置值转换为int会使情况更糟。最后是我来自gameLoop()

update()函数

1
2
3
4
5
6
void GameState::update(sf::Time dt) {  

    if (m_gameEngine.getActivePlayer().isMoving()) {
        m_gameEngine.getActivePlayer().moveForward(dt);        
    }
}


我认为您正在使它变得比所需的复杂得多。您的特定问题很可能是时间问题:经过的时间(dt)可能足以让您简单地越过目标位置(添加的1不足以补偿)。除非您要执行固定的时间步长运动,否则您就无法避免这种情况(如果您想了解更多,只需查找该术语即可)。

但是,我将以完全不同的方式来实现运动,这也将使整个实现更加容易和动态。

首先,我使用矢量定义游戏板及其位置。如果要使用更复杂的路径(例如分支或快捷方式),则可能需要定义自己的链接列表或类似内容。

在我的示例中,回到比赛场地,我通过一系列位置(每个位置代表玩家可能站立的一个可能的场地)来定义木板:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::vector<sf::Vector2f> board;
board.push_back({75, 75});
board.push_back({150, 50});
board.push_back({250, 50});
board.push_back({325, 75});
board.push_back({350, 150});
board.push_back({350, 250});
board.push_back({325, 325});
board.push_back({250, 350});
board.push_back({150, 350});
board.push_back({75, 325});
board.push_back({50, 250});
board.push_back({50, 150});

玩家位置本身为简单起见,我只有一个代币a"只是一个浮点数,代表比赛场上当前代币位置的"索引":

1
float playerPos = 0.f;

听起来奇怪吗?是的,或多或少。数字的整数部分表示索引,而小数点后面的部分表示字段之间的实际位置。例如,值为3.75表示令牌在第四字段(索引3)和第五字段(索引4)之间为75%。

绘制游戏板非常简单,我已将其简化为仅为每个字段绘制一些圆形:

1
2
3
4
for (auto &pos : board) {
    field.setPosition(pos);
    window.draw(field);
}

现在要绘制令牌,我们必须确定令牌之前和之后的点:

1
2
const sf::Vector2f playerPosLast = board[static_cast<std::size_t>(playerPos)];
const sf::Vector2f playerPosNext = board[static_cast<std::size_t>(playerPos + 1) % board.size()];

强制类型转换将取整值,在第二种情况下,我只是确保我们会溢出回到木板的开始。

要确定点之间的位置,我们只需减去舍入后的位置:

1
const float step = playerPos - static_cast<std::size_t>(playerPos);

虽然我们现在拥有计算令牌可视表示位置的所有内容,但我想添加一个小的"跳跃偏移量"以动画化字段之间的路径:

1
const sf::Vector2f jumpOffset(0, 25.f * (2.f * (step - .5)) * (2.f * (step - .5)) - 25.f);

此后的实际数学运算在这里并不重要,但是由于步长范围是0到1,因此很容易使它成为类似于"路径"的抛物线(偏移量从0更改为-25,然后在点之间重新转换为0 )。

现在,我们只需要使用一些矢量数学来设置令牌的位置(根据计算出的step确定前一个字段和下一个字段之间的点,并添加跳转偏移量):

1
2
player.setPosition(playerPosLast + (playerPosNext - playerPosLast) * step + jumpOffset);
window.draw(player);

但是如何移动播放器?很简单如果要让播放器向前移动4个字段,请将4添加到playerPos。如果要向后移2个字段,请减去2。一旦溢出到末尾,请确保将其移回木板的有效范围。

如果编译并运行以下演示,您将得到一个简单的窗口,其中令牌无限期地从一个字段跳到另一个字段:

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <SFML/Graphics.hpp>
#include <vector>

int main()
{
    sf::RenderWindow window({480, 480},"Board Game");

    std::vector<sf::Vector2f> board;
    board.push_back({75, 75});
    board.push_back({150, 50});
    board.push_back({250, 50});
    board.push_back({325, 75});
    board.push_back({350, 150});
    board.push_back({350, 250});
    board.push_back({325, 325});
    board.push_back({250, 350});
    board.push_back({150, 350});
    board.push_back({75, 325});
    board.push_back({50, 250});
    board.push_back({50, 150});

    sf::CircleShape field(25, 24);
    field.setFillColor(sf::Color::White);
    field.setOutlineColor(sf::Color::Black);
    field.setOutlineThickness(2);
    field.setOrigin({25, 25});

    sf::CircleShape player(20, 3);
    player.setFillColor(sf::Color::Red);
    player.setOutlineColor(sf::Color::Black);
    player.setOutlineThickness(2);
    player.setOrigin({20, 20});

    float playerPos = 0.f;

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;
                default:
                    break;
            }
        }
        window.clear({0, 127, 127});
        for (auto &pos : board) {
            field.setPosition(pos);
            window.draw(field);
        }

        const sf::Vector2f playerPosLast = board[static_cast<std::size_t>(playerPos)];
        const sf::Vector2f playerPosNext = board[static_cast<std::size_t>(playerPos + 1) % board.size()];
        const float step = playerPos - static_cast<std::size_t>(playerPos);

        const sf::Vector2f jumpOffset(0, 25.f * (2.f * (step - .5)) * (2.f * (step - .5)) - 25.f);
        player.setPosition(playerPosLast + (playerPosNext - playerPosLast) * step + jumpOffset);
        window.draw(player);

        window.display();

        if ((playerPos += .001f) > board.size())
            playerPos -= board.size();
    }
}