引擎工具岗实习了接近一年,修改过引擎源码,也实现了各式各样需求,回顾起来还真是一长段路,感谢组内同事的帮助和网上各优秀博客(如Unity Editor 编辑器扩展功能相关和Intel GPA安卓逆向相关)。于此就简略记录下引擎工具岗的基础内容,权当是离职总结回顾了,且行且看,尽力而为,不要害怕,不要后悔。
编辑器工具
程序不仅有GamePlay向,也有Editor向,为策划美术提供工具使其能更好输出内容。在个人开发/小团队中可能不甚重要(或者根本没有),但如果是大型项目的话那就另当别论了。
编辑器分类
大概可以分为两类,一类单开一个窗口,另一类在Inspector界面中显示
窗口编辑器
窗口编辑器的主体结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| using UnityEditor;
public class TestWindow : EditorWindow { static TestWindow window; [MenuItem("Windows/TestWindow")] static void Open() { window = GetWindow<TestWindow>(false, "TestWindow", true); window.position = new Rect(600, 100, 450, 500); window.Show(); } }
|
检视编辑器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using UnityEditor; using UnityEngine;
[CustomEditor(typeof(TestScript))] public class TestScriptInspector : Editor { TestScript m_target; private void OnEnable() { m_target = target as TestScript; }
public override void OnInspectorGUI() { base.OnInspectorGUI();
if (GUILayout.Button("按钮")) { Debug.Log("按下按钮"); } } }
|
编辑器面板
接下来是面板中如何组织各种结构,无可否认现在odin什么确实更整洁美观,但Unity自己的Editor更加易用易改。个人接触的工具均没有像Player Setting那般复杂的,所以更偏好使用Unity自带的Editor。
对象控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| EditorGUILayout.LabelField("标题", "内容"); foldOut = EditorGUILayout.Foldout(foldOut, "显示域"); if (foldOut) { toggle = EditorGUILayout.Toggle("开关", toggle); intVal = EditorGUILayout.IntField("整型输入", intVal); stringVal = EditorGUILayout.TextField("文本输入", stringVal); vecVal = EditorGUILayout.Vector4Field("向量输入", vecVal); layerVal = EditorGUILayout.LayerField("层级选择", layerVal); tagVal = EditorGUILayout.TagField("标签选择", tagVal); color = EditorGUILayout.ColorField("颜色选择", color); curve = EditorGUILayout.CurveField("动画曲线", curve); } foldOut = EditorGUILayout.Foldout(foldOut, "枚举对象"); if (foldOut) { testEnum = (TestEnum)EditorGUILayout.EnumPopup("单选枚举", testEnum); multEnum = (MultEnum)EditorGUILayout.EnumFlagsField("多选枚举", multEnum); } if (GUILayout.Button("按钮")) { Debug.Log("按下按钮"); } reorderableList.DoLayoutList();
|
其中的可拖动列表需要引入UnityEditorInternal(优化数组显示),并在绘制前对ReorderableList进行初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| reorderableList = new ReorderableList(listItems, typeof(string)); reorderableList.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, "可拖动列表"); }; reorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { listItems[index] = EditorGUI.TextField(rect, listItems[index]); }; reorderableList.onAddCallback = (ReorderableList list) => { listItems.Add("New Item"); }; reorderableList.onRemoveCallback = (ReorderableList list) => { listItems.RemoveAt(list.index); };
|
界面排版
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| EditorGUILayout.BeginHorizontal(); GUI.backgroundColor = Color.green; if (GUILayout.Button("左按钮")) { Debug.Log("按下按钮"); } EditorGUILayout.Space(10); GUI.backgroundColor = Color.blue; if (GUILayout.Button("右按钮")) { Debug.Log("按下按钮"); } EditorGUILayout.EndHorizontal();
|
消息提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| EditorGUILayout.HelpBox("一般提示,最常用的提示", MessageType.Info); EditorGUILayout.HelpBox("警告提示,不会吧不会吧真有人管warning", MessageType.Warning); EditorGUILayout.HelpBox("错误提示,噔噔咚恭喜是bug而非feature呢", MessageType.Error); if(GUILayout.Button("我秦始皇打钱")) { string title = "信他还是信我秦始皇", text = "勇士,朕乃始皇帝,现被困于蜀道山中," + "若勇士能解朕之困,待朕携百万秦甲,重御六合,朕当以勇士为相,赏金千两。"; if (EditorUtility.DisplayDialog(title, text, "义不容辞", "立刻付款")) { Debug.Log("与此同时几百公里外的蜀道山中,新建的信号基站被夜色染成了黑色,矗立在山顶," + "星星在天空中俯瞰着这片大地,只不过这千百年前投下的目光,现在才来得及匆匆一瞥。"); } else { Debug.Log("良久之后,寂朗的山中似乎传来了一声悠悠的叹息:“已经三千年过去了吗?" + "原来朕竟沉睡了如此之久。”“那宛渠人果不负朕!”"); } }
|
Odin编辑器
既然前面提到过那就再稍微展示下,毕竟Odin是卖插件的,单独开了Attributes Example Window展示它的各种特性,感觉也不用我多做赘述了。
可视化编辑器
这个倒是在辞职后做毕设时遇到的对话编辑器的需求,但当时毕竟自己单人开发,最后为了效率选择用CSV硬写。之后才开始了解相关,做了个对话编辑器并将可视化编辑器的内容并入此文。目前比较有名的开源节点编辑器有XNode和NodeGraphProcessor,个人比较喜欢XNode的界面,最终选择使用其来实现对话编辑器。下图是阶段性成果,内容选取的是《胆怯色上发光的画布》中的一段。
XNode中分为Graph(整个可视化的图),Node(自定义的节点)和Port(节点间连线的端口)。为创建自定义的可视化编辑器,需要Graph脚本(创建对应的图),Node脚本(定义节点的内容),NodeEditor脚本(非必须,定义节点中的元素在图中如何绘制)。可以完全接入Odin并交由其控制,也可以使用Unity进行序列化并调用GUI进行绘制,个人选择的是后者(没钱买odin.jpg)。
编辑器文件操作
这里有点需要注意,对直接Load下的Prefab文件进行修改的话是无法保存回去的,必须实例化后再进行修改保存。
1 2 3 4 5 6 7 8 9 10
| AssetDatabase.CopyAsset(OldPath, NewPath) AssetDatabase.LoadAssetAtPath<Type>(Path) PrefabUtility.SaveAsPrefabAsset(File, Path);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefab/Cube.prefab"); GameObject obj = Instantiate(prefab); obj.AddComponent<TestScript>(); PrefabUtility.SaveAsPrefabAsset(obj, "Assets/Prefab/Cube.prefab"); Destroy(obj);
|
截帧,性能分析与逆向
截帧
1. Unity自带的Frame Debug
方便快捷,可以查看渲染顺序,渲染所用shader及纹理相关信息,可助快速定位问题
2. RenderDoc
可以查看顶点数据,shader代码并修改预览效果,遇到棘手渲染问题时可以一试
性能分析
可以通过Unity自带的Profiler分析Unity各项函数开销,也可以通过代码开启记录以了解新功能模块的性能情况(更进一步可以试试Profile Analyzer,之前没有相关需要就没深入了解)
逆向
个人习惯在电脑上开模拟器使用Intel GPA逆向,首先开启Graphics Monitor的Auto-detect Launched Applications,之后运行游戏,在需要的场景截帧,最后在Graphics Frame Analyzer加载相关截图即可得到模型资源信息。