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

Unity C#:ScriptableObject派生类数组的多态调用实现咨询

解决方案

问题核心:C#的重载解析是编译时确定的——你的quizItems数组存的是基类QuizItem实例,编译器在编译RunTask(quizItems[itemNumber])时,只能匹配QuizItem类型的重载,但你并没有定义,因此报错。

下面提供三种可行的实现方式,按推荐程度排序:

1. 多态实现(推荐,符合OOP设计)

这是最适合Unity ScriptableObject场景的方案,让每个任务子类自己负责执行逻辑,无需在Manager里做类型判断。

步骤1:修改基类QuizItem

添加抽象的RunTask方法,让所有子类必须实现:

using UnityEngine;

public abstract class QuizItem : ScriptableObject
{
    // 抽象方法,子类必须重写
    public abstract void RunTask();
}

步骤2:实现子类逻辑

在每个任务子类中重写RunTask方法:

// 开放式任务子类
[CreateAssetMenu(fileName = "新开放式任务", menuName = "问答游戏/开放式任务")]
public class OpenItem : QuizItem
{
    public override void RunTask()
    {
        Debug.Log("执行开放式任务逻辑");
    }
}

// 四选一任务子类
[CreateAssetMenu(fileName = "新四选一任务", menuName = "问答游戏/四选一任务")]
public class OneFourItem : QuizItem
{
    public override void RunTask()
    {
        Debug.Log("执行四选一任务逻辑");
    }
}

步骤3:简化QuizManager

直接调用基类的RunTask方法,多态会自动执行子类的实现:

using UnityEngine;

public class QuizManager : MonoBehaviour
{
    [SerializeField] QuizItem[] quizItems;
    int itemNumber = 0;

    void GetTask()
    {
        if (itemNumber >= quizItems.Length) return;
        
        // 直接调用,自动匹配子类逻辑
        quizItems[itemNumber].RunTask();
        itemNumber++;
    }
}

优点:符合开闭原则,新增任务类型只需加子类,无需修改Manager代码;逻辑清晰,任务逻辑封装在各自的ScriptableObject中。

2. 类型判断(快速临时方案)

如果不想修改基类,可以通过运行时类型判断,手动转换后调用对应重载:

using UnityEngine;

public class QuizManager : MonoBehaviour
{
    [SerializeField] QuizItem[] quizItems;
    int itemNumber = 0;

    void GetTask()
    {
        if (itemNumber >= quizItems.Length) return;
        
        var currentItem = quizItems[itemNumber];
        // 模式匹配转换类型
        if (currentItem is OpenItem openItem)
        {
            RunTask(openItem);
        }
        else if (currentItem is OneFourItem oneFourItem)
        {
            RunTask(oneFourItem);
        }
        else
        {
            Debug.LogWarning($"未处理的任务类型:{currentItem.GetType()}");
        }
        
        itemNumber++;
    }

    void RunTask(OpenItem item)
    {
        Debug.Log("open");
    }

    void RunTask(OneFourItem item)
    {
        Debug.Log("one four");
    }
}

优点:无需修改现有基类和子类;缺点:新增任务类型必须修改GetTask的判断逻辑,扩展性差,代码冗余。

3. 访问者模式(复杂场景适配)

如果业务逻辑需要集中在QuizManager中处理,而非分散在子类,可以用访问者模式:

步骤1:定义访问者接口和基类Accept方法

using UnityEngine;

public abstract class QuizItem : ScriptableObject
{
    public abstract void Accept(IQuizTaskVisitor visitor);
}

// 访问者接口,对应每个任务类型的处理方法
public interface IQuizTaskVisitor
{
    void Visit(OpenItem openItem);
    void Visit(OneFourItem oneFourItem);
}

步骤2:子类实现Accept方法

[CreateAssetMenu(fileName = "新开放式任务", menuName = "问答游戏/开放式任务")]
public class OpenItem : QuizItem
{
    public override void Accept(IQuizTaskVisitor visitor)
    {
        visitor.Visit(this);
    }
}

[CreateAssetMenu(fileName = "新四选一任务", menuName = "问答游戏/四选一任务")]
public class OneFourItem : QuizItem
{
    public override void Accept(IQuizTaskVisitor visitor)
    {
        visitor.Visit(this);
    }
}

步骤3:QuizManager实现访问者接口

using UnityEngine;

public class QuizManager : MonoBehaviour, IQuizTaskVisitor
{
    [SerializeField] QuizItem[] quizItems;
    int itemNumber = 0;

    void GetTask()
    {
        if (itemNumber >= quizItems.Length) return;
        
        quizItems[itemNumber].Accept(this);
        itemNumber++;
    }

    public void Visit(OpenItem openItem)
    {
        Debug.Log("open");
    }

    public void Visit(OneFourItem oneFourItem)
    {
        Debug.Log("one four");
    }
}

优点:业务逻辑集中在Manager,适合复杂交互场景;缺点:新增任务类型需要修改访问者接口和Manager实现,扩展性一般。

内容的提问来源于stack exchange,提问作者PhiCato

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 12:15:13