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不操作脚本。
如果有更好的方法欢迎各位朋友在下面给我留言,谢谢。