一、Unity的資源(Asset)和對象(UnityEngine.Objects)
資源(Asset):是硬盤中的文件,存儲在Unity工程的Assets文件夾內。例如,紋理(Texture),材質(Material)和FBX文件等,它們都是資源。一些資源的數據格式是Unity原生支持的,例如材質。有些資源則需要轉換為原生的數據格式后才能被Unity使用,例如FBX文件。
對象(UnityEngine.Object):代表序列化數據的集合,表示某個資源的具體實例。它可以是Unity引擎使用的任何類型的資源,例如網格,Sprite,音頻剪輯或動畫剪輯。所有的對象(Object)都是UnityEngine.Object基類的子類。
幾乎所有的對象(Object)類型都是內建的,其中有兩種比較特殊的類型。
ScriptableObject為開發者提供了一套便捷的系統,供開發者自定義數據類型。這些類型可以被Unity直接序列化或反序列化,並在Unity編輯器的檢視器窗口中進行操作。
MonoBehaviour提供了鏈接MonoScript的容器。MonoScript是一種內部數據類型,Unity用它保存對某個特定程序集和命名空間中特定腳本類的引用,MonoScript本身不包含任何實際的可執行代碼。
資源(Asset)與對象(Object)是一種一對多的關系,即一個資源文件可能會包括多個Object。
二、對象之間的引用
所有UnityEngine.Objects都可以引用其他的UnityEngine.Objects。這里“其他的Object”可能存在於相同的資源文件中,或需要從其他資源文件導入。例如,一個材質Object通常有一個或多個紋理Object的引用。這些紋理Object一般是從一個或多個紋理資源文件中導入的(例如PNG或JPG文件)。
序列化后,這些引用由兩部分數據組成:文件GUID和本地ID。文件GUID用於識別資源(Asset)文件中目標資源(Resource)的存儲位置。而本地唯一的ID負責識別單個資源文件中的Object,因為一個資源文件可能會包含多個Object。
比如一個特效做成的Prefab,直接用文本打開prefab和prefab.meta后綴的兩個文件。
GUID:存儲於.meta文件中。Unity會在首次導入資源文件時生成.meta文件,並和資源文件一起存儲在相同的目錄中。GUID提供了文件存儲位置的抽象,這樣一個文件GUID就對應一個具體的文件,因此我們才能隨意移動這個文件而不破壞所有相關Object對這個文件的引用。
fileFormatVersion: 2
guid: 87160fe309c6cd4458c5f56188b57684
timeCreated: 1568165766
licenseType: Pro
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
本地ID:是唯一的,使用文本編輯器打開prefab文件,可以看下關於這個預設的所有屬性都在配置里面,100100000就是本地ID。任何資源(Asset)文件中都可能含有(或通過導入產生)多個UnityEngine.Object資源(Resource),因此需要一個本地ID來對其中的Object做明確區分。一個大的預設里面會有多個gameobject,相當於總的預設會記錄子物體的本地ID,這樣才能關聯到每一個子物體。
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &100100000
Prefab:
m_ObjectHideFlags: 1
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications: []
m_RemovedComponents: []
m_ParentPrefab: {fileID: 0}
m_RootGameObject: {fileID: 1891991147743598}
m_IsPrefabParent: 1
--- !u!1 &1053155390742798
總結:Guid相當於指向一個資源路徑;本地ID相當於指向於具體的游戲對象Object
三、資源引用分析
我們知道了資源和對象的關系后,對資源引用過程已經清楚了。那么怎么分析資源的引用關系呢?
AssetDatabase.AssetPathToGUID(path)
AssetDatabase.GUIDToAssetPath(guid)
看代碼就大概了解,這兩個方法可以將guid和path進行互相轉換,因此可以根據預設里引用到的所有guid值,進而可以找到這個資源所引用到對象。
四、Unity資源引用的機制
我們在Unity如果創建一個Materials,然后指定一個有兩張圖片屬性的Shader,接着引用了兩張紋理。這時候打開材質可以看到是有引用到兩個紋理m_Texture。可以看下原先的屬性:
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_Name: 5400_fangkuai
m_Shader: {fileID: 4800000, guid: b5f72fe4d91f50b47920a7498eeaf32a, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _AmitTex:
m_Texture: {fileID: 2800000, guid: 35a133778d8d5f64788367aa21dd2589, type: 3}
m_Scale: {x: 0.5, y: 1}
m_Offset: {x: 0, y: 0}
- _FlowTex:
m_Texture: {fileID: 2800000, guid: 4cccfa2e1ba52a342b545da00de76a29, type: 3}
m_Scale: {x: -2, y: -2}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AmitIntensity: 2
- _FlowIntensity: 4
- _FlowTexUSpeed: 0.5
- _FlowTexVSpeed: -0.8
m_Colors:
- _Color: {r: 0.32352942, g: 0.5521299, b: 1, a: 1}
- _FlowColor: {r: 0, g: 0.28539556, b: 0.61764705, a: 1}
如果這時我改一下Shader,而且這個Shader只引用一張紋理,我不賦值。這個材質引用的紋理就變成了三張,前面Shader引用到的紋理依舊在引用。
m_TexEnvs:
- _AmitTex:
m_Texture: {fileID: 2800000, guid: 35a133778d8d5f64788367aa21dd2589, type: 3}
m_Scale: {x: 0.5, y: 1}
m_Offset: {x: 0, y: 0}
- _Amitex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _FlowTex:
m_Texture: {fileID: 2800000, guid: 4cccfa2e1ba52a342b545da00de76a29, type: 3}
m_Scale: {x: -2, y: -2}
m_Offset: {x: 0, y: 0}
總結:這個引用機制就是每次改變時,只增加新的引用,舊的引用不會去刪除。
這個機制的好處:是當又改成之前的shader時,會自動引用到紋理和設置屬性。
這個機制的壞處:資源會導致冗余,打包時會打到多余的資源對象,加載時也會加載多余的資源對象。
五、Unity資源冗余
我們知道了引用機制,那么可以發現一些應該避免和注意的問題。
- 當有對象被刪除時,引用此對象的依賴依然存在,應該處理。
- 材質:材質會引用紋理,而紋理是根據材質所引用的Shader決定,所以應該針對所有材質進行處理紋理的引用。
- 粒子系統(Particle System):粒子系統可以引用FBX和材質,在Renderer中可以引用到FBX網格,這部分也會引起FBX和紋理引用冗余。
六、資源打包冗余
打包冗余是指相同的對象,比如紋理被重復打進多個AB包,這樣會造成包體變大,加載重復資源的問題。
所以我們應該根據這些特性或者說機制,來分析資源引用的關系,再來進行引用的優化。所以針對這種情況會不同的資源先進行分析,最后再根據分析的情況來進行優化。