如何在Unity中制作超级无敌大汉堡 【Unity EditorWindow】
在上一篇文章中,我们知道了如何通过MenuItem来在场景中生成一个GameObject。这篇文章会和大家一起通过Unity的EditorWindow将一堆汉堡材料组合成一个大汉堡。素材来源于Code Monkey 油管
EditorWindow 窗口
上一次我们使用了MenuItem在场景中生成了一个GameObject,这一次我们需要通过MenuItem生成一个编辑器窗口(EditorWindow)
public class FoodWindow : EditorWindow
{
[MenuItem("Window/FoodWindow &f")]
static void InitWindow()
{
var window = EditorWindow.GetWindow(typeof(FoodWindow));
window.Show();
}
}
这里代码很简单,MenuItem生成了菜单选项以及快捷键alt+f
,当我们按下alt+f
时就会触发InitWindow函数,在这个函数中我们调用了GetWindow函数,它会显示对应的窗口,如果屏幕上没有,就会创建一个。由于此时我们还没有向窗口添加任何元素,因此是一片空白。
IMGUI 显示文本
IMGUI意味着即时模式(immediate mode)GUI,和基于GameObject的UI系统不同,这个模式需要在OnGUI
函数中编写代码,然后每帧显示对应的UI。
我们向先前的代码中添加OnGUI
函数,这里我们显示一行文本,并且设置了红色的字体样式。
private void OnGUI()
{
GUIStyle tempFontStyle = new GUIStyle();
tempFontStyle.normal.textColor = Color.red;
EditorGUILayout.LabelField("This is a label.", tempFontStyle);
}
获取食材所在文件夹
要想制作一个大汉堡,首先我们需要获取食物prefab所在的文件夹,这里我们希望达到的效果是鼠标单击目标文件夹,然后点击我们Food窗口中的按钮就能获得食物prefab所在的路径。
很明显,这里我们需要一个button以及Label或者textField来显示文件夹路径。
private void OnGUI()
{
EditorGUILayout.TextField("食材目录", ingredientPath);
if (GUILayout.Button("设置食材目录为选中项", GUILayout.ExpandWidth(false)))
{
var folderGUID = Selection.assetGUIDs[0];
ingredientPath = AssetDatabase.GUIDToAssetPath(folderGUID);
}
}
这里还有一个需要注意的点是GUILayout.ExpandWidth
函数返回了一个GUILayoutOption
使得按钮大小和文字匹配。
我们将代码整理一下,并通过EditorGUILayout.BeginHorizontal
修改UI布局,将textField和Button放在同一行上。
private string ingredientPath = "Please input the path where ingredients are stored";
private void OnGUI()
{
GetFolderBySelection(nameof(ingredientPath), ref ingredientPath);
}
private void GetFolderBySelection(string pathName, ref string refPathVar)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.TextField(pathName, refPathVar);
if (GUILayout.Button("设置为选中目录", GUILayout.ExpandWidth(false)))
{
var folderGUID = Selection.assetGUIDs[0];
var assetPath = AssetDatabase.GUIDToAssetPath(folderGUID);
refPathVar = assetPath;
}
EditorGUILayout.EndHorizontal();
}
加载目录中的食材Prefab
知道食材存储的目录后,我们希望从中加载食材的prefab。由于我们不知道该怎么加载,所以此时我们需要打开bing,向搜索栏中输入 unity get assets in folder。然后bing就会告诉我们需要使用AssetDatabase.FindAssets,需要注意的是返回值是GUID Note that GUIDs will be returned.
我们获得GUID后还需要将其转化为绝对路径,再通过这个路径,从prefab中加载我们的GameObject。
这里需要区分一下两个类AssetDatabase和Resources,一个是用于生产时期游戏资源的加工,一个用于游戏运行时动态加载资源。
private void TryLoadIngredient()
{
ingredientList.Clear();
var guidArray = AssetDatabase.FindAssets("t:Prefab",new string[] { ingredientPath });
foreach(var guidStr in guidArray)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guidStr);
var gameObj = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
ingredientList.Add(gameObj);
}
}
private void ShowIngredient()
{
foreach(var ingredient in ingredientList)
{
EditorGUILayout.ObjectField(ingredient, typeof(GameObject), false);
}
}
}
生成汉堡
最后我们只要将食材prefab实例化到场景中,然后放置在同一个父节点gameobject下,再调用PrefabUtility.SaveAsPrefabAsset
将其保存为prefab。 文件名后缀必须是prefab
private void GenerateFoodByButton()
{
if(ingredientList.Count > 0 && GUILayout.Button("生成食物"))
{
var food = new GameObject("Food");
for(int i = 0; i< ingredientList.Count;i++)
{
var sceneObj = MonoBehaviour.Instantiate(ingredientList[i]);
sceneObj.transform.parent = food.transform;
}
PrefabUtility.SaveAsPrefabAsset(food, ingredientPath+"/food.prefab");
MonoBehaviour.DestroyImmediate(food);
}
}
最终代码和效果展示
public class FoodWindow : EditorWindow
{
[MenuItem("Window/FoodWindow &f")]
static void InitWindow()
{
var window = EditorWindow.GetWindow(typeof(FoodWindow));
window.Show();
}
private string ingredientPath = "Please input the path where ingredients are stored";
private List<GameObject> ingredientList = new List<GameObject>();
private void OnGUI()
{
GetFolderBySelection(nameof(ingredientPath), ref ingredientPath);
ShowIngredient();
GenerateFoodByButton();
}
private void GetFolderBySelection(string pathName, ref string refPathVar)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.TextField(pathName, refPathVar);
if (GUILayout.Button("设置为选中目录", GUILayout.ExpandWidth(false)))
{
var folderGUID = Selection.assetGUIDs[0];
var assetPath = AssetDatabase.GUIDToAssetPath(folderGUID);
refPathVar = assetPath;
TryLoadIngredient();
}
EditorGUILayout.EndHorizontal();
}
private void TryLoadIngredient()
{
ingredientList.Clear();
var guidArray = AssetDatabase.FindAssets("t:Prefab",new string[] { ingredientPath });
foreach(var guidStr in guidArray)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guidStr);
var gameObj = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
ingredientList.Add(gameObj);
}
}
private void ShowIngredient()
{
foreach(var ingredient in ingredientList)
{
EditorGUILayout.ObjectField(ingredient, typeof(GameObject), false);
}
}
private void GenerateFoodByButton()
{
if(ingredientList.Count > 0 && GUILayout.Button("生成食物"))
{
var food = new GameObject("Food");
for(int i = 0; i< ingredientList.Count;i++)
{
var sceneObj = MonoBehaviour.Instantiate(ingredientList[i]);
sceneObj.transform.parent = food.transform;
}
PrefabUtility.SaveAsPrefabAsset(food, ingredientPath+"/food");
MonoBehaviour.DestroyImmediate(food);
}
}
}
补充
当你使用EditorWindow 生成了很多prefab时,你可能希望让自动化继续下去。相对于手动将这些prefab拖拽到另一个prefab的字段中。我们可以指定一个目录,在运行时加载这些resource。
var foodPrefabs = Resources.LoadAll(resourcePrefabFolder,typeof(GameObject)).Cast<GameObject>();