You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity运行时如何复制含抽象父类的TurretController子类全变量至GameObject

问题

我需要在Unity运行时将一个脚本的所有变量(public、protected、private)完整复制到另一个GameObject中。该脚本拥有抽象父类TurretController,子类包括Arc、Square、Single、Rows、Omni,代码中以抽象父类类型引用(因可能是任意子类)。

我的需求是实现飞船炮塔预览:飞船搭载多个炮塔脚本,在菜单中需将指定炮塔脚本复制到PreviewShip对象上展示。

我尝试了两种方法均失败:

  1. 尝试替换炮塔对象并保留指定脚本,但循环删除了所有脚本:
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不是同一个引用,导致循环里所有炮塔都会被判定为不等于原引用,全部被删除。

  1. 扩展方法仅复制了脚本的基础版本,而非运行时更新后的变量:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:43:13