如何在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。

这里需要区分一下两个类AssetDatabaseResources,一个是用于生产时期游戏资源的加工,一个用于游戏运行时动态加载资源。

  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>();
posted @ 2023-04-12 00:51  dewxin  阅读(80)  评论(0编辑  收藏  举报