Unity读书系列《Unity3D游戏开发》——拓展编辑器(一)

文章目录

  • 前言
  • 一、扩展Project视图
    • 1、右键扩展菜单(Asset)
    • 2、监听事件
    • 3、拓展布局
  • 二、扩展Hierarchy视图
    • 1、拓展菜单(GameObject)
    • 2、拓展布局
    • 3、重写菜单
  • 三、扩展Inspector视图
    • 1、扩展原生组件
    • 2、扩展继承组件
  • 四、扩展Scene视图
    • 1、绘制辅助元素
    • 2、辅助UI
    • 3、常驻辅助UI
  • 五、扩展Game视图
  • 总结


前言

本篇文章是对前文关于编辑器拓展的探讨的延伸。即使内置的Unity编辑器再强大,也无法满足所有不同产品和游戏的需求。为了解决这个问题,Unity提供了编辑器拓展的API接口。我们可以通过代码反射的方式修改内置的系统编辑器,同时,游戏开发者也可以利用EditorGUI接口编写适合自己的专属游戏编辑器。这涵盖了从简单的一键换字体、材质、一键打包、管理、优化,到复杂的技能编辑器、关卡编辑器等功能。

特别需要注意的是,由于内容涉及较多且较为复杂,会分2节进行详细讨论。在本文的第一部分中,我们将总结最基础和最实用的编辑器拓展知识。

本文所有代码均在Gitee参考工程,如有需要请自取。


一、扩展Project视图

Project视图是掌握Unity项目的生死大权的地方,包括创建、删除等重要操作。在这里,我们可以通过右键点击实现Asset菜单的拓展。在进行这项任务之前,首先需要将脚本文件保存到名为Editor的文件夹下,并引入UnityEditor命名空间。

1、右键扩展菜单(Asset)

右键创建物体

using UnityEngine;
using UnityEditor;public class AssetEditor
{[MenuItem("Assets/Tools/CreateSphere",false,1)]//数值越小越靠前static void Createxx() {GameObject.CreatePrimitive(PrimitiveType.Sphere);}
}

如图我点击CreateSphere按钮就创建了一个球体到场景当中。
在这里插入图片描述

2、监听事件

在大型或规范的项目中,通常会有严格的项目规范,包括对资源的归类等方面。例如,如果你将贴图移动到了脚本文件夹,项目可能会判断这样的操作是不合法的,并阻止你进行修改。
1、监听资源的删除、创建、移动、保存等操作,在进行操作后会输出绑定的委托。

    //监听事件[InitializeOnLoadMethod]static void InitializeOnLoadMethod() {EditorApplication.projectChanged += delegate (){Debug.Log("怎么回事,老弟。你是不是刚动了资源?");};}

嘿嘿,知识点还没完,[InitializeOnLoadMethod]写在方法 前则会使该方法在C#代码编译完成后首先调用。
2、当需要重新具体的删除、创建方法时必须继承UnityEditor.AssetModificationProcessor,具体方法如下:

public class AssetEventEditor : UnityEditor.AssetModificationProcessor
{//监听事件[InitializeOnLoadMethod]static void InitializeOnLoadMethod(){EditorApplication.projectChanged += delegate (){Debug.Log("怎么回事,老弟。你是不是刚动了资源?");};}//监听"双击左键打开资源"事件public static bool IsOpenForEdit(string assetPath, out string message){message = null;Debug.LogFormat("assetPath:{0}", assetPath);return true;//true表示该资源可以打开,false表示不允许打开}//监听"资源即将被创建"事件public static void OnWillCreateEdit(string path){Debug.LogFormat("创建资源的路径:{0}", path);}//监听"资源即将被保存"事件public static string[] OnWillSaveAssets(string[] paths) {if (paths != null){Debug.LogFormat("保存资源的路径:{0}",string.Join(",",paths));}return paths;}//监听"资源即将被移动"事件public static AssetMoveResult OnWillMoveAsset(string oldPath,string newPath) {Debug.LogFormat("资源从路径{0}移动到路径{1}", oldPath,newPath);return AssetMoveResult.DidNotMove;//DidNotMove表示可以移动,DidMove表示不可以移动}//监听"资源即将被删除"事件public static AssetDeleteResult OnWillDeleteAsset(string assetPath) {Debug.LogFormat("资源从路径{0}删除", assetPath);return AssetDeleteResult.DidNotDelete;//DidNotDelete表示可以移动,DidDelete表示不可以移动}}

3、拓展布局

选中资源后出现按钮,并监听按钮的点击事件

    //选中资源后出现按钮,并监听按钮的点击事件[InitializeOnLoadMethod]static void InitializeOnLoadMethod(){EditorApplication.projectWindowItemOnGUI = delegate (string guid, Rect selectionRect){//在Project试图中选择一个资源if (Selection.activeObject && guid == AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(Selection.activeObject))){//设置拓展按钮区域float width = 80f;selectionRect.x += (selectionRect.width - width);selectionRect.y += 2;selectionRect.width = width;GUI.color = Color.red;//点击事件if (GUI.Button(selectionRect,"click")){Debug.LogFormat("点击:{0}", Selection.activeObject.name);}GUI.color = Color.white;}};}

在这里插入图片描述

二、扩展Hierarchy视图

在Hierarchy(层次)视图中,右键点击相当于打开菜单栏的GameObject栏目。

1、拓展菜单(GameObject)

细心的读者已经看出来了,下面代码对比上面写的仅仅将菜单栏目从"Assets"换成了"GameObject"。

    //右键创建物体[MenuItem("GameObject/Tools/CreateSphere", false, 1)]//数值越小越靠前static void Createxx(){GameObject.CreatePrimitive(PrimitiveType.Sphere);}

2、拓展布局

粗心的读者这下也已经看出来了,下面的代码复刻了之前的代码,将 EditorApplication 后的 GUI 委托修改为 Hierarchy 窗口专属的,并将参数从资源的 GUID 变为 instanceID 实例 ID。此外,按钮引入了本地图片。在各种插件中,编辑器引入图片的操作屡见不鲜,有时为了资源规范会整理插件的图标和图片位置,别忘了根据实际情况修改相关代码。
在这里插入图片描述

3、重写菜单

通过以上学习,我们了解了如何在原有基础上扩展编辑器。那么,能否完全重写呢?当然可以。

1、下面,我们将学习如何重新创建 Image 的逻辑。因为在创建 Image 时,Unity 默认会自动勾选 RaycastTarget,如果我们不需要它具有点击功能,就会有额外的性能开销。使用下面的代码,我们可以创建不勾选 RaycastTarget 的 Image。

  //创建Image默认不勾选RaycastTarget[MenuItem("GameObject/UI/Image0")]static void CreateImage() {if (Selection.activeTransform){if (Selection.activeTransform.GetComponentInParent<Canvas>()){Image image = new GameObject("image").AddComponent<Image>();image.raycastTarget = false;image.transform.SetParent(Selection.activeTransform, false);//设置选中状态Selection.activeTransform = image.transform;}}}

完整版会有检测视图是否有Canvas组件,没有则自动创建等功能。

2、重写菜单:

   //重写菜单[InitializeOnLoadMethod]static void StartInitializeOnLoadMethod(){EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyGUI;}static void OnHierarchyGUI(int instanceID, Rect selectionRect){//Event.current监听当前事件,如果监听到鼠标抬起则执行自定义事件(也就是我们的自定义菜单)if (Event.current != null && selectionRect.Contains(Event.current.mousePosition) && Event.current.button == 1 && Event.current.type <= EventType.MouseUp){GameObject selectedGameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;//判断是否满足条件if (selectedGameObject){Vector2 mousePosition = Event.current.mousePosition;EditorUtility.DisplayPopupMenu(new Rect(mousePosition.x, mousePosition.y, 0, 0), "Window/Test", null);Event.current.Use();}}}[MenuItem("Window/Test/Test1")]static void Test1(){}[MenuItem("Window/Test/Test2")]static void Test2(){}

重写完成后,右键视图中的实例将会弹出自定义菜单
在这里插入图片描述

三、扩展Inspector视图

Inspector(检视)视图是用来展示组件及资源的详细信息面板。unity自身提供的各类组件的面板能够满足我们正常的需求,但我们偶尔会希望在某些面板上添加快捷按钮或者某些逻辑。

1、扩展原生组件

摄像机是典型的原生组件,我们CustomEditor进行自定义组件,重写OnInspectorGUI在base.OnInspectorGUI()这个原有元素接口上下添加按钮。

using UnityEditor;
using UnityEngine;[CustomEditor(typeof(Camera))]
public class CameraEditor : Editor
{public override void OnInspectorGUI(){if (GUILayout.Button("拓展按钮-上")){}base.OnInspectorGUI(); if (GUILayout.Button("拓展按钮-下")){}}
}

如下便绘制了两个按钮,不过要注意该组件限制了按钮必须加在最上面或者最下面。
在这里插入图片描述

2、扩展继承组件

1、Unity将大量的Editor绘制方法封装进了DLL,通常来讲我们无法调用其中方法。想要解决可以使用反射获取内部对象,然后调用想要使用的未公开的方法。

using System.Reflection;
using UnityEditor;
using UnityEngine;[CustomEditor(typeof(Transform))]
public class TransformEditor : Editor
{private Editor m_Editor;private void OnEnable(){m_Editor = Editor.CreateEditor(target, Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.TransformInspector", true));}public override void OnInspectorGUI(){if (GUILayout.Button("拓展按钮")){}m_Editor.OnInspectorGUI();//原有信息面板// base.OnInspectorGUI();}
}

2、Context菜单
点击组件的设置按钮(或鼠标右键),会弹出Context菜单,里面有Copy、Reset等操作按钮。我们有时候想对特定组件进行自定义的操作,例如我想在Transform的Context菜单添加NewContext按钮,只需更改MenuItem里第一个参数为"CONTEXT/Transform/NewContext"接口。想给Camer加就将Transform替换成Camera,想给所有组件加就替换成"Compoment"。

    [MenuItem("CONTEXT/Transform/New Context")]static void NewContext(MenuCommand menuCommand){Debug.LogFormat("组件名称:{0}",menuCommand.context.name);}

下面演示如何重写特定脚本的系统方法。作者建议最好延迟一帧以防止在编辑模式下代码同步出现问题。在测试 Unity 2021 版本时,貌似没有出现问题。此外,需要注意的是,书中的部分接口可能已经过时,个人已经替换成最新的版本(以2021.3为准)。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class ContextScript : MonoBehaviour
{[ContextMenu("Remove Component")]void RemoveComponent(){Debug.Log("RemoveComponent");//等一帧再删除自己,防止引擎底层错误UnityEditor.EditorApplication.delayCall = delegate () {DestroyImmediate(this);};}
}

脚本中使用宏定义(使用宏定义的原因是为了在发布后剔除无效代码),联动脚本中的变量在编辑模式下实现功能。下面代码就让NewContext按钮操作了脚本中的变量——将ContextScript脚本中的str变量从"原始"改成了"原神"。

using System.Collections;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;public class ContextScript : MonoBehaviour
{public string str = "原始";
#if UNITY_EDITOR//宏定义操作脚本变量[MenuItem("CONTEXT/ContextScript/New Context")]static void NewContext(MenuCommand menuCommand){ContextScript contextScript = menuCommand.context as ContextScript;contextScript.str = "原神";}
#endif
}

请添加图片描述

四、扩展Scene视图

Unity的Scene视图是一个用于编辑场景的窗口。在Scene视图中,你可以直观地查看、编辑和组织你的游戏场景。

1、绘制辅助元素

场景编辑中我们有时会需要线段、不同形状的元素来帮助我们快速编辑。下面我们将使用Gizmos.cs工具类绘制简单元素。

using UnityEngine;public class GizmoScirpt : MonoBehaviour
{ //在鼠标点击到脚本挂载的物体的身上的时候运行private void OnDrawGizmosSelected(){Gizmos.color = Color.red;//画线Gizmos.DrawLine(transform.position, Vector3.one);//立方体Gizmos.DrawCube(Vector3.one, Vector3.one);}
}

在这里插入图片描述
我们发现未点击挂载脚本的物体时,立方体和线条消失了。如果想让绘制的物体一直出现,可以使用 OnDrawGizmos 方法。具体用法有很多,比如技能范围展示、地形和陷阱的实际范围绘制等,都能使项目更高效进行。

   //不依赖对象,会一直执行private void OnDrawGizmos(){Gizmos.DrawSphere(transform.position, 2.0f);}

2、辅助UI

我们在Scene视图中可以在各种组件中添加EditorGUI以获得便利。下面演示如何在Scene中给Camer添加位置信息。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;[CustomEditor(typeof(Camera))]
public class GUIEditor : Editor
{private void OnSceneGUI(){Camera camera = target as Camera;if (camera != null){Handles.color = Color.red;Handles.Label(camera.transform.position, camera.transform.position.ToString());Handles.BeginGUI();GUI.backgroundColor = Color.red;if (GUILayout.Button("click",GUILayout.Width(200f))){Debug.LogFormat("click = {0}", camera.name);}GUILayout.Label("Label");Handles.EndGUI();}}
}

在这里插入图片描述

最后注意如果你的脚本不生效,可能是该脚本与CameraEditor脚本互斥冲突,因为都用了"[CustomEditor(typeof(Camera))]",默认先创建的脚本会生效。

3、常驻辅助UI

常驻辅助UI或者说固定辅助UI,顾名思义,无需游戏对象即可常驻Scene视图,有些类似OnDrawGizmosSelected和OnDrawGizmos。

using UnityEngine;
using UnityEditor;//常驻辅助UI
public class GUIEditor2 : MonoBehaviour
{[InitializeOnLoadMethod]static void InitializeOnLoadMethod() {SceneView.duringSceneGui += delegate (SceneView sceneView){Handles.BeginGUI();GUI.Label(new Rect(0, 0, 50f, 50f), "标题");GUI.Button(new Rect(0, 20f, 50f, 50f), AssetDatabase.LoadAssetAtPath<Texture>("Assets/unity.png"));Handles.EndGUI();};}
}

如下,Scene视图左上角多出了一个UI
在这里插入图片描述

五、扩展Game视图

通常来讲运行游戏才能执行脚本的生命周期。如果想在非运行模式下也可以执行脚本,在脚本上添加[ExecuteInEditMode],那么该脚本可以在编辑模式下生效,如果不想在发布后出现可以使用宏定义来剔除。

using UnityEngine;#if UNITY_EDITOR//编辑器模式下依然执行生命周期
[ExecuteInEditMode]
public class GameScript : MonoBehaviour
{private void OnGUI(){if (GUILayout.Button("Click")){Debug.Log("Click");}GUILayout.Label("Click!");}
}#endif

总结

累死了,本篇详细讲述了 Unity 编辑器的五大视图的拓展方法。原本想分为多篇,但为了整体性,将其整合在一起。下一篇文章字数减少但会更加深入地探讨,并详细解释面板和编辑器源码的相关内容。有不愿透露姓氏的杨姓砖家建议认真阅读完本篇并亲自进行代码试验,然后再查看下一篇。
创作不易,觉得有用的请大家多点赞、评论、收藏,毕竟不收钱,甚至说不定因为哪个知识点恰巧能在面试里帮助到你,提升你的薪资,哈哈。

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

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

相关文章

【每日一题】5.LeetCode——环形链表

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&…

【智能家居入门之环境信息监测】(STM32、ONENET云平台、微信小程序、HTTP协议)

作为入门本篇只实现微信小程序接收下位机上传的数据&#xff0c;之后会持续发布如下项目&#xff1a;①可以实现微信小程序控制下位机动作&#xff0c;真正意义上的智能家居&#xff1b;②将网络通讯协议换成MQTT协议再实现上述功能&#xff0c;此时的服务器也不再是ONENET&…

【动态规划】【字符串】【行程码】1531. 压缩字符串

作者推荐 视频算法专题 本文涉及知识点 动态规划汇总 LeetCode 1531. 压缩字符串 II 行程长度编码 是一种常用的字符串压缩方法&#xff0c;它将连续的相同字符&#xff08;重复 2 次或更多次&#xff09;替换为字符和表示字符计数的数字&#xff08;行程长度&#xff09;…

MyBatis详解(6)-- 分页及缓存

MyBatis详解&#xff08;6&#xff09; 分页缓存特点&#xff1a;限制&#xff1a;术语&#xff1a;加载分类立即加载&#xff1a;延迟加载&#xff1a;mybatis缓存&#xff1a; 缓存的适用性MyBatis 缓存分类一级缓存注意&#xff1a;一级缓存未命中二级缓存二级缓存的优劣自定…

qq通讯录怎么关闭?QQ好友删除了怎么恢复?

在QQ中&#xff0c;通讯录是我们管理好友和进行聊天的重要工具&#xff0c;但有时候我们可能需要一些隐私保护&#xff0c;不让一些用户通过手机通讯录添加自己。如果您正在思考qq通讯录怎么关闭以及恢复意外删除的好友&#xff0c;本文将为您详细介绍如何关闭QQ通讯录和恢复被…

php实现多进程的几种方式

目录 一&#xff1a;使用pcntl扩展库 二&#xff1a;使用Swoole扩展 三&#xff1a;使用多进程模式PHP-FPM 在PHP中实现多进程主要有以下几种方式&#xff1a; 一&#xff1a;使用pcntl扩展库 pcntl扩展库提供了多线程相关的函数&#xff0c;如pcntl_fork()用于创建子进程…

详解操作系统各章大题汇总(死锁资源分配+银行家+进程的PV操作+实时调度+逻辑地址->物理地址+页面置换算法+磁盘调度算法)

文章目录 第三章&#xff1a;死锁资源分配图例一例二 第三章&#xff1a;银行家算法第四章&#xff1a;进程的同步与互斥做题步骤PV操作的代码小心容易和读者写者混 1.交通问题&#xff08;类似读者写者&#xff09;分析代码 2.缓冲区问题&#xff08;第二个缓冲区是复制缓冲区…

RK3568驱动指南|驱动基础进阶篇-进阶6 内核运行ko文件实验——系统调用

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

Idea编写mapper.xml文件提示表名和字段

一、连接database 二、setting- > language -> sql Dialects中 的选项设为 mysql就可以了 三、测试

从零开始做题:逆向 ret2shellcode orw

1.题目信息 BUUCTF在线评测 下载orw时防病毒要关闭 2.题目分析 orw是open、read、write的简写。有时候binary会通过prctl、seccomp进行沙箱保护&#xff0c;并不能getshell。只能通过orw的方式拿到flag。 fdopen&#xff08;‘./flag’); # 打开flag文件&#xff0c;得到fd…

Android MTE技术详解

1.MTE概念 MTE&#xff08;内存标记扩展&#xff09;是ARM v8.5-A新增的一项缓解内存安全的机制。在Android Linux现有的安全机制中&#xff0c;类似的机制有ASAN、HWSAN。但两者因为性能开销代价高昂&#xff0c;不适用于广泛部署&#xff08;仅调试使用&#xff09;。MTE当前…

语义分割 | 基于 VGG16 预训练网络和 Segnet 架构实现迁移学习

Hi&#xff0c;大家好&#xff0c;我是源于花海。本文主要使用数据标注工具 Labelme 对猫&#xff08;cat&#xff09;和狗&#xff08;dog&#xff09;这两种训练样本进行标注&#xff0c;使用预训练模型 VGG16 作为卷积基&#xff0c;并在其之上添加了全连接层。基于标注样本…

【算法专题】动态规划综合篇

动态规划7.0 1. 最长公共子序列2. 不相交的线3. 不同的子序列4. 通配符匹配5. 正则表达式匹配6. 交错字符串7. 两个字符串的最小ASCII删除和8. 最长重复子数组 1. 最长公共子序列 题目链接 -> Leetcode -1143.最长公共子序列 Leetcode -1143.最长公共子序列 题目&#xf…

单张图像三维重建RealFusion 360◦ Reconstruction of Any Object from a Single Image

Luke Melas-Kyriazi&#xff0c; Iro Laina&#xff0c; Christian Rupprecht&#xff0c; Andrea Vedaldi.RealFusion 360◦ Reconstruction of Any Object from a Single Image。RealFusion: 360 Reconstruction of Any Object from a Single Image Abstract We consider th…

升降机SEW MOVIDRIVE变频器设置

SEW变频器Startup 安装好MOVITOOL软件 打开项目开始配置通信连接 以太网连接为例 在此栏输入需要连接的SEW变频器的IP地址,例如:172.25.20.120 设置完成单击“OK” 当设置完成后,请一路OK回到软件管理画面 通信配置正确的软件界面状态 单击红色刷新图标 一切准备就绪后…

DjangoURL调度器(一)

一、介绍 当一个用户请求 Django 站点的一个页面&#xff0c;下面是 Django 系统决定执行哪个 Python 代码使用的算法&#xff1a; Django确定要使用的根URLconf模块&#xff0c;一般是在settings中的ROOT_URLCONF设置的值&#xff0c;但是如果传入 HttpRequest 对象具有一个ur…

Pytest 识别case规则

一、Python测试框架&#xff0c;主要特点有以下几点&#xff1a; 简单灵活&#xff0c;容易上手&#xff1b;支持参数化&#xff1b;能够支持简单的单元测试和复杂的功能测试&#xff0c;还可以用来做selenium/appnium等自动化测试、接口自动化测试&#xff08;pytestrequests…

C#简单使用Yolov5的Onnx格式模型进行目标检测

背景 最近要离职了&#xff0c;同事需要了解一下C#如何使用yolov5系列onnx格式模型进行目标检测&#xff0c;由于其对C#不熟练&#xff0c;可能会影响公司后续的开发进度&#xff0c;所以趁着还在&#xff0c;赶紧把手尾搞好。 方案 1、创建一个C# DotNet 8 控制台项目[可千…

LabVIEW动态数据交换实现数据通信

LabVIEW动态数据交换实现数据通信 介绍了LabVIEW软件在驱动一般多功能接口卡中的应用。LabVIEW作为一种图形化编程平台&#xff0c;被广泛应用于自动测量系统、工业过程自动化等领域。利用LabVIEW驱动实验室中常用的多功能接口卡&#xff0c;以实现数据采集和分析。 系统主要…

bxCAN 主要特性

bxCAN 主要特性 ● 支持 2.0 A 及 2.0 B Active 版本 CAN 协议 ● 比特率高达 1 Mb/s ● 支持时间触发通信方案 发送 ● 三个发送邮箱 ● 可配置的发送优先级 ● SOF 发送时间戳 接收 ● 两个具有三级深度的接收 FIFO ● 可调整的筛选器组&#xff1a; — CAN1 和…