unity 创建prefab怎么从prefab中提取模型

Unity中根据资源搜索其在prefab中的引用 - 推酷
Unity中根据资源搜索其在prefab中的引用
项目开发笔记 (九)
今天第二款业余独立游戏终于成功在GooglePlay上发布了,共计花了约1个月多的业余时间(每天下班后)。代码行数刚好1W出头。第一次以付费下载的模式上线,不管能不能卖出去但整个过程都非常有趣 每天都非常高效,思绪缠绕。对比公司的项目,真是效率低下。查看我最近三月的日志 关于公司项目的每天就2-3条信息,真是忍无可忍啊。废话说到这里,最近写了一个Unity中根据资源反查在prefab中引用的工具,今天我扯扯其中的原理。
预设,类似各种UI编辑器编辑后的输出文件,Unity中几乎任何事物都可以打包成预设,然后通过外部文件的形式再加载进程序里。不过是PNG/JPG等图片图集资源; GameObject Chartater之类的对象资源。
Unity中提供的搜索选项
Unity本身提供了一个资源引用搜索的选项,不过是针对当前Scene进行逐个资源搜索的,使用如图
这个选项并不能满足我们的需求,除了搜索某个资源在那个prefab被引用之外,在项目后期 我们还可能需要删除尚未被引用过的资源。
引用关系- References
一般的预设 并不是直接把PNG,JPG 合并成一个文件,而是添加它们的引用,预设与其说是UI界面等的导出文件生成文件,不如直接说是UI界面等的描述文件,他描述了某个界面引用了那个图片 那个按钮默认状态是什么 按钮显示Title是什么。举个例子,我们新建一个Panel(UGUI),随便添加一张图片,然后打包成prefab,如图所示
这样生成的prefab是加密的,需要在Editor中设置一下:
这样的设置下生成的prefab可以直接使用二进制编辑器查看里面内容,我刚刚打的prefab里面的部分内容:
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: }
m_GameObject: {fileID: 127900}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -, guid: f5f67c52d6ccd202a3bd8, type: 3}
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: .}
m_Sprite: {fileID: 10907, guid: , type: 0}
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
--- !u!114 &
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: }
m_GameObject: {fileID: 127902}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -, guid: f5f67c52d6ccd202a3bd8, type: 3}
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Sprite: {fileID: , guid: 45ec39cbae118f9cf92d953, type: 3}
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
--- !u!222 &
从中可以看出
1.设计prefab变量命名方式的程序员真是和我一样属于强迫症重度患者。成员变量命名也一丝不苟
2.prefab中详细记录了每一项的信息
3.prefab中引用资源是使用guid的方式引用的(关键点)
查了一下,Unity为每个资源都分配了一个guid,然后根据guid来引用各个资源。获取某个资源的GUID的代码如下:
var path = AssetDatabase.GetAssetPath(o);
string strGUID = AssetDatabase.AssetPathToGUID(path);
// 由此得出,上面的图片(那个猪)GUID为:45ec39cbae118f9cf92d953
在拿到图片的GUID字符串之后,到prefab里反查可以得到在 71 行的地方:
m_Sprite: {fileID: , guid: 45ec39cbae118f9cf92d953, type: 3}
如此就可以判断图片被这个prefab引用过一次。使用搜索GUID的方式可以在任何prefab中搜索任何资源的引用次数。我实现了一个工具,里面包含了基本的功能:
1.搜索1个资源(纹理,图片,材质,特效等)在N个prefab中被引用的次数
2.搜索N个资源(纹理,图片,材质,特效等)在N个prefab中被引用的次数
3.找到N个资源(纹理,图片,材质,特效等)中尚未被引用过的资源
代码如下:
PluginMenu.cs
//*************************************************************************
PluginMenu.cs
资源被预设引用反查工具
//*************************************************************************
//-------------------------------------------------------------------------
using System.IO;
using UnityE
using UnityE
using UnityEngine.UI;
public static class PluginMenu
#region Plugins Menu
[MenuItem(&Plugins/UI/资源引用分析&, false, 3)]
public static void OpenRes2Check()
ResourceCheckTool.Open();
#endregion
ResourceCheckTool.cs
//*************************************************************************
ResourceCheckTool.cs
资源被预设引用反查工具
//*************************************************************************
//-------------------------------------------------------------------------
using System.Collections.G
using UnityE
using UnityE
using System.IO;
using Object = UnityEngine.O
using System.T
public class ResourceCheckTool : EditorWindow
#region Member variables
//-------------------------------------------------------------------------
private List
m_SelectPathL
private List
m_SelectGUIDL
private List
private List
M_ResNotBeR
private Dictionary
& m_DicResP
private string m_strUIPerfabP
private string m_strCurrentPerfabP
private Vector2 m_scrollP
//-------------------------------------------------------------------------
#endregion
#region Public Method
//-------------------------------------------------------------------------
public static void Open()
GetWindowWithRect
(new Rect(0, 0, 700, 800), true);
//-------------------------------------------------------------------------
void OnGUI()
if (null != m_SelectPathList && 0 != m_SelectPathList.Count)
m_scrollPos = GUILayout.BeginScrollView(m_scrollPos, true, true, GUILayout.Height(800));
GUILayout.Space(10);
GUI.backgroundColor = Color.
if (GUILayout.Button(&重新分析 - UI&, GUILayout.Height(50)))
__GetSelectItem();
__GetAllPrefabs(m_strCurrentPerfabPath);
__CheckEveryPrefab();
m_strCurrentPerfabPath = m_strUIPerfabP
GUILayout.Space(10);
GUILayout.Label(&当前预设资源路径为:&);
GUILayout.Label(m_strCurrentPerfabPath);
if (0 == m_SelectPathList.Count)
GUILayout.Label(&-------------------------------------------------------------------------&);
GUILayout.Label(&当前分析的资源为:&);
GUILayout.Label(&此资源尚未被任何预设引用过,可以考虑删除!&);
for (int nInedx = 0; nInedx != m_SelectPathList.C ++nInedx)
if (m_DicResPrefabs.TryGetValue(m_SelectGUIDList[nInedx], out lists))
if (null != lists && 0 != lists.Count)
GUILayout.Label(&-------------------------------------------------------------------------&);
GUILayout.Label(&当前分析的资源为:&);
GUILayout.Label(m_SelectPathList[nInedx]);
GUILayout.Label(&当前分析的资源引用信息为:&);
foreach (string str in lists)
GUILayout.Label(str);
M_ResNotBeRefs.Add(m_SelectPathList[nInedx]);
if (0 != M_ResNotBeRefs.Count)
GUILayout.Label(&-------------------------------------------------------------------------&);
GUILayout.Label(&以下为完全没被引用过的资源:&);
foreach (string str in M_ResNotBeRefs)
GUILayout.Label(str);
M_ResNotBeRefs.Clear();
GUILayout.Label(&-------------------------------------------------------------------------&);
GUILayout.EndScrollView();
GUILayout.Space(10);
GUILayout.Label(&当前没有资源被选中,选择你需要分析的资源然后点【分析】&);
GUILayout.Space(100);
GUI.backgroundColor = Color.
if (GUILayout.Button(&分析 - UI&, GUILayout.Height(50)))
__GetSelectItem();
__GetAllPrefabs(m_strUIPerfabPath);
__CheckEveryPrefab();
m_strCurrentPerfabPath = m_strUIPerfabP
GUILayout.Space(10);
//-------------------------------------------------------------------------
#endregion
#region private Method
//-------------------------------------------------------------------------
private void __Init()
if (null == m_SelectPathList)
m_SelectPathList = new List
m_SelectPathList.Clear();
if (null == m_SelectGUIDList)
m_SelectGUIDList = new List
m_SelectGUIDList.Clear();
if (null == m_PrefabList)
m_PrefabList = new List
m_PrefabList.Clear();
if (null == m_DicResPrefabs)
m_DicResPrefabs = new Dictionary
m_DicResPrefabs.Clear();
if (null == M_ResNotBeRefs)
M_ResNotBeRefs = new List
M_ResNotBeRefs.Clear();
m_strUIPerfabPath = &Assets/Resources/UI/&;
//-------------------------------------------------------------------------
private void __GetSelectItem()
foreach (Object o in Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets))
var path = AssetDatabase.GetAssetPath(o);
// 过滤掉meta文件和文件夹
if (path.Contains(&.meta&) || path.Contains(&.&) == false)
m_SelectPathList.Add(path);
m_SelectGUIDList.Add(AssetDatabase.AssetPathToGUID(path));
Debug.Log(&path = & + path);
Debug.Log(&GUID = & + AssetDatabase.AssetPathToGUID(path));
//-------------------------------------------------------------------------
private void __GetAllPrefabs(string strPrefabsPath)
if (null == m_SelectPathList || 0 == m_SelectPathList.Count)
Debug.Log(&请在Project中选择需要分析的资源!&);
var dirArr = Directory.GetDirectories(strPrefabsPath);
for (int i = 0; i & dirArr.L i++)
var pathArr = __GetFiles(dirArr[i]);
for (int j = 0; j & pathArr.L j++)
var filePath = pathArr[j];
var progress = i * 1f / dirArr.Length + (j + 1f) / pathArr.Length / dirArr.L
// Debug.Log(&filePath: &+ &[& + i + &]& + &[& + j + &] = & + filePath);
m_PrefabList.Add(filePath);
var paths = __GetFiles(strPrefabsPath);
for (int j = 0; j & paths.L j++)
var filePath = paths[j];
// Debug.Log(&filePath: & +
&[& + j + &] = & + filePath);
m_PrefabList.Add(filePath);
Debug.Log(& m_PrefabList.Count = & + m_PrefabList.Count);
//-------------------------------------------------------------------------
private void __CheckEveryPrefab()
if (null == m_SelectGUIDList || 0 == m_SelectGUIDList.Count)
if (null == m_SelectPathList || 0 == m_SelectPathList.Count)
if (null == m_PrefabList || 0 == m_PrefabList.Count)
m_DicResPrefabs.Clear();
int nLen = m_SelectPathList.C
for (int nInedx = 0; nInedx != nL ++nInedx)
EditorUtility.DisplayProgressBar(&搜索预设引用关系&, &搜索中...& + m_SelectPathList[nInedx], nInedx / nLen);
string strFilePath = m_SelectPathList[nInedx];
string strFileGUID = m_SelectGUIDList[nInedx];
if (!m_DicResPrefabs.TryGetValue(strFileGUID, out list))
list = new List
list.Clear();
m_DicResPrefabs.Add(strFileGUID, list);
if (null != list)
__CheckByGUID(strFileGUID, ref list);
EditorUtility.ClearProgressBar();
//-------------------------------------------------------------------------
private void __CheckByGUID(string strGUID, ref
if (null == strGUID || null == list)
if (null == m_PrefabList || 0 == m_PrefabList.Count)
int nLen = m_PrefabList.C
string strPrefabFile =
for (int nIndex = 0; nIndex != nL ++nIndex)
strPrefabFile = m_PrefabList[nIndex];
FileStream fs = new FileStream(strPrefabFile, FileMode.Open, FileAccess.Read);
byte[] buff = new byte[fs.Length];
fs.Read(buff, 0, (int)fs.Length);
string strText = Encoding.Default.GetString(buff);
int nStar = 0;
int nCount = 0;
while (-1 != nStar)
nStar = strText.IndexOf(strGUID, nStar);
if (-1 != nStar)
if (0 != nCount)
strText = m_PrefabList[nIndex] + & 在此资源中被引用 & + nCount + & 次&;
list.Add(strText);
catch (System.Exception ex)
Debug.Log(&__CheckByGUID Error&);
//-------------------------------------------------------------------------
/// 获取目录下的所有对象路径,去掉了.meta
是否递归获取
string[] __GetFiles(string path, bool recursive = true)
var resultList = new List
var dirArr = Directory.GetFiles(path, &*&, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
for (int i = 0; i & dirArr.L i++)
if (Path.GetExtension(dirArr[i]) != &.meta&)
resultList.Add(dirArr[i].Replace('\\', '/'));
return resultList.ToArray();
//-------------------------------------------------------------------------
#endregion
运行效果如下:
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致程序写累了,就来玩玩酷跑小游戏吧,嘿嘿。
雨松MOMO送你一首歌曲,嘿嘿。
Unity3D研究院编辑器之不实例化Prefab获取删除更新组件(十五)
Unity3D研究院编辑器之不实例化Prefab获取删除更新组件(十五)
围观13370次
编辑日期: 字体:
感谢楼下的牛逼回复更正一下,我表示我也是才知道。。
其实不需要实例化也能查找,你依然直接用GetComponentsInChildren&&(true),对传true即可。。。这样搞还很麻烦。。。唯一关注是能否把Missing的脚本序列化找出来??
使用 GetComponentsInChildren&&(true) 可以直接把Project视图里的子对象找出来!!!!
代码是这样的
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
[MenuItem("Assets/Delete")] static void delete ()
GameObject prefab = AssetDatabase.LoadAssetAtPath&GameObject&("Assets/GameObject.prefab");&
//删除MeshCollider
MeshCollider [] meshColliders = prefab.GetComponentsInChildren&MeshCollider&(true);
foreach(MeshCollider meshCollider in meshColliders){&
GameObject.DestroyImmediate(meshCollider,true);
//删除空的Animation组件
Animation [] animations = prefab.GetComponentsInChildren&Animation&(true);
foreach(Animation animation in animations){
if( animation.clip == null){
GameObject.DestroyImmediate(animation,true);
//删除missing的脚本组件
MonoBehaviour [] monoBehaviours = prefab.GetComponentsInChildren&MonoBehaviour&(true);
foreach(MonoBehaviour monoBehaviour in monoBehaviours){&&
if(monoBehaviour == null){
Debug.Log("有个missing的脚本");
//GameObject.DestroyImmediate(monoBehaviour,true);&
//遍历Transform的名子, 并且给某个游戏对象添加一个脚本
Transform [] transforms = prefab.GetComponentsInChildren&Transform&(true);
foreach(Transform transfomr in transforms){
if(transfomr.name == "GameObject (1)"){
Debug.Log(transfomr.parent.name);
transfomr.gameObject.AddComponent&BoxCollider&();
//遍历Transform的名子, 删除某个GameObject节点
foreach(Transform transfomr in transforms){
if(transfomr.name == "GameObject (2)"){
GameObject.DestroyImmediate(transfomr.gameObject,true);
}&&&&&&&&&&&&&&&&EditorUtility.SetDirty(prefab); }
今天有朋友说不能删除missing的脚本, 我试了一下确实不行。 随后查了一下, 可以用这个方法来删除,
12345678910111213141516171819202122232425262728293031323334353637
[MenuItem("Edit/Cleanup Missing Scripts")] static void CleanupMissingScripts () {&&&& for(int i = 0; i & Selection.gameObjects.Length; i++)&&&& {&&&&&&&& var gameObject = Selection.gameObjects[i];&&&&&&&& &&&&&&&& // We must use the GetComponents array to actually detect missing components&&&&&&&& var components = gameObject.GetComponents&Component&();&&&&&&&& &&&&&&&& // Create a serialized object so that we can edit the component list&&&&&&&& var serializedObject = new SerializedObject(gameObject);&&&&&&&& // Find the component list property&&&&&&&& var prop = serializedObject.FindProperty("m_Component");&&&&&&&& &&&&&&&& // Track how many components we've removed&&&&&&&& int r = 0;&&&&&&&& &&&&&&&& // Iterate over all components&&&&&&&& for(int j = 0; j & components.Length; j++)&&&&&&&& {&&&&&&&&&&&& // Check if the ref is null&&&&&&&&&&&& if(components[j] == null)&&&&&&&&&&&& {&&&&&&&&&&&&&&&& // If so, remove from the serialized component array&&&&&&&&&&&&&&&& prop.DeleteArrayElementAtIndex(j-r);&&&&&&&&&&&&&&&& // Increment removed count&&&&&&&&&&&&&&&& r++;&&&&&&&&&&&& }&&&&&&&& }&&&&&&&& &&&&&&&& // Apply our changes to the game object&&&&&&&& serializedObject.ApplyModifiedProperties();&&&&&&&& //这一行一定要加!!!&&&&&&&& EditorUtility.SetDirty(gameObject);&&&& } }
昨天晚上睡觉的时候脑洞打开。因为做项目的时候我们可能要在编辑器上做很多检查工具一类的东西。 这里我说几个典型的例子,比如空的Animation组件、丢失的脚本、没用的meshCollider组件。这些东西我们是不需要的,但是美术可能不会不小心加到prefab里。
以前的做法是 先要把Prefab 实例化 Instance以后
GetComponentsInChildren
把所有的组件都取出来。 在进行遍历删除。 然后还要DestroyImmediate 它。 。那么如果prefab数量比较多的话,那么检查一次时间是很漫长的。
如果你只是想找组件 空脚本 一类的。用如下代码就可以不实例化并且找出来。
如果你想不实例化并且修改数据的话,那么可以考虑用下面的方法。
1.先把prefab 序列化的方式改成text 用File就可以把prefab的文本信息读出来。
File.ReadAllText("xxx/xxx.prefab")
2.prefab文本序列化的结构,如下图所示,看到!u!111了吗
111 是一组id .它是有意义的(它表示Animation),标着着这个组件是个啥东西。 具体是什么含义大家可以去这里查
3.自定义脚本
如果我想查一下看看prefab有没有绑定我自己写的脚本怎么办呢?如下图所 ,guid这一栏 就写的是你的脚本的guid了。
然后在脚本对应的mate文件里就记录这这个脚本的guid ,如果这两个id匹配,那么就说明这个prefab里挂着这个脚本了。
最后就交给正则表达式做第一步的匹配吧。 这样的话第一步就可以筛选掉一大批prefab了。 如果还需要进行验证在进一步的Instance来检查吧。。
本文固定链接:
转载请注明:
雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
如果您愿意花10块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。
您可能还会对这些文章感兴趣!阅读(19326)
本次教程,我们来创建一个简单的Prefab组件。
教程参考自人人素材翻译组出品的翻译教程《Unity游戏引擎的基础入门视频教程》。
说到Prefab,中文翻译为预设体,在Unity官方的书本《Unity4.X从入门到精通》中的解释是:可以理解为是一个游戏对象及其组件的集合,目的是使游戏对象及资源能够被重复使用。相同的对象可以通过一个预设体来创建,此过程可理解为实例化。
& & &存储在项目文件中(Project视图)的状态时,预设体作为一个资源,可应用在一个项目中的不同场景中。当拖动预设体到场景中就会创建一个实例。该实例与其原始预设体的有关联的,对预设体进行更改,实例会同步修改。Prefab可以提升资源的利用率和开发的效率。
& & &好比某个Prefab是我们创建好的一个标准的房间模型,当我们在场景中需要建立另一个房间的时候,不需要我们再手动的将地板,墙,天花板拼接,而是可以直接拖一个Prefab到Scene视图中,并对这个Prefab进行修改就可以了。
& & &下面开始创建我们的Prefab组件。接上一篇教程,现在我们场景内,有三个没有吻合的模型。如下图:
接下来我们把分别单击选中这三部分,并在Inspector视图中,找到Position,将他们的XYZ坐标都修改为0,0,0,
我们会发现模型会变为下图这样。
我们发现MineShaft_Wall中心在墙的中间,所以我们要修改下他的Y坐标。选中墙壁模型,将MineShaft_Wall墙壁模
型的Y坐标改为3就可以了,事实上这个模型墙壁高度为6个单位。
& & &这里要注意一下,如果你要单方向移动一个模型,即保持其他两个坐标值不变只改变一个坐标值,那么你就要按
住坐标系三个方向其中的一个并拖动就可以了。
接下来我们要做的是把现在位于中间的墙壁,移动到一侧,并复制几面墙移动到四周。直接拖动或者修改坐标可能
会产生各种误差,这里我们采用顶点捕捉(Vertex Snapping)。
& & & 我们首先选中我们要移动的物体,然后按下V,我们会发现当我们移动鼠标的时候坐标轴会只在模型中的顶点
停留。如下图,当你们鼠标挪到左下角的时候,会在左下方的定点出现坐标轴。
这时我们可以单击鼠标,选中这个顶点坐标轴处的正方形区域(如图中的黄色小正方形),拖动这个模型,可以让
这个顶点与下面我们鼠标挪到的另一个顶点结合。如下图,我选中这个顶点后,将其拖动至地板左下角的顶点上,
让这两个顶点相合。这是一个快捷的移动模型的方法。
接下来我们要借用这一面墙复制出另一面墙,并移动位置。我们选中我们刚移动的这面墙体,按下Ctrl+D(Mac下为
Command+D)来复制一下,我们会在Hierarchy视图下发现多了一个MineShaft_Wall,然后我们采取刚才同样的方
法,按下V,选取墙体的左下角坐标轴,拖到地板的右下角顶点,我们刚才复制的墙体就到已有墙体的对面。但是
我们前面提到过,我们的墙体不从正面看是看不到的,平移后我们相当于看到的是墙体的另一面,所以这里才会只
有一个坐标系而没有墙。如果我们稍微向右再挪动一点儿视角,就会看到我们复制出来的这面墙。
图一:未挪动视角
图二:挪动视角
我们要做的是把这面墙体旋转180度。选中这面墙,按下E,激活Rotate工具,坐标轴会发生如下变化:
这里我们按住绿色线确保Y轴角度不便,然后按住CTRL(Mac下按住Command)逆时针方向一直拖动,按住CTRL
是以15度为单位来旋转,旋转后我们会发现Inspector视图中,Y角度变成了-180(有时会是179.99),当然也可以
直接在Inspector视图额Rotation里面直接修改角度,要确保你修改的坐标轴是正确的。旋转完成后按W回归原始坐
&现在我们开始将这个模型放入一个Prefab组件中。先在Project视图内,ImportedAssets文件夹内新建Prefabs文件
夹,专门用来放置我们的预设体。
然后进入Prefabs文件夹,右键,Create,Prefab,将新建的空的Prefab命名为PFB_Straight。
下面我们将视线移动到Hierarchy视图中,我们可以看到四个蓝色名称,蓝色名称表明该物体是预设体或者从文件中
拖入到场景内的。现在要把物体放入Prefab中,我们可以发现当这些物体全部选中后是无法拖进我们新建的Prefab
中的。这里我们需要在Hierarchy中创建一个空的游戏物体------菜单栏,GameObject,Create Empty,这样Hierarchy
中会多出一个空的GameObject。我们对其命名为PFB_Straight,没错,和我们的Prefab名字相对应,并且现在这个
object的名称是白色的。下面我们来为此object与我们的四个物体建立父子关系,并将其导入到Prefab中。
选中全部四个物体,然后拖动到PFB_Straight中,会形成如图一的父子关系,接下来选中PFB_Straight,并拖入到
Project视图中我们新建的Prefab中,就完成了Prefab的初步创建,如图二。
& & &图二中,Hierarchy中物体全部变为蓝色,而且我们的Prefab在界面右下角有了预览,我们可以在预览中用鼠标
拖动来查看Prefab的内容。
至此,一个很简单的Prefab就创建好了,下面我们将这个组件拖入到场景中,我们会发现我们已经可以将我们之前
创建的组件很简单的假如场景中。这就是Prefab的优点,大大提高了工作的效率。
我们现在可以选中一个组件,按住V,并利用前面提到过的顶点捕捉(Vertex Snapping)来建立一个长长的隧道。
注意要选中整个组件,而不是组件的一部分,不要只把地板或者墙壁移动了,如果移动错了,记得按CTRL
(Command)+Z来取消操作。大家可以来试试创建一个隧道。
By Mr.Losers
阅读排行榜

我要回帖

更多关于 unity3d 游戏模型提取 的文章

 

随机推荐