Unity中如何让MovementPattern子类自动填充对应ScriptableObject子类数据?
太空侵略者项目中移动模式与数据类的优化实现方案
问题背景
我正在开发一款类似太空侵略者的项目,需要给敌人、子弹等对象分配移动模式。已创建MovementPattern基类,以及作为ScriptableObject的MovementPatternData基类,计划用子类存储对应移动模式的参数。
目前实现的SinusMovementPattern方式繁琐,需额外声明子类数据字段,且忽略了基类的MovementPatternData。尝试在字段声明处初始化子类数据对象时会报错,无法在编辑器模式下提前初始化并修改数据。
需求:创建MovementPattern的子类对象时,自动填充对应的MovementPatternData子类对象,替换原有的基类数据类型,需基于自定义编辑器、ScriptableObject和[SerializeReference]给出具体实现指导。
实现方案
一、重构数据类与基类
首先修正MovementPatternData的序列化逻辑,利用[SerializeReference]实现多态序列化支持:
1. 修正MovementPatternData基类与子类
// ScriptableObject自带序列化支持,无需[Serializable]标签 public class MovementPatternData : ScriptableObject { [SerializeField] protected float moveSpeed = 5f; public float MoveSpeed => moveSpeed; } // 添加菜单,方便在编辑器中直接创建数据资产 [CreateAssetMenu(fileName = "SinusMovementData", menuName = "Movement Patterns/Sinus")] public class SinusMovementPatternData : MovementPatternData { [SerializeField] public float amplitude = 1f; [SerializeField] public float frequency = 1f; [SerializeField] public Vector2 direction = Vector2.down; }
2. 重构MovementPattern基类
[RequireComponent(typeof(Rigidbody2D))] public class MovementPattern : MonoBehaviour { protected Rigidbody2D rb; // [SerializeReference]支持多态序列化,允许编辑器中直接赋值子类对象 [SerializeReference] [SerializeField] protected MovementPatternData movementPatternData; protected virtual void Awake() { rb = GetComponent<Rigidbody2D>(); // 编辑器模式下自动补全数据对象 #if UNITY_EDITOR if (movementPatternData == null) { AutoAssignData(); } #endif } // 子类重写此方法,指定对应的数据类型 protected virtual void AutoAssignData() { } public virtual void SetMovementPatternData(MovementPatternData newMovementPatternData) { movementPatternData = newMovementPatternData; } public virtual MovementPatternData GetMovementPatternData() { return movementPatternData; } }
二、实现子类的自动数据分配
以SinusMovementPattern为例,重写AutoAssignData方法,在编辑器模式下自动创建并赋值对应的数据资产:
public class SinusMovementPattern : MovementPattern { protected override void AutoAssignData() { #if UNITY_EDITOR // 创建对应的数据实例 SinusMovementPatternData data = ScriptableObject.CreateInstance<SinusMovementPatternData>(); data.name = $"SinusData_{gameObject.name}"; movementPatternData = data; // 将资产保存到项目目录,避免退出编辑器后丢失 UnityEditor.AssetDatabase.CreateAsset(data, $"Assets/MovementPatterns/{data.name}.asset"); UnityEditor.AssetDatabase.SaveAssets(); #endif } // 强类型Set方法,方便外部调用 public void SetMovementPatternData(SinusMovementPatternData newData) { base.SetMovementPatternData(newData); } // 实现正弦移动逻辑 protected void Update() { if (movementPatternData is SinusMovementPatternData sinusData) { Vector2 currentPos = rb.position; float offset = Mathf.Sin(Time.time * sinusData.frequency) * sinusData.amplitude; Vector2 newPos = currentPos + sinusData.direction * sinusData.MoveSpeed * Time.deltaTime; newPos.x += offset; // 横向正弦偏移,可按需调整 rb.MovePosition(newPos); } } }
三、自定义编辑器优化(可选)
创建自定义编辑器,提升组件添加时的操作体验:
using UnityEditor; using UnityEngine; [CustomEditor(typeof(MovementPattern), true)] public class MovementPatternEditor : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); MovementPattern pattern = (MovementPattern)target; if (pattern.GetMovementPatternData() == null) { if (GUILayout.Button("自动创建对应移动数据")) { pattern.AutoAssignData(); EditorUtility.SetDirty(pattern); } } } // 添加组件菜单,自动创建数据 [MenuItem("GameObject/Add Component/Movement Patterns/Sinus Movement")] public static void AddSinusMovement() { GameObject selected = Selection.activeGameObject; if (selected != null) { SinusMovementPattern pattern = selected.AddComponent<SinusMovementPattern>(); pattern.AutoAssignData(); EditorUtility.SetDirty(pattern); } } }
注意事项
[SerializeReference]需要Unity 2020.1及以上版本支持,请确保Unity版本符合要求。- 自动创建的ScriptableObject资产必须保存到项目目录,否则退出编辑器后会丢失,代码中已包含保存逻辑。
- 也可手动通过右键菜单创建
SinusMovementPatternData资产,再拖拽到组件的movementPatternData字段中使用。
内容的提问来源于stack exchange,提问作者Adam Wizard
相关产品推荐
相关产品推荐

