【Unity】Reflectionを使って、クラス(コンポーネント)の変数を全てコピーする

UnityではインスペクターでPaste Component As Valueを使えばコンポーネントの値をコピーすることが出来ますが、大量にコンポーネントがあると一つ一つコピーするのは大変です。
また、この変数はコピーしたいけど、この変数はコピーしたくない、といった柔軟な対応も出来ません。

スクリプトから、あるコンポーネントの変数を、別のゲームオブジェクトのコンポーネントに丸ごとコピーする方法が分かったのでメモしておきます。

●コピー対象のクラス(enemy)

using UnityEngine;

public class Enemy : MonoBehaviour
{

   public string subName;

   public int level;
   public float attack;
   public float diffence;
   public float speed;
    
   [SerializeField]
   private string privateString;
   
}

●enemyをコピーするためのクラス
コピーしたくない変数はfield.Nameで場合分けしています。

using UnityEngine;
using System.Reflection;

public class CopyEnemy : MonoBehaviour
{

   public Enemy copySource;

   public Enemy[] pasteTarget;

   [ContextMenu("Copy All")]
   public void CopyAll()
   {
       foreach (Enemy enemy in pasteTarget)
       {
           System.Type type = enemy.GetType();

           FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
           foreach (FieldInfo field in fields)
           {
               //コピーしたくない項目はここに書いておく
               if (field.Name == "subName") continue;

               field.SetValue(enemy, field.GetValue(copySource));
           }
       }

   }

}

コメント 2020-08-24 142038

CopyAll()を実行することで、copySourceに設定したenemyAの変数を、pasteTargetに設定したenemyB、enemyCにコピーします。
[ContextMenu("Copy All")]を書いているので、インスペクターの右上にあるメニューからCopy Allを選べばCopyAll()を実行できます。

通常はprivateな変数は他のクラスからは取得できませんが、以下のように(BindingFlags.NonPublic | BindingFlags.Instance)をつけることで、privateな変数も取得できるようです。

    FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);


一体の敵オブジェクトに設定したステータスを、大量の敵オブジェクトにコピーするときなどにこの方法は使えると思います。

本来はプレハブを活用するのが正しい方法なんでしょうけど、場合によってはこの方法が便利なこともあるかと…

応用として、以下のようにsaveとloadの仕組みを作れば、プレハブを使わずにコンポーネントの変数を外部ファイルとして管理できます。
privateな変数も[SerializeField]をつけておけば保存可能です。

using UnityEngine;
using System.IO;
using System.Reflection;

public class CopyEnemy : MonoBehaviour
{

   public Enemy copySource;

   public Enemy[] pasteTarget;
   
   [ContextMenu("Copy All")]
   public void CopyAll()
   {
       foreach (Enemy enemy in pasteTarget)
       {
           System.Type type = enemy.GetType();

           FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
           foreach (FieldInfo field in fields)
           {
               //コピーしたくない項目はここに書いておく
               if (field.Name == "subName") continue;

               field.SetValue(enemy, field.GetValue(copySource));
           }
       }

   }
   

   [ContextMenu("save all")]
   public void SaveAll()
   {
       foreach (Enemy enemy in pasteTarget)
       {
           string IDname = enemy.gameObject.name;

           string json = JsonUtility.ToJson(enemy);
           File.WriteAllText(IDname, json);
       }

   }


   [ContextMenu("load all")]
   public void LoadAll()
   {
       foreach (Enemy enemy in pasteTarget)
       {
           string IDname = enemy.gameObject.name;

           string json = File.ReadAllText(IDname);
           JsonUtility.FromJsonOverwrite(json, enemy);
       }

   }

}

いいなと思ったら応援しよう!