【问题标题】:Allow the user to arrange methods允许用户安排方法
【发布时间】:2012-09-27 23:53:05
【问题描述】:

假设我有一个Player 课程。它有一个接收方向参数的Move 方法、一个接收两个整数的ChangePosition 方法、一个Disappear void 方法等。

我希望允许用户(可能使用列表框)能够将其中一些方法添加到 Player 对象的行为中。例如,他们可以选择让它向右移动,然后向下移动,然后消失。然后将他们选择的方法添加到事件中,以便当该事件发生时,他们选择的方法将执行。

以前有人做过类似的事情吗?做这个的最好方式是什么? (我今年 13 岁,对设计模式了解不多)

【问题讨论】:

    标签: c# winforms events delegates


    【解决方案1】:

    首先,您应该将所有游戏逻辑放入一个 Engine 项目中,使用一个 Engine 类,该类单独负责(处理所有游戏逻辑并与 UI 和框架 [以及更低层,如果有是任何])。

    其次,您可以拥有一个EngineAction 类,该类具有枚举EngineActionType(Move、ChangePos、Disappear、...)和其他属性。大致思路是这样的:

    有一个枚举来保存类型:

    public enum EngineActionType
    {
        Move,
        ChangePos,
        Disappear,
        ...
    }
    

    创建一个抽象类,对所有引擎动作类进行逻辑分组,并使用公共类型属性添加它们:

    public abstract class EngineAction
    {
        public readonly EngineActionType Type;
    
        protected EngineAction(EngineActionType type)
        {
            this.Type = type;
        }
    }
    

    为每个引擎操作创建一个类,将所有需要的参数作为属性保存。它应该派生自 EngineAction 并将适当的 EngineActionType 发送到基本构造函数:

    public class EngineActionMove : EngineAction
    {
        public int XDelta;
        public int YDelta;
    
        public EngineActionMove() : base(EngineActionType.Move)
        {
        }
    }
    
    public class EngineActionChangePos : EngineAction
    {
        ...
    }
    
    ...
    

    毕竟,您可以将这些对象放在一个列表中,按照用户的命令,并对其进行迭代,根据它们的类型一个一个地执行它们:

    foreach (EngineAction action in actionsToDo)
    {
        switch (action.Type)
        {
            case EngineActionType.Move:
                EngineActionMove mvAction = (EngineActionMove)action;
    
                // Example:
                player.Position = player.Position.Offset(mvAction.XDelta,
                                                         mvAction.YDelta);
                break;
            case EngineActionType.ChangePos:
                EngineActionChangePos cpAction = (EngineActionChangePos)action;
    
                // Example:
                player.Position = new Point(cpAction.X, cpAction.Y);
                break;
            case EngineActionType.Disappear:
                // Just make the player disappear,
                // because the operation needs no parameters
                player.Disappear();
                break;
            case ...:
            default:
                // maybe throw an error here, to find errors during development
                // it helps you find non-handled action types
        }
    }
    

    进一步提示:UI 中的逻辑越少越好。总是。

    编辑:Charleh 的好主意:

    public interface IEngineAction
    {
        void Do(Player target);
    }
    
    public class EngineActionMove : IEngineAction
    {
        public int XDelta;
        public int XDelta;
    
        public void Do(Player target)
        {
            // Example
            target.Position = ...
        }
    }
    
    public class EngineActionDisappear : IEngineAction
    {
        public void Do(Player target)
        {
            // Example
            target.Visible = false;
        }
    }
    
    ...
    

    添加到列表示例:

    var actionsToDo = new List<IEngineAction>();
    
    actionsToDo.Add(new EngineActionDisappear());
    actionsToDo.Add(new EngineActionMove { XDelta = 10, YDelta = 20 });
    

    和迭代:

    foreach (IEngineAction action in actionsToDo)
    {
        action.Do(player);
    }
    

    【讨论】:

    • 好消息,虽然我可能会将每个动作创建为一个单独的类,该类知道如何对所有实现接口的目标对象进行操作。通过这种方式,您可以在列表中对动作进行排队,而无需在主要参与者的“动作”正文中编写案例语句。这也意味着您可以使用组合而不是从公共基础对象继承,并避免需要拥有一个超类
    • @Charleh 非常好的建议。确实,最好让他们实现一个 IEngineAction 接口。好点子!我会把它添加到答案中!
    • 谢谢!我以为我将不得不使用委托或匿名方法以及我不太了解的东西,但你的回答帮助我意识到接口的力量。
    • 很高兴你喜欢这个想法,最近一直在使用 Caliburn.Micro 并开始意识到简单的接口对组合有多么强大 - 实现一个接口而不是让这个庞大的复杂接口更有意义继承链,其中链底部的微小变化可能意味着一切都中断 - 添加一点功能是一件细致而细节繁重的事情......!我做过一些游戏引擎,我发现组合是保持功能简单的更简单方法
    【解决方案2】:

    有很多方法可以做到这一点。一种方法是使用工厂模式和策略模式。

    public static class PlayerBuilder
    {
        public static Player BuildPlayer(BuildDefinition definition)
        {
            //logic in here to build a player -- pseudo-return value below as an example
            var strategy = new PlayerMovementStrategy();
            return new Player(strategy);
        }
    }
    

    您的BuildDefinition 类可以根据您的用户选择构建。听起来他们不能只添加任何东西,而是从预定义选项列表中进行选择。根据您的 UI 选择创建适当的策略。

    abstract class MovementStrategy
    {
        public abstract void Move();
        public abstract void ChangePosition(Direction d);    
    }
    class PlayerMovementStrategy
    {
        public override void Move()
        {
            //move
        }
        public override void ChangePosition(Direction d)
        {
            //change position
        }
    }
    abstract class VisibilityStrategy
    {
        public abstract void Disappear();
    }
    class PlayerVisibilityStrategy
    {
        public void Disappear()
        {
            //poof 
        }
    }
    
    class Player
    {
         private readonly PlayerMovementStrategy movement;
         private readonly PlayerVisibilityStrategy visibility;
    
         public Player(PlayerMovementStrategy movement, PlayerVisibilityStrategy visibility)
         {
             this.movement = movement;
             this.visibility = visibility;
         }
    
         public void Disappear()
         {
             visibility.Disappear();
         }         
         public void Move(Direction d)
         {
             movement.Move(d);
         }
    }
    

    【讨论】:

      【解决方案3】:

      另一种选择是使用 Action 泛型,只需添加您想要的方法,然后您就可以在任何特定的播放器对象上调用这些操作。

      这只是对别人提出的界面设计思路的一种替代,但你的风格可能更喜欢这种方法。

      Action<Player> playerActions = p => {}; //Do Nothing
      playerActions += p => { p.Move(3); }; //Move
      playerActions += p => { p.ChangePosition(1, 1); }; //Change position
      playerActions += p => { p.Disappear(); }; //Disappear
      
      //Invoke Actions
      Player player = new Player();
      playerActions(player);
      
      //Invoke on another object as well
      Player player2 = new Player();
      playerActions(player2);
      

      【讨论】:

      • 更简单...我也喜欢这个主意。
      • 您能告诉我如何将操作列表添加到事件中吗?
      猜你喜欢
      • 2017-08-28
      • 2013-01-06
      • 2014-03-26
      • 2014-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-23
      相关资源
      最近更新 更多