gpt4 book ai didi

c++ - 在 Unity 中用 C++ 实现组件系统

转载 作者:可可西里 更新时间:2023-11-01 16:11:07 25 4
gpt4 key购买 nike

我一直在尝试制作一个类似于 Unity 的基于组件的系统,但使用 C++。我想知道如何 GetComponent() Unity 实现的方法有效。这是一个非常强大的功能。具体来说,我想知道它使用什么样的容器来存储其组件。
我在此函数的克隆中需要的两个标准如下。 1. 我还需要返回任何继承的组件。例如,如果 SphereCollider继承对撞机,GetComponent<Collider>()将返回 SphereCollider附在GameObject ,但是 GetComponent<SphereCollider>()不会返回任何 Collider随附的。 2.我需要快速的功能。最好是使用某种散列函数。
对于标准一,我知道我可以使用类似于以下实现的东西

std::vector<Component*> components
template <typename T>
T* GetComponent()
{
for each (Component* c in components)
if (dynamic_cast<T>(*c))
return (T*)c;
return nullptr;
}
但这不符合快速的第二个标准。为此,我知道我可以做这样的事情。
std::unordered_map<type_index, Component*> components
template <typename T>
T* GetComponent()
{
return (T*)components[typeid(T)];
}
但同样,这不符合第一个标准。
如果有人知道结合这两个功能的某种方法,即使它比第二个示例慢一点,我也愿意牺牲一点。谢谢!

最佳答案

由于我正在编写自己的游戏引擎并采用相同的设计,因此我想我会分享我的结果。
概述
我为我想用作 Components 的类编写了自己的 RTTI。我的 GameObject实例。打字量减少#define两个宏:CLASS_DECLARATIONCLASS_DEFINITIONCLASS_DECLARATION声明唯一 static const std::size_t将用于识别 class类型 ( Type ) 和 virtual允许对象遍历它们的函数 class通过调用它们的同名父类函数( IsClassType )来实现层次结构。CLASS_DEFINITION定义了这两件事。即Type被初始化为 class 的字符串化版本的哈希值名称(使用 TO_STRING(x) #x ),以便 Type比较只是一个 int 比较而不是一个字符串比较。std::hash<std::string>是使用的散列函数,它保证相等的输入产生相等的输出,并且冲突次数接近于零。

除了散列冲突的低风险之外,这种实现还有一个额外的好处,即允许用户创建自己的 Component使用这些宏的类而不必引用|扩展一些主include enum class 的文件s,或使用 typeid (仅提供运行时类型,不提供父类)。

添加组件
此自定义 RTTI 简化了 Add|Get|RemoveComponent 的调用语法仅指定 template类型,就像 Unity 一样。AddComponent方法完美地将通用引用可变参数包转发到用户的构造函数。因此,例如,用户定义的 Component -派生 class CollisionModel可以有构造函数:

CollisionModel( GameObject * owner, const Vec3 & size, const Vec3 & offset, bool active );
然后稍后用户只需调用:
myGameObject.AddComponent<CollisionModel>(this, Vec3( 10, 10, 10 ), Vec3( 0, 0, 0 ), true );
注意 Vec3 的显式构造s 因为如果使用推导的初始化列表语法(如 { 10, 10, 10 }),完美转发可能无法链接不管 Vec3的构造函数声明。

此自定义 RTTI 还解决了 std::unordered_map<std::typeindex,...> 的 3 个问题解决方案:
  • 即使使用 std::tr2::direct_bases 进行层次结构遍历最终结果仍然是 map 中相同指针的重复项。
  • 用户不能添加多个等效类型的组件,除非使用允许/解决冲突而不覆盖的映射,这会进一步减慢代码速度。
  • 没有不确定和缓慢dynamic_cast需要,直接static_cast .

  • 获取组件 GetComponent只使用 static const std::size_t Typetemplate type 作为 virtual bool IsClassType 的参数方法并迭代 std::vector< std::unique_ptr< Component > >寻找第一场比赛。
    我还实现了一个 GetComponents可以获取请求类型的所有组件的方法,同样包括从父类获取。
    请注意 static成员(member) Type可以在有和没有类的实例的情况下访问。
    另请注意 Typepublic , 为每个 Component 声明- 派生类,...并大写以强调其灵活使用,尽管它是 POD 成员。

    移除组件
    最后, RemoveComponent用途 C++14的 init-capture 传递相同的 static const std::size_t Typetemplate输入一个 lambda,所以它基本上可以进行相同的 vector 遍历,这次得到一个 iterator到第一个匹配元素。

    代码中有一些关于更灵活实现的想法的注释,更不用说 const所有这些的版本也可以很容易地实现。

    编码
    类.h
    #ifndef TEST_CLASSES_H
    #define TEST_CLASSES_H

    #include <string>
    #include <functional>
    #include <vector>
    #include <memory>
    #include <algorithm>

    #define TO_STRING( x ) #x

    //****************
    // CLASS_DECLARATION
    //
    // This macro must be included in the declaration of any subclass of Component.
    // It declares variables used in type checking.
    //****************
    #define CLASS_DECLARATION( classname ) \
    public: \
    static const std::size_t Type; \
    virtual bool IsClassType( const std::size_t classType ) const override; \

    //****************
    // CLASS_DEFINITION
    //
    // This macro must be included in the class definition to properly initialize
    // variables used in type checking. Take special care to ensure that the
    // proper parentclass is indicated or the run-time type information will be
    // incorrect. Only works on single-inheritance RTTI.
    //****************
    #define CLASS_DEFINITION( parentclass, childclass ) \
    const std::size_t childclass::Type = std::hash< std::string >()( TO_STRING( childclass ) ); \
    bool childclass::IsClassType( const std::size_t classType ) const { \
    if ( classType == childclass::Type ) \
    return true; \
    return parentclass::IsClassType( classType ); \
    } \

    namespace rtti {

    //***************
    // Component
    // base class
    //***************
    class Component {
    public:

    static const std::size_t Type;
    virtual bool IsClassType( const std::size_t classType ) const {
    return classType == Type;
    }

    public:

    virtual ~Component() = default;
    Component( std::string && initialValue )
    : value( initialValue ) {
    }

    public:

    std::string value = "uninitialized";
    };

    //***************
    // Collider
    //***************
    class Collider : public Component {

    CLASS_DECLARATION( Collider )

    public:

    Collider( std::string && initialValue )
    : Component( std::move( initialValue ) ) {
    }
    };

    //***************
    // BoxCollider
    //***************
    class BoxCollider : public Collider {

    CLASS_DECLARATION( BoxCollider )

    public:

    BoxCollider( std::string && initialValue )
    : Collider( std::move( initialValue ) ) {
    }
    };

    //***************
    // RenderImage
    //***************
    class RenderImage : public Component {

    CLASS_DECLARATION( RenderImage )

    public:

    RenderImage( std::string && initialValue )
    : Component( std::move( initialValue ) ) {
    }
    };

    //***************
    // GameObject
    //***************
    class GameObject {
    public:

    std::vector< std::unique_ptr< Component > > components;

    public:

    template< class ComponentType, typename... Args >
    void AddComponent( Args&&... params );

    template< class ComponentType >
    ComponentType & GetComponent();

    template< class ComponentType >
    bool RemoveComponent();

    template< class ComponentType >
    std::vector< ComponentType * > GetComponents();

    template< class ComponentType >
    int RemoveComponents();
    };

    //***************
    // GameObject::AddComponent
    // perfect-forwards all params to the ComponentType constructor with the matching parameter list
    // DEBUG: be sure to compare the arguments of this fn to the desired constructor to avoid perfect-forwarding failure cases
    // EG: deduced initializer lists, decl-only static const int members, 0|NULL instead of nullptr, overloaded fn names, and bitfields
    //***************
    template< class ComponentType, typename... Args >
    void GameObject::AddComponent( Args&&... params ) {
    components.emplace_back( std::make_unique< ComponentType >( std::forward< Args >( params )... ) );
    }

    //***************
    // GameObject::GetComponent
    // returns the first component that matches the template type
    // or that is derived from the template type
    // EG: if the template type is Component, and components[0] type is BoxCollider
    // then components[0] will be returned because it derives from Component
    //***************
    template< class ComponentType >
    ComponentType & GameObject::GetComponent() {
    for ( auto && component : components ) {
    if ( component->IsClassType( ComponentType::Type ) )
    return *static_cast< ComponentType * >( component.get() );
    }

    return *std::unique_ptr< ComponentType >( nullptr );
    }

    //***************
    // GameObject::RemoveComponent
    // returns true on successful removal
    // returns false if components is empty, or no such component exists
    //***************
    template< class ComponentType >
    bool GameObject::RemoveComponent() {
    if ( components.empty() )
    return false;

    auto & index = std::find_if( components.begin(),
    components.end(),
    [ classType = ComponentType::Type ]( auto & component ) {
    return component->IsClassType( classType );
    } );

    bool success = index != components.end();

    if ( success )
    components.erase( index );

    return success;
    }

    //***************
    // GameObject::GetComponents
    // returns a vector of pointers to the the requested component template type following the same match criteria as GetComponent
    // NOTE: the compiler has the option to copy-elide or move-construct componentsOfType into the return value here
    // TODO: pass in the number of elements desired (eg: up to 7, or only the first 2) which would allow a std::array return value,
    // except there'd need to be a separate fn for getting them *all* if the user doesn't know how many such Components the GameObject has
    // TODO: define a GetComponentAt<ComponentType, int>() that can directly grab up to the the n-th component of the requested type
    //***************
    template< class ComponentType >
    std::vector< ComponentType * > GameObject::GetComponents() {
    std::vector< ComponentType * > componentsOfType;

    for ( auto && component : components ) {
    if ( component->IsClassType( ComponentType::Type ) )
    componentsOfType.emplace_back( static_cast< ComponentType * >( component.get() ) );
    }

    return componentsOfType;
    }

    //***************
    // GameObject::RemoveComponents
    // returns the number of successful removals, or 0 if none are removed
    //***************
    template< class ComponentType >
    int GameObject::RemoveComponents() {
    if ( components.empty() )
    return 0;

    int numRemoved = 0;
    bool success = false;

    do {
    auto & index = std::find_if( components.begin(),
    components.end(),
    [ classType = ComponentType::Type ]( auto & component ) {
    return component->IsClassType( classType );
    } );

    success = index != components.end();

    if ( success ) {
    components.erase( index );
    ++numRemoved;
    }
    } while ( success );

    return numRemoved;
    }

    } /* rtti */
    #endif /* TEST_CLASSES_H */

    类.cpp
    #include "Classes.h"

    using namespace rtti;

    const std::size_t Component::Type = std::hash<std::string>()(TO_STRING(Component));

    CLASS_DEFINITION(Component, Collider)
    CLASS_DEFINITION(Collider, BoxCollider)
    CLASS_DEFINITION(Component, RenderImage)

    主程序
    #include <iostream>
    #include "Classes.h"

    #define MORE_CODE 0

    int main( int argc, const char * argv ) {

    using namespace rtti;

    GameObject test;

    // AddComponent test
    test.AddComponent< Component >( "Component" );
    test.AddComponent< Collider >( "Collider" );
    test.AddComponent< BoxCollider >( "BoxCollider_A" );
    test.AddComponent< BoxCollider >( "BoxCollider_B" );

    #if MORE_CODE
    test.AddComponent< RenderImage >( "RenderImage" );
    #endif

    std::cout << "Added:\n------\nComponent\t(1)\nCollider\t(1)\nBoxCollider\t(2)\nRenderImage\t(0)\n\n";

    // GetComponent test
    auto & componentRef = test.GetComponent< Component >();
    auto & colliderRef = test.GetComponent< Collider >();
    auto & boxColliderRef1 = test.GetComponent< BoxCollider >();
    auto & boxColliderRef2 = test.GetComponent< BoxCollider >(); // boxColliderB == boxColliderA here because GetComponent only gets the first match in the class hierarchy
    auto & renderImageRef = test.GetComponent< RenderImage >(); // gets &nullptr with MORE_CODE 0

    std::cout << "Values:\n-------\ncomponentRef:\t\t" << componentRef.value
    << "\ncolliderRef:\t\t" << colliderRef.value
    << "\nboxColliderRef1:\t" << boxColliderRef1.value
    << "\nboxColliderRef2:\t" << boxColliderRef2.value
    << "\nrenderImageRef:\t\t" << ( &renderImageRef != nullptr ? renderImageRef.value : "nullptr" );

    // GetComponents test
    auto allColliders = test.GetComponents< Collider >();
    std::cout << "\n\nThere are (" << allColliders.size() << ") collider components attached to the test GameObject:\n";
    for ( auto && c : allColliders ) {
    std::cout << c->value << '\n';
    }

    // RemoveComponent test
    test.RemoveComponent< BoxCollider >(); // removes boxColliderA
    auto & boxColliderRef3 = test.GetComponent< BoxCollider >(); // now this is the second BoxCollider "BoxCollider_B"

    std::cout << "\n\nFirst BoxCollider instance removed\nboxColliderRef3:\t" << boxColliderRef3.value << '\n';

    #if MORE_CODE
    // RemoveComponent return test
    int removed = 0;
    while ( test.RemoveComponent< Component >() ) {
    ++removed;
    }
    #else
    // RemoveComponents test
    int removed = test.RemoveComponents< Component >();
    #endif

    std::cout << "\nSuccessfully removed (" << removed << ") components from the test GameObject\n";

    system( "PAUSE" );
    return 0;
    }

    输出
        Added:
    ------
    Component (1)
    Collider (1)
    BoxCollider (2)
    RenderImage (0)

    Values:
    -------
    componentRef: Component
    colliderRef: Collider
    boxColliderRef1: BoxCollider_A
    boxColliderRef2: BoxCollider_A
    renderImageRef: nullptr

    There are (3) collider components attached to the test GameObject:
    Collider
    BoxCollider_A
    BoxCollider_B


    First BoxCollider instance removed
    boxColliderRef3: BoxCollider_B

    Successfully removed (3) components from the test GameObject
    旁注:授予 Unity 使用 Destroy(object)而不是 RemoveComponent ,但我的版本现在适合我的需求。

    关于c++ - 在 Unity 中用 C++ 实现组件系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44105058/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com