游戏逻辑可动态扩展架构实现模式

时间:2014-05-26 13:49:47

标签: c# design-patterns mvvm

在编写游戏时,有很多情况需要动态地将逻辑注入到现有类中,而不会产生不必要的依赖。

举个例子,我有一只可能受冻结能力影响的兔子因此不能跳跃。 它可以像这样实现:

class Rabbit
{
    public bool CanJump { get; set; }

    void Jump()
    {
        if (!CanJump) return;
        ...
    }
}

但是如果我有多种能力可以阻止它跳跃?我不能只设置一个属性,因为某些情况可以同时激活。

另一种解决方案?

class Rabbit
{
    public bool Frozen { get; set; }
    public bool InWater { get; set; }
    bool CanJump { get { return !Frozen && !InWater; } }
}

坏。 Rabbit类无法知道它可能遇到的所有情况。谁知道游戏设计师还想要添加什么呢?可能是一种能够改变区域重力的能力吗?

可以为CanJump属性制作一堆bool值吗?不,因为能力可以不按激活的顺序停用。

我需要一种分离能力逻辑的方法,以阻止兔子从兔子身上跳下来。

一个可能的解决方案是进行特殊检查事件:

class Rabbit
{
    class CheckJumpEventArgs : EventArgs
    {
        public bool Veto { get; set; }
    }

    public event EventHandler<CheckJumpEvent> OnCheckJump;

    void Jump()
    {
        var args = new CheckJumpEventArgs();
        if (OnCheckJump != null) OnCheckJump(this, args);
        if (!args.Veto) return;
        ...
    }
}

但这是很多代码!真正的Rabbit类会有很多这样的属性(运行状况和速度属性等)。

我正在考虑从MVVM模式中借用一些东西,在这种模式中,您可以通过一种可以从外部轻松扩展的对象的所有属性和方法来实现。然后我想这样用它:

class FreezeAbility
{
   void ActivateAbility() 
   {
       _rabbit.CanJump.Push(ReturnFalse);
   }

   void DeactivateAbility() 
   {
       _rabbit.CanJump.Remove(ReturnFalse);
   }

   // should be implemented as instance member
   // so it can be "unsubscribed"
   bool ReturnFalse(bool previousValue)
   {
       return false;
   }
}

这种做法好吗?我还应该考虑什么?什么是其他合适的选项和模式?任何现成的解决方案?


更新

问题不在于如何动态地向对象添加不同的行为,而是如何使用外部逻辑扩展其(或其行为)实现。我不需要添加不同的行为,但我需要一种方法来修改退出行为,我还需要一种可能性来撤消更改。

5 个答案:

答案 0 :(得分:0)

这听起来像是规范模式(http://en.wikipedia.org/wiki/Specification_pattern

的情况

基本上你可能会实现一个CanJumpSpecification,可以让兔子满意。

这样,决定你的兔子是否可以跳跃的业务逻辑被封装在其规范中,从而将其从兔子中移除。

规格在多态情况下也可高度重复使用。假设你不仅有一只兔子,还有一只鸭子,它们在跳跃时可能都受到同样的规则的影响。

您可以通过实施复合规范(也在维基百科文章中描述)进一步将规范链接到更复杂的规范。

答案 1 :(得分:0)

明显的方式

public interface ICanJump
{
    public void Jump();
}
public Rabbit: Animal, ICanJump
{
    public void Jump() { ... }
}

Animal animal = new Rabbit();
if(animal is ICanJump)
    animal.Jump();

行为

abstract class Animal
{
    abstract int Y { get; set; }
}
public class JumpingAnimal
{
    Animal _instance;
    public JumpingAnimal(Animal animal) { _instance = animal; }

    public void Jump()
    {
        _instance.Y += 10;
    }
}

Animal animal = new Rabbit();
var jump = new JumpingAnimal(animal);
jump.Jump();

关键是你可以将行为与动物分开存储(他们不必实现它,但是在摘要/基类中应该有一些东西可以使这种情况发生)。< / p>

清除?

答案 2 :(得分:0)

编辑
抱歉误读了这个问题。我对目标的新理解。

我们有一种行为(比如跳跃)的动物我们希望根据动物的状态(比如说,冷冻)或环境的状态(比如,重力或温度)进行修改,

我们不想继续扩展动物,但希望能够配置它。

我有部分解决方案来扩展功能。也许它就足够了,或者可以作为起点。

我们需要两个新概念

IEnvironment
并非完全需要,你提到重力的变化几乎是一边,但很简单,包括

IStateful / IState
我们需要一些标准来测试。我们可以将动物定义为包含一系列我们可以添加或删除的状态,而不是添加到界面中。

public interface IEnvironment {
    double Gravity { get; set; }
}   

public interface IState {
    string Name { get; }
    bool Is(object target);
}

public interface IStateful {
    void SetState(string  name);
    void SetState(string name, Func<object, bool> test);
    void ClearState(string name);
    bool IsInState(string name);
}

我们仍然拥有(大部分)相同的 WithBehaviours 类,我们只是添加状态并将配置外部化

public abstract class WithBehaviors : IStateful {

    private readonly List<IState> _states;
    private readonly List<Behavior> _behaviors;
    private readonly IEnvironment _environment;

    protected WithBehaviors(IEnvironment environment) {
        _environment = environment;
        _behaviors = new List<Behavior>();
        _states = new List<IState>();
    }

    #region IStateful


    public void SetState(string name) {
        SetState(name , o=> true);
    }

    public void SetState(string name, Func<object, bool> test) {
        if (_states.Any(s => Match(s, name))) {
        throw new ArgumentException();
        }
        _states.Add(new State(name, test));
    }

    public void ClearState(string name) {
        _states.RemoveAll(s => Match(s, name));
    }

    public bool IsInState(string name) {
        var theState = _states.FirstOrDefault(s => Match(s, name));
        return theState != null && theState.Is(this);
    }

    private static bool Match(IState state, string name) {
        return state.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase);
    }

    #endregion

    public void RegisterBehaviour(string name, Action<object> defaultAction) {
        _behaviors.Add(new Behavior(name, defaultAction));
    }

    public void RegisterBehaviorModifier(
        string name, 
        Func<IEnvironment, IStateful, bool> check,
        Action<object> replacementAction = null
    ) {
        ActOn(name, 
            b => b.BehaviorActions.Add(new BehaviorAction(check, 
                                                          replacementAction ?? (o =>{}))));
    }


    public void Invoke(string behaviourName) {
        ActOn(behaviourName, behavior => {
            var replacement = behavior.BehaviorActions.FirstOrDefault(b => b.Check(_environment, this));
            if (replacement == null) {
                behavior.DefaultAction(this);
            } else {
                replacement.Action(this);
            }
        });
    }

    private void ActOn(string name, Action<Behavior> action) {
        var behavior = _behaviors.FirstOrDefault(b => name.Equals(b.Name, StringComparison.CurrentCultureIgnoreCase));
        if (behavior != null) {
            action(behavior);
        }
    }

    private class Behavior {
        public Behavior(string name, Action<object> defaultAction) {
            Name = name;
            DefaultAction = defaultAction;
            BehaviorActions = new   List<BehaviorAction>();
        }

        public string Name { get; private set; }
        public Action<object> DefaultAction { get; private set; }
        public List<BehaviorAction> BehaviorActions { get; private set; }
    }

    private class BehaviorAction {

        public BehaviorAction(Func<IEnvironment, IStateful, bool> check, Action<object> action) {
            Check = check;
            Action = action;
        }

        public Func<IEnvironment, IStateful, bool> Check { get; private set; }
        public Action<object> Action { get; private set; }

    }

}

public class WithBehaviors<T> : WithBehaviors where T : class {

    public WithBehaviors(IEnvironment environment) : base(environment) {}

    public void RegisterBehaviour(string name, Action<T> defaultAction) {
        base.RegisterBehaviour(name, obj => defaultAction((T)obj));
    }

    public void RegisterBehaviorModifier(
        string name,
        Func<IEnvironment, IStateful, bool> check,
        Action<T> replacementAction = null
    ) {
        base.RegisterBehaviorModifier(name, 
                                      check,
                                      (replacementAction != null)
                                          ? (Action<object>)(o => replacementAction((T)o))
                                          : null);
    }

}


public  class Rabbit : WithBehaviors<Rabbit> {
    public Rabbit(IEnvironment environment) : base(environment){}

    public int XVal { get; set; }

}

public class State : IState {

    private readonly Func<object, bool> _test;

    public State(string name, Func<object, bool> test = null) {
        Name = name;
        _test = test;
    }

    public string Name { get; private set; }

    public bool Is(object target) {
        return _test(target);
    }
}

国家包括对目标的测试以及简单的是/否,例如疲劳状态可以定义为能量低于一定水平。可能是过度工程化。

[TestClass]
public class BehaviorsFixture {

    #region Setup

    private Mock<IEnvironment> _mockEnvironment;

    private Rabbit CreateRabbit() {

        var buggs = new Rabbit(_mockEnvironment.Object);
        buggs.RegisterBehaviour(Behaviors.Jump, r => r.XVal += 10);

        // gravity GTE 30, cannot jump
        buggs.RegisterBehaviorModifier(Behaviors.Jump, (e, o) => e.Gravity >= 30);

        // gravity GTE 20, (but LT 30), jumps 5
        buggs.RegisterBehaviorModifier(Behaviors.Jump, (e, o) => e.Gravity >= 20, r => r.XVal += 5);

        // if the rabbit is frozen it cannot jump
        buggs.RegisterBehaviorModifier(Behaviors.Jump, (e, o) => o.IsInState(States.Frozen));

        // if the rabbit is chilled it can only jump 2
        buggs.RegisterBehaviorModifier(Behaviors.Jump, (e, o) => o.IsInState(States.Chilled), r => r.XVal += 2);

        return buggs;
    }

    #endregion

    [TestInitialize]
    public void TestInitialize() {
    _mockEnvironment = new Mock<IEnvironment>();
    _mockEnvironment.SetupProperty(mk => mk.Gravity, 9.81);
    }


    [TestMethod]
    public void JumpingInGravity() {

        var buggs = CreateRabbit();

        Assert.AreEqual(0, buggs.XVal);

        buggs.Invoke(Behaviors.Jump);
        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(20, buggs.XVal);

        // higher gravity means can only jump 5
        _mockEnvironment.Object.Gravity = 20.0;

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(25, buggs.XVal);


        // even higher gravity, cannot jump
        _mockEnvironment.Object.Gravity = 30.0;

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(25, buggs.XVal);


        // set gravity back to normal - can jump
        _mockEnvironment.Object.Gravity = 9.81;

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(35, buggs.XVal);

    }

    [TestMethod]
    public void JumpingWhenCold() {

        var buggs = CreateRabbit();

        Assert.AreEqual(0, buggs.XVal);

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(10, buggs.XVal);

        // if frozen, cannot jump
        buggs.SetState(States.Frozen);

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(10, buggs.XVal);

        // remove, can jump again
        buggs.ClearState(States.Frozen);

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(20, buggs.XVal);

        // if chilled, can jump a bit
        buggs.SetState(States.Chilled);

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(22, buggs.XVal);

        // remove, can jump again
        buggs.ClearState(States.Chilled);

        buggs.Invoke(Behaviors.Jump);

        Assert.AreEqual(32, buggs.XVal);

        }
    }

}

突出的问题与

有关
  • 检查的优先级 - 目前,我们触发的第一个是我们使用的那个。仔细订购配置可以给出正确答案,但容易出错。也许添加优先级并在检查时按相同方式排序
  • 现有功能的修改 - 目前我们可以更换它但是说重力允许我们只跳半正常,没有办法允许这个(除了仔细配置)

我希望这更接近你所寻求的。再次抱歉这个混乱。

答案 3 :(得分:0)

这似乎需要装饰器模式,您可以通过使用相同类型的类进行装饰来覆盖或增强类的行为,并根据需要重复。外部Rabbit类控制最终行为,因此消费类不会怀疑它的作用,它只是调用方法。 ShedThisBehavior()方法返回一个移除了最外层行为的Rabbit。

public class Rabbit
{
    protected Rabbit _innerRabbit;
    public virtual void Jump()
    {
        //do something jumpy
    }
    public Rabbit ShedThisBehavior()//aka Get My Inner Rabbit
    {
        if (_innerRabbit == null)
        {
            //this will only happen when the most inner rabbit is reached
            return this; 
        }
        return _innerRabbit;
    }
}

//override the base behavior and don't jump because the rabbit is frozen
public class FrozenRabbit:Rabbit
{

    public FrozenRabbit(Rabbit innerRabbit)
    {
        _innerRabbit = innerRabbit;
    }

    public override void Jump()
    {
        //don't jump
    }
}

//override the base behavior and don't jump because the rabbit is wet
public class WetRabbit : Rabbit
{

    public WetRabbit(Rabbit innerRabbit)
    {
        _innerRabbit = innerRabbit;
    }

    public override void Jump()
    {
        //don't jump
    }
}

//ignore the inner rabit, and jump twice
public class VeryJumpyRabbit : Rabbit
{

    public VeryJumpyRabbit(Rabbit innerRabbit)
    {
        _innerRabbit = innerRabbit;
    }

    public override void Jump()
    {
        base.Jump();
        base.Jump();
    }
}

//do whatever the inner rabbit does for jumping
public class OtherAttributeRabbit : Rabbit
{

    public OtherAttributeRabbit(Rabbit innerRabbit)
    {
        _innerRabbit = innerRabbit;
    }

    public override void Jump()
    {
        _innerRabbit.Jump();
    }
}

答案 4 :(得分:0)

你可以使用Chain of Responsibility模式,其中Rabbit类由FrozenRabbit和HeavyRabbit处理程序扩展,修改跳跃距离(或受处理程序负责的因素影响的Rabbit的任何其他因素)

通过这种方式,您可以在游戏变得更加复杂时添加更多处理程序,同时保持Rabbit类的清洁。

您需要确保根据环境或兔子的状态以正确的顺序使用正确的处理程序。