====== Object Layout ====== **WARNING: This is an //extremely// advanced subject, and is not recommended for users unfamiliar with unsafe code or C++.** All classes that inherit from UnityEngine.Object are actually wrappers for a native object on the C++ side of the engine. The address of the native object is stored in the class, as seen in the layout below: class Object { // Cached pointer to the native object IntPtr m_CachedPtr; // Cached instance ID from native object int m_InstanceID; // True purpose unknown string m_UnityRuntimeErrorString; } The cached pointer can be accessed through reflection in two ways: * Accessing the m_CachedPtr field * Calling the 'IntPtr GetCachedPtr()' method Once you have retrieved the pointer, you can access the native object by defining the appropriate structs and using unsafe code. ===== The C++ Class Hierarchy ===== The C++ class hierarchy is as follows: NonCopyable -> Object -> EditorExtension -> NamedObject You can define this from C# using the following set of types (for the sake of brevity, these are simplified definitions): public struct NonCopyable { public IntPtr MethodTable; } public struct AllocationRootWithSalt { public uint m_Salt; public uint m_RootReferenceIndex; } public struct MemLabelId { #if UNITY_EDITOR || DEVELOPMENT_BUILD public AllocationRootWithSalt m_RootReferenceWithSalt; #endif public int identifier; } public struct ScriptingGCHandle { public IntPtr m_Handle; public int m_Weakness; public IntPtr m_Object; } // Note that this struct layout differs between Unity versions. // This is correct for Unity 2020, but hasn't been tested with // other engine versions. // // The type has been renamed to NativeObject in order to avoid // conflicts with UnityEngine.Object. // public struct NativeObject { public NonCopyable Base; public int m_InstanceID; // This is represented on the C++ side as several bit-fields. public int m_BitFields; public IntPtr m_EventIndex; #if UNITY_EDITOR || DEVELOPMENT_BUILD public MemLabelId m_FullMemoryLabel; #endif public ScriptingGCHandle m_MonoReference; #if UNITY_EDITOR public uint m_DirtyIndex; public ulong m_FileIDHint; public bool m_IsPreviewSceneObject; #endif #if UNITY_EDITOR || DEVELOPMENT_BUILD public int m_ObjectProfilerListIndex; #endif } public struct PPtr where T : unmanaged { public int m_InstanceID; } // Renamed to NativePrefab to avoid name clashes public struct NativePrefab { public NativeObject Base; // ... } // Renamed to NativePrefabInstance to avoid name clashes public struct NativePrefabInstance { public NativeObject Base; // ... } public struct EditorExtension { public NativeObject Base; #if UNITY_EDITOR public PPtr m_CorrespondingSourceObject; public PPtr m_DeprecatedExtensionPtr; public PPtr m_PrefabAsset; public PPtr m_PrefabInstance; public bool m_IsClonedFromPrefabObject; #endif } public struct ConstantString { public unsafe byte* m_Buffer; } public struct NamedObject { public EditorExtension Base; public ConstantString m_Name; } All of the above information was obtained from inspecting the .pdb files that are included with Unity installations. Some versions of Unity contain more information in these files than others. ===== Advanced Use Cases ===== Once you have access to native objects, you can perform many different actions that are usually only possible in the editor via SerializedObject and SerializedProperty. For example, changing the m_LineSpacing value on a Font object: // Since these structures are accessed through pointers, // we don't need to define the entire structure in order // to use it. Fonts have several more fields in them that // are not particularly useful to access in this way. public struct NativeFont { public NamedObject Base; public float m_LineSpacing; public int m_FontSize; } // Make a new Font and access its native object. Font f = new Font(); var fp = (NativeFont*)f.GetCachedPtr(); // Now we can simply set m_LineSpacing on it fp->m_LineSpacing = 12f; // This should print 12 Debug.Log(f.lineHeight);