In the editor you’ll have those instances, as the editor itself executes like a normal program. But when you drag&drop an instance into another field in the inspector, Unity does a lot of additional things. First of all, of course it knows which instance you dropped in there, and it’ll actually also set that instance as one would expect. So yes, there’s actually an assignment of a reference.
However, there’s more happening in the background. Unity serializes all the changes that are done in the editor, unless you bypass that mechanism. So besides setting the reference to that particular instance, it also saves the new state of the component that you’ve just changed. With regards to the instance that you just linked to your component, it’ll save a GUID that’s associated with the instance when it was initially created, as well as with its data on disk.
This applies to all UnityEngine.Objects (nowadays you can also serialize reference to normal types, but that’s done in a similar fashion, but with some limitations).
Anyway, what ends up on disk is a huge data blob, like a data repository with every distinct instance in it… every occurrence of a “reference” to such an instance will be expressed by its associated GUID, which itself is just data at that moment.
When neither the editor nor Unity runs, there’s nothing like an actual instance available. You only have the source code, which sort of describes the infrastructure of your program and the data, which is separated from the source code. That data is just not a part of the source code. Your source code doesn’t know about the existence of any concrete instance, because it simply doesn’t run.
In fact, you can tell Unity to serialize everything as plain text (which has many benefits btw) during development.
Here’s a part of a newly created scene, values may differ on your PC:
--- !u!1 &128627299
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 128627302}
- component: {fileID: 128627301}
- component: {fileID: 128627300}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &128627300
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 128627299}
m_Enabled: 1
--- !u!20 &128627301
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 128627299}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &128627302
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 128627299}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
When you start Unity, it’ll begin to deserialize the data when it’s needed.
The exact algorithm is not known, but no matter how they pull it off, it’s roughly this idea:
It knows there’s scene A and that scene contains an object called with GUID “128627299”, which is remembered along with many other information for that scene. Now via the ID, the object’s data can be found and it deserializes that data, creates a GameObject.
Each GO happens to have a Transform, which also got a GUID (here: 128627302) when it was created initially.
Now find the data for 128627299, deserialize it, add a Transform component to the GO that was just created, and inject the transform data into the transform component. Voila, your GO was recovered.
But there’s more:
m_Component:
- component: {fileID: 128627302} // transform
- component: {fileID: 128627301} // camera
- component: {fileID: 128627300} // audio listener
So now it repeats the whole process for everything in your scene.
For that particular object, we’ve already said we needed a Transform, but it still has to create the camera and the audio listener. They will also be mapped to their GUIDs, so whenever another component refers to a component with GUID “128627301”, go check if that one has already been deserialized and thus exists. If it does exist, simply inject the reference - no more work needed. If it doesn’t exist, find the data via the GUID, deserialize it, create everything required in order to get it into a healthy state, and inject it.
Long story short: By looking at source code or the IL code, you won’t see any of those instances. In many cases you can see how an instance is acquired. You can follow methods calls, you can see what is done, and how it’s done, you may find hard-coded initializations of instances… but you won’t find runtime data in terms of “actual instances”. Because these do only exist when you run the actual program.