Unity运行时如何复制含抽象父类的TurretController子类全变量至GameObject
问题
我需要在Unity运行时将一个脚本的所有变量(public、protected、private)完整复制到另一个GameObject中。该脚本拥有抽象父类TurretController,子类包括Arc、Square、Single、Rows、Omni,代码中以抽象父类类型引用(因可能是任意子类)。
我的需求是实现飞船炮塔预览:飞船搭载多个炮塔脚本,在菜单中需将指定炮塔脚本复制到PreviewShip对象上展示。
我尝试了两种方法均失败:
- 尝试替换炮塔对象并保留指定脚本,但循环删除了所有脚本:
public void CopyTurret(GameObject turrets, TurretController turretToKeep) { // REPLACE the current turrets with an instantiated copy of the turrets argument Destroy(transform.Find("Turrets").gameObject); Instantiate(original: turrets, parent: transform); /* go through all the turrets, and remove the ones that arent the turret name to only get the specific turret on the ship */ foreach (TurretController turret in GetComponentsInChildren<TurretController>()) if (turret != turretToKeep) Destroy(turret); }
问题原因:Instantiate生成的是全新对象,里面的TurretController实例和原turretToKeep不是同一个引用,导致循环里所有炮塔都会被判定为不等于原引用,全部被删除。
- 扩展方法仅复制了脚本的基础版本,而非运行时更新后的变量:
public static void CopyComponent<T>(this Component comp, GameObject destination, bool copyVariables) where T : Component { destination.AddComponent(typeof(T)); }
问题原因:这段代码只是添加了一个新组件,完全没有复制任何变量值。
可行解决方案
方法一:用反射复制所有字段(支持所有访问修饰符)
通过反射遍历原组件的所有字段(包括私有、保护、继承字段),将运行时的值复制到新组件中:
using System.Reflection; using UnityEngine; public static class ComponentCopyExtensions { public static T CopyComponent<T>(this T original, GameObject destination) where T : Component { // 获取组件实际类型(处理子类实例) System.Type type = original.GetType(); // 给目标对象添加同类型组件 T copy = destination.AddComponent(type) as T; // 获取所有字段:公开/私有/保护/实例/继承的字段 FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); foreach (FieldInfo field in fields) { // 跳过Unity内部非序列化的特殊字段(比如m_CachedPtr这类) if (field.IsNotSerialized && !field.Name.StartsWith("m_")) continue; object value = field.GetValue(original); field.SetValue(copy, value); } return copy; } }
使用示例:
// 获取原飞船上选中的炮塔实例 TurretController originalTurret = ...; // 获取预览飞船对象 GameObject previewShip = GameObject.Find("PreviewShip"); // 先清理预览飞船上的旧炮塔 foreach (TurretController oldTurret in previewShip.GetComponentsInChildren<TurretController>()) { Destroy(oldTurret.gameObject); // 直接删除整个炮塔对象更彻底 } // 创建预览炮塔的载体对象,挂载到预览飞船的炮塔父节点 GameObject newTurretObj = new GameObject("PreviewTurret"); newTurretObj.transform.SetParent(previewShip.transform.Find("Turrets")); // 复制组件及所有变量 originalTurret.CopyComponent(newTurretObj);
方法二:直接实例化目标炮塔对象(更高效)
如果原炮塔是独立的GameObject(或预制体),直接实例化整个对象到预览飞船上,能完整保留所有运行时变量,比复制组件更高效:
public void UpdateTurretPreview(TurretController selectedTurret) { // 清理预览飞船上的旧炮塔 Transform turretsParent = GameObject.Find("PreviewShip").transform.Find("Turrets"); foreach (Transform child in turretsParent) { Destroy(child.gameObject); } // 实例化选中的炮塔对象,挂载到预览父节点 GameObject previewTurret = Instantiate(selectedTurret.gameObject, turretsParent); previewTurret.name = "PreviewTurret"; }
注:哪怕原炮塔是飞船上的子物体,Instantiate也会复制整个子物体及其所有组件、运行时变量,完美适配预览需求。
修复你第一种方法的问题
如果坚持用替换炮塔父物体的方式,把引用比较改成类型/名称匹配即可:
public void CopyTurret(GameObject turretsPrefab, System.Type turretTypeToKeep) { Destroy(transform.Find("Turrets").gameObject); GameObject newTurrets = Instantiate(turretsPrefab, transform); foreach (TurretController turret in newTurrets.GetComponentsInChildren<TurretController>()) { // 通过类型匹配保留目标炮塔,也可以换成名称匹配 if (turret.GetType() != turretTypeToKeep) { Destroy(turret.gameObject); } } }
使用时传入选中炮塔的类型:CopyTurret(turretsPrefab, selectedTurret.GetType());
内容的提问来源于stack exchange,提问作者Sep
相关产品推荐
相关产品推荐

