簡體   English   中英

用c++從Unity實現組件系統

[英]Implementing Component system from Unity in c++

我一直在嘗試制作一個類似於 Unity 的基於組件的系統,但使用 C++。 我想知道 Unity 實現的GetComponent()方法是如何工作的。 這是一個非常強大的功能。 具體來說,我想知道它使用什么樣的容器來存儲其組件。

我在此函數的克隆中需要的兩個標准如下。 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)];
}

但同樣,這不符合第一個標准。

如果有人知道結合這兩個功能的某種方法,即使它比第二個示例慢一點,我也願意犧牲一點。 謝謝!

由於我正在編寫自己的游戲引擎並采用相同的設計,因此我想我會分享我的結果。

概述

我為我想用作我的GameObject實例的Components的類編寫了自己的 RTTI。 通過#define兩個宏來減少打字量: CLASS_DECLARATIONCLASS_DEFINITION

CLASS_DECLARATION聲明了唯一的static const std::size_t將用於標識class類型( Type ),以及一個允許對象通過調用它們的同名父類函數( IsClassType )來遍歷它們的class層次結構的virtual函數。

CLASS_DEFINITION定義了這兩件事。 Type被初始化為class名的字符串化版本的散列(使用TO_STRING(x) #x ),因此Type比較只是一個 int 比較而不是一個字符串比較。

std::hash<std::string>是使用的散列函數,它保證相等的輸入產生相等的輸出,並且沖突次數接近於零。


除了散列沖突的低風險之外,這個實現還有一個額外的好處,它允許用戶使用這些宏創建他們自己的Component類,而不必參考|擴展enum class的一些主include文件,或使用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的顯式構造,因為如果使用推導的初始化列表語法(如{ 10, 10, 10 }則完美轉發可能無法鏈接{ 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< Component > >尋找第一個匹配項。

我還實現了一個GetComponents方法,該方法可以獲取請求類型的所有組件,同樣包括從父類獲取。

請注意, static成員Type可以在有和沒有類的實例的情況下訪問。

另請注意, Typepublic ,為每個Component派生類聲明,...並且大寫以強調其靈活使用,盡管它是 POD 成員。


移除組件

最后, RemoveComponent使用C++14的 init-capture 將template類型的相同static const std::size_t Type傳遞到 lambda 中,因此它基本上可以執行相同的向量遍歷,這一次獲得第一個匹配的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 ,但我的版本現在適合我的需求。

抱歉,如果這不是您要查找的內容,但我有一個想法,將無序映射與類型索引一起使用,並在一些元編程和 TR2 的幫助下,將多個指向組件的指針放入映射中,包括其直接基類作為附加鍵。 因此getComponent<SphereCollider>()getComponent<Collider>()以及向下轉換將具有相同的指針對象。

#include <tr2/type_traits>
#include <tuple>
#include <typeindex>
#include <unordered_map>
#include <iostream>

class Component {
public:
  virtual ~Component() {}
};

class GameObject {
public:
  template <typename T>
  void addComponent(T *component);

  template <typename T>
  T *getComponent();

  std::unordered_map<std::typeindex, Component *> components;
};

template <typename>
struct direct_bases_as_tuple {};

template <typename... Types>
struct direct_bases_as_tuple<std::tr2::__reflection_typelist<Types...>> {
  typedef std::tuple<Types...> type;
};

template <std::size_t N, typename ComponentBases, typename ComponentType>
struct AddComponent {
  GameObject *owner;

  explicit AddComponent(GameObject *owner) : owner(owner) {}

  void operator()(ComponentType *component) {
    AddComponent<N-1, ComponentBases, ComponentType>{owner}(component);

    using BaseType = std::tuple_element<N-1, ComponentBases>::type;

    owner->components[typeid(BaseType)] = component;
  }
};

template <typename ComponentBases, typename ComponentType>
struct AddComponent<0u, ComponentBases, ComponentType> {
  GameObject *owner;

  explicit AddComponent(GameObject *owner) : owner(owner) {}

  void operator()(ComponentType *component) {
    return;
  }
};

template <typename T>
void GameObject::addComponent(T *component) {
  using ComponentBases = direct_bases_as_tuple<std::tr2::direct_bases<ComponentType>::type>::type;

  constexpr classCount = std::tuple_size<ComponentBases>::value;

  AddComponent<classCount, ComponentBases, T>{this}(component);

  components[typeid(T)] = component;
}

template <typename T>
T * GameObject::getComponent() {
  auto iter = components.find(typeid(T));

  if (iter != std::end(components)) {
    return dynamic_cast<T *>(iter->second);
  }

  return nullptr;
}

class Collider : public Component {};
class SphereCollider : public Collider {};

int main() {
  GameObject gameObject;
  gameObject.addComponent(new SphereCollider);

  //get by derived class
  SphereCollider *sphereColliderA = gameObject.getComponent<SphereCollider>();

  //get by subclass
  SphereCollider *sphereColliderB = dynamic_cast<SphereCollider *>(
    gameObject.getComponent<Collider>()
  );

  if (sphereColliderA == sphereColliderB) {
    std::cout << "good" << std::endl;
  }
}

我創建了AddComponent結構以在編譯時遞歸遍歷組件基類,並在每次迭代中插入具有相應類(鍵)的指針(值)。 助手 struct direct_bases_as_tuple靈感來自Andy Prowl將直接基數更改為元組的答案 我使用 GCC 4.9.2 使用 C++11 特性編譯了這個。

我知道這篇文章已經有人回答了,但是如果你查看游戲編程模式,在這本書中他有一個名為服務定位器的設計模式,最后,它說 Unity 將此模式與組件模式一起使用。 我希望我能回答更具體的問題,但這可能是解決這個問題的另一種方法。

Unity 引擎與分叉的 Mono 運行時鏈接,在該運行時上執行 Unity 腳本。

UnityEngine.Component

public class Component : Object
  {
    .
    .        
    [TypeInferenceRule(TypeInferenceRules.TypeReferencedByFirstArgument)]
    public Component GetComponent(Type type)
    {
        return this.gameObject.GetComponent(type);
    }

    [GeneratedByOldBindingsGenerator]
    [MethodImpl(MethodImplOptions.InternalCall)]
    internal extern void GetComponentFastPath(Type type, IntPtr oneFurtherThanResultValue);

    [SecuritySafeCritical]
    public unsafe T GetComponent<T>()
    {
        CastHelper<T> castHelper = default(CastHelper<T>);
        this.GetComponentFastPath(typeof(T), new IntPtr((void*)(&castHelper.onePointerFurtherThanT)));
        return castHelper.t;
    }
    .
    .
}

C# 代碼執行本機調用,稱為 Icalls 對已使用 C# 運行時庫 API 綁定到 C# 方法的 C++ 方法。 作為規則,無體(未實現)方法需要externabstractpartial說明符,因此所有內部調用都標記為extern 當運行時看到具有[MethodImpl(MethodImplOptions.InternalCall)]屬性的方法時,它知道它需要進行 Icall,因此它查找綁定到的函數並跳轉到該地址。

Icall 在 C# 中不需要是static ,它會自動將組件的 this MonoObject傳遞給 C++ 處理程序函數。 如果它們是static那么通常會使用 C# shim 方法故意將 this 對象作為參數傳遞,並使 shim 方法成為靜態 Icall。 使用 Icalls,類型不會被編組,除非它們是 blittable 類型,這意味着所有其他類型都作為MonoObjectMonoString等傳遞。

通常 C++ 方法是函數或靜態方法,但我認為它們也可以是非靜態方法,只要它們不是虛擬的,因為運行時無法修復地址。

UnityEngine.GameObject

public sealed class GameObject : Object
 {  
    .
    .
    public GameObject(string name)
    {
      GameObject.Internal_CreateGameObject(this, name);
    }

    public GameObject()
    {
      GameObject.Internal_CreateGameObject(this, (string) null);
    }

    [WrapperlessIcall]
    [TypeInferenceRule(TypeInferenceRules.TypeReferencedByFirstArgument)]
    [MethodImpl(MethodImplOptions.InternalCall)]
    public extern Component GetComponent(System.Type type);

    [WrapperlessIcall]
    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern void Internal_CreateGameObject([Writable] GameObject mono, string name);
    .
    .
 }

GameObject的 C# 構造函數包含對本機方法的調用。 構造函數的主體在初始化 C# 對象之后運行,這樣就已經有一個 this 指針。 Internal_CreateGameObject是實際調用的靜態填充函數。

有人使用 mono 實現了他們自己的 C++ Internal_CreateGameObject的示例:

bool GameObjectBinding::init()
{
    MonoClass *gameObjectClass = Mono::get().getClass("GameObject");
    gameObject_NativeID_Field = mono_class_get_field_from_name(gameObjectClass, "nativeID");

    MonoClass *transformClass = Mono::get().getClass("Transform");
    transform_NativeID_Field = mono_class_get_field_from_name(transformClass, "nativeID");

    mono_add_internal_call("GameEngine_CS.GameObject::internal_createGameObject", GameObjectBinding::createGameObject);
    mono_add_internal_call("GameEngine_CS.GameObject::internal_deleteGameObject", GameObjectBinding::deleteGameObject);
    mono_add_internal_call("GameEngine_CS.GameObject::internal_getGameObject", GameObjectBinding::getGameObject);

    mono_add_internal_call("GameEngine_CS.GameObject::internal_getTransform", GameObjectBinding::getTransform);

    return true;
}

void GameObjectBinding::createGameObject(MonoObject * monoGameObject)
{
    Object *newObject = LevelManager::get().getCurrentLevel()->createObject(0);
    mono_field_set_value(monoGameObject, gameObject_NativeID_Field, (void*)newObject->getID());
}

mono_add_internal_call已用於將此方法綁定到GameObjectBinding::createGameObject ,將 this 指針作為MonoObject指針傳遞給該方法。 然后創建原生對象來表示GameObject ,並且mono_field_set_value然后被用於將設置NativeID C#的對象的字段的新的原生對象的ID。 這樣就可以從作為 C# 對象的內部實現的MonoObject訪問本機對象。 GameObject本質上由 2 個對象表示。

public sealed class GameObject : Object
 {
     .
     .
     private UInt32 nativeID;
     public UInt32 id { get { return nativeID; } }
     .
     .
 }

該字段在運行時使用綁定

mono_set_dirs( "/Library/Frameworks/Mono.framework/Home/lib", "/Library/Frameworks/Mono.framework/Home/etc" );
mono_config_parse( nullptr );
const char* managedbinarypath = "C:/Test.dll";
MonoDomain* domain = mono_jit_init(managedbinarypath)
MonoAssembly* assembly = mono_domain_assembly_open (domain, managedbinarypath);
MonoImage* image = mono_assembly_get_image (assembly);
MonoClass* gameobjectclass = mono_class_from_name(image, "ManagedLibrary", "GameObject");
gameObject_NativeID_Field = mono_class_get_field_from_name( gameobjectclass, "nativeID" );

GetComponent<T>()typeof(T) GetComponent<T>()傳遞給GetComponentFastPath (本機調用),后者也傳遞組件的 this 指針。 GetComponentFastPath本機實現將接收它作為該類型的MonoObject*MonoReflectionType* 然后綁定的 C++ 方法將在MonoReflectionType*上調用mono_reflection_type_get_type()以獲取MonoType* (這里是原始類型: https : //github.com/samneirinck/cemono/blob/master/src/native/inc/mono/ mono/metadata/blob.h ),或者對於對象類型,您可以使用mono_class_from_mono_type()MonoType*獲取MonoClass* 然后它將獲取附加到 Component 的游戲對象並在某些內部數據結構中搜索該對象具有的組件。

有人使用 mono 實現了他們自己的 C++ GetComponent

id ModuleScriptImporter::RegisterAPI()
{
    //GAMEOBJECT
    mono_add_internal_call("TheEngine.TheGameObject::CreateNewGameObject", (const void*)CreateGameObject);
    mono_add_internal_call("TheEngine.TheGameObject::AddComponent", (const void*)AddComponent);
    mono_add_internal_call("TheEngine.TheGameObject::GetComponent", (const void*)GetComponent);
}

MonoObject* ModuleScriptImporter::GetComponent(MonoObject * object, MonoReflectionType * type)
{
    return current_script->GetComponent(object, type);
}

MonoObject* CSharpScript::GetComponent(MonoObject* object, MonoReflectionType* type)
{
    if (!CheckMonoObject(object))
    {
        return nullptr;
    }

    if (currentGameObject == nullptr)
    {
        return nullptr;
    }

    MonoType* t = mono_reflection_type_get_type(type);
    std::string name = mono_type_get_name(t);

    const char* comp_name = "";

    if (name == "CulverinEditor.Transform")
    {
        comp_name = "Transform";
    }

    MonoClass* classT = mono_class_from_name(App->importer->iScript->GetCulverinImage(), "CulverinEditor", comp_name);
    if (classT)
    {
        MonoObject* new_object = mono_object_new(CSdomain, classT);
        if (new_object)
        {
            return new_object;
        }
    }
    return nullptr;
}

C# 方法可以從 C++ 調用:

MonoMethodDesc* desc = mono_method_desc_new (const char *name, gboolean include_namespace);
MonoClass* class = mono_class_from_name (MonoImage *image, const char* name_space, const char *name);
MonoMethod* method = mono_method_desc_search_in_class (MonoMethodDesc *desc, MonoClass *klass);
MonoMethod* method = mono_method_desc_search_in_image (MonoMethodDesc *desc, MonoImage *image);
MonoObject* obj = mono_runtime_invoke (MonoMethod *method, void *obj, void **params,
                       MonoObject **exc);

參見: https : //gamedev.stackexchange.com/questions/115573/how-are-methods-like-awake-start-and-update-called-in-unity/183091#183091

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM