微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

c – Unity的GetComponent()如何工作?

我一直在尝试制作一个类似于Unity的基于组件的系统,但在C中.我想知道Unity实现的GetComponent()方法是如何工作的.这是一个非常强大的功能.具体来说,我想知道它用于存储其组件的容器类型.

我在克隆这个函数时需要的两个标准如下. 1.我还需要返回任何继承的组件.例如,如果SphereCollider继承Collider,则GetComponent< Collider>()将返回附加到GameObject的SphereCollider,但GetComponent< SphereCollider>()不会返回附加的任何Collider.我需要快速功能.优选地,它将使用某种散列函数.

对于标准一,我知道我可以使用类似于以下实现的东西

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

但同样,这不符合第一个标准.

如果有人知道某种方法来组合这两个特征,即使它比第二个例子慢一点,我也愿意牺牲一点点.谢谢!

解决方法:

由于我正在编写自己的游戏引擎并采用相同的设计,我想我会分享我的结果.

概观

我为我喜欢用作GameObject实例的组件的类编写了自己的RTTI.通过#defineing两个宏来减少输入量:CLASS_DECLaraTION和CLASS_DEFinitioN

CLASS_DECLaraTION声明将用于标识类类型(Type)的唯一静态const std :: size_t,以及允许对象通过调用其同名父类函数(IsClasstype)来遍历其类层次结构的虚函数.

CLASS_DEFinitioN定义了这两件事.即,Type被初始化为类名的字符串化版本的哈希值(使用TO_STRING(x)#x),因此类型比较只是一个int比较而不是字符串比较.

的std ::散列&LT的std :: string&GT是使用的哈希函数,它保证相等的输入产生相等的输出,并且冲突的数量接近于零.

除了散列冲突的低风险之外,这种实现还有一个额外的好处,即允许用户使用这些宏创建自己的Component类,而无需参考扩展enum类的一些master包含文件,或者使用typeid(仅提供运行时类型,而不是父类.

AddComponent

自定义RTTI简化了Add | Get | RemoveComponent的调用语法,仅指定模板类型,就像Unity一样.

AddComponent方法完美地将通用引用可变参数包转发给用户的构造函数.因此,例如,用户定义的组件派生类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的显式构造,因为如果使用推导的初始化列表语法(如{10,10,10},无论Vec3的构造函数声明如何),完美转发都可能无法链接.

自定义RTTI还解决了std :: unordered_map< std :: typeindex,...>的3个问题.解:

>即使使用std :: tr2 :: direct_bases进行层次结构遍历,最终结果仍然是地图中相同指针的重复.
>除非使用允许/解决冲突而不覆盖的映射,否则用户无法添加多个等效类型的组件,这会进一步降低代码速度.
>不需要且不需要缓慢的dynamic_cast,只需要一个简单的static_cast.

GetComponent

GetComponent只使用模板类型的静态const std :: size_t类型作为虚拟bool IsClasstype方法的参数,并迭代std :: vector<的std ::的unique_ptr&LT组分> &GT寻找第一场比赛.

我还实现了一个GetComponents方法,它可以获取所请求类型的所有组件,同样包括父类获取.

请注意,可以使用和不使用类的实例访问静态成员Type.

另请注意,Type是public,为每个Component派生类声明,并且尽管是POD成员,但大写以强调其灵活使用.

RemoveComponent

最后,RemoveComponent使用C 14的init-capture将相同的静态const std :: size_t类型的模板类型传递给lambda,这样它基本上可以执行相同的向量遍历,这次获得第一个匹配元素的迭代器.

代码中有一些关于更灵活实现的想法的注释,更不用说所有这些的const版本也可以轻松实现.

编码

Classes.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 @R_42_4045@ion 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 */

Classes.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)

main.cpp中

#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(对象)而不是RemoveComponent,但我的版本现在适合我的需要.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐