Unity-编辑器扩展

news2025/5/23 20:13:49

之前我们关于Unity的讨论都是针对于Unity底层的内容或者是代码层面的东西,这一次我们来专门研究Unity可视化的编辑器,在已有的基础上做一些扩展。

基本功能

首先我们来认识三个文件夹:

Editor,Gizmos,Editor Default Resources

这三个文件夹在Unity中比较特殊,当你符合命名要求以及文件路径要求之后它就会被Unity编辑器自动识别为这些特殊文件夹。

比如:

然后我们来了解一下操作编辑器的内容:

[MenuItem]

我们在Editor文件夹中新建一个脚本后放入以下函数:

    [MenuItem("MyTool/DeleteAllObj", true)]
    private static bool DeleteValidate()
    {
        if (Selection.objects.Length > 0)
            return true;
        else
            return false;
    }
    [MenuItem("MyTool/DeleteAllObj", false)]
    private static void MyToolDelete()
    {
        //Selection.objects 
        foreach (Object item in Selection.objects)
        {
            Undo.DestroyObjectImmediate(item);
        }
    }

MenuItem会帮助我们在Unity编辑器窗口新建一个可选项,代码中新建的可选项名为MyTool,其中点击MyTool我们定义了一个DeleteAllObj按钮,点击之后执行下列的DeleteValidate函数。后续跟到的一个bool变量代表的是一个验证函数和一个执行函数:true下面跟的是验证函数而false下面跟的是执行函数。验证函数会动态的检查我们菜单项是否可用,可用就高亮显示而不可用就会变灰;如果验证函数返回true,菜单项可用,我们才能去执行执行函数。

[MenuItem(“CONTEXT/组件名/按钮名”)]

[MenuItem(“CONTEXT/组件名/按钮名”)] 是 Unity 编辑器扩展中用于 ​在特定组件的上下文菜单中添加自定义功能按钮​ 的关键语法。因为是在已有的组件上添加自定义按钮,所以组件名称必须是已有的组件名称。

比如你加入这段代码在Editor文件的脚本中:

    [MenuItem("CONTEXT/Rigidbody/Init")]
    private static void RigidbodyInit()
    {
        Debug.Log("12345");
    }

然后你去场景中任一物体的刚体组件上:

点击按钮后就会执行你的语句。

MenuCommand

用于获取当前操作的组件,在代码里使用,比如以下代码:

    [MenuItem("CONTEXT/PlayerHealth/Init")]
    static void Init(MenuCommand cmd)
    {
        PlayerHealth health = cmd.context as PlayerHealth;
    }

可以看到我们的代码中直接用MenuCommand cmd定义了当前组件的实例。

ContextMenu、ContextMenuItem

对于ContextMenu来说,通过在脚本方法上标注 [ContextMenu("菜单项名称")],可在该组件的 ​Inspector 右键菜单​ 中添加一个按钮,点击后触发对应方法。

比如以下代码:
 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ResetHealth : MonoBehaviour
{
    [ContextMenu("Reset Health")]
    void Resethealth()
    {
        // 重置血量逻辑
    }
}

那么在Inspector中:

ContextMenuItem 的作用则是通过在字段上标注 [ContextMenuItem("菜单项名称", "方法名")],可在 Inspector 中该字段的 ​右键菜单​ 中添加操作按钮,直接修改字段值或触发逻辑。

比如以下代码:

    [ContextMenuItem("随机生成", "RandomizeName")]
    public string characterName;
    
    void RandomizeName() {
        characterName = "Player_" + Random.Range(1, 100);
    }

效果如下:

Selection

用于获取选择的游戏物体

  • Selection.activeGameObject 返回第一个选择的场景中的对象
  • Selection.gameObjects 返回场景中选择的多个对象,包含预制体等
  • Selection.objects 返回选择的多个对象

用法如下:

using UnityEditor;
using UnityEngine;

public static class DeleteAll // 改为静态类
{
    [MenuItem("MyTool/Delete Selected Objects")]
    static void DeleteSelected()
    {
        // 加入安全校验
        if (Selection.objects.Length == 0)
        {
            Debug.LogWarning("未选择任何对象");
            return;
        }

        foreach (Object obj in Selection.objects)
        {
            if (obj != null)
            {
                Undo.DestroyObjectImmediate(obj);
            }
        }
    }
}

我们选中这个黄色的cube,之后打开MyTool中的Delete Selected Objects:

就会删除了。

自定义Inspector面板

学会了操作Unity编辑器的基本操作后,我们来尝试自己写出一个Inspector界面:

这是我们正常的Inspector界面。

我们先写这样一个脚本:

using UnityEngine;
public enum Course
{
    Chinese,
    Mathematics,
    English
}

public class InspectorExample : MonoBehaviour
{
    public int intValue;
    public float floatValue;
    public string stringValue;
    public bool boolValue;
    public Vector3 vector3Value;
    public Course enumValue = Course.Chinese;
    public Color colorValue = Color.white;
    public Texture textureValue;
}

这个脚本定义了一个枚举和一些变量,其Inspector如图:

然后这个时候我们再根据这个InspectorExample类来写下面的这个脚本:

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(InspectorExample))]
public class InspectorExampleEditor : Editor
{
    //target指该编辑器类绘制的目标类,需要将它强转为目标类
    private InspectorExample _target { get { return target as InspectorExample; } }

    //GUI重新绘制
    public override void OnInspectorGUI()
    {
        //EditorGUILayout.LabelField("IntValue",_target.intValue.ToString(),EditorStyles.boldLabel);
        //_target.intValue = EditorGUILayout.IntSlider(new GUIContent("Slider"),_target.intValue, 0, 10);
        //_target.floatValue = EditorGUILayout.Slider(new GUIContent("FloatValue"), _target.floatValue, 0, 10);
        _target.intValue = EditorGUILayout.IntField("IntValue", _target.intValue);
        _target.floatValue = EditorGUILayout.FloatField("FloatValue", _target.floatValue);
        _target.stringValue = EditorGUILayout.TextField("StringValue", _target.stringValue);
        _target.boolValue = EditorGUILayout.Toggle("BoolValue", _target.boolValue);
        _target.vector3Value = EditorGUILayout.Vector3Field("Vector3Value", _target.vector3Value);
        _target.enumValue = (Course)EditorGUILayout.EnumPopup("EnumValue", (Course)_target.enumValue);
        _target.colorValue = EditorGUILayout.ColorField(new GUIContent("ColorValue"), _target.colorValue);
        _target.textureValue = (Texture)EditorGUILayout.ObjectField("TextureValue", _target.textureValue, typeof(Texture), true);
    }
}

效果如图:

不难看出这段函数中最重要的内容就是EditorGUILayout这个函数:

这是一个自定义Inspector的方法,还有一种是:

using UnityEditor;

[CustomEditor(typeof(InspectorExample))]
public class InspectorExampleEditor : Editor
{
    //定义序列化属性
    private SerializedProperty intValue;
    private SerializedProperty floatValue;
    private SerializedProperty stringValue;
    private SerializedProperty boolValue;
    private SerializedProperty vector3Value;
    private SerializedProperty enumValue;
    private SerializedProperty colorValue;
    private SerializedProperty textureValue;

    private void OnEnable()
    {
        //通过名字查找被序列化属性。
        intValue = serializedObject.FindProperty("intValue");
        floatValue = serializedObject.FindProperty("floatValue");
        stringValue = serializedObject.FindProperty("stringValue");
        boolValue = serializedObject.FindProperty("boolValue");
        vector3Value = serializedObject.FindProperty("vector3Value");
        enumValue = serializedObject.FindProperty("enumValue");
        colorValue = serializedObject.FindProperty("colorValue");
        textureValue = serializedObject.FindProperty("textureValue");
    }

    public override void OnInspectorGUI()
    {
        //表示更新序列化物体
        serializedObject.Update();
        EditorGUILayout.PropertyField(intValue);
        EditorGUILayout.PropertyField(floatValue);
        EditorGUILayout.PropertyField(stringValue);
        EditorGUILayout.PropertyField(boolValue);
        EditorGUILayout.PropertyField(vector3Value);
        EditorGUILayout.PropertyField(enumValue);
        EditorGUILayout.PropertyField(colorValue);
        EditorGUILayout.PropertyField(textureValue);
        //应用修改的属性值,不加的话,Inspector面板的值修改不了
        serializedObject.ApplyModifiedProperties();
    }
}

这两种自定义Inspector的方法的区别在于:

可以这样理解两种自定义Inspector的方法的差异:第一种是我们统一将Inspector里的内容转换为属性(Property),然后统一对属性进行操作;第二种则是我们通过Unity里提供的接口直接对Inspector的内容进行修改。

效果如图:

看起来似乎没有什么区别,那只是因为我们没有在重载的OnInspectorGUI()方法中加入新东西而已。

我们往里面加一个:

    // 大标题(加粗+蓝色字体)
    var titleStyle = new GUIStyle(EditorStyles.boldLabel) {
        fontSize = 16,
        normal = { textColor = new Color(0.2f, 0.6f, 1f) }
    };
    EditorGUILayout.LabelField("★ 老公今晚不在家 ★", titleStyle);

效果如图:

这样就实现了我们的自定义Inspector了,总结来说:首先我们需要一个InspectorExample这样的脚本作为基础,然后在Editor中通过序列化或者直接字段操作来修改InspectorExample在Inspector上显示的内容即可。


创建编辑器窗体

学会自定义Inspector之后我们来学习如何在编辑器中创建窗(window),这方面Unity也已经帮助我们写好了API,我们直接去调用并设置即可。

Unity有三种窗类:ScriptableWizard,EditorWindow和PopupWindowContent。

可以看到,第一种窗口最大的特色就是自动生成输入控件,所以第一种窗口往往都是作为需求输入来使用;第二种窗口则是有着更完整的生命周期,且其功能更全面;第三种窗口则是一个临时性的弹窗,点一点按钮就会关闭的那种,更适合作为提示窗口。

我们分别来使用代码生成这三种窗口试试:

using UnityEngine;
using UnityEditor;

public class WindowExample1 : ScriptableWizard
{
    public string msg = ""; 

    //显示窗体
    [MenuItem("MyWindow/First Window")]
    private static void ShowWindow()
    {
        ScriptableWizard.DisplayWizard<WindowExample1>("WindowExample1", "确定", "取消");
    }

    //显示时调用
    private void OnEnable()
    {
        Debug.Log("OnEnable");
    }

    //更新时调用
    private void OnWizardUpdate()
    {
        Debug.Log("OnWizardUpdate");

        if (string.IsNullOrEmpty(msg))
        {
            errorString = "请输入信息内容";//错误提示
            helpString = "";//帮助提示
        }
        else
        {
            errorString = "";
            helpString = "请点击确认按钮";
        }
    }

    //点击确定按钮时调用
    private void OnWizardCreate()
    {
        Debug.Log("OnWizardCreate");
    }

    //点击第二个按钮时调用
    private void OnWizardOtherButton()
    {
        Debug.Log("OnWizardOtherButton");
    }

    //当ScriptableWizard需要更新其GUI时,将调用此函数以绘制内容
    //为GUI绘制提供自定义行为,默认行为是按垂直方向排列绘制所有公共属性字段
    //一般不重写该方法,按照默认绘制方法即可
    protected override bool DrawWizardGUI()
    {
        return base.DrawWizardGUI();
    }

    //隐藏时调用
    private void OnDisable()
    {
        Debug.Log("OnDisable");
    }

    //销毁时调用
    private void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}

放入Editor中后在“MyWindow”中点击“First Window”,效果如图:

然后是:

using UnityEngine;
using UnityEditor;

public class WindowExample2 : EditorWindow
{
    private static WindowExample2 window;//窗体实例

    //显示窗体
    [MenuItem("MyWindow/Second Window")]
    private static void ShowWindow()
    {
        window = EditorWindow.GetWindow<WindowExample2>("Window Example");
        window.Show();
    }

    //显示时调用
    private void OnEnable()
    {
        Debug.Log("OnEnable");
    }

    //绘制窗体内容
    private void OnGUI()
    {
        EditorGUILayout.LabelField("Your Second Window", EditorStyles.boldLabel);
    }

    //固定帧数调用
    private void Update()
    {
        Debug.Log("Update");
    }

    //隐藏时调用
    private void OnDisable()
    {
        Debug.Log("OnDisable");
    }

    //销毁时调用
    private void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}

效果如下: 


可以看到打开这个窗口后也会有类似于MonoBehaviour一样的生命周期函数存在并提供使用,所以说非常全能。

最后是:

using UnityEngine;
using UnityEditor;

public class WindowExample3 : EditorWindow
{
    private static WindowExample3 window;
    private PopWindowExample popWindow = new PopWindowExample();
    private Rect buttonRect;

    //显示窗体
    [MenuItem("MyWindow/Third Window")]
    private static void ShowWindow()
    {
        window = EditorWindow.GetWindow<WindowExample3>("Window Example 3");
        window.Show();
    }

    //绘制窗体内容
    private void OnGUI()
    {
        GUILayout.Label("Popup example", EditorStyles.boldLabel);
        if (GUILayout.Button("Popup Options", GUILayout.Width(200)))
        {
            PopupWindow.Show(buttonRect, popWindow);
        }
        //获取GUILayout最后用于控件的矩形
        if (Event.current.type == EventType.Repaint)
            buttonRect = GUILayoutUtility.GetLastRect();
    }
}

public class PopWindowExample : PopupWindowContent
{
    bool toggle = true;

    //开启弹窗时调用
    public override void OnOpen()
    {
        Debug.Log("OnOpen");
    }

    //绘制弹窗内容
    public override void OnGUI(Rect rect)
    {
        EditorGUILayout.LabelField("PopWindow");
        toggle = EditorGUILayout.Toggle("Toggle", toggle);
    }

    //关闭弹窗时调用
    public override void OnClose()
    {
        Debug.Log("OnClose");
    }

    public override Vector2 GetWindowSize()
    {
        //设置弹窗的尺寸
        return new Vector2(200, 100);
    }
}

效果如图:

针对不同的情境我们使用不同的窗体。

Gizmos辅助调试工具

Gizmos是Scene场景的可视化调试或辅助工具,相信在实际的游戏开发中大家都不会陌生,我们经常需要Gizmos绘制一些诸如射线检测,碰撞检测之类的实际检测范围来看交互逻辑有没有写错。

最常见的相关函数是OnDrawGizmo或者OnDrawGizmosSelected方法:OnDrawGizmos在每一帧都会被调用,其渲染的Gizmos是一直可见的,而OnDrawGizmosSelected是当物体被选中的时候才会显示。

public class GizmosExample : MonoBehaviour
{
    //绘制效果一直显示
    private void OnDrawGizmos()
    {
        var color = Gizmos.color;
        Gizmos.color = Color.white;
        Gizmos.DrawCube(transform.position, Vector3.one);
        Gizmos.color = color;
    }
    //绘制效果在选中对象时显示
    private void OnDrawGizmosSelected()
    {
        var color = Gizmos.color;
        Gizmos.color = Color.white;
        Gizmos.DrawWireCube(transform.position, Vector3.one);
        Gizmos.color = color;
    }
}

现在我们来试试如何绘制:

我们先新建一个类TargetExample。

using UnityEngine;

// 目标脚本需挂载到场景中的游戏对象
public class TargetExample : MonoBehaviour
{

}

然后我们在Editor文件夹中写这样一个脚本:

using UnityEngine;
using UnityEditor;

public class GizmosTest
{
    //表示物体显示并且被选择的时候,绘制Gizmos
    [DrawGizmo(GizmoType.Active | GizmoType.Selected)]
    //第一个参数需要指定目标类,目标类需要挂载在场景对象中
    private static void MyCustomOnDrawGizmos(TargetExample target, GizmoType gizmoType)
    {
        var color = Gizmos.color;
        Gizmos.color = Color.white;
        //target为挂载该组件的对象
        Gizmos.DrawCube(target.transform.position, Vector3.one);
        Gizmos.color = color;
    }
}

然后随便新建一个empty:

点击后就能看到:

我们再来一个比较常见的关于Gizmos的应用:

    private Camera mainCamera;
    private void OnDrawGizmos()
    {
        if (mainCamera == null)
            mainCamera = Camera.main;
        Gizmos.color = Color.green;
        //设置gizmos的矩阵   
        Gizmos.matrix = Matrix4x4.TRS(mainCamera.transform.position, mainCamera.transform.rotation, Vector3.one);
        Gizmos.DrawFrustum(Vector3.zero, mainCamera.fieldOfView, mainCamera.farClipPlane, mainCamera.nearClipPlane, mainCamera.aspect);
    }

效果如图:

扩展Scene视图

用大白话来说,扩展Scene视图就是给Unity的场景编辑窗口“加外挂”,让开发者能更方便地调试和编辑游戏场景。具体能干三件事:​加辅助显示,加自定义工具,改操作方式。

怎么实现呢?

在这里我们就不能不提一个很好用的小工具——Handles了。

Unity 的 ​Handles 类是专为 ​Scene 视图设计的交互式 3D 控件工具,简单来说就是给开发者提供了一套“可视化操作杆”,可以直接在场景中用鼠标拖拽、旋转、缩放物体或参数,而无需反复修改代码或 Inspector 面板中的数值。以下是它的核心功能和应用场景:

我们来看下面这个代码:

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(SceneExt))]
public class SceneExtEditor : Editor
{
    //获取SceneExt脚本对象
    private SceneExt _target { get { return target as SceneExt; } }

    private void OnSceneGUI()
    {
        //操作句柄
        Handles.Label(_target.transform.position,_target.transform.name + " : " + _target.transform.position);

        //绘制GUI的内容必须要在BeginGUI、EndGUI的方法对中
        Handles.BeginGUI();
        //设置GUI绘制的区域
        GUILayout.BeginArea(new Rect(50, 50, 200, 200));
        GUILayout.Label("Scene 扩展练习");
        GUILayout.EndArea();
        Handles.EndGUI();
    }
}

效果如图:

需要注意的是,我们关于Handles绘制GUI的内容必须在BeginGUI和EndGUI之间。

关于Handles类还有很多可用的内容,比如我们这样写一个脚本挂载在目标物体上:

using UnityEngine;

public class SceneExt : MonoBehaviour
{
    public bool showLabel = true;
    public bool showLine = true;
    public bool showSlider = true;
    public bool showRadius = true;
    public bool showCircleHandleCap = true;
    public bool showSphereHandleCap = true; 
    public bool showGUI = true;

    public Vector3 sliderPos = Vector3.forward;
    public float areaRadius = 1;
    public float circleSize = 1;
}

然后我们在Editor文件夹中这样写一个脚本:

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(SceneExt))]
public class SceneExtEditor : Editor
{
    //获取SceneExt脚本对象
    private SceneExt _target { get { return target as SceneExt; } }

    private void OnSceneGUI()
    {
        if (_target.showLabel)
        {
            //操作句柄,显示文本
            Handles.Label(_target.transform.position + Vector3.up * 0.5f, _target.transform.name + " : " + _target.transform.position);
        }

        if (_target.showLine)
        {
            //修改句柄的颜色
            Handles.color = Color.yellow;
            //绘制一条线
            Handles.DrawLine(_target.transform.position, Vector3.up * 5);
        }

        if (_target.showSlider)
        {
            Handles.color = Color.red;
            //绘制一个可以沿着某个轴向的3D滑动条
            _target.sliderPos = Handles.Slider(_target.sliderPos, _target.transform.forward);
        }

        if (_target.showRadius)
        {
            Handles.color = Color.blue;
            //绘制一个半径控制手柄
            _target.areaRadius = Handles.RadiusHandle(Quaternion.identity, _target.transform.position, _target.areaRadius);
        }

        if (_target.showCircleHandleCap)
        {
            //获取Y轴的颜色
            Handles.color = Handles.yAxisColor;
            //绘制一个圆环
            Handles.CircleHandleCap(0, _target.transform.position + Vector3.up * 2, Quaternion.Euler(90, 0, 0), _target.circleSize, EventType.Repaint);
        }

        if (_target.showSphereHandleCap)
        {
            Handles.color = Color.green;
            //绘制一个球形
            Handles.SphereHandleCap(1, _target.transform.position, Quaternion.identity, HandleUtility.GetHandleSize(_target.transform.position), EventType.Repaint);
        }

        if (_target.showGUI)
        {
            //绘制GUI的内容必须要在BeginGUI、EndGUI的方法对中
            Handles.BeginGUI();
            //设置GUI绘制的区域
            GUILayout.BeginArea(new Rect(50, 50, 200, 200));
            GUILayout.Label("Scene 扩展练习");
            GUILayout.EndArea();
            Handles.EndGUI();
        }
    }
}

效果如图:

EditorPrefs、ScriptableObject、Undo

Unity编辑器为开发者提供了类似PlayerPrefs的数据保存方式EditorPrefs。EditorPrefs是适用于编辑器模式,而PlayerPrefs适用于游戏运行时。
EditorPrefs提供了四种数据的保存:int,float,string,bool
通过Set方法保存下数据,下次则通过Get方法来获取数据,HasKey方法可以判断是否存在该数据的保存,删除数据调用DeleteKey方法即可。

比如下列这段代码:

using UnityEngine;
using UnityEditor;

public class WindowExample2 : EditorWindow
{
    private static WindowExample2 window;//窗体实例
    private string tempMsg;

    //显示窗体
    [MenuItem("MyWindow/Second Window")] 
    private static void ShowWindow()
    {
        window = EditorWindow.GetWindow<WindowExample2>("Window Example");
        window.Show();
    }

    private void OnEnable()
    {
        if (EditorPrefs.HasKey("TempMsg"))
        {
            tempMsg = EditorPrefs.GetString("TempMsg");
        }
    }

    private void OnGUI()
    {
        tempMsg = EditorGUILayout.TextField("Temp Msg", tempMsg);
        if (GUILayout.Button("Save"))
        {
            EditorPrefs.SetString("TempMsg", tempMsg);
        }
    }
}

我们调用EditorPrefs.HasKey()方法来检测是否有"TempMsg"键,有的话用Get方法获取,需要的话用Set方法设置键和值进行保存。

效果如图:

点击save之后就会存储“123”到EditorPrefs中。

ScriptableObject则是一个我们都不陌生的内容,早在很久之前我们就用SO来实现内容的存储过。

ScriptableObject是Unity引擎中一种特殊的可编程数据容器,它允许开发者将数据以资源文件(.asset)的形式存储在项目中,既能在编辑器模式下持久化数据,又能高效管理游戏运行时配置。

Undo则是专门用于撤销编辑器模式下的操作,如:

Undo.RegisterCreatedObjectUndo : 记录新建的对象状态,可以撤销新建的对象
Undo.RecordObject:记录对象的状态,需要在修改之前调用
Undo.AddComponent:可以撤销新挂载的组件
Undo.DestroyObjectImmediate:可以撤销删除对象的操作
Undo.SetTransformParent:可以撤销修改父对象的操作

我们用一个实例来学习其用法:

using UnityEditor;
using UnityEngine;

public class UndoTest
{
    [MenuItem("Tools/Create Obj")]
    private static void CreateObj()
    {
        GameObject newObj = new GameObject("Undo");
        Undo.RegisterCreatedObjectUndo(newObj, "CreateObj");
    }

    [MenuItem("Tools/Move Obj")]
    private static void MoveObj() 
    {
        //获取选中的场景对象
        Transform trans = Selection.activeGameObject.transform;
        if (trans)
        {
            Undo.RecordObject(trans, "MoveObj");
            trans.position += Vector3.up;
        }
    }

    [MenuItem("Tools/AddComponent Obj")]
    private static void AddComponentObj() 
    {
        //获取选中的场景对象
        GameObject selectedObj = Selection.activeGameObject;
        if (selectedObj)
        {
            Undo.AddComponent(selectedObj,typeof(Rigidbody));
        }
    }

    [MenuItem("Tools/Destroy Obj")]
    private static void DestroyObj()
    {
        //获取选中的场景对象
        GameObject selectedObj = Selection.activeGameObject;
        if (selectedObj)
        {
            Undo.DestroyObjectImmediate(selectedObj);
        }
    }

    [MenuItem("Tools/SetParent Obj")]
    private static void SetParentObj()
    {
        //获取选中的场景对象
        Transform trans = Selection.activeGameObject.transform;
        Transform root = Camera.main.transform;
        if (trans)
        {
            Undo.SetTransformParent(trans, root, trans.name);
        }
    }
}

效果如下:

AssetPostprocessor

AssetPostprocessor是Unity引擎中用于自动化资源导入流程的核心工具,它允许开发者在资源(纹理、模型、音频等)导入前后插入自定义逻辑,实现资源处理的标准化、优化和批量化。

一言以蔽之,AssetPostprocessor就是一个在导入资源时在导入前后对资源进行处理的一个工具。

AssetPostprocessor是基于周期函数来实现功能的,也就是PreprocesshPostprocess。

这是一些常见的方法:

OnPreprocessTexture:在导入纹理贴图之前调用
OnPreprocessModel:在导入模型之前调用
OnPreprocessAudio:在导入音频之前调用

OnPostprocessTexture:在导入纹理贴图之后调用
OnPostprocessModel:在导入模型之后调用
OnPostprocessAudio:在导入音频之后调用
OnPostprocessAllAssets:所有资源的导入,删除,移动操作都会调用该方法

比如以下代码:

using UnityEngine;
using UnityEditor;

public class AssetsImport : AssetPostprocessor
{
    private void OnPreprocessTexture()
    {
        Debug.Log("OnPreprocessTexture:" + this.assetPath);
        TextureImporter importer = this.assetImporter as TextureImporter;
        importer.textureType = TextureImporterType.Sprite;
        importer.maxTextureSize = 512;
        importer.mipmapEnabled = false;
    }

    public void OnPostprocessTexture(Texture2D tex)
    {
        Debug.Log("OnPostprocessTexture:" + this.assetPath);
    }
}

我们随便拖入一张图:

后台会输出:

且这张图的属性会被修正为:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2384124.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Lucide:一款精美的开源矢量图标库,前端图标新选择

名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、前言&#xff1a;为何选择 Lucide&#xff1f;二、Lucide 是什么&#xff1f;1.…

Mac如何允许安装任何来源软件?

打开系统偏好设置-安全性与隐私&#xff0c;点击右下角的解锁按钮&#xff0c;选择允许从任何来源。 如果没有这一选项&#xff0c;请到打开终端&#xff0c;输入命令行&#xff1a;sudo spctl --master-disable, 输入命令后回车&#xff0c;输入电脑的开机密码后回车。 返回“…

2025最新版Visual Studio Code for Mac安装使用指南

2025最新版Visual Studio Code for Mac安装使用指南 Installation and Application Guide to The Latest Version of Visual Studio Code in 2025 By JacksonML 1. 什么是Visual Studio Code&#xff1f; Visual Studio Code&#xff0c;通常被称为 VS Code&#xff0c;是由…

【嵙大o】C++作业合集

​ 参考&#xff1a; C swap&#xff08;交换&#xff09;函数 指针/引用/C自带-CSDN博客 Problem IDTitleCPP指针CPP引用1107 Problem A编写函数&#xff1a;Swap (I) (Append Code)1158 Problem B整型数据的输出格式1163 Problem C时间&#xff1a;24小时制转12小时制1205…

C++23 范围迭代器作为非范围算法的输入 (P2408R5)

文章目录 一、引言二、C23及范围迭代器的背景知识2.1 C23概述2.2 范围迭代器的概念 三、P2408R5提案的内容3.1 提案背景3.2 提案内容 四、范围迭代器作为非范围算法输入的优势4.1 代码简洁性4.2 提高开发效率4.3 更好的兼容性 五、具体的代码示例5.1 使用范围迭代器进行并行计算…

2025.05.20【Treemap】树图数据可视化技巧

Multi-level treemap How to build a treemap with group and subgroups. Customization Customize treemap labels, borders, color palette and more 文章目录 Multi-level treemapCustomization Treemap 数据可视化技巧什么是 TreemapTreemap 的应用场景如何在 R 中绘制 T…

深入了解Springboot框架的启动流程

目录 1、介绍 2、执行流程 1、运行run方法 2、初始化SpringApplication对象 1、确定容器类型 3、加载所有的初始化器 4、加载Spring上下文监听器 5、设置程序运行的主类 3、进入run方法 1、开启计时器 2、Headless模式配置 3、获取并启用监听器 4、准备环境 1、设…

LLaMA-Factory微调LLM-Research/Llama-3.2-3B-Instruct模型

1、GPU环境 nvidia-smi 2、pyhton环境安装 git clone https://github.com/hiyouga/LLaMA-Factory.git conda create -n llama_factory python3.10 conda activate llama_factory cd LLaMA-Factory pip install -e .[torch,metrics] 3、微调模型下载&#xff08;LLM-Research/…

3.8.1 利用RDD实现词频统计

在本次实战中&#xff0c;我们通过Spark的RDD实现了词频统计功能。首先&#xff0c;准备了包含单词的文件并上传至HDFS。接着&#xff0c;采用交互式方式逐步完成词频统计&#xff0c;包括创建RDD、单词拆分、映射为二元组、按键归约以及排序等操作。此外&#xff0c;还通过创建…

Spring Ioc和Aop,Aop的原理和实现案例,JoinPoint,@Aspect,@Before,@AfterReturning

DAY25.2 Java核心基础 Spring两大核心&#xff1a;Ioc和Aop IOC Ioc容器&#xff1a;装载bean的容器&#xff0c;自动创建bean 三种方式&#xff1a; 1、基于xml配置&#xff1a;通过在xml里面配置bean&#xff0c;然后通过反射机制创建bean&#xff0c;存入进Ioc容器中 …

[解决conda创建新的虚拟环境没用python的问题]

问题复现 使用conda create -n env的时候&#xff0c;在对应的虚拟环境的文件里面找不到对应的python文件 为什么 首先&#xff0c;我们来看一下创建环境时的触发链路&#xff1a; 这表明当前环境中找不到Python可执行文件。 解决方法 所以很明显&#xff0c;我们需要指定…

【C++】控制台小游戏

移动&#xff1a;W向上&#xff0c;S上下&#xff0c;A向左&#xff0c;D向右 程序代码&#xff1a; #include <iostream> #include <conio.h> #include <windows.h> using namespace std;bool gameOver; const int width 20; const int height 17; int …

配合本专栏前端文章对应的后端文章——从模拟到展示:一步步搭建传感器数据交互系统

对应文章&#xff1a;进一步完善前端框架搭建及vue-konva依赖的使用&#xff08;Vscode&#xff09;-CSDN博客 目录 一、后端开发 1.模拟传感器数据 2.前端页面呈现数据后端互通 2.1更新模拟传感器数据程序&#xff08;多次请求&#xff09; 2.2&#x1f9e9; 功能目标 …

springboot IOC

springboot IOC IoC Inversion of Control Inversion 反转 依赖注入 DI &#xff08;dependency injection &#xff09; dependency 依赖 injection 注入 Qualifier 预选赛 一文带你快速理解JavaWeb中分层解耦的思想及其实现&#xff0c;理解 IOC和 DI https://zhuanlan.…

Ajax01-基础

一、AJAX 1.AJAX概念 使浏览器的XMLHttpRequest对象与服务器通信 浏览器网页中&#xff0c;使用 AJAX技术&#xff08;XHR对象&#xff09;发起获取省份列表数据的请求&#xff0c;服务器代码响应准备好的省份列表数据给前端&#xff0c;前端拿到数据数组以后&#xff0c;展…

生成树协议(STP)配置详解:避免网络环路的最佳实践

生成树协议&#xff08;STP&#xff09;配置详解&#xff1a;避免网络环路的最佳实践 生成树协议&#xff08;STP&#xff09;配置详解&#xff1a;避免网络环路的最佳实践一、STP基本原理二、STP 配置示例&#xff08;华为交换机&#xff09;1. 启用生成树协议2. 配置根桥3. 查…

面向 C 语言项目的系统化重构实战指南

摘要: 在实际开发中,C 语言项目往往随着功能演进逐渐变得混乱:目录不清、宏滥用、冗余代码、耦合高、测试少……面对这样的“技术债积累”,盲目大刀阔斧只会带来更多混乱。本文结合 C 语言的特点,从项目评估、目录规划、宏与内联、接口封装、冗余剔除、测试与 CI、迭代重构…

Python Pandas库简介及常见用法

Python Pandas库简介及常见用法 一、 Pandas简介1. 简介2. 主要特点&#xff08;一&#xff09;强大的数据结构&#xff08;二&#xff09;灵活的数据操作&#xff08;三&#xff09;时间序列分析支持&#xff08;四&#xff09;与其他库的兼容性 3.应用场景&#xff08;一&…

第十六届蓝桥杯复盘

文章目录 1.数位倍数2.IPv63.变换数组4.最大数字5.小说6.01串7.甘蔗8.原料采购 省赛过去一段时间了&#xff0c;现在复盘下&#xff0c;省赛报完名后一直没准备所以没打算参赛&#xff0c;直到比赛前两天才决定参加&#xff0c;赛前两天匆匆忙忙下载安装了比赛要用的编译器ecli…

【已解决】HBuilder X编辑器在外接显示器或者4K显示器怎么界面变的好小问题

触发方式&#xff1a;主要涉及DPI缩放问题&#xff0c;可能在电脑息屏有概率触发 修复方式&#xff1a; 1.先关掉软件直接更改屏幕缩放&#xff0c;然后打开软件&#xff0c;再关掉软件恢复原来的缩放&#xff0c;再打开软件就好了 2.(不推荐&#xff09;右键HBuilder在属性里…