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
相关产品推荐
相关产品推荐

