Unity研究院之Prefab和GameObject的正向和逆向查找引用
我發現很多美工兄弟都愛問程序Unity3d為什么總丟材質? 我不排除U3d有BUG的情況下會丟材質?但是其實很多時候是人為操作而引起的。
1.不保存就在上傳
這個操作太恐怖了,切記!!在 U3D里面你無論操作了什么,當你要上傳svn的時候一定要先保存場景,Ctrl+S 切記切記!!如果不保存就上傳很有可能就會丟材質。
2.我的電腦明明沒事,怎么你哪里就丟材質?
我發現一個很有意思的現象,每次走到美術電腦前看它的svn工程時,我都會發現各種的“黃嘆號”整個工程嚴重的沖突。然后程序和美術的對話就是:“這資源有用沒?沒用刪了, 我重新更新。。。” 哈哈哈 我覺得可能只有程序員才會對沖突非常敏感吧,但我覺得這件事不能怪美術,美術是很抽象的東西,不向程序要具體到少一個分號就無法進行了。
3.管理好材質貼圖
如果項目不管理好材質和貼圖,后起你會發現有很多完全一樣的圖片只是名子不一樣,但是還都在使用中。。這個很有可能是好幾個美術同時在做,這樣會造成資源的浪費。
4.特效丟材質
我舉個實際的例子,我們項目場景和特效是兩個人來做。之前總遇到場景特效莫名其妙丟材質的問題。原因就是做特效的人把特效修改了,但是做場景的又不知道,因為可能這個特效在好多場景上都在使用中。
如下圖所示,在Preiject下面方的Prefab 如果拖入Hierarchy視圖中,它倆是具有關聯關系的,如果你直接在Project視圖中修改了Prefab那么所有Hierarchy視圖中關聯它的Prefab都會得到修改。小改動這樣是沒問題的,如果發生一些比較大的改動,那么Hierarchy視圖中Prefab可能就會丟失或者壞掉。但是假如你有很多場景都引用同一個Prefab,這么多場景會很難找到的。
正向在Hierarch視圖中選擇一個prefab,然后在右側Inspector視圖中點擊Select會自動找到Project視圖中的Prefab對象。那么反向如何從Project視圖中找到Hierarchy視圖中引用的Prefab呢?
如下圖所示,Unity3d做了一個功能,在Project視圖中選擇一個Prefab,然后右鍵選擇Find References In Scene ,那么此時Unity會自動幫你在當前場景中找到Hierarchy視圖中引用這個Prefab的GameObject。
但是這個方法有點局限性,就是它只能找到當前場景的,假設你的這個Prefab在很多場景中都引用了,那么這樣是找不到了,為了美術我決定寫一個小小的工具,嘿嘿。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
[MenuItem("Assets/Check Prefab Use ?")]
private static void OnSearchForReferences()
{
//確保鼠標右鍵選擇的是一個Prefab
if(Selection.gameObjects.Length != 1)
{
return;
}
//遍歷所有游戲場景
foreach(EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if(scene.enabled)
{
//打開場景
EditorApplication.OpenScene(scene.path);
//獲取場景中的所有游戲對象
GameObject []gos = (GameObject[])FindObjectsOfType(typeof(GameObject));
foreach(GameObject go in gos)
{
//判斷GameObject是否為一個Prefab的引用
if(PrefabUtility.GetPrefabType(go) == PrefabType.PrefabInstance)
{
UnityEngine.Object parentObject = EditorUtility.GetPrefabParent(go);
string path = AssetDatabase.GetAssetPath(parentObject);
//判斷GameObject的Prefab是否和右鍵選擇的Prefab是同一路徑。
if(path == AssetDatabase.GetAssetPath(Selection.activeGameObject))
{
//輸出場景名,以及Prefab引用的路徑
Debug.Log(scene.path + " " + GetGameObjectPath(go));
}
}
}
}
}
}
public static string GetGameObjectPath(GameObject obj)
{
string path = "/" + obj.name;
while (obj.transform.parent != null)
{
obj = obj.transform.parent.gameObject;
path = "/" + obj.name + path;
}
return path;
}
|
如下圖所示,我就可以找到哪個場景引用了這個Prefab。 這里我沒有用GameObject和Prefab的名子來判斷,我覺得最好也不要用名字,因為Hierarchy視圖中的名子完全可以隨意修改的,如果被改了的話就肯定找不到了,而且場景那么多完全有重名的情況。
是不是很簡單呢?嘿嘿。
或者還有個辦法,就是運行時動態的載入Prefab,但是有些場景特效其實萬全沒有任何意義,僅僅是展示而已,程序也無需對它進行操作。
有了這個小工具,那么以后美術改完Prefab的時候,運行一下連帶的看看某些場景上是否正常的顯示了?嘿嘿嘿嘿。
Unity3D研究院之Prefab里面的Prefab關聯問題
最近在做UI部分中遇到了這樣的問題,就是Prefab里面預制了Prefab。可是在Unity里面一旦Prefab預制了Prefab那么內部的Prefab就失去關聯。導致與如果要改內部的Prefab需要把所有引用的地方全部改一遍。今天在逛國外網站看到了一個老外的思路,原文在這里 http://framebunker.com/blog/poor-mans-nested-prefabs/
下面直接上代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
#endif
using System.Collections.Generic;
[ExecuteInEditMode]
public class PrefabInstance : MonoBehaviour
{
public GameObject prefab;
#if UNITY_EDITOR
// Struct of all components. Used for edit-time visualization and gizmo drawing
public struct Thingy {
public Mesh mesh;
public Matrix4x4 matrix;
public List<Material> materials;
}
[System.NonSerializedAttribute] public List<Thingy> things = new List<Thingy> ();
void OnValidate () {
things.Clear();
if (enabled)
Rebuild (prefab, Matrix4x4.identity);
}
void OnEnable () {
things.Clear();
if (enabled)
Rebuild (prefab, Matrix4x4.identity);
}
void Rebuild (GameObject source, Matrix4x4 inMatrix) {
if (!source)
return;
Matrix4x4 baseMat = inMatrix * Matrix4x4.TRS (-source.transform.position, Quaternion.identity, Vector3.one);
foreach (MeshRenderer mr in source.GetComponentsInChildren(typeof (Renderer), true))
{
things.Add(new Thingy () {
mesh = mr.GetComponent<MeshFilter>().sharedMesh,
matrix = baseMat * mr.transform.localToWorldMatrix,
materials = new List<Material> (mr.sharedMaterials)
});
}
foreach (PrefabInstance pi in source.GetComponentsInChildren(typeof (PrefabInstance), true))
{
if (pi.enabled && pi.gameObject.activeSelf)
Rebuild (pi.prefab, baseMat * pi.transform.localToWorldMatrix);
}
}
// Editor-time-only update: Draw the meshes so we can see the objects in the scene view
void Update () {
if (EditorApplication.isPlaying)
return;
Matrix4x4 mat = transform.localToWorldMatrix;
foreach (Thingy t in things)
for (int i = 0; i < t.materials.Count; i++)
Graphics.DrawMesh (t.mesh, mat * t.matrix, t.materials[i], gameObject.layer, null, i);
}
// Picking logic: Since we don't have gizmos.drawmesh, draw a bounding cube around each thingy
void OnDrawGizmos () { DrawGizmos (new Color (0,0,0,0)); }
void OnDrawGizmosSelected () { DrawGizmos (new Color (0,0,1,.2f)); }
void DrawGizmos (Color col) {
if (EditorApplication.isPlaying)
return;
Gizmos.color = col;
Matrix4x4 mat = transform.localToWorldMatrix;
foreach (Thingy t in things)
{
Gizmos.matrix = mat * t.matrix;
Gizmos.DrawCube(t.mesh.bounds.center, t.mesh.bounds.size);
}
}
// Baking stuff: Copy in all the referenced objects into the scene on play or build
[PostProcessScene(-2)]
public static void OnPostprocessScene() {
foreach (PrefabInstance pi in UnityEngine.Object.FindObjectsOfType (typeof (PrefabInstance)))
BakeInstance (pi);
}
public static void BakeInstance (PrefabInstance pi) {
if(!pi.prefab || !pi.enabled)
return;
pi.enabled = false;
GameObject go = PrefabUtility.InstantiatePrefab(pi.prefab) as GameObject;
Quaternion rot = go.transform.localRotation;
Vector3 scale = go.transform.localScale;
go.transform.parent = pi.transform;
go.transform.localPosition = Vector3.zero;
go.transform.localScale = scale;
go.transform.localRotation = rot;
pi.prefab = null;
foreach (PrefabInstance childPi in go.GetComponentsInChildren<PrefabInstance>())
BakeInstance (childPi);
}
#endif
}
|
用法比較簡單,比如我有兩個Prefab,inside嵌入在Big里面。如下圖所示,把PrefabInstance腳本掛在Big上,然后把inside拖入下方。
OK 無論怎么修改inside這個Prefab,當實例化Big的時候都能得到最新修改的Inside這個Prefab。
持續思考:
界面預覽問題,就是我在布界面的時候,我需要把子集Prefab界面控件拖進來預覽效果。如果用上述思路UI的Prefab就必須通過腳本自動生成。一份是預覽用的也就是不需要腳本的,一份是只帶腳本運行時動態生成的。在處理自動生成UIPrefab的時候可以利用tag 比如像這種需要內嵌的Prefab標記一個特殊的tag,在Editor下完成Prefab的導出。另外布界面的時候不需要綁定腳本,而上述腳本的綁定也應該由Editor導出Prefab的時候完成。
總之一切布界面的時候只操作Prefab不操作腳本。
如果有更好的方法歡迎各位朋友在下面給我留言,謝謝。