提高基于组件的游戏引擎的效率

Posted

技术标签:

【中文标题】提高基于组件的游戏引擎的效率【英文标题】:Increase efficiency of component-based game engine 【发布时间】:2018-09-28 22:10:50 【问题描述】:

我正在尝试构建一个基于组件的游戏引擎,我的效率肯定是欠缺的。这个问题在我的碰撞检测中最为明显,因为我将每个游戏对象与其他游戏对象进行比较以查看它们是否发生碰撞。这里是碰撞检测功能:

void PhysicsSystem::update(float dt) 
std::vector<GameEngine::GameObject> moveObjects = manager->getAllObjectsWithComponent("move");
std::vector<GameEngine::GameObject> physicsObjects = manager->getAllObjectsWithComponent("physics");

for (int i = 0; i < moveObjects.size(); i++) 
    MoveComponent* mComponent = static_cast<MoveComponent*>(manager->getComponentByType("move", moveObjects[i]));
    PhysicsComponent* pComponent = static_cast<PhysicsComponent*>(manager->getComponentByType("physics", moveObjects[i]));
    RenderComponent* rComponent = static_cast<RenderComponent*>(manager->getComponentByType("render", moveObjects[i]));

    if (pComponent == nullptr || mComponent == nullptr || rComponent == nullptr) 
        continue;
    

    if (!pComponent->isSolid()) 
        continue;
    

    glm::vec4 coords1 = rComponent->getRenderCoords();


    for (int j = 0; j < physicsObjects.size(); j++) 
        PhysicsComponent* pComponent2 = static_cast<PhysicsComponent*>(manager->getComponentByType("physics", physicsObjects[j]));
        RenderComponent* rComponent2 = static_cast<RenderComponent*>(manager->getComponentByType("render", physicsObjects[j]));
        if (pComponent2 == nullptr || rComponent2 == nullptr) 
            continue;
        

        if (!pComponent2->isSolid()) 
            continue;
        


        glm::vec4 coords2 = rComponent2->getRenderCoords();



        int dist = sqrt(pow((coords1.x - coords2.x), 2) + pow((coords1.y - coords2.y), 2));
        if (dist > pComponent->getCollisionRadius()) 
            continue;
        

        if (GameEngine::Physics::checkCollision(coords1, coords2)) 
            pComponent->addCollision(coords2);
        
    

我试图通过使用碰撞半径忽略不接近当前游戏对象的游戏对象来提高效率,但这似乎没有任何作用,真正导致问题的代码行是

PhysicsComponent* pComponent2 = static_cast<PhysicsComponent*>(manager->getComponentByType("physics", physicsObjects[j]));
RenderComponent* rComponent2 = static_cast<RenderComponent*>(manager->getComponentByType("render", physicsObjects[j]));

这些调用我的 GameObjectManager 类中的一个函数。这是该函数的代码:

Component* GameObjectManager::getComponentByType(std::string type, GameObject object) 
    std::unordered_map<std::string, std::unordered_map<GLuint, Component*>>::iterator it = componentsByType.find(type);
    if (it == componentsByType.end()) 
        return nullptr;
    
    std::unordered_map<GLuint, Component*>::iterator it2 = it->second.find(object.getGameObjectID());
    if (it2 == it->second.end()) 
        return nullptr;
    

    return it2->second;

如果我去掉这两条线,游戏速度会大大加快。有什么我做错了吗?我认为在 unordered_map 中查找对象是一个恒定时间操作,所以我不确定如何提高速度。有没有更有效的方式来处理我的组件?任何帮助将不胜感激,谢谢!

【问题讨论】:

您使用的是优化/发布版本吗? 通常你不会遍历对象而是遍历组件。在您的情况下,可能是物理组件。这将为您节省一次搜索。我已经看到 ECS 是用组件向量实现的,而不是哈希图,但这需要重写大部分代码库。 【参考方案1】:

这里有很多地方我看到了小的改进,尽管其中一些可能会带来相对较大的改进。您正在制作大量不需要制作的潜在大型数据的副本。

    moveObjectsphysicsObjects 是向量,是原始数据的副本。这些需要是副本吗,或者它们可以是const &amp; 存储在游戏管理器中的向量吗? 您对pComponentmComponentrComponent 进行了多项检查。如果任何检查失败,则继续下一个对象。获取其中一个值,检查nullptr,然后获取下一个值。先做pComponent,然后在其他两个之前检查它是否牢固。 对pComponent2rComponent2j 循环执行相同的操作。 检查距离时,不要计算平方根。而是比较距离的平方。 通过const &amp;object 参数传递给getComponentByType,这样您就不会复制它。您只能访问其中的一个字段。 使用基于范围的 for 循环。如果这不可能,请考虑将向量的大小存储在一个变量中,而不是在每次循环迭代时调用size。编译器可能会优化冗余调用,但唯一确定的方法是检查生成的优化代码。

【讨论】:

7.重新考虑GameObjectManager::getComponentByTypeconst auto&amp; moveMap = componentsByType.at("move"); const auto&amp; physicMap = componentsByType.at("physics"); 将避免大量查找/散列。 这些是非常有用的提示,我看到我的 FPS 有了飞跃,尤其是在实施 #2 之后。它仍然没有我希望的那么快,但这些是一些非常聪明的修复,绝对让我在正确的轨道上思考,非常感谢!【参考方案2】:

您在内部循环中查询组件moveObjects.size() 次,这意味着很多冗余工作。

您应该在收集组件的主循环之前放置一个预处理循环:

for (int j = 0; j < physicsObjects.size(); j++) 
    PhysicsComponent* pComponent2 = static_cast<PhysicsComponent*>(manager->getComponentByType("physics", physicsObjects[j]));
    RenderComponent* rComponent2 = static_cast<RenderComponent*>(manager->getComponentByType("render", physicsObjects[j]));
    if (pComponent2 == nullptr || rComponent2 == nullptr) 
        continue;
    

    if (!pComponent2->isSolid()) 
        continue;
    

    // add pComponent2 and rComponent2 into an array here

然后,在内部循环中,使用收集到的数据,而不是从管理器中查询。

注意,如果您有很多对象,您可能希望将它们放入一些空间分区数据结构(octree/Kd-tree/BSP-tree)中以避免O(n^2) 运行时间。

【讨论】:

以上是关于提高基于组件的游戏引擎的效率的主要内容,如果未能解决你的问题,请参考以下文章

Storm-Engine 基于 C++ 的开源游戏引擎

Unity 2D精灵(Sprite)与2D物理组件

开发手机游戏需要开发工具和引擎吗

什么是白鹭引擎

什么是游戏引擎?

Unity游戏引擎都有哪些优势