Unity中多控制对象InputActionMap共性重构方案咨询
这个问题我在Unity项目里踩过坑——泛型基类的继承加上重复的输入绑定代码确实让人头大。咱们可以从几个方向重构,既解决代码重复,又能让子类正常调用派生类的方法:
方案一:泛型子类的分层继承
把RunnerControls改成带约束的泛型类,让它能适配Runner及其子类,这样JumperControls继承它时就能保留Jumper的类型,同时复用Run动作的绑定逻辑。
首先修复原代码里的一个小问题:perform事件需要绑定的是Action<InputAction.CallbackContext>委托,直接写controlled.Run()会立即执行方法,而不是绑定回调,得用lambda表达式包裹。
修改后的代码如下:
// 基类保持不变 public abstract class Controls<T> { protected T controlled; public Controls(InputActionMap actionMap, T controlled) { SetupInputCallbacks(actionMap); this.controlled = controlled; } protected abstract void SetupInputCallbacks(InputActionMap actionMap); } // 改成泛型子类,约束T必须是Runner或其子类 public class RunnerControls<T> : Controls<T> where T : Runner { public RunnerControls(InputActionMap actionMap, T controlled) : base(actionMap, controlled) { } protected override void SetupInputCallbacks(InputActionMap actionMap) { InputAction runAction = actionMap.FindAction("Run"); // 用lambda绑定回调,传递CallbackContext参数 runAction.perform += (ctx) => controlled.Run(); } } // JumperControls继承泛型的RunnerControls,指定T为Jumper public class JumperControls : RunnerControls<Jumper> { public JumperControls(InputActionMap actionMap, Jumper controlled) : base(actionMap, controlled) { } protected override void SetupInputCallbacks(InputActionMap actionMap) { // 先调用父类方法绑定Run动作,避免重复代码 base.SetupInputCallbacks(actionMap); // 再绑定Jump动作 InputAction jumpAction = actionMap.FindAction("Jump"); jumpAction.perform += (ctx) => controlled.Jump(); } }
这个方案完美贴合你原有的继承架构,既复用了Run的绑定逻辑,又让controlled保留了Jumper的类型,能正常调用Jump()。
方案二:提取通用绑定逻辑到工具类(组合模式)
如果不想用复杂的泛型继承,可以把通用的输入绑定逻辑抽成静态工具类,通过组合而非继承来复用代码,更简洁灵活。
// 通用输入绑定工具类 public static class InputBindingUtils { public static void BindRunAction(InputActionMap actionMap, Runner runner) { InputAction runAction = actionMap.FindAction("Run"); runAction.perform += (ctx) => runner.Run(); } } // RunnerControls直接调用工具类方法 public class RunnerControls : Controls<Runner> { public RunnerControls(InputActionMap actionMap, Runner controlled) : base(actionMap, controlled) { } protected override void SetupInputCallbacks(InputActionMap actionMap) { InputBindingUtils.BindRunAction(actionMap, controlled); } } // JumperControls同样复用工具类的Run绑定,再添加自己的Jump绑定 public class JumperControls : Controls<Jumper> { public JumperControls(InputActionMap actionMap, Jumper controlled) : base(actionMap, controlled) { } protected override void SetupInputCallbacks(InputActionMap actionMap) { InputBindingUtils.BindRunAction(actionMap, controlled); InputAction jumpAction = actionMap.FindAction("Jump"); jumpAction.perform += (ctx) => controlled.Jump(); } }
这种方式把通用逻辑集中到工具类,后续修改Run动作的绑定逻辑时,只需要改工具类即可,不用修改多个Controls子类。
方案三:基于接口的契约绑定(扩展性最强)
定义接口描述对象的动作能力,让基类自动根据接口绑定对应的动作,彻底消除重复代码,后续新增动作或对象类型时扩展性极强。
// 定义动作接口 public interface ICanRun { void Run(); } public interface ICanJump { void Jump(); } // 让对象实现对应接口 public class Runner : MonoBehaviour, ICanRun { public void Run() { /* ... */ } } public class Jumper : Runner, ICanJump { public void Jump() { /* ... */ } } // 修改基类,自动根据接口绑定动作 public abstract class Controls<T> { protected T controlled; public Controls(InputActionMap actionMap, T controlled) { this.controlled = controlled; SetupInputCallbacks(actionMap); } protected virtual void SetupInputCallbacks(InputActionMap actionMap) { // 如果对象实现了ICanRun,绑定Run动作 if (controlled is ICanRun runner) { InputAction runAction = actionMap.FindAction("Run"); runAction.perform += (ctx) => runner.Run(); } // 如果对象实现了ICanJump,绑定Jump动作 if (controlled is ICanJump jumper) { InputAction jumpAction = actionMap.FindAction("Jump"); jumpAction.perform += (ctx) => jumper.Jump(); } } } // 子类可以简化成空类(如果不需要额外逻辑) public class RunnerControls : Controls<Runner> { public RunnerControls(InputActionMap actionMap, Runner controlled) : base(actionMap, controlled) { } } public class JumperControls : Controls<Jumper> { public JumperControls(InputActionMap actionMap, Jumper controlled) : base(actionMap, controlled) { } }
这个方案的优势在于:后续新增Swimmer类实现ICanSwim,只需要在基类里加一段ICanSwim的检查逻辑,所有SwimmerControls都会自动绑定Swim动作,完全不需要重复编写绑定代码。
内容的提问来源于stack exchange,提问作者user17646270
相关产品推荐
相关产品推荐

