碰撞并不一致

时间:2018-04-09 20:59:24

标签: c++ sdl collision-detection collision entitymanager

我目前正在使用SDL为一个项目开发一个Mario Bros副本,我刚刚实施了碰撞,虽然我碰到了一些奇怪的地方,碰撞有时会起作用,有时会赢得“碰撞”。每次加载游戏时都会这样做。这些gif几乎可以解释我的意思: enter image description here

enter image description here

enter image description here

正如你所看到的,碰撞有时会起作用,但每次重新加载游戏时,结果都会改变,我实际上不知道为什么。还有其他一些奇怪的事情发生在例如你可以看到围绕每个实体的红色边界,这是他们的碰撞盒但有时你可以在gif中看到,有些块不是尽管发生了碰撞,但仍会显示碰撞框。我过去也做过有时甚至没有渲染的块,虽然暂时没有发生过。

我如何加载地图来自一个如下所示的txt文件: enter image description here

然后使用我的LevelMap类加载:

bool LevelMap::CreateMap(std::string path)
{
std::vector<std::string> lines = std::vector<std::string>();
std::fstream file;
file.open(path, std::fstream::in);


char text[256];
std::string currentLine;
while (!file.eof())
{
    file.getline(text, 256);
    currentLine = text;
    currentLine.erase(std::remove_if(currentLine.begin(), currentLine.end(), 
std::isspace), currentLine.end()); //Removes Spaces from String

    //Checks if String is at correct Width
    if (currentLine.size() != mapWidth)
    {
        std::cout << "Map Has Incorrect Width!" << std::endl;
        return false;
    }

    lines.push_back(currentLine);
}

// Loop over every tile position,
for (int y = 0; y < mapHeight; y++)
{
    for (int x = 0; x < mapWidth; x++)
    {
        char tileType = lines.at(y)[x]; //Gets Each Tile Value

        Entity* entity = LoadEntity(tileType, x, y);
        if (entity != nullptr)
        {
            entityManager->AddEntity(entity);
        }
    }
}

return true;
}

Entity* LevelMap::LoadEntity(char tileType, int x, int y)
{
x *= (SCREEN_WIDTH / mapWidth);
y *= (SCREEN_WIDTH / mapWidth);

switch (tileType)
{
    //Creates Block
    case '1':
        return CreateBlock(Vector2D(x, y));
    break;

    //Creates Pipe facing Left
    case '2':
        return CreatePipe(Vector2D(x, y), FACING::FACING_RIGHT);
    break;

    //Creates Pipe facing Right
    case '3':
        return CreatePipe(Vector2D(x, y), FACING::FACING_LEFT);
    break;

    //Creates Player
    case 'P':
        return CreatePlayer(Vector2D(x, y));
    break;

    default:
        return nullptr;
    break;

}
}

Entity* LevelMap::CreateBlock(Vector2D position)
{
    Block* block = new Block(renderer, position, this);
    return block;
}

Entity* LevelMap::CreatePipe(Vector2D position, FACING direction)
{
    Pipe* pipe = new Pipe(renderer, position, direction, this);
    return pipe;
}

Entity* LevelMap::CreatePlayer(Vector2D position)
{
    Player* player = new Player(renderer, position, this);
    return player;
}

它基本上获取该.txt文件中的每个数字,使用它来使用LoadEntity函数创建实体,该函数根据数字返回实体。然后将此实体添加到我称为EntityManager的另一个类中的Vector中,该类用于处理游戏中的所有实体。

这样就可以加载我的地​​图了。我的碰撞代码如下所示:

void Collision::Update(Rect2D boundingBox)
{
    this->boundingBox = boundingBox;
}

void Collision::DrawBoundingBox(SDL_Renderer* renderer)
{
SDL_Rect* rect = new SDL_Rect();
rect->x = boundingBox.X;
rect->y = boundingBox.Y;
rect->w = boundingBox.width;
rect->h = boundingBox.height;

SDL_RenderDrawRect(renderer, rect);
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
}

bool Collision::Intersects(Entity* entity)
{
Rect2D entityBoundingBox = entity->GetCollsion()->GetCollisionBox();

//Checks if two rectangles intersect 
if(boundingBox.X + boundingBox.width < entityBoundingBox.X  || 
entityBoundingBox.X + entityBoundingBox.width < boundingBox.X || 
boundingBox.Y + boundingBox.height < entityBoundingBox.Y || 
entityBoundingBox.Y + entityBoundingBox.height < boundingBox.Y)
{
    return false;
}
else
{
    return true;
}
}

更新函数更新每个实体周围的边界框,DrawBoundBox函数呈现您在gif中看到的红色边框,然后使用相交函数检查调用函数的实体是否与传递的函数相交在实体的碰撞中。

这基本上是主要部分,如果需要,我会解释更多但是对于碰撞的更新,它在实体类更新功能中被调用:

void Entity::CollisionUpdate()
{
    Rect2D boundingBox = Rect2D(position.X, position.Y, sprite.width, 
sprite.height);
    collsion->Update(boundingBox); //Updates the Collsion Position
}

实体的更新和渲染由我提到的实体管理器中的更新和渲染功能调用:

void EntityManager::Update(float deltaTime, SDL_Event e)
{
if(!entities.empty())
{
    //Runs the Update function of all entities and adds Entites that need to be deleted to a list
    for(const auto entity : entities)
    {       
        if(entity.second->ShouldDestroy())
        {
            toDeleteEntities.push_back(entity.second);
        }

        //std::cout << entity.second->GetTag() << std::endl;
        entity.second->Update(deltaTime, e);
    }

    //Removes entities that need to be deleted
    for(int i = 0; i < toDeleteEntities.size(); i++)
    {
        RemoveEntity(toDeleteEntities[i], i);
    }
}
}

void EntityManager::Render()
{
if(!entities.empty())
{
    for(const auto entity : entities)
    {
        entity.second->Render();
    }
}
}

然后最后由LevelMap调用实体管理器的更新和渲染函数。

对于长段文字感到抱歉。我希望有足够的细节让别人弄清楚错误是什么以及为什么会发生这种情况,我非常感谢你的帮助。如果您需要更多详细信息,请随便询问。

修改 在实体的更新函数中调用CollisionUpdate(),如上所述由EntityManager调用:

void Entity::Update(float deltaTime, SDL_Event e)
{
//Screen Warps
if (position.X <= 0 - GetTexture()->GetWidth())
{
    position.X = SCREEN_WIDTH - GetTexture()->GetWidth();
}
else if (position.X >= SCREEN_WIDTH)
{
    position.X = 0 + GetTexture()->GetWidth();
}

if (collsion != nullptr)
{
    CollisionUpdate();
}
}

此外,在玩家和敌人更新功能中都会调用相交功能:

void Player::CollisionUpdate()
{
Entity::CollisionUpdate();

std::vector<Entity*> entities = map->GetEntityManager()->GetEntities("");
for (int i = 0; i < entities.size(); i++)
{
    if (entities[i]->GetCollsion() != nullptr)
    {
        //What Player Collides With
        if(collsion->Intersects(entities[i]))
        {
            if (entities[i]->GetTag() == "Block")
            {
                SetGravity(false);
            }
            else
            {
                SetGravity(true);
            }
        }
    }
}
}

GetEntities函数是EntityManager类的一部分,它将获得所有具有传入标记的实体的Vector,因此Block将有一个&#34; Block&#34;标记,如果你将它传递给函数,那么它只会返回一个带有块的函数。 这是你想看到的GetEntities函数:

std::vector<Entity*> EntityManager::GetEntities(std::string tag)
{
std::vector<Entity*> entityList;

//If Tag is not empty, find certain entities with tag
if (tag != "")
{
    for (const auto entity : entities)
    {
        if (entity.second->GetTag() == tag)
        {
            entityList.push_back(entity.second);
        }
    }
}
else if (tag == "") //Else find all entities
{
    for (const auto entity : entities)
    {
        entityList.push_back(entity.second);
    }
}

return entityList;
}

3 个答案:

答案 0 :(得分:1)

我想出了这个问题。这是我检查碰撞导致问题的实体的方式。我所拥有的是一个for循环,它将循环遍历每个实体并检查实体是否与它们发生冲突,如果它发生则会禁用Gravity。

问题是for循环会立即进入下一个实体并检查碰撞。所以它会让实体与一个块碰撞并且会停止Gravity,但是它会进入列表中的下一个实体,看到我们没有碰撞它然后启用Gravity,尽管仍然与同一个块碰撞。

所以不要只检查单个实体的碰撞,例如Mario下的块: enter image description here

它反而检查了碰撞所有这些: enter image description here

因为我们当前没有与其他所有块碰撞,所以它再次启用了Gravity。

答案 1 :(得分:0)

尝试在碰撞功能中切换返回语句。当他们碰撞或返回true时,你似乎正在返回false。

答案 2 :(得分:0)

if(boundingBox.X + boundingBox.width < entityBoundingBox.X  || 
   entityBoundingBox.X + entityBoundingBox.width < boundingBox.X || 
   boundingBox.Y + boundingBox.height < entityBoundingBox.Y || 
   entityBoundingBox.Y + entityBoundingBox.height < boundingBox.Y)


 //                        X  Y  W  H                      
 let boundingBox        = {10,10,10,10}
 let entityBoundingBox  = {10,20,10,10}

10 + 10 < 10 == false
10 + 10 < 10 == false
10 + 10 < 20 == false
20 + 10 < 10 == false

这在你的逻辑中返回true,意味着碰撞。这些不会发生碰撞。很确定你的碰撞逻辑是不正确的。