🗃️

【Unity C#】スクリプトでコンポーネントをコピーする

2022/09/15に公開

はじめに

組み込みコンポーネントの設定を実行中にコピーする必要があったので実装してみました。
publicなフィールド、プロパティを読み取り上書きします。
大体のケースではPrefabなどで十分だと思うので、どうしても必要な場合のみ使ってください。

結論

using System;
using System.Reflection;
using UnityEngine;

public static class ComponentUtility
{
    public static T CopyFrom<T>(this T self, T other) where T : Component
    {
        Type type = typeof(T);

        FieldInfo[] fields = type.GetFields();
        foreach (var field in fields)
        {
            if (field.IsStatic) continue;
            field.SetValue(self, field.GetValue(other));
        }

        PropertyInfo[] props = type.GetProperties();
        foreach (var prop in props)
        {
            if (!prop.CanWrite || !prop.CanRead || prop.Name == "name") continue;
            prop.SetValue(self, prop.GetValue(other));
        }

        return self as T;
    }
    public static T AddComponent<T>(this GameObject self, T other) where T : Component
    {
        return self.AddComponent<T>().CopyFrom(other);
    }
}

拡張メソッドを使っているのでgameObject.AddComponent(component)の形で使えるようになっています。
hoge.CopyFrom(fuga)のようにコンポーネントの値を上書きすることもできます。

内容

publicなフィールドとプロパティを取得して代入しています。
厳密なコピーではないですが、大体のケースではこれで十分だと思います。

備考:GetFields(BindingFlags)

BindingFlagsを指定することで、NonPublicなフィールド(protected/internalのみ?)も取得・設定できるようです。
安全性的にあまりよろしくないのではないかと思い組み込みませんでしたが、もしやりたければ

type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)

のようにフィールドを取得する部分を書き換えてください。
https://docs.microsoft.com/ja-jp/dotnet/api/system.type.getfields?view=net-6.0#system-type-getfields(system-reflection-bindingflags)

また、こちらの例では基底クラスのフィールド(?)も取得するようになっているようです。
ただ、変更してはいけないものも上書きできてしまうようで、私が試した限りではクラッシュしてしまいました。何か解決方法があれば教えてください。
https://gist.github.com/mattatz/1c039dce5ef251dcef7b628c878bea32

参考リンク

https://answers.unity.com/questions/458207/copy-a-component-at-runtime.html
https://smdn.jp/programming/dotnet-samplecodes/reflection/3a87684e024111eb907175842ffbe222/

Discussion