实现碰撞响应,当前实现执行太多检查

时间:2013-12-15 17:01:21

标签: c++ collision sfml

当两个实体之间的碰撞导致与第三个实体的第二次碰撞时,我不知道如何处理我的碰撞响应。

蓝色箭头是最右边实体的速度,下面的数字是单个碰撞响应。

单次碰撞

在开始时,最左边和中间的实体不会相互碰撞,但是在最右边的实体与中间实体碰撞之后,速度中间实体获得现在使它与最左边的实体碰撞

最终结果应如何

或者如果最左边的实体是墙 - > 0,0,-v

当前实施:检查冲突,直到完成所有操作

碰撞处理前的速度

  • 0,0,< -10
  • 碰撞:2& 3

如果实体速度因碰撞而发生变化,请执行新的检查

1.碰撞检查后:

  • 0,< -5,< -5
  • 碰撞:1& 2

碰撞检查后:

  • < -2.5,< -2.5,< -5
  • 碰撞:2& 3

3.碰撞检查后:

  • < -2.5,< -3.75,< -3.75
  • 碰撞:1& 2

4.碰撞检查后:

  • < -3.125,< -3.125,< -3.75
  • 碰撞:2& 3

第n次碰撞检查后:

  • < -3.33,< -3.33,< -3.33
  • 没有碰撞
  • 继续

问题:正如我们清楚地看到的那样,通过这种方式进行过多的碰撞检查以解决简单的碰撞问题。我该如何改进呢?

使用sfml编写的示例代码:

我的测试用例中有3种类型的实体:

  • 可推
  • 可通过

实体,速度和boundingRect:

碰撞响应:

  • wall< - > pushable:不能移动到墙内,推开可推出的实体
  • pushable< - > pushable:两者都被排斥在一起,所以他们不会碰撞
  • wall< - > wall:跳过墙与所有可通过地形的检查之间的碰撞检查

示例代码中的控件

  • 箭头键:移动绿色实体
  • 鼠标左键:产生可推送的实体
  • 鼠标右键:生成墙体
  • 鼠标中键:产生移动的可推送实体

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

const sf::Vector2f entitySize(16.f, 16.f);

bool isMovingUp = false;
bool isMovingDown = false;
bool isMovingRight = false;
bool isMovingLeft = false;

enum Category
{
    wall,
    pushable,
    passable,
};

class Entity
{
    public:
        Entity(sf::Vector2f position, sf::Color color, Category type) : position(position), type(type)
        {
            shape.setSize(entitySize);
            shape.setFillColor(sf::Color::Transparent);
            shape.setOutlineColor(color);
            shape.setOutlineThickness(1.f);
        }

        sf::Vector2f position;
        sf::Vector2f velocity;
        sf::Vector2f adjustedVelocity;
        sf::Vector2f defaultVelocity;
        sf::RectangleShape shape;
        Category type;
};

void DrawEntity(sf::RenderWindow& window, Entity& entity)
{
    entity.shape.setPosition(entity.position - entity.shape.getSize() / 2.f);
    window.draw(entity.shape);
}

sf::FloatRect GetBoundingRect(const Entity& entity)
{
    return sf::FloatRect(entity.position+entity.velocity - entitySize / 2.f, entitySize);
}

void HandleCollision(std::vector<Entity>& entities)
{
    bool allCollisionsChecked = false;

    while(!allCollisionsChecked)
    {
        allCollisionsChecked = true;

        // Pair all possible combinations, but only once per pair
        for (auto first = entities.begin(); first != entities.end(); ++first)
        {
            for (auto second = std::next(first); second != entities.end(); ++second)
            {
                if (first->type == Category::passable || second->type == Category::passable || first->type == Category::wall && second->type == Category::wall)
                    continue;

                sf::FloatRect intersection;
                if (GetBoundingRect(*first).intersects(GetBoundingRect(*second), intersection))
                {
                    if (second->position == first->position)
                        second->position.x += entitySize.x;

                    sf::Vector2f direction = second->position - first->position;
                    sf::Vector2f offset;

                    // X collision
                    if (abs(direction.x) > abs(direction.y))
                        offset.x = ((direction.x<0)?-1:1)*intersection.width;

                    // Y collision
                    if (abs(direction.x) < abs(direction.y))
                        offset.y = ((direction.y<0)?-1:1)*intersection.height;

                    if(first->type == Category::pushable && second->type == Category::pushable)
                    {
                        first->velocity -= offset / 2.f;
                        second->velocity += offset / 2.f;
                        allCollisionsChecked = false;
                    }
                    else if(first->type == Category::pushable)
                    {
                        first->velocity -= offset;
                        allCollisionsChecked = false;
                    }
                    else if(second->type == Category::pushable)
                    {
                        second->velocity += offset;
                        allCollisionsChecked = false;
                    }
                }
            }
        }
    }
}

void handlePlayerInput(sf::Keyboard::Key key, bool isPressed)
{
    switch(key)
    {
    case sf::Keyboard::Up:
        isMovingUp = isPressed;
        break;
    case sf::Keyboard::Down:
        isMovingDown = isPressed;
        break;
    case sf::Keyboard::Left:
        isMovingLeft = isPressed;
        break;
    case sf::Keyboard::Right:
        isMovingRight = isPressed;
        break;
    }
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(1280, 720), "SFML Application");
    window.setVerticalSyncEnabled(true);

    std::vector<Entity> entities;

    Entity player(sf::Vector2f(1280/2, 720/2), sf::Color::Green, Category::pushable);
    entities.push_back(player);

    size_t cols = 1280/int(entitySize.x);
    size_t rows = 720/int(entitySize.y);

    for (size_t i=0; i < cols*rows; ++i)
        if (i%cols == rows/5 && i/cols > rows/6 && i/cols < rows*5/6 || i%cols >= rows/5 && i%cols <= rows*4/5 && (i/cols == rows/6 || i/cols == rows*5/6))
            entities.push_back(Entity(sf::Vector2f(entitySize.x*(i%cols)+entitySize.x/2, entitySize.y*(i/cols)+entitySize.y/2), sf::Color::Yellow, Category::wall));
        //else
            //entities.push_back(Entity(sf::Vector2f(entitySize.x*(i%cols)+entitySize.x/2, entitySize.y*(i/cols)+entitySize.y/2), sf::Color::Transparent, Category::passable));

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::MouseButtonPressed)
            {
                sf::Vector2i pixel(event.mouseButton.x, event.mouseButton.y);
                sf::Vector2f coord = window.mapPixelToCoords(pixel);

                if (event.mouseButton.button == sf::Mouse::Left)
                {
                    Entity pushable(coord, sf::Color::Blue, Category::pushable);
                    entities.push_back(pushable);
                }
                else if (event.mouseButton.button == sf::Mouse::Right)
                {
                    Entity wall(coord, sf::Color::Yellow, Category::wall);
                    entities.push_back(wall);
                }
                else if (event.mouseButton.button == sf::Mouse::Middle)
                {
                    Entity mover(coord, sf::Color::Magenta, Category::pushable);
                    mover.defaultVelocity.x = -1.f;
                    entities.push_back(mover);
                }
            }

            switch (event.type)
            {
                case sf::Event::KeyPressed:
                    handlePlayerInput(event.key.code, true);
                    break;

                case sf::Event::KeyReleased:
                    handlePlayerInput(event.key.code, false);
                    break;

                case sf::Event::Closed:
                    window.close();
                    break;
            }
        }

        if (isMovingUp)
            entities[0].velocity.y -= 5.f;
        if (isMovingDown)
            entities[0].velocity.y += 5.f;
        if (isMovingLeft)
            entities[0].velocity.x -= 5.f;
        if (isMovingRight)
            entities[0].velocity.x += 5.f;

        if (entities[0].velocity.x != 0.f && entities[0].velocity.y != 0.f)
        {
            entities[0].velocity.x /= std::sqrt(2.f);
            entities[0].velocity.y /= std::sqrt(2.f);
        }

        HandleCollision(entities);

        // Apply and reset velocities
        for (Entity& e : entities)
        {
            e.position += e.velocity;
            e.velocity = e.defaultVelocity;
        }

        // Draw
        window.clear();
        for (Entity& e : entities)
            DrawEntity(window, e);
        window.display();
    }
}

示例结果

洋红色实体以恒定速度向左移动,黄色实体在墙上移动。

没有检查其他碰撞造成的碰撞

期望的结果:

检查碰撞时的相同情况,直到所有问题都解决了

我怎么能实现这个目标?

0 个答案:

没有答案