為了實現游戲人物外形的定制,專門研究了Unity官方換裝demo 。
1.基礎
BuildPipeline.BuildAssetBundle 把gameobject生成包,可以用於替換和更新
www.assetBundle 動態載入包
SkinnedMeshRenderer 骨骼模型渲染器,美術導出的時候每個子模型都帶SkinnedMeshRenderer
2.基礎模型生成
基礎模型是從characters目錄下找fbx模型(名稱里面不帶@),如果找到(如female.fbx),則把模型下所有有SkinnedMeshRenderer的gameobject刪除,然后給模型本身加了個SkinnedMeshRenderer。
因為female是帶animation的,所以這樣一個基礎模型就生成了,此模型帶animations, 並且帶一個SkinnedMeshRenderer。
3.子模型包的生成
子模型包是根據前面找到的fbx模型的子模型,主要包括face, eye, shoes, top ,pants, hair幾個部分,子模型包包括三個部分:一個pretab,用到的所有的material, bonenames,
4.生成過程
生成過程主要在兩個函數:
public GameObject Generate() //生成基礎模型
public GameObject Generate(GameObject root) //生成root下的子模型
每一個子模型包對應的類是CharacterElement,整個模型的構成過程,就是集合所有子模型SkinnedMeshRenderer的materials, mesh的CombineInstance,還有bonenames,
SkinnedMeshRenderer r = root.GetComponent<SkinnedMeshRenderer>();
r.sharedMesh = new Mesh();
r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);
r.bones = bones.ToArray();
r.materials = materials.ToArray();
全部賦值到了基礎模型的SkinnedMeshRenderer上
5. SkinedMeshRender:該對象負責網格繪制。主要數據成員包括
var bones : Transform[] 骨骼
var materials : Material[] 材質
var sharedMesh : Mesh 網格
其中Mesh的主要成員是
vertices : Vector3[] 頂點
boneWeights : BoneWeight[] 骨骼權重
boneWeights數組與vertices數組對應,表示對應下標的頂點運動受骨骼影響的權重。BoenWeight結構記錄了骨骼在SkinedMeshRender.bones數組中的索引。
6.網格和材質的對應關系
一張實際的網格只能施加一個材質。因此,當render所使用的mesh包含多個實際網格(sub mesh),它對每個sub mesh所施加的材質實際上是materials數組中對應下標的材質。
7. 合並網格(CombineMeshes)函數的第二個參數是設置是否將多個子網格合並成一張實際的網格。正如前面所述,一個實際的網格只能施加一個材質, 所以只有被合並的所有網格原來使用的就是同一個材質(即共享材質)時,將它們真正合並才能正確應用材質。否則,應該將該參數置為false,表示不實際合 並這些sub mesh,而是將它們作為被合並后Mesh對象的sub mesh。
四、數組對應問題:網格頂點和骨骼、sub mesh和材質之間的對應都是通過數組下標進行的,所以操作時保證新生成的個數組下標對應關系正確是非常重要的。
這是例子中組合創建模型的主要函數,我將自己理解后的備注添加在里面。
- // Creates a character based on the currentConfiguration recycling a
- // character base, this way the position and animation of the character
- // are not changed.
- // 這個函數實際上並沒有將各部分的子網格合並成一張網,而只是將他們合並到
- // 同一個Mesh下作為sub mesh。因為一張網格只能用一個材質,只有所有子網格
- // 都共享同一個材質時,合並成一張網才能保證材質應用正確。
- public GameObject Generate(GameObject root) {
- // The SkinnedMeshRenderers that will make up a character will be
- // combined into one SkinnedMeshRenderers using multiple materials.
- // This will speed up rendering the resulting character.
- List<CombineInstance> combineInstances = new List<CombineInstance>();
- List<Material> materials = new List<Material>();
- List<Transform> bones = new List<Transform>();
- //獲得構成骨架的所有Transform
- Transform[] transforms = root.GetComponentsInChildren<Transform>();
- //一次處理構成身體的各部分
- foreach (CharacterElement element in currentConfiguration.Values)
- {
- //GetSkinnedMeshRenderer()內部Instantiat了一個由該部分肢體Assets構成的
- //GameObject,並返回Unity自動為其創建SinkedMeshRender。
- SkinnedMeshRenderer smr = element.GetSkinnedMeshRenderer();
- //注意smr.materials中包含的材質數量和順序與下面的sub mesh是對應的
- materials.AddRange(smr.materials);
- for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
- {
- CombineInstance ci = new CombineInstance();
- ci.mesh = smr.sharedMesh;
- ci.subMeshIndex = sub;
- combineInstances.Add(ci);
- }
- // As the SkinnedMeshRenders are stored in assetbundles that do not
- // contain their bones (those are stored in the characterbase assetbundles)
- // we need to collect references to the bones we are using
- // 網格點與骨骼的對應關系是通過Mesh數據結構中的BoneWeight數組來實現的。該數組
- // 與網格頂點數組對應,記錄了每個網格點受骨骼(骨骼記錄在SinkedMeshRender的bones
- // 數組中,按下標索引)影響的權重。
- // 而此處,示例程序提供的肢體Assets並不包含骨骼,而是返回骨骼名稱。因此,推斷
- // GetBoneNames()返回的骨骼名稱應該與實際骨骼數組的順序相同。
- foreach (string bone in element.GetBoneNames())
- {
- foreach (Transform transform in transforms)
- {
- //通過名字找到實際的骨骼
- if (transform.name != bone) continue;
- bones.Add(transform);
- break;
- }
- }
- Object.Destroy(smr.gameObject);
- }
- // Obtain and configure the SkinnedMeshRenderer attached to
- // the character base.
- // 至此,combineInstances、bones和materials三個數組中的數據對應關系是正確的。
- // 合並時,第二個參數是fals,表示保持子網格不變,只不過將它們統一到一個Mesh里
- // 來管理,這樣只需采用一個SkinedMeshRender繪制,效率較高。
- SkinnedMeshRenderer r = root.GetComponent<SkinnedMeshRenderer>();
- r.sharedMesh = new Mesh();
- r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);
- r.bones = bones.ToArray();
- r.materials = materials.ToArray();
- return root;
- }
