Unity编辑器扩展(外挂)

每日一句:未来的样子藏在现在的努力里

目录

什么是编译器开发

C#特性[System.Serializable]

特殊目录

命名空间

/*检视器属性控制*/

    //添加变量悬浮提示文字

    //给数值设定范围(最小0,最大150)

//指定输入框,拥有5行

//默认显示5行,最多显示10行内容,再多用滚动条控制显示区域

//给小齿轮增加一个回调函数

编辑器外挂

弹窗

编辑器扩展案例


什么是编译器开发

       对编译器实现功能扩展,一般会使用它开发项目工具,或实现unity插件

C#特性[System.Serializable]

用于在C#运行时,传递程序中各种元素(类,结构体,变量,方法,枚举,组件)的行为信息的声明标签。一个声明标签是通过放置在它所在应用元素的前面的方括号“[]”中来描述

特殊目录

Plugins:需要跨语言调用的代码逻辑代码存储目录,手机SDK接入

Resources:存储跟随游戏包的资源目录

StreamingAssets:只读,存储跟随游戏包的资源目录

编辑器目录:Editor

制作多目录合并

项目中建立的Editor目录,编译器相关的逻辑和资源会放在其内部,相关内容,在打包生成时,不会一起生成到项目中,玩家也不会使用到编译器相关的内容

Editor目录下的脚本,无法挂载在场景对象下

命名空间

Unity代码逻辑命名空间:UnityEngine

Unity编译器命名空间:UnityEditor,此命名空间,不要出现在游戏被发布的逻辑代码中,会导致项目打包失败

/*检视器属性控制*/

如果对象不标记为可序列化,则Unity在存储的时候,会认为它不可被序列化,那么也就无法被显示

[Serializable]

public class Numerical

{

    public int Atk;

    public int Def;

}

public class Test : MonoBehaviour

{

    //监视面板显示对象

    public Numerical nul;

}

//一个成员变量可以添加多个特性

    //添加变量悬浮提示文字

    [Tooltip("不要填写大于150岁的年龄")]

    //给数值设定范围(最小0,最大150)

    [Range(0,150)]

    public int Age;

//指定输入框,拥有5行

    [Multiline(5)]

public string NickName;

//默认显示5行,最多显示10行内容,再多用滚动条控制显示区域

    [TextArea(5, 10)]

    public string Description;

//给小齿轮增加一个回调函数

    [ContextMenu("输出攻防比")]

    public  void PrintAD()

    {

        Debug.Log("角色攻防比");

}

编辑器外挂

//使生命周期函数,在编辑器状态下可以执行,游戏中也可以正常使用

//Update()在场景中对象发生变化或项目组织发生变化时会执行

[ExecuteInEditMode]

//当前组件依赖于盒子碰撞体

//当前组件挂载在对象时,盒子碰撞体会一起被添加上去

//当Player组件没有被移除时,盒子碰撞体不能被删除

[RequireComponent(typeof(BoxCollider))]

public class Player : MonoBehaviour

{

    public int ID;

    public GameObject Weapon;

    public Texture Cloth;

    public PlayerProfression Pro;

    public PlayerLoveColor LoveColor;

    public List<string> Items;

    public float Ack;

}

//单选使用十进制理解,即不同数代表不同选项

public enum PlayerProfression

{

    Warrior=0,

    Wizard=1,

}

//多选使用二进制理解,即不同位代表不同选项

public enum PlayerLoveColor

{

    Green=1,

    Red=2,

    Pink=4,

}

using UnityEditor;

[CustomEditor(typeof(Player))]

//将编辑器开发脚本与需要编辑的组件脚本建立外挂关联关系

//外挂脚本因为存储在Editor目录下,所以不会被打入最终的游戏包

public class PlayerEditor : Editor

{

    Player _Component;

    //当关联组件所在对象被选中或被添加时调用

    private void OnEnable()

    {

        _Component = target as Player;

    }

    //当关联组件所在对象被取消或被移出时调用

    private void OnDisable()

    {

        _Component = null;

    }

    public override void OnInspectorGUI()

    {

        //标题显示

        EditorGUILayout.LabelField("人物相关属性");

        _Component.ID = EditorGUILayout.IntField("玩家ID", _Component.ID);

        //.TextField()文本    .FolatField()浮点数    .Toggle()布尔     .Vector3Field()向量

        //.ColorField()颜色

        //对象数据类型绘制(1.标题 2.原始组件的值 3.成员变量的类型

        //4.是否可以将场景中的对象拖给这个成员变量)

        _Component.Weapon = EditorGUILayout.ObjectField("持有武器", _Component.Weapon, typeof(GameObject), true) as GameObject;

        //纹理

        _Component.Cloth= EditorGUILayout.ObjectField("衣服材质贴图", _Component.Weapon, typeof(Texture), false) as Texture;

        //枚举数据类型绘制

        //整数转枚举

        //int id=0;

        //PLAYER.PROFESSION P=(PLAYER_PROFESSION)id;

        //单选枚举(标题,组件上的原始值)

        _Component.Pro = (PlayerProfression)EditorGUILayout.EnumPopup("玩家职业", _Component.Pro);

        //多选枚举(标题,组件上的原始值)

        _Component.LoveColor= (PlayerLoveColor)EditorGUILayout.EnumFlagsField("玩家喜欢的颜色", _Component.LoveColor);

        //终极数据类型绘制

        //更新可序列化数据

        serializedObject.Update();

        //通过成员变量名找到组件上的成员变量

        SerializedProperty sp = serializedObject.FindProperty("Items");

        //可序列化数据绘制(取到的数据,标题,是否将所有获得的序列化数据显示出俩)

        EditorGUILayout.PropertyField(sp, new GUIContent("道具信息"), true);

        //将修改的数据,写入到可序列化的原始数据中

        serializedObject.ApplyModifiedProperties();

        //滑动条绘制

        //滑动条显示(1.标题,2.原始变量,最小值,最大值)

        _Component.Ack = EditorGUILayout.Slider(new GUIContent("玩家攻击力"), _Component.Ack, 0, 100);

        if(_Component.Ack>80)

        {

            //显示消息框(红色)

            EditorGUILayout.HelpBox("攻击力太高了", MessageType.Error);

        }

        if (_Component.Ack <20)

        {

            //显示消息框(红色)

            EditorGUILayout.HelpBox("攻击力太低了", MessageType.Warning);

        }

        //按钮显示和元素排列

        GUILayout.Button("来个按钮");

        GUILayout.Button("来个按钮");

        if(GUILayout.Button("测试点击"))

        {

            Debug.Log("测试点击");

        }

        //开始横向排列绘制

        EditorGUILayout.BeginHorizontal();

        GUILayout.Button("再来个按钮");

        GUILayout.Button("再来个按钮");

        //结束横向排列绘制

        EditorGUILayout.EndHorizontal();

    }

}

弹窗

using UnityEditor;

using UnityEngine;

public class PopWindw : EditorWindow

{

    [MenuItem("工具/创建窗口")]

    static void OpenWindow()

    {

        PopupWindow window = GetWindow<PopupWindow>(false, "弹窗标题", true);

        window.minSize = new Vector2(400, 300);

        window.maxSize = new Vector2(800, 600);

    }

}

编辑器扩展案例

using System.Collections.Generic;

using UnityEngine;

[ExecuteInEditMode]//在编辑模式下可以执行一些生命周期函数

public class NodeManager : MonoBehaviour

{

    //存储了所有编辑器下点击生成的点,并使用预制体显示

    public List<GameObject> nodes;

    private void Update()

    {

        for (int i = 0; i < nodes.Count - 1; i++)

        {

            Debug.DrawLine(nodes[i].transform.position, nodes[i + 1].transform.position, Color.red, Time.deltaTime);

        }

    }

}

using UnityEditor;

using UnityEngine;

[CustomEditor(typeof(NodeManager))]

public class NodeManagerEditor : Editor

{

    NodeManager manager;

    bool isEditor = false;

    //当选中带有NodeManager组件对象时,获得组件

    private void OnEnable()

    {

        manager = (NodeManager)target;

    }

    //绘制组件的生命周期函数

    public override void OnInspectorGUI()

    {

        serializedObject.Update();

        SerializedProperty nodes = serializedObject.FindProperty("nodes");

        //Debug.Log("nodes" + nodes);

        EditorGUILayout.PropertyField(nodes, new GUIContent("路径"), true);

        serializedObject.ApplyModifiedProperties();

        //返回 bool 如果向 SerializedObject 应用了任何待处理的属性更改,则返回 true。

        if (!isEditor && GUILayout.Button("开始编辑"))

        {

            NodeWindow.OpenWindow(manager.gameObject);

            //调用打开界面方式

            isEditor = true;

        }

        else if (isEditor && GUILayout.Button("结束编辑"))

        {

            NodeWindow.CloseWindow();

            isEditor = false;

        }

        if (GUILayout.Button("删除最后一个节点"))

        {

            RemoveAtLast();

        }

        else if (GUILayout.Button("删除所有节点"))

        {

            RemoveAll();

        }

    }

    RaycastHit hit;

    //当选中关联的脚本挂载的物体

    //当鼠标在Scene视图下发生时,执行该方法,如鼠标移动

    private void OnSceneGUI()

    {

        if (!isEditor)//非编辑器下不能生成路点

        {

            return;

        }

        //当鼠标按下左键时发射一条射线

        //非运行时,使用Event类

        if (Event.current.button == 0 && Event.current.type == EventType.MouseDown)

        {

            //从鼠标的位置需要发射射线了

            //因为是从Scene视图下发射射线,跟场景中的摄像机并没有关系,所以不能使用相机发射射线的方式

            //从编辑器GUI中的一个点向世界定义一条射线,参数一般都是鼠标的坐标

            Debug.Log("aaa");

            //将 2D GUI 位置转换为世界空间射线HandleUtility.GUIPointToWorldRay

            Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);

            Debug.Log(ray);

            //Debug.DrawLine(Event.current.mousePosition,  Color.red);

            Debug.Log(Physics.Raycast(ray, out hit, 1000));

            if (Physics.Raycast(ray, out hit, 1000))

            {

                Debug.Log("bbb");

                //需要在检测到的点实例化路点

                InstancePathNode(hit.point + Vector3.up * 0.1f);

            }

        }

    }

    //生成节点

    void InstancePathNode(Vector3 position)

    {

        GameObject prefab = Resources.Load<GameObject>("PathNode");

        Debug.Log(prefab);

        GameObject pathNode = Instantiate<GameObject>(prefab, position, Quaternion.identity, manager.transform);

        manager.nodes.Add(pathNode);//把生成的路点添加到列表里

    }

    //删除最后一个节点

    void RemoveAtLast()

    {

        //保证有节点才能删节点

        if (manager.nodes.Count > 0)

        {

            //从场景中删除游戏物体

            DestroyImmediate(manager.nodes[manager.nodes.Count - 1]);

            //把该节点从列表中移除

            manager.nodes.RemoveAt(manager.nodes.Count - 1);

        }

    }

    //删除所有的节点

    void RemoveAll()

    {

        //遍历删除所有的节点物体

        for (int i = 0; i < manager.nodes.Count; i++)

        {

            if (manager.nodes[i] != null)

            {

                DestroyImmediate(manager.nodes[i]);

            }

            manager.nodes.Clear();

        }

    }

}

public class NodeWindow : EditorWindow

{

    static NodeWindow window;

    static GameObject nodeManager;

    public static void OpenWindow(GameObject manager)

    {

        nodeManager = manager;

        //真正开启了一个窗口

        window = EditorWindow.GetWindow<NodeWindow>();

    }

    private void Update()

    {

        Selection.activeGameObject = nodeManager;

    }

    public static void CloseWindow()

    {

        window.Close();

    }

}

链接:https://pan.baidu.com/s/1Kr2vYQKIoA897YY-l3j8kg?pwd=acap

提取码:acap

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

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

相关文章

软件测试|使用Pytest、Allure Step和Allure Attach创建详细测试报告

引言 在软件开发过程中&#xff0c;测试是不可或缺的一部分。为了更好地展示测试结果并定位问题&#xff0c;结合Pytest测试框架和Allure测试报告工具可以创建清晰、详细的测试报告。本文将介绍如何使用Pytest、Allure的allure.step()和allure.attach()功能来创建具有丰富信息…

springboot 物业管理系统

springboot mysql mybatisthymeleaf 基础信息管理 房屋信息 用户信息 业主信息 租房信息 公告管理 日常管理 财务管理

快速排序-排序算法

算法思想 快速排序采用的仍然是分治的思想。 Step1.每次在无序的序列中选取一个基准数。 Step2.然后将大于和小于基准数的元素分别放置于基准数两边。&#xff08;前面部分的元素均小于或等于基准数&#xff0c;后面部分均大于或等于基准数&#xff09; Step3.然后采用分治法&…

预训练中文GPT2(包括重新训练tokenizer)

训练数据 1.json后缀的文件 2.数据是json line格式&#xff0c;一行一条json 3. json结构如下 {"content": "①北京和上海户籍的游客可获得韩国多次签证&#xff1b;②“整容客”可以不经由韩国使领馆、直接在网上申请签证&#xff1b;③中泰免签的实施日期…

C++ 深度优先搜索DFS || 模版题:排列数字

给定一个整数 n &#xff0c;将数字 1∼n 排成一排&#xff0c;将会有很多种排列方法。 现在&#xff0c;请你按照字典序将所有的排列方法输出。 输入格式 共一行&#xff0c;包含一个整数 n 。 输出格式 按字典序输出所有排列方案&#xff0c;每个方案占一行。 数据范围 1…

前端页面优化做的工作

1.分析模块占用空间 new (require(webpack-bundle-analyzer).BundleAnalyzerPlugin)() 2.使用谷歌浏览器中的layers&#xff0c;看下有没有影响性能的模块&#xff0c;或者应该销毁没销毁的 3.由于我们页面中含有很大的序列帧动画&#xff0c;所以会导致页面性能低&#xff0…

质量好洗地机有哪些?洗地机口碑榜

在很多人眼中&#xff0c;洗地机可能被简单地视为一种高价的拖把&#xff0c;但作为一个经验丰富的洗地机测评博主&#xff0c;我要强调洗地机在家务工作中的巨大价值。它不仅仅是一种清洁工具&#xff0c;更是集扫地、拖地、洗地以及擦干地板等多项功能于一身的强大设备。通过…

Transformer从菜鸟到新手(六)

引言 上篇文章介绍了如何在多GPU上分布式训练&#xff0c;本文介绍大模型常用的一种推理加速技术——KV缓存。 KV Cache KV缓存(KV Cache)是在大模型推理中常用的一种技巧。我们知道在推理阶段&#xff0c;Transformer也只能像RNN一样逐个进行预测&#xff0c;也称为自回归。…

关于报错 curl: (56) Recv failure: Connection reset by peer

curl ip没问题 curl localhost 则报错 curl: (56) Recv failure: Connection reset by peer 出现这个报错有很多原因, 其中之一就是terminal代理 而关闭代理应用之后, 其实由于配置的终端都是 export指定的代理 所以导致还是一直报错. 通过 curl -v 可以发现 指向了代理ip和…

深入浅出理解SPP、ASPP、DSPP、MDSPP空间金字塔池化系列结构(综合版)

一、参考资料 目标检测&#xff1a;SPP-net SPP原理及实现 金字塔池化系列的理解SPP、ASPP SPP&#xff0c;PPM、ASPP和FPN结构理解和总结 二、空间金字塔池化(SPP) 原始论文&#xff1a;[1] 1. 引言 传统的卷积神经网络中&#xff0c;池化层通常采用固定的池化层级和固定…

使用SpringCache操作Redis缓存数据

SpringCache概念 SpringCache是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单的加一个注解&#xff0c;就能实现缓存功能。 SpringCache提供了一层抽象&#xff0c;底层可以切换不同的缓存实现&#xff0c;例如&#xff1a; EHCacheCaffeineRedis 使…

2024 年 1 月安全更新修补了 58 个漏洞(Android )

谷歌发布了针对 Android 平台 58 个漏洞的补丁&#xff0c;并修复了 Pixel 设备中的 3 个安全漏洞&#xff0c;拉开了 2024 年的序幕。 Android 2024 年 1 月更新的第一部分以 2024 年 1 月 1 日安全补丁级别发布在设备上&#xff0c;解决了框架和系统组件中的 10 个安全漏洞&…

学习笔记17——通俗易懂的三次握手四次挥手

提供一种博主本人觉得很好理解的三次握手和四次挥手场景&#xff0c;帮助记忆 三次握手过程 初始状态&#xff1a;客户端处于closed状态&#xff0c;服务器处于listen监听转台客户端向服务器发送一个SYN连接请求&#xff0c;并告诉对方自己此时初始化序列号为x&#xff0c;发送…

TYPE-C接口取电芯片介绍和应用场景

随着科技的发展&#xff0c;USB PDTYPE-C已经成为越来越多设备的充电接口。而在这一领域中&#xff0c;LDR6328Q PD取电芯片作为设备端协议IC芯片&#xff0c;扮演着至关重要的角色。本文将详细介绍LDR6328Q PD取电芯片的工作原理、应用场景以及选型要点。 一、工作原理 LDR63…

【昕宝爸爸小模块】HashMap用在并发场景存在的问题

HashMap用在并发场景存在的问题 一、✅典型解析1.1 ✅JDK 1.8中1.2 ✅JDK 1.7中1.3 ✅如何避免这些问题 二、 ✅HashMap并发场景详解2.1 ✅扩容过程2.2 ✅ 并发现象 三、✅拓展知识仓3.1 ✅1.7为什么要将rehash的节点作为新链表的根节点3.2 ✅1.8是如何解决这个问题的3.3 ✅除了…

【SSO】统一授权中心v1.0.0版本正式上线(多租户)

目录 背景 体验 技术栈 菜单 示例 背景 为了方便权限管理、用户登录授权、应用授权等&#xff0c;特地开发了当前的统一授权中心。 体验 邮箱注册即可登录体验 后台系统&#xff1a;https://sso.behappyto.cn/#/switch 技术栈 vue3tsspringbootmybatismysql 菜单 …

SpringBoot用MultipartFile.transferTo传递相对路径的问题

问题描述&#xff1a; 打算给自己的项目添加一个上传文件保存功能&#xff0c;于是我使用MultipartFile.transferTo()来完成这个功能&#xff0c;由于我的项目要部署到服务器&#xff0c;所以我使用了相对路径把上传的文件保存到当前项目的工作目录下&#xff0c;但是报错了&am…

C++上位软件通过Snap7开源库访问西门子S7-200/LOGO PLC/合信M226ES PLC V存储区的方法

前言 在前面例程中谈到了C 通过Snap7开源库S7通信库跟西门子S7-1200PLC/S7-1500PLC以及合信CTMC M226ES PLC/CPU226 PLC通信的方式方法和应用例程。但是遗憾的是Snap7中根据官方资料显示只能访问PLC的 DB区、MB区、C区、T区 、I区、Q区&#xff0c;并没有提到有关如何访问S7-20…

在学习爬虫前的准备

1. 写一个爬虫程序需要分几步 获取网页内容。 我们会通过代码给一个网站服务器发送请求&#xff0c;它会返回给我们网页上的内容。 在我们平时使用浏览器访问服务器内容是&#xff0c;本质上也是向服务器发送一个请求&#xff0c;然后服务器返回网页上的内容。只不过浏览器还会…

K8s Pod详解

1.Pod结构 每个Pod中都可以包含一个或者多个容器&#xff0c;这些容器可以分为两类&#xff1a; 用户程序所在的容器&#xff0c;数量可多可少 Pause容器&#xff0c;这是每个Pod都会有的一个根容器&#xff0c;它的作用有两个&#xff1a; 可以以它为依据&#xff0c;评估整个…