当前位置: 首页 > news >正文

【Framework-Client系列】UIGenerate介绍

        今天来介绍一下Framework-Client框架中的UIGenerate部分,UIGenerate是用来创建UI界面的自定义工具,可以快速创建出UI界面所需的Atlas、Prefab、Script、Texture等。

功能介绍

        首先我们通过菜单栏来打开UIGenerate窗口,如下图。

        第一个输入框用于输入面板的名称,Atlas、Prefab、Script、Texture会以面板名称创建对应的目录和资源。

       “是否为子面板”勾选框用于标记创建的面板是否为子面板,子面板创建的资源会放置在主面板的目录下。勾选时则会显示主面板名称的输入框,反之则不显示。

        第二个输入框用于输入主面板名称,只有在创建子面板时才需要输入主面板名称。

        接下来四个勾选框是分别对应生成Atlas、Prefab、Script、Texture不同的资源,只有勾选时才会生成对应的资源。根据需要还可以增加默认创建的UI资源,采用勾选框的目的也是为了选择更加的灵活。

        “全部勾选”“全部取消”两个按钮是为了方便勾选用的。

        点击“生成UI组件”按钮则会创建勾选的对应资源。

        这里我们使用UIGenerate创建了一个TestPanel面板。并生成了Atlas、Prefab、Script、Texture相关的资源和目录,如下图。

        下图是.spriteatlas资源,并且给Atlas默认指定了Texture目录。

        下图是.prefab资源,并且Prefab中默认创建了PanelMask对象。

        下图是TestPanel所需的.cs文件,并且实现了基础结构的编写。

        下图则是创建了放置TestPanel所需的Texture目录,由于会有子界面的存在,所以在Texture目录下又分别为主界面和子界面分别创建了对应的目录。

        最后在创建成功后,会在控制台输出创建成功的日志,显示资源名称和资源所在位置。点击日志则可以直接跳转到对应的资源。

功能实现

        由于UIGenerate涉及的功能比较多,所以分到了多个文件中进行实现。

        UIGenerate窗口实现

       UIGenerateEditor负责UIGenerate窗口的实现,在 OnGUI() 方法里绘制输入框、勾选框、按钮等。这里主要说一下的是Atlas、Prefab、Script、Texture通过创建各自的UIGenerate类对象处理各自的生成逻辑,部分代码如下。

private void OnGUI()
{
    GUILayout.Label("请输入要创建面板的名称");
    mPanelName = GUILayout.TextField(mPanelName);

    mIsSubPanel = GUILayout.Toggle(mIsSubPanel, "是否为子面板");
    if (mIsSubPanel)
    {
        GUILayout.Label("输入主面板的名称");
        mMainPanelName = GUILayout.TextField(mMainPanelName);
    }
    else
    {
        mMainPanelName = mPanelName;
    }

    GUILayout.BeginHorizontal();
    mIsGenerateAtlas = GUILayout.Toggle(mIsGenerateAtlas, "生成 Atlas");
    mIsGeneratePrefab = GUILayout.Toggle(mIsGeneratePrefab, "生成 Prefab");
    GUILayout.EndHorizontal();

    GUILayout.BeginHorizontal();
    mIsGenerateScript = GUILayout.Toggle(mIsGenerateScript, "生成 Script");
    mIsGenerateTexture = GUILayout.Toggle(mIsGenerateTexture, "生成 Texture");
    GUILayout.EndHorizontal();

    GUILayout.BeginHorizontal();
    if (GUILayout.Button("全部勾选"))
    {
        mIsGenerateAtlas = true;
        mIsGeneratePrefab = true;
        mIsGenerateScript = true;
        mIsGenerateTexture = true;
    }

    if (GUILayout.Button("全部取消"))
    {
        mIsGenerateAtlas = false;
        mIsGeneratePrefab = false;
        mIsGenerateScript = false;
        mIsGenerateTexture = false;
    }
    GUILayout.EndHorizontal();

    if (GUILayout.Button("生成UI组件"))
    {
        if (CheckName())
        {
            if (mIsGeneratePrefab)
                GeneratePrefab();

            if (mIsGenerateScript)
                GenerateScript();

            if (mIsGenerateTexture)
                GenerateTexture();

            if (mIsGenerateAtlas)
                GenerateAtlas();
        }
    }
}

private void GeneratePrefab()
{
    UIGeneratePrefab uiGeneratePrefab = new UIGeneratePrefab(mPanelName, mIsSubPanel, mMainPanelName);
    uiGeneratePrefab.GeneratePrefab();
    AssetDatabase.Refresh();
}

private void GenerateScript()
{
    UIGenerateScript uiGenerateScript = new UIGenerateScript(mPanelName, mIsSubPanel, mMainPanelName);
    uiGenerateScript.GenerateScript();
    AssetDatabase.Refresh();
}

private void GenerateTexture()
{
    UIGenerateTexture uiGenerateTexture = new UIGenerateTexture(mPanelName, mIsSubPanel, mMainPanelName);
    uiGenerateTexture.GenerateTexture();
    AssetDatabase.Refresh();
}

private void GenerateAtlas()
{
    UIGenerateAtlas uiGenerateAtlas = new UIGenerateAtlas(mPanelName, mIsSubPanel, mMainPanelName);
    uiGenerateAtlas.GenerateAtlas();
    AssetDatabase.Refresh();
}

        在这里值得一提的是,由于生成完成之后会调用 AssetDatabase.Refresh() 方法,因此Unity会将UIGenerate窗口进行刷新,此时 UIGenerateEditor 类会被重新创建,这也意味着记录Atlas勾选框状态的 mIsGenerateAtlas 变量会被初始化 。这也导致了每次创建UI完成后,勾选框会被重新勾选上。(Prefab、Script、Texture也有同样的问题)

        解决的办法是用 PlayerPrefs 在 OnDisable() 时记录下勾选框的状态,并在 OnEnable() 时重新读取状态。

private void OnEnable()
{
    mIsGenerateAtlas = PlayerPrefs.GetString("IsGenerateAtlas", "true") == "true";
    mIsGeneratePrefab = PlayerPrefs.GetString("IsGeneratePrefab", "true") == "true";
    mIsGenerateScript = PlayerPrefs.GetString("IsGenerateScript", "true") == "true";
    mIsGenerateTexture = PlayerPrefs.GetString("IsGenerateTexture", "true") == "true";
}

private void OnDisable()
{
    PlayerPrefs.SetString("IsGenerateAtlas", mIsGenerateAtlas ? "true" : "false");
    PlayerPrefs.SetString("IsGeneratePrefab", mIsGeneratePrefab ? "true" : "false");
    PlayerPrefs.SetString("IsGenerateScript", mIsGenerateScript ? "true" : "false");
    PlayerPrefs.SetString("IsGenerateTexture", mIsGenerateTexture ? "true" : "false");
}

        UIGeneratePrefab实现

       这里预先创建了UIPanel模版,通过加载模版,然后重新保存到指定目录,完成Panel面板的创建,部分代码如下。

public void GeneratePrefab()
{
    string fileName = $"{mPanelName}.prefab";
    string fileFullPath = $"{Application.dataPath}/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Prefabs/{fileName}";
    string fileRelativePath = $"Assets/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Prefabs/{fileName}";

    if (!File.Exists(fileFullPath))
    {
        string directoryPath = $"{Application.dataPath}/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Prefabs";
        CreateDirectory(directoryPath);

        GameObject uiPanel = GetUIPanel();
        GameObject panelPrefab = PrefabUtility.SaveAsPrefabAsset(uiPanel, fileRelativePath);

        DelayDebugLog($"<color=yellow>{fileName}</color> create succeed. Path is <color=yellow>{fileFullPath}</color>", panelPrefab);
    }
    else
    {
        GameObject panelPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(fileRelativePath);
        Debug.Log($"<color=yellow>{fileName}</color> already exist. Path is <color=yellow>{fileFullPath}</color>", panelPrefab);
    }
}

private GameObject GetUIPanel()
{
    string uiPanelPath = $"Assets/{FrameworkDefine.UIComponentRootDirectoryPath}/UIPanel.prefab";
    GameObject uiPanelGameObject = AssetDatabase.LoadAssetAtPath<GameObject>(uiPanelPath);

    Transform panelMask = uiPanelGameObject.transform.Find("PanelMask");
    Image image = panelMask.GetComponent<Image>();
    image.sprite = AssetDatabase.LoadAssetAtPath<Sprite>($"Assets/{FrameworkDefine.UICommonPanelMaskPath}");

    return uiPanelGameObject;
}

        UIGenerateScript实现

        Script的创建主要是文件操作,将类文件模版替换类名,然后将编辑好的字符串模版保存为 .cs 文件,部分示例代码如下。

        将Panel类的基础代码单独写在 GetPanelContent() 方法内,调用时字符串会自动替换面板名称。在 CreateFile() 文件内,使用 File.Create() 创建 FileStream 对象,并将content内容写入其中,保存为 .cs 文件。Controller和View文件与之类似。

public void GenerateScript()
{
    string directoryPath = $"{Application.dataPath}/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Scripts";
    CreateDirectory(directoryPath);

    string fileName = $"{mPanelName}.cs";
    string content = GetPanelContent();
    CreateFile(fileName, content);
}

private void CreateFile(string pFileName, string pContentString)
{
    string fileFullPath = $"{Application.dataPath}/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Scripts/{pFileName}";
    string fileRelativePath = $"Assets/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Scripts/{pFileName}";

    if (!File.Exists(fileFullPath))
    {
        byte[] byteArray = UtilCollection.DataUtil.StringToByteArray(pContentString);
        FileStream fileStream = File.Create(fileFullPath);
        fileStream.Write(byteArray, 0, byteArray.Length);
        fileStream.Close();

        EditorApplication.delayCall += () =>
        {
            TextAsset textAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(fileRelativePath);
            Debug.Log($"<color=yellow>{pFileName}</color> create succeed. Path is <color=yellow>{fileFullPath}</color>", textAsset);
        };
    }
    else
    {
        TextAsset textAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(fileRelativePath);
        Debug.Log($"<color=yellow>{pFileName}</color> already exist. Path is <color=yellow>{fileFullPath}</color>", textAsset);
    }
}

private string GetPanelContent()
{
	return $@"
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Framework.UI;

namespace Game.UI
{{
    public class {mPanelName} : PanelBase<{mPanelName}, {mPanelName}Controller, {mPanelName}View>
    {{
        protected override void OnInit()
        {{
            Debug.LogError(""{mPanelName} Init"");
        }}
    }}
}}";
}

        

        UIGenerateTexture实现

        UIGenerateTexture比较简单,只是创建Texture目录,示例代码如下。

        这里需要说明一下的是,为了输出日志可以点击跳转到目录,所以需要通过EditorApplication.delayCall延迟调用的方式,获取目录对象。因为在创建的目录的当前帧是无法获取到目录对象的。

public void GenerateTexture()
{
    string textureFullPath = $"{Application.dataPath}/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Textures/{mPanelName}";
    string textureRelativePath = $"Assets/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Textures/{mPanelName}";
    if (!Directory.Exists(textureFullPath))
    {
        Directory.CreateDirectory(textureFullPath);

        EditorApplication.delayCall += () =>
        {
            Object textureDirectory = AssetDatabase.LoadAssetAtPath<Object>(textureRelativePath);
            Debug.Log($"<color=yellow>{mPanelName}</color> Texture Directory create succeed. File path is:<color=yellow>{textureFullPath}</color>", textureDirectory);
        };
    }
    else
    {
        Object textureDirectory = AssetDatabase.LoadAssetAtPath<Object>(textureRelativePath);
        Debug.Log($"<color=yellow>{mPanelName}</color> Texture Directory already exist. File path is:<color=yellow>{textureFullPath}</color>", textureDirectory);
    }
}

        UIGenerateAtlas实现

        UIGenerateAtlas通过 SpriteAtlas 来创建 .spriteatlas 文件,并指定Texture目录,部分示例代码如下。

        这里获取Texture目录没有用延迟调用,是因为UIGenerateEditor中,先调用的UIGenerateTexture,再调用的UIGenerateAtlas。如果顺序相反的话,则需要通过延迟调用来获取Texture目录

public void GenerateAtlas()
{
    string directoryPath = $"{Application.dataPath}/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Atlas";
    CreateDirectory(directoryPath);

    string fileName = $"Atlas_{mPanelName}.spriteatlas";
    string atlasFullPath = $"{Application.dataPath}/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Atlas/{fileName}";
    string atlasFileRelativePath = $"Assets/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Atlas/{fileName}";

    if (!File.Exists(atlasFullPath))
    {
        SpriteAtlas spriteAtlas = new SpriteAtlas();
        SpriteAtlasPackingSettings packSetting = new SpriteAtlasPackingSettings()
        {
            blockOffset = 1,
            enableRotation = false,
            enableTightPacking = false,
            padding = 4,
        };
        spriteAtlas.SetPackingSettings(packSetting);

        string textureRelativePath = $"Assets/{FrameworkDefine.UIRootDirectoryPath}/{mMainPanelName}/Textures/{mPanelName}";
        Object textureDirectory = AssetDatabase.LoadAssetAtPath<Object>(textureRelativePath);
        spriteAtlas.Add(new[] { textureDirectory });

        AssetDatabase.CreateAsset(spriteAtlas, atlasFileRelativePath);
        Object atlasObject = AssetDatabase.LoadAssetAtPath<Object>(atlasFileRelativePath);
        Debug.Log($"<color=yellow>{fileName}</color> atlas create succeed. File path is:<color=yellow>{atlasFullPath}</color>", atlasObject);
    }
    else
    {
        Object atlasObject = AssetDatabase.LoadAssetAtPath<Object>(atlasFileRelativePath);
        Debug.Log($"<color=yellow>{fileName}</color> atlas already exist. File path is:<color=yellow>{atlasFullPath}</color>", atlasObject);
    }
}

相关文档链接

Editor编写常用方法汇总:https://blog.csdn.net/huoyixian/article/details/129587964

AssetDatabase官方文档:https://docs.unity3d.com/cn/2022.2/ScriptReference/AssetDatabase.html

http://www.dtcms.com/a/107589.html

相关文章:

  • MTK AWB 色温曲线的进一步猜想
  • 时尚优雅奢华品牌包装徽标设计衬线英文字体安装包 Kagea – Luxury Women Ligature Font
  • 司南评测集社区 3 月上新一览!
  • 启服云商标管理软件:律师事务所的高效之选
  • 数据结构优化DP总结
  • SvelteKit 最新中文文档教程(17)—— 仅服务端模块和快照
  • 智能配电箱:重塑未来电力管理的核心枢纽
  • 大模型显卡网络
  • 爱普生RX8901CE实时时钟模块赋能智能监控系统的精准
  • 某某航空 同盾 blackbox 补环境
  • 华为云数据库服务实践
  • 如何解决跨系统审批慢、人工干预多的问题?
  • es自定义ik分词器中文词库实现热更新
  • Elasticsearch 证书问题解决
  • 基于pycharm的YOLOv11模型训练方法
  • mac 最新的chrome版本配置selenium的方式
  • React 文件上传新玩法:Aliyun OSS 加持的智能上传组件
  • d202542
  • 架构师面试(二十五):分布式存储 Leader 设计
  • 元素定位-cssSelector
  • SSM框架学习(Day-1)
  • Kubernetes 入门篇之 Node 安装与部署
  • 视频分析设备平台EasyCVR视频结构化AI智能分析:筑牢校园阳光考场远程监控网
  • 基于 Cesium.js 的交互式绘图工具库
  • IO 端口与 IO 内存
  • 电流与电压的守护者
  • 3. 矩阵置零
  • Vue + Axios + Mock.js 全链路实操:从封装到数据模拟的深度解析
  • 两种方法证明r(A+B)<=r(A,B)<=r(A)+r(B)
  • 【一起来学kubernetes】33、Hpa使用详解