伏雨朝寒悉不胜,那能还傍杏花行。去年高摘斗轻盈。漫惹炉烟双袖紫,空将酒晕一衫青。人间何处问多情。 ———— 纳兰容若
Unity
最近都在写编辑器拓展的小工具,感觉做这方面的内容写起来就像自己真的参与到了项目中x。大概完成的是一个GI检索的小工具,包含几个功能
- 检索在Hierarchy中选择的物体并广度优先(Queue)找到其最下层含有LODgroup或者Renderer的物体,并将其GI信息显示在面板上。同时有FrozeWindow功能,便于美术在Editor中操作。
- 检索后支持在页面内勾选物体通过“Apply Selection in Hierarchy”进行反选至Hierarchy,此操作支持在面板内多选并一次性勾选。
- 为了便于单个物体的调整,暴露了values<->Control的转换接口,可以直接在这里做简单的调整
- Expand和Collapse的功能
一般编写一个Editor拓展需要两样东西,首先是内容脚本,然后是继承EditorWindow 的window脚本。其他的GUI拓展也是一样的。这次用的是Unity的Treeview封装功能,所以我们就根据文档来建模板,官方文档跳转 官方其实封装得还挺完整的,记录一下自己在编写过程中遇到的小坑。
继承Treeview
public ArtTransformTreeView (TreeViewState state,MultiColumnHeader multicolumnHeader)
: base (state,multicolumnHeader)
{
rowHeight = kRowHeights;
columnIndexForTreeFoldouts = 1;
showAlternatingRowBackgrounds = true;
showBorder = true;
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f;
Reload ();
}
像这里就是C#和C++派生类写构造函数的写法(当然base好像是C#特有的关键字),意思是这两也是这个Treeview构造函数的参数,然后下面就把一些Treeview里Protected的参数加上一些值。
Window里面的Onenable
主要就是实现上面提到的构造函数,基本上就是构造出这个State和Header然后去初始化Treeview State的功能是标记的状态之类的Collumn 便于后面在RowGUi方法中可以调用,
public MyMultiColumnHeader(MultiColumnHeaderState state)
: base(state)
{
mode = Mode.DefaultHeader;
}
public Mode mode
{
get
{
return m_Mode;
}
set
{
m_Mode = value;
switch (m_Mode)
{
case Mode.LargeHeader:
canSort = true;
height = 37f;
break;
case Mode.DefaultHeader:
canSort = true;
height = DefaultGUI.defaultHeight;
break;
case Mode.MinimumHeaderWithoutSorting:
canSort = false;
height = DefaultGUI.minimumHeight;
break;
}
}
}
protected override void ColumnHeaderGUI (MultiColumnHeaderState.Column column, Rect headerRect, int columnIndex)
{
// Default column header gui
base.ColumnHeaderGUI(column, headerRect, columnIndex);
// Add additional info for large header
if (mode == Mode.LargeHeader)
{
// Show example overlay stuff on some of the columns
if (columnIndex > 2)
{
headerRect.xMax -= 3f;
var oldAlignment = EditorStyles.largeLabel.alignment;
EditorStyles.largeLabel.alignment = TextAnchor.UpperRight;
GUI.Label(headerRect, 36 + columnIndex + "%", EditorStyles.largeLabel);
EditorStyles.largeLabel.alignment = oldAlignment;
}
}
}
Header嘛,顾名思义就是表格的头条,这里贴代码的原因是解释一下getset方法的妙用,可以让非法输入直接过滤掉,两个字,安全。剩下window里面都是操作相关的脚本了。
修改Treeview生成中重要的脚本
在本个脚本中,Treeview是封装好的一个类,提供了基本的建表方法, 我们只需要改他们的这个重要方法就行,上面的图也可以看出,BuildRoot和BuildRows是最重要的,不过前者好像只是做了一个树根,重写BuildRows可以根据用的方法获得所有想要的物体。然后上面那张图没写的是BuildRows之后有一个RowGUI方法,这个在原来的TreeView里面就已经实现了,主要是可以生成item的名字,它会自动获取Buildrows之后的args作为要素,所以我们的root直接就会被建立。这是RowGUI方法里面的一部分,它会为每一个item也就是每一个row逐个调用 名字后面还有一个调用父类的方法,主要是父类包含了名字的部分,我们复写的其实只是toggle的部分。
其他的小提示
ExpandAll()方法
按照文档,理论上我们可以直接调用实现Expandall()方法,只需要重写GetRecursiveChild但是发现报了一个我甚至无从debug的空指针bug,说这个函数传入的参数连Getgameobject都获取不到,然后我也不知道要怎么继续去debug了,所以不如自己重写一个。发现Treeview里面的Setexpand还能用,直接建树的时候就保存一组数据就可以了。
同步Hierarchy方法
获取Scene然后用SceneManager的方法获取即可,一般方法都包括了一个Selection类封装好了,我们只需要调用即可。
脚本工具的撤回功能
在编写的时候加上这下面的代码使得系统知道你做出了改变
Undo.RecordObject(m_gameObject,"GISingleModification2");
m_gameObject.receiveGI=(ReceiveGI)EditorGUI.EnumPopup(cellRect,m_gameObject.receiveGI);
PrefabUtility.RecordPrefabInstancePropertyModifications(m_gameObject);
EnumFlag
被老板提醒enum这类属性删除的时候假如没有一个nothing或者有多个值的时候可以用下面的方式删除,但是个中原因自己并没有太清楚,于是查了一下这个叫做掩码
StaticEditorFlags flags = GameObjectUtility.GetStaticEditorFlags(gameObject);
GameObjectUtility.SetStaticEditorFlags(gameObject, flags & (~(StaticEditorFlags.ContributeGI)));
举一个例子就是用二进制代表更多的数,看到一篇博客里面用了一个有趣的比喻,如果我要确定8个瓶子里哪个有毒,只需要三只了老鼠,因为
000=0
001=1
010=2
011=3
100=4
101=5
110=6
111=7
1此时代表的是死掉的老鼠,也就是分别将1、3、5、7号瓶子的药混起来给老鼠1吃,2、3、6、7号瓶子的药混起来给老鼠2吃,4、5、6、7号瓶子的药混起来给老鼠3吃。分药的过程可以反推来理解,假如3号瓶有毒,被毒死的就是12两只老鼠,所以12都需要吃3号瓶————类似用结果反推现实,同理只需要10个老鼠就可以确定1000个瓶子。
// 是否允许查询,二进制第1位,0表示否,1表示是
public static final int ALLOW_SELECT = 1 << 0; // 0001
// 是否允许新增,二进制第2位,0表示否,1表示是
public static final int ALLOW_INSERT = 1 << 1; // 0010
// 是否允许修改,二进制第3位,0表示否,1表示是
public static final int ALLOW_UPDATE = 1 << 2; // 0100
// 是否允许删除,二进制第4位,0表示否,1表示是
public static final int ALLOW_DELETE = 1 << 3; // 1000
那么这个位运算就来了,假如我有四个enumflag,我就相当于四只老鼠,然后我们就很方便用一个int就可以存权限的开关了,这里补充位运算的方法,|代表加上一个(有1个1就为1),~代表取反,&代表包含(有两个1才为1),
flag & XXX_FLAG != 0 或者 flag & XXX_FLAG = XXX_FLAG
。
去除属性则用 flag &= ~XXX_FLAG;
(相当于先给flag反了之后只有去除的值为0,然后包含运算一下)上面代码的例子是先用一个flag来确定然后在直接等于去set。
位运算的其他用法
这个还有其他的用法,比如
a&1 = 0 偶数
a&1 = 1 奇数
//求平均值防止INT溢出
public int average(int x, int y){
return (x&y)+((x^y)>>1);
}
//判断正整数是否是2的幂
public boolean power2(int x) {
return ((x&(x-1))==0)&&(x!=0);
}
类型
C# 里面强制转换可以用 as 如果转化失败会返回null 如果用(blabla)就可能会报错 判断数据类型可以用is
找到LOD和renderer物体的方法
感觉理论上可能有更便捷的方法,我这里用的是一个广度优先搜索,维护了一个内部不存在父子关系的Queue作为根物体列表实现的,有一点点像树的遍历的非递归方法。
protected override TreeViewItem BuildRoot()
{
return new TreeViewItem {id = 0, depth = -1};
}
//初始化找到根物体
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
//Scene scene = SceneManager.GetSceneAt (0);
//var gameObjectRoots = scene.GetRootGameObjects ();
var rows = GetRows () ?? new List<TreeViewItem> (200);
rows.Clear ();
List<GameObject> gameObjectRoots = new List<GameObject>();
expandID.Clear();
if (!Frozetoggle)
{
List<int> filterid = Selection.instanceIDs.ToList();
//avoid resort gameobject or random pick or scene
//过滤子物体和父物体多选以及scene
for (int i = filterid.Count - 1; i >= 0; i--)
{
int tempid = filterid[i];
if (!GetGameObject(tempid))
{
filterid.Remove(filterid[i]);
continue;
} //scene?
while (GetGameObject(tempid).transform.parent)
{
tempid = GetGameObject(tempid).transform.parent.gameObject.GetInstanceID();
if (filterid.Contains(tempid))
{
filterid.Remove(filterid[i]);
break;
}
}
}
if (filterid.Count == 0 ) { return rows; }
//队列遍历找到所有有lod的和所有有renderer的根物体
Queue<int> retreiveid = new Queue<int>(filterid);
while (retreiveid.Count != 0)
{
var id = retreiveid.Dequeue();
GameObject temp = GetGameObject(id);
var templod = temp.GetComponent<LODGroup>();
var tempmeshr = temp.GetComponent<MeshRenderer>();
if (templod != null || tempmeshr != null)
{
if (!gameObjectRoots.Contains(temp))
gameObjectRoots.Add(temp);
}
else
{
for (int i = 0; i < temp.transform.childCount; ++i)
{
var sonid = temp.transform.GetChild(i).gameObject.GetInstanceID();
retreiveid.Enqueue(sonid);
}
}
}
FrozegameObjectRoots = gameObjectRoots;
}
else
{
gameObjectRoots = FrozegameObjectRoots;
}
//找到根物体之后开始找儿物体
foreach (var gameObject in gameObjectRoots)
{
var item = CreateTreeViewItemForGameObject (gameObject);
expandID.Add(item.id);
root.AddChild (item);
rows.Add (item);
if (gameObject.transform.childCount > 0)
{
if (IsExpanded (item.id))
{
AddChildrenRecursive (gameObject, item, rows);
}
else
{
item.children = CreateChildListForCollapsedParent ();
}
}
}
SetupDepthsFromParentsAndChildren (root);
return rows;
}
Enjoy
pppradjpax2024-11-14 02:42
真棒!
yzcddkxrgn2024-11-14 09:45
博主太厉害了!
esobgsqplb2024-11-15 09:00
《为了N》日本剧高清在线免费观看:https://www.jgz518.com/xingkong/42539.html
zxofncjjuj2024-11-20 06:49
博主太厉害了!
cazkypxjkx2024-11-25 04:28
《压力之下:美国女足世界杯队》记录片高清在线免费观看:https://www.jgz518.com/xingkong/20551.html
ekffrihnoh2024-12-01 03:08
《冈仁波齐》剧情片高清在线免费观看:https://www.jgz518.com/xingkong/57817.html
tvcoagyjpu2024-12-01 08:03
《法式家乡菜第一季》记录片高清在线免费观看:https://www.jgz518.com/xingkong/68616.html
hvchazxwin2024-12-01 18:24
《河盗》动作片高清在线免费观看:https://www.jgz518.com/xingkong/95499.html
cqqdoafxaz2024-12-07 00:42
《独行》记录片高清在线免费观看:https://www.jgz518.com/xingkong/162900.html
tdzyxotewb2024-12-15 09:35
《陪护一小时》剧情片高清在线免费观看:https://www.jgz518.com/xingkong/47418.html