在Unity编辑器中实现组件的复制与粘贴:完整指南

news2024/9/25 17:21:47

内容将会持续更新,有错误的地方欢迎指正,谢谢!
 

在Unity编辑器中实现组件的复制与粘贴:完整指南
     
TechX 坚持将创新的科技带给世界!

拥有更好的学习体验 —— 不断努力,不断进步,不断探索
TechX —— 心探索、心进取!

助力快速掌握 组件复制与粘贴

为初学者节省宝贵的学习时间,避免困惑!


前言:

  在Unity的开发过程中,特别是当你在编辑场景中处理多个对象时,如何高效地管理和操作组件是一项关键技能。手动为每个GameObject重新添加组件,尤其是在处理复杂场景时,是一个耗时的过程。

  为此,本文将介绍如何在Unity编辑器中编写一个工具,允许你复制和粘贴组件,并提供多种粘贴模式,如序列化和非序列化,同时忽略某些不必要的组件。你还可以根据需要清除特定组件。


文章目录

  • 一、创建组件忽略列表
  • 二、实现组件复制粘贴删除功能
    • 2.1、复制组件
      • 2.1.1 复制所有组件
      • 2.1.2 复制所有自定义Mono脚本组件
      • 2.1.3 复制所有内建组件
    • 2. 2、粘贴组件
      • 2.2.1 附加模式粘贴组件
      • 2.2.2 覆盖模式粘贴组件
    • 2.3、删除组件
  • 三、项目地址


一、创建组件忽略列表


在复制组件的时候,我们允许忽略一些组件不进行复制,那么在复制组件的时候就不会复制这些类型的组件,同时也不会进行粘贴。

using UnityEngine;
using System.Collections.Generic;

namespace CopyPasteComponents.Editor
{
    [CreateAssetMenu(fileName = "ComponentsIgnoreList", menuName = "ScriptableObjects/ComponentsIgnoreList", order = 1)]
	public class ComponentsIgnoreList : ScriptableObject
	{
        public List<string> IgnoreList = new List<string>();
    }
}

在这里我们对Transform、MeshRenderer和MeshFilter组件进行忽略,因为这3个组件是组成一个游戏对象最基本的组件,一般不需要进行复制粘贴。

在这里插入图片描述



二、实现组件复制粘贴删除功能


以下是组件的复制粘贴与删除的具体功能:

public class CopyPasteComponents
{
    static Component[] copiedComponentCaches;
    static Dictionary<GameObject, Component[]> pastedComponentCaches = new Dictionary<GameObject, Component[]>();
    static ComponentsIgnoreList componentsIgnoreList;
    static Type[] ignoreComponentTypes;

    [InitializeOnLoadMethod]
    static void Init()
    {
        componentsIgnoreList = Resources.Load<ComponentsIgnoreList>("ComponentsIgnoreList");

        Assembly assembly = Assembly.Load("UnityEngine.CoreModule");

        ignoreComponentTypes = componentsIgnoreList.IgnoreList.Select(item =>assembly.GetType(item)).ToArray();
    }
 
    static Component GetNewAddComponents(GameObject targetObj)
    {
        // 获取粘贴后的SerializedObject
        SerializedObject serializedObjectAfter = new SerializedObject(targetObj);
        SerializedProperty componentProperty = serializedObjectAfter.FindProperty("m_Component");
        serializedObjectAfter.Update();

        // 获取新添加的组件
        SerializedProperty newComponentProperty = componentProperty.GetArrayElementAtIndex(componentProperty.arraySize - 1);
        SerializedProperty componentReference = newComponentProperty.FindPropertyRelative("component");
        Object componentObject = componentReference.objectReferenceValue;
        return componentObject as Component;
    }

    static void StorePastedComponent(GameObject targetObj, Component[] component)
    {
        if (!pastedComponentCaches.ContainsKey(targetObj))
        {
            pastedComponentCaches.Add(targetObj, null);
        }

        pastedComponentCaches[targetObj] = component;
    }

    static bool HasDisallowMultipleComponent(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DisallowMultipleComponent), false);
        return attributes.Length > 0;
    }

2.1、复制组件


首先,让我们来看看如何实现组件的复制功能。我们提供了三种不同的组件复制方法:复制所有组件、复制自定义Mono组件以及复制内置组件。

2.1.1 复制所有组件


这里通过GetComponents()来获取对象上的所有继承了Component的组件,包含所有的内建组件和Mono组件。

同时对于在忽略列表里面的组件类型不会进行复制。

[MenuItem("GameObject/ComponentsCopy_Paste/Copy All Components", priority = 0)]
static void CopyAllComponents(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    copiedComponentCaches = targetObj.GetComponents<Component>();

    copiedComponentCaches = copiedComponentCaches.Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();
}

2.1.2 复制所有自定义Mono脚本组件


这里通过GetComponents()来获取对象上的所有的自定义脚本组件。

同时对于在忽略列表里面的组件类型不会进行复制。

 [MenuItem("GameObject/ComponentsCopy_Paste/Copy Mono Components", priority = 1)]
 static void CopyMonoComponents(MenuCommand menuCommand)
 {
     GameObject targetObj = menuCommand.context as GameObject;

     copiedComponentCaches = targetObj.GetComponents<MonoBehaviour>();

     copiedComponentCaches = copiedComponentCaches.Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();
 }

2.1.3 复制所有内建组件


这里通过GetComponents()来获取对象上的所有继承了Component的组件,并通过判断获取的到的组件是否继承自MonoBehaviour,如果不继承MonoBehaviour那么认为是内建组件。

同时对于在忽略列表里面的组件类型不会进行复制。

[MenuItem("GameObject/ComponentsCopy_Paste/Copy Built-in Components", priority = 2)]
static void CopyBuiltInComponents(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    copiedComponentCaches = targetObj.GetComponents<Component>();

    copiedComponentCaches = copiedComponentCaches.Where(item => !typeof(MonoBehaviour).IsAssignableFrom(item.GetType())).ToArray();

    copiedComponentCaches = copiedComponentCaches.Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();
}

2. 2、粘贴组件


粘贴功能分为多种模式,用户可以选择是否将复制的组件序列化粘贴,或者选择以附加模式粘贴(保留原有组件),或者选择覆盖模式(覆盖同类型组件)。

在粘贴时,我们会检查目标GameObject上是否已经存在相同类型的组件。如果不允许重复添加的组件已经存在,则不会粘贴该组件。

2.2.1 附加模式粘贴组件


以附加模式进行粘贴组件会在不改变对象所有的原有组件的情况下,对对象添加新的组件,在这种模式下提供非序列化和序列化两种方法。

当使用非序列化模式时,这个时候就只会添加组件并不会对组件复制,相当于使用AddComponent()方法添加组件。

当使用序列化模式时,这个时候不仅会添加组件同时还会把组件的值也复制过来,相当与组件右键菜单中的Copy Component 和Paste Component As New。

[MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Additional_NoSerialize", priority = 13)]
static void PasteComponents_Additional_NoSerialize(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    Component[] newAddComponents = PasteComponents_Additional(targetObj, false, out List<Component> ignoreComponents);

    StorePastedComponent(targetObj, newAddComponents);
}

[MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Additional_Serialize", priority = 14)]
static void PasteComponents_Additional_Serialize(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    Component[] newAddComponents = PasteComponents_Additional(targetObj, true, out List<Component> ignoreComponents);

    StorePastedComponent(targetObj, newAddComponents);
}
 
static Component[] PasteComponents_Additional(GameObject targetObj, bool isSerialize, out List<Component> ignoreComponents)
{
    List<Component> newAddComponents = new List<Component>();

    ignoreComponents = new List<Component>();

    for (int i = 0; i < copiedComponentCaches.Length; i++)
    {
        Component[] targetComs = targetObj.GetComponents(copiedComponentCaches[i].GetType());

        //对于新添加的组件,需要判断该组件是否允许多次添加,
        //如果允许直接添加,如果不允许,判断是否已经存在,如果存在,则不粘贴,如果不存在,则粘贴

        if (targetComs == null || !HasDisallowMultipleComponent(copiedComponentCaches[i].GetType()))
        {
            if (isSerialize)
            {
                ComponentUtility.CopyComponent(copiedComponentCaches[i]);

                ComponentUtility.PasteComponentAsNew(targetObj);
            }
            else
            {
                targetObj.AddComponent(copiedComponentCaches[i].GetType());
            }
        }
        else
        {
            ignoreComponents.Add(copiedComponentCaches[i]);
        }

        Component newAddCom = GetNewAddComponents(targetObj);

        if (!newAddComponents.Contains(newAddCom))
            newAddComponents.Add(newAddCom);
    }

    return newAddComponents.ToArray();
}

2.2.2 覆盖模式粘贴组件


以覆盖模式进行粘贴组件在粘贴组件时会改变对象的原有组件,在为对象粘贴组件时,在这种模式下提供非序列化和序列化两种方法。

当使用非序列化模式时,如果粘贴组件时原对象上已经有同类型的组件,那么不对该组件做任何处理,对于粘贴的组件在原对象上没有的或则同类型粘贴的组件比原对象多的都会添加为新脚本,因为是非序列化所以新家的脚本不进行赋值。

当使用序列化模式时,对于同类型的组件会对原有组件进行赋值,对于粘贴的组件在原对象上没有的或则同类型粘贴的组件比原对象多的都会添加为新脚本并且进行赋值。

[MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Override_NoSerialize", priority = 15)]
static void PasteComponents_Override_NoSerialize(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    Component[] newAddComponents = PasteComponents_Override(targetObj, false, out List<Component> ignoreComponents);

    StorePastedComponent(targetObj, newAddComponents);
}

[MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Override_Serialize", priority = 16)]
static void PasteComponents_Override_Serialize(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    Component[] newAddComponents = PasteComponents_Override(targetObj, true, out List<Component> ignoreComponents);

    StorePastedComponent(targetObj, newAddComponents);
}

static Component[] PasteComponents_Override(GameObject targetObj, bool isSerialize, out List<Component> ignoreComponents)
{
    List<Component> targetComCaches = new List<Component>();

    List<Component> newAddComponents = new List<Component>();

    ignoreComponents = new List<Component>();

    for (int i = 0; i < copiedComponentCaches.Length; i++)
    {
        ComponentUtility.CopyComponent(copiedComponentCaches[i]);

        //刷新顺序依赖于GetComponents的顺序
        Component[] targetComs = targetObj.GetComponents(copiedComponentCaches[i].GetType());
        Component targetCom = null;

        //剔除已经操作过的组件
        if (targetComs != null)
            targetCom = targetComs.Where(item => !targetComCaches.Contains(item))?.FirstOrDefault();

        if (targetCom == null)
        {
            //对于新添加的组件,需要判断该组件是否允许多次添加,
            //如果允许直接添加,如果不允许,判断是否已经存在,如果存在,则不粘贴,如果不存在,则粘贴
            if (targetComs == null || !HasDisallowMultipleComponent(copiedComponentCaches[i].GetType()))
            {
                if (isSerialize)
                    ComponentUtility.PasteComponentAsNew(targetObj);  // 粘贴新的组件
                else
                    targetObj.AddComponent(copiedComponentCaches[i].GetType());
            }
            else
            {
                ignoreComponents.Add(copiedComponentCaches[i]);
            }

            targetCom = GetNewAddComponents(targetObj);

            if (!newAddComponents.Contains(targetCom))
                newAddComponents.Add(targetCom);
        }
        else
        {
            //对已经存在的脚本,不序列化时不做任何处理
            if (isSerialize)
                ComponentUtility.PasteComponentValues(targetCom);
        }

        targetComCaches.Add(targetCom);
    }

    return newAddComponents.ToArray();
}

2.3、删除组件


为防止粘贴过多组件后影响GameObject原有的结构,我们也提供了清除组件的功能。清除功能包括:

  • 清除粘贴的组件: 仅删除通过工具粘贴的组件。
  • 清除所有组件: 删除除忽略列表之外的所有组件。
  • 清除粘贴的组件: 删除Mono组件。
  • 清除所有组件: 删除内建组件。

清除粘贴的组件只能删除从粘贴而来的组件,在粘贴组件时会将对象和组件放入缓存中,删除时只能删除与缓存一样的组件,不可以影响到原有的组件。

在删除所有组件时需要注意,这里会忽略掉忽略列表中的组件。

在删除内建组件的时候注意,Transform组件是不允许删除的。

[MenuItem("GameObject/ComponentsCopy_Paste/Clear Pasted Components", priority = 28)]
static void ClearPastedComponents(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    if (pastedComponentCaches.ContainsKey(targetObj))
    {
        Component[] components = pastedComponentCaches[targetObj];

        foreach (var item in components)
        {
            Object.DestroyImmediate(item);
        }
    }
}

[MenuItem("GameObject/ComponentsCopy_Paste/Clear All Components", priority = 29)]
static void ClearAllComponents(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    var components = targetObj.GetComponents<Component>();

    components = components.Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();

    for (int i = 0; i < components.Length; i++)
    {
        Object.DestroyImmediate(components[i]);
    }
}

[MenuItem("GameObject/ComponentsCopy_Paste/Clear Mono Components", priority = 29)]
static void ClearMonoComponents(MenuCommand menuCommand)
{
    GameObject targetObj = menuCommand.context as GameObject;

    var components = targetObj.GetComponents<MonoBehaviour>();

    components = components.Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();

    for (int i = 0; i < components.Length; i++)
    {
        Object.DestroyImmediate(components[i]);
    }
}

[MenuItem("GameObject/ComponentsCopy_Paste/Clear Built-in Components", priority = 29)]
 static void ClearBuiltInComponents(MenuCommand menuCommand)
 {
     GameObject targetObj = menuCommand.context as GameObject;

     var components = targetObj.GetComponents<Component>();

     components = components.Where(item => !typeof(MonoBehaviour).IsAssignableFrom(item.GetType()) && item.GetType() != typeof(Transform)).ToArray();

     for (int i = 0; i < components.Length; i++)
     {
         Object.DestroyImmediate(components[i]);
     }
 }


三、项目地址


以下是项目地址,已经整理成了Package包,有需要的小伙伴门可以自取:

https://gitcode.com/CTLittleNewbie/com.fxb.copypastecomponents_v1.0.0/overview





TechX —— 心探索、心进取!

每一次跌倒都是一次成长

每一次努力都是一次进步


END
感谢您阅读本篇博客!希望这篇内容对您有所帮助。如果您有任何问题或意见,或者想要了解更多关于本主题的信息,欢迎在评论区留言与我交流。我会非常乐意与大家讨论和分享更多有趣的内容。
如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。
在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!

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

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

相关文章

低空经济——载具

低空经济的政策大家都知道&#xff0c;开放低空&#xff0c;是经济和社会发展的需要&#xff0c;但是步子肯定不会太大&#xff0c;先刺激资本进行投入&#xff0c;之后看看再说&#xff0c;一定是这样模式。 我们知道&#xff0c;新能源车&#xff0c;基本实现了弯道超车&…

基于springboot vue网上摄影工作室系统设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

jni动态库“%1 不是有效的win32应用程序”问题的解决以及一些windows下dll有关命令的记录

一、前因 在windows下用cmakeVS编译了一个jni动态库&#xff0c;再使用java测试程序调用这个动态库的时候报错&#xff1a;“%1 不是有效的win32应用程序” 对于这类问题&#xff0c;一般从以下几个方面考虑&#xff1a; 动态库文件损坏动态库或者其依赖库文件路径错误导致找…

高效职场助手

在现代职场中&#xff0c;高效的工作软件是提升生产力的关键。以下是我为您精选的五款高效工作软件&#xff0c;它们各具特色&#xff0c;能够满足不同工作场景的需求&#xff1a; 1 亿可达 亿可达作为一款自动化工具&#xff0c;亿可达被誉为国内版的免费Zaiper。它允许用…

一个Windows管道的简单示例

今天为大家带来一个Windows管道通信的演示实例,服务端以单线程、同步方式与客户端通信,在某一时刻只能服务于一个客户端,可用于简单的进程间通信的场景。 服务端(SingleInstancePipeSvr)和客户端(NamedPipeClient)都是用Visual Studio 2022 Community创建,分别…

抖音上下边框中间视频效果怎么做

在抖音这个以短视频为主要内容的平台上&#xff0c;创作出既美观又富有信息量的视频至关重要。尤其是当我们手中的素材多为横屏拍摄&#xff0c;而抖音则以竖屏展示为主时&#xff0c;如何巧妙地处理这些素材&#xff0c;使它们更好地适应平台需求&#xff0c;成为了许多创作者…

Django之Haystack对接搜索引擎框架Elasticsearch

Django之Haystack对接Elasticsearch Haystack概述安装依赖环境准备Haystack配置 Haystack建立数据索引创建模型对象创建搜索索引类创建模板文件执行数据库迁移生成索引渲染模板执行测试 搜索请求和结果渲染的自定义处理概述创建搜索视图配置URL创建搜索模板自定义结果渲染执行测…

电脑数据怎么恢复?这10种数据恢复方法你一定要知道!

在如今这个数字化时代&#xff0c;电脑已经成为我们生活和工作中不可或缺的一部分。我们在电脑中存储着大量重要的数据&#xff0c;如照片、文档、视频、音乐等。然而&#xff0c;有时候意外总是不可避免&#xff0c;电脑数据可能会因为各种原因丢失&#xff0c;比如误删除、格…

父母血型与子女血型对照表

人类的血型是有遗传性的&#xff0c;父母的血型直接影响孩子的血型&#xff0c;我们可以根据父母血型推测孩子血型&#xff0c;排除不可能血型&#xff0c;那么下面就由血型分析为大家揭晓下父母血型与孩子血型对照表&#xff08;排除基因突变&#xff09; 血型遗传规律表又称血…

IMS注册流程中的基本路由寻址过程

目录 1. SIP 协议栈在 TCP/IP 中的位置 2. 看看 SIP 协议栈各层的寻址功能 本课程,以 IMS 注册流程为例,讲解 IP 网络网元是如何完成 IP 层寻址的 第一跳:UE->P-CSCF 第二跳:P-CSCF->DNS 第三跳:P-CSCF->I-CSCF 第四跳:I-CSCF->HSS 第五跳:I-CSCF-&…

深入解析Debian与Ubuntu:技术特点与用户使用指南

深入解析Debian与Ubuntu&#xff1a;技术特点与用户使用指南 引言 Debian和Ubuntu作为两大知名的Linux发行版&#xff0c;不仅在历史和理念上有所不同&#xff0c;在技术特点和用户使用方法上也各具特色。本文将深入解析它们的技术特点&#xff0c;并提供用户使用指南&#x…

SSM+Vue大学生社团管理系统

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 spring-mybatis.xml3.5 spring-mvc.xml3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质创作…

东华大学《2020年+2022年824自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《25届东华大学824自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2020年真题 2022年真题 Part1&#xff1a;2020年2022年完整版真题 2020年真题 2022年…

惊艳桌面时钟软件 为你的桌面打造专属时间管理!

在快节奏的现代生活中&#xff0c;时间是最宝贵的资源之一。无论是在工作还是生活中&#xff0c;我们都需要时刻关注时间&#xff0c;在桌面显示一个时钟&#xff0c;可以让你更方便的掌握时间。芝麻时钟 &#xff08;下载地址&#xff1a;https://clock.zhimasoft.cn/?bili&a…

Jmeter——参数化的9种方法

jmeter工具无论做接口测试还是性能测试&#xff0c;参数化都是一个必须掌握且非常有用的知识点。参数化的使用场景: 1&#xff09;多个请求都是同一个ip地址&#xff0c;若服务器地址更换了&#xff0c;则脚本需要更改每个请求的ip 2&#xff09;注册账号&#xff0c;不允许账…

【图形学】数学基础

行矩阵和列矩阵 在图形处理中&#xff0c;矩阵操作时最基本的操作&#xff0c;但是不同的系统中对矩阵的存储是不同&#xff0c;比如OpenGL和Cg中就是不一样的&#xff1a; 比如一个4x4矩阵 [m11,m12,m13,m14m21,m22,m23,m24m31,m32,m33,m34m41,m42,m43,m44]在实现中通常将其…

海康威视网络摄像头校时、修改分辨率等操作汇总!

目录 操作环境&#xff1a; 操作原因&#xff1a; 解决办法&#xff1a; 手动校准时间&#xff0c;随着时间推移&#xff0c;可能再次出现偏差&#xff0c;操作简单&#xff1a; 【IVMS-4200】对设备批量校时操作指导&#xff1a;操作之前先把本地服务器的时间改对&#x…

【学习笔记】Kylin-Desktop-V10-SP1 麒麟系统知识2——账户设置

提示&#xff1a;学习麒麟Kylin-Desktop-V10-SP1系统账户设置相关知识&#xff0c;包含用户名修改、用户密码修改、账户类型修改、开机自动登录、免密登录、添加用户、删除用户&#xff0c;以及修改登录选项相关知识 一、前期准备 成功安装麒麟系统&#xff08;本次实验的安装…

视频压缩成文件怎么弄?4招教会你

视频文件过大&#xff0c;不仅占用存储空间&#xff0c;还影响传输效率。但是&#xff0c;压缩视频时又担心会损失画质。有没有一种方法&#xff0c;能在不牺牲画质的前提下&#xff0c;实现视频文件的压缩呢&#xff1f;答案是肯定的。 想快速学会怎么视频压缩不改变清晰度&a…

手机二要素接口如何用C#实现调用

一、什么是手机二要素&#xff1f; 手机二要素又称运营商二要素&#xff0c;运营商二要素核验&#xff0c;实名核验&#xff0c;手机号核验&#xff0c;手机二要素核验&#xff0c;即传入姓名、手机号码&#xff0c;校验此两项是否一致。实时核验&#xff0c;返回校验结果&…