在 Unity 中用 C++ 实现组件系统 [英] Implementing Component system from Unity in c++

查看:50
本文介绍了在 Unity 中用 C++ 实现组件系统的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我在此函数的克隆中需要的两个条件如下.1. 我还需要返回任何继承的组件.例如,如果SphereCollider 继承了Collider,GetComponent() 将返回附加到GameObjectSphereCollider,但 GetComponent() 不会返回任何附加的 Collider.2.我需要快速的功能.最好是使用某种哈希函数.

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

std::vector组件模板 T* GetComponent(){对于每个(组件中的组件* c)if (dynamic_cast(*c))返回 (T*)c;返回 nullptr;}

但这不符合快速的第二个标准.为此,我知道我可以做这样的事情.

std::unordered_map组件模板 T* GetComponent(){返回 (T*)components[typeid(T)];}

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

如果有人知道结合这两个功能的某种方法,即使它比第二个示例慢一点,我也愿意牺牲一点.谢谢!

解决方案

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

概述

我为我想用作GameObject 实例的Components 的类编写了自己的RTTI.通过 #define 两个宏来减少输入量:CLASS_DECLARATIONCLASS_DEFINITION

CLASS_DECLARATION 声明了唯一的 static const std::size_t,用于识别 class 类型(Typecode>),以及一个 virtual 函数,它允许对象通过调用同名的父类函数来遍历它们的 class 层次结构 (IsClassType).

CLASS_DEFINITION 定义了这两件事.即 Type 被初始化为 class 名称的字符串化版本的散列(使用 TO_STRING(x) #x),这样 Type 比较只是一个 int 比较,而不是一个字符串比较.

std::hash<std::string> 是使用的哈希函数,它保证相等的输入产生相等的输出,并且冲突次数接近于零.


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


添加组件

这个自定义 RTTI 将 Add|Get|RemoveComponent 的调用语法简化为仅指定 template 类型,就像 Unity 一样.

AddComponent 方法完美地将通用引用可变参数包转发到用户的构造函数.因此,例如,用户定义的 Component 派生的 class CollisionModel 可以具有构造函数:

CollisionModel(GameObject * owner, const Vec3 & size, const Vec3 & offset, bool active );

然后用户只需调用:

myGameObject.AddComponent(this, Vec3( 10, 10, 10 ), Vec3( 0, 0, 0 ), true );

请注意 Vec3 的显式构造,因为如果使用像 { 10, 10, 10 } 这样推导出的初始化列表语法,完美转发可能无法链接 不管 Vec3 的构造函数声明.


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

  1. 即使使用 std::tr2::direct_bases 进行层次遍历,最终结果仍然是映射中相同指针的重复项.
  2. 用户不能添加多个等效类型的组件,除非使用的映射允许/解决冲突而不覆盖,这会进一步减慢代码速度.
  3. 不需要不确定和缓慢的dynamic_cast,只需直接的static_cast.


获取组件

GetComponent 只是使用 template 类型的 static const std::size_t Type 作为 virtual bool IsClassType 的参数 方法并迭代 std::vector<;std::unique_ptr<组件> 寻找第一个匹配项.

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

请注意,static 成员 Type 可以在有和没有类实例的情况下访问.

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


移除组件

最后,RemoveComponent 使用 C++14 的 init-capture 来传递相同的 static const std::size_t Typetemplate 输入一个 lambda,所以它基本上可以进行相同的向量遍历,这次是获取一个 iterator 到第一个匹配元素.


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


代码

类.h

#ifndef TEST_CLASSES_H#define TEST_CLASSES_H#include <字符串>#include <功能>#include <向量>#include <内存>#include <算法>#define TO_STRING( x ) #x//****************//类声明////这个宏必须包含在 Component 的任何子类的声明中.//它声明了用于类型检查的变量.//****************#define CLASS_DECLARATION( 类名) 上市:                                                                                     静态常量 std::size_t 类型;virtual bool IsClassType( const std::size_t classType ) const override;//****************//类定义////这个宏必须包含在类定义中才能正确初始化//用于类型检查的变量.请特别注意以确保//指示正确的父类或运行时类型信息//不正确.仅适用于单继承 RTTI.//****************#define CLASS_DEFINITION( parentclass, childclass ) const std::size_t childclass::Type = std::hash<std::string >()( TO_STRING( childclass ) );ool childclass::IsClassType( const std::size_t classType ) const { if ( classType == childclass::Type ) 返回真;返回 parentclass::IsClassType( classType );} 命名空间 rtti {//****************//零件//基类//****************类组件{上市:静态常量 std::size_t 类型;virtual bool IsClassType( const std::size_t classType ) const {返回类类型 == 类型;}上市:虚拟 ~Component() = 默认值;组件( std::string && initialValue ):值(初始值){}上市:std::string 值 =未初始化";};//****************//碰撞器//****************类碰撞器:公共组件{CLASS_DECLARATION(碰撞器)上市:碰撞器( std::string && initialValue ): 组件( std::move(initialValue ) ) {}};//****************//盒子碰撞器//****************类 BoxCollider : 公共碰撞器 {CLASS_DECLARATION(BoxCollider)上市:BoxCollider( std::string && initialValue ): Collider( std::move(initialValue ) ) {}};//****************//渲染图像//****************类RenderImage:公共组件{CLASS_DECLARATION(渲染图像)上市:RenderImage( std::string && initialValue ): 组件( std::move(initialValue ) ) {}};//****************//游戏对象//****************类游戏对象{上市:std::vector获取组件();模板<类组件类型 >int RemoveComponents();};//****************//游戏对象::添加组件//使用匹配的参数列表将所有参数完美转发到 ComponentType 构造函数//DEBUG: 务必将这个 fn 的参数与所需的构造函数进行比较,以避免完美转发失败的情况//EG:推导的初始化列表、仅声明的静态 const int 成员、0|NULL 代替 nullptr、重载的 fn 名称和位域//****************模板(std::forward(params)...));}//****************//游戏对象::获取组件//返回匹配模板类型的第一个组件//或者是从模板类型派生的//EG:如果模板类型是Component,components[0]类型是BoxCollider//然后将返回 components[0] 因为它派生自 Component//****************模板<类组件类型 >组件类型游戏对象::GetComponent() {for(自动&&组件:组件){if ( 组件-> IsClassType( ComponentType::Type ) )返回 *static_cast<组件类型 * >( component.get() );}返回 *std::unique_ptr<组件类型 >( nullptr );}//****************//游戏对象::移除组件//删除成功返回真//如果组件为空,或者不存在这样的组件,则返回 false//****************模板<类组件类型 >bool GameObject::RemoveComponent() {如果 ( components.empty() )返回假;汽车&index = std::find_if( components.begin(),组件.end(),[ classType = ComponentType::Type ]( auto & component ) {返回组件-> IsClassType( classType );});bool 成功 = 索引 != components.end();如果(成功)组件.擦除(索引);返回成功;}//****************//游戏对象::GetComponents//遵循与 GetComponent 相同的匹配标准,返回指向所请求组件模板类型的指针向量//注意:编译器可以选择复制省略或移动构造 componentsOfType 到这里的返回值中//TODO:传入所需的元素数量(例如:最多 7 个,或仅前 2 个),这将允许 std::array 返回值,//除非用户不知道 GameObject 有多少这样的组件,否则需要一个单独的 fn 来获取它们 *all*//TODO:定义一个可以直接抓取到请求类型的第n个组件的GetComponentAt()//****************模板<类组件类型 >std::vector<组件类型 * >游戏对象::GetComponents() {std::vector<组件类型 * >组件类型;for(自动&&组件:组件){if ( 组件-> IsClassType( ComponentType::Type ) )componentsOfType.emplace_back(static_cast(component.get()));}返回 componentsOfType;}//****************//游戏对象::移除组件//返回成功删除的次数,如果没有删除则返回 0//****************模板<类组件类型 >int GameObject::RemoveComponents() {如果 ( components.empty() )返回0;int numRemoved = 0;布尔成功=假;做 {汽车&index = std::find_if( components.begin(),组件.end(),[ classType = ComponentType::Type ]( auto & component ) {返回组件-> IsClassType( classType );});成功 = 索引 != components.end();如果(成功){组件.擦除(索引);++numRemoved;}} while ( 成功 );返回 numRemoved;}}/* rtti */#endif/* TEST_CLASSES_H */


类.cpp

#include "Classes.h";使用命名空间 rtti;const std::size_t Component::Type = std::hash()(TO_STRING(Component));CLASS_DEFINITION(组件,碰撞器)CLASS_DEFINITION(碰撞器,BoxCollider)CLASS_DEFINITION(组件,渲染图像)


main.cpp

#include #include "Classes.h";#define MORE_CODE 0int main( int argc, const char * argv ) {使用命名空间 rtti;游戏对象测试;//添加组件测试test.AddComponent<组件 >(组件");test.AddComponent<对撞机>(对撞机");test.AddComponent(渲染图像");#万一std::cout <<添加:
------
Component	(1)
Collider	(1)
BoxCollider	(2)
RenderImage	(0)

";//获取组件测试汽车&componentRef = test.GetComponent<组件 >();汽车&colliderRef = test.GetComponent<对撞机>();汽车&boxColliderRef1 = test.GetComponent();//使用 MORE_CODE 0 获取 &nullptrstd::cout <<值:
-------
componentRef:		";<<组件引用值<<
colliderRef:		"<<colliderRef.value<<
boxColliderRef1:	"<<boxColliderRef1.value<<
boxColliderRef2:	"<<boxColliderRef2.value<<
renderImageRef:		"<<( &renderImageRef != nullptr ? renderImageRef.value : nullptr");//获取组件测试auto allColliders = test.GetComponents<对撞机>();std::cout <<"

有 (<< allColliders.size() << ") 碰撞器组件附加到测试游戏对象:
";for ( auto && c : allColliders ) {std::cout <<c->值<<'
';}//移除组件测试test.RemoveComponent();#万一std::cout <<
从测试游戏对象中成功删除(<<删除<<)组件
";系统(暂停");返回0;}


输出

 添加:------组件 (1)对撞机 (1)BoxCollider (2)渲染图像 (0)价值观:-------componentRef: 组件colliderRef: 碰撞器boxColliderRef1:BoxCollider_AboxColliderRef2:BoxCollider_ArenderImageRef: nullptr有 (3) 个碰撞器组件附加到测试游戏对象:对撞机BoxCollider_ABoxCollider_B删除了第一个 BoxCollider 实例boxColliderRef3:BoxCollider_B从测试游戏对象中成功移除 (3) 个组件

旁注:Unity 使用 Destroy(object) 而不是 RemoveComponent,但我的版本现在适合我的需求.

I've been experimenting with making a component based system similar to Unity's, but in C++. I'm wondering how the GetComponent() method that Unity implements works. It is a very powerful function. Specifically, I want to know what kind of container it uses to store its components.

The two criteria I need in my clone of this function are as follows. 1. I need any inherited components to be returned as well. For example, if SphereCollider inherits Collider, GetComponent<Collider>() will return the SphereCollider attached to the GameObject, but GetComponent<SphereCollider>() will not return any Collider attached. 2. I need the function to be fast. Preferably, it would use some kind of hash function.

For criteria one, I know that I could use something similar to the following implementation

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

But that doesn't fit the second criteria of being fast. For that, I know I could do something like this.

std::unordered_map<type_index, Component*> components
template <typename T>
T* GetComponent()
{
    return (T*)components[typeid(T)];
}

But again, that doesn't fit the first criteria.

If anybody knows of some way to combine those two features, even if it's a little slower than the second example, I would be willing to sacrifice a little bit. Thank you!

解决方案

Since I'm writing my own game engine and incorporating the same design, I thought I'd share my results.

Overview

I wrote my own RTTI for the classes I cared to use as Components of my GameObject instances. The amount of typing is reduced by #defineing the two macros: CLASS_DECLARATION and CLASS_DEFINITION

CLASS_DECLARATION declares the unique static const std::size_t that will be used to identify the class type (Type), and a virtual function that allows objects to traverse their class hierarchy by calling their parent-class function of the same name (IsClassType).

CLASS_DEFINITION defines those two things. Namely the Type is initialized to a hash of a stringified version of the class name (using TO_STRING(x) #x), so that Type comparisons are just an int compare and not a string compare.

std::hash<std::string> is the hash function used, which guarantees equal inputs yield equal outputs, and the number of collisions is near-zero.


Aside from the low risk of hash collisions, this implementation has the added benefit of allowing users to create their own Component classes using those macros without ever having to refer to|extend some master include file of enum classs, or use typeid (which only provides the run-time type, not the parent-classes).


AddComponent

This custom RTTI simplifies the call syntax for Add|Get|RemoveComponent to just specifying the template type, just like Unity.

The AddComponent method perfect-forwards a universal reference variadic parameter pack to the user's constructor. So, for example, a user-defined Component-derived class CollisionModel could have the constructor:

CollisionModel( GameObject * owner, const Vec3 & size, const Vec3 & offset, bool active );

then later on the user simply calls:

myGameObject.AddComponent<CollisionModel>(this, Vec3( 10, 10, 10 ), Vec3( 0, 0, 0 ), true );

Note the explicit construction of the Vec3s because perfect-forwarding can fail to link if using deduced initializer-list syntax like { 10, 10, 10 } regardless of Vec3's constructor declarations.


This custom RTTI also resolves 3 issues with the std::unordered_map<std::typeindex,...> solution:

  1. Even with the hierarchy traversal using std::tr2::direct_bases the end result is still duplicates of the same pointer in the map.
  2. A user can't add multiple Components of equivalent type, unless a map is used that allows/solves collisions without overwriting, which further slows down the code.
  3. No uncertain and slow dynamic_cast is needed, just a straight static_cast.


GetComponent

GetComponent just uses the static const std::size_t Type of the template type as an argument to the virtual bool IsClassType method and iterates over std::vector< std::unique_ptr< Component > > looking for the first match.

I've also implemented a GetComponents method that can get all components of the requested type, again including getting from the parent-class.

Note that the static member Type can be accessed both with and without an instance of the class.

Also note that Type is public, declared for each Component-derived class, ...and capitalized to emphasize its flexible use, despite being a POD member.


RemoveComponent

Lastly, RemoveComponent uses C++14's init-capture to pass that same static const std::size_t Type of the template type into a lambda so it can basically do the same vector traversal, this time getting an iterator to the first matching element.


There are a few comments in the code about ideas for a more flexible implementation, not to mention const versions of all these could also easily be implemented.


The Code

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 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 */


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:
------
Component	(1)
Collider	(1)
BoxCollider	(2)
RenderImage	(0)

";

    // 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:
-------
componentRef:		"  << componentRef.value
              << "
colliderRef:		"                   << colliderRef.value    
              << "
boxColliderRef1:	"                 << boxColliderRef1.value
              << "
boxColliderRef2:	"                 << boxColliderRef2.value
              << "
renderImageRef:		"                << ( &renderImageRef != nullptr ? renderImageRef.value : "nullptr" );

    // GetComponents test
    auto allColliders = test.GetComponents< Collider >();
    std::cout << "

There are (" << allColliders.size() << ") collider components attached to the test GameObject:
";
    for ( auto && c : allColliders ) {
        std::cout << c->value << '
';
    }

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

    std::cout << "

First BoxCollider instance removed
boxColliderRef3:	" << boxColliderRef3.value << '
';

#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 << "
Successfully removed (" << removed << ") components from the test GameObject
";

    system( "PAUSE" );
    return 0;
}


Output

    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

Side-note: granted Unity uses Destroy(object) and not RemoveComponent, but my version suits my needs for now.

这篇关于在 Unity 中用 C++ 实现组件系统的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆