碰撞不一致

Collision isn't consistent

我目前正在使用 SDL 为一个项目开发 Mario Bros 副本,我刚刚实施了 Collision,尽管我已经 运行 进入 st运行ge 中,Collision 有时会起作用有时我每次加载游戏时都不会。这些 gif 几乎可以解释我的意思:

如您所见,碰撞有时会起作用,但每次我重新加载游戏时,结果都会发生变化,我实际上不知道为什么。还有其他 st运行ge 事情会发生,例如你可以看到每个实体周围的红色边框,那是它们的碰撞框,但有时你可以在 gif 中看到,有些方块没有碰撞框尽管有碰撞,但仍显示。我过去也有块有时甚至不渲染,虽然已经有一段时间没有发生了。

我加载地图的方式是从如下所示的 txt 文件加载地图:

然后使用我的 LevelMap Class:

加载
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 函数创建一个实体,该函数 return 是一个基于数字的实体。然后将此实体添加到另一个 class 中的 Vector,我调用了 EntityManager,它用于处理游戏中的所有实体。

这就是加载我的地​​图。我的碰撞代码如下所示:

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;
}
}

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

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

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 class 的一部分,它将获得所有具有传入标记的实体的矢量,因此 Block 将具有一个 "Block" 标记,如果您传递将其放入函数中,那么它只会 return 一个带有块的函数。 这就是你要看的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;
}

尝试在碰撞函数中切换 return 语句。当它们发生碰撞时,您似乎 returning 错误或 returning 正确。

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

这returns符合你的逻辑,意思是碰撞。这些不会发生冲突。很确定你的碰撞逻辑不正确。

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

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

因此,与其仅仅检查与单个实体(例如马里奥下的方块)的碰撞:

而是针对所有这些检查碰撞:

而且因为我们目前没有与所有其他方块发生碰撞,所以它再次启用重力。