将自定义永久/临时行为添加到基本类型

时间:2012-01-28 20:44:32

标签: c# design-patterns architecture xna

我有一个名为IComponent的基本核心接口。

public interface IComponent
{
    void Initialize();

    void Update();

    void Draw(Color tint);

    BoundingBox BoundingBox { get; }

    bool Initialized { get; }
}

它可以被任何可绘制的类使用(按钮,文本,纹理......)

我想开发一组可应用于任何IComponent的效果,例如:

Shake(IComponent component, TimeSpan duration)

- >使Component在给定的持续时间内振动。它的工作原理是,首先存储传递的组件的中心位置,并在每次更新时将其抵消,直到持续时间结束。

Translate(IComponent component, Vector2 destination, TimeSpan timeToReach)

- >使Component在一定时间后移动到给定目标。它通过在每次更新时逐步抵消组件来工作。

你可以想象更多......

所以假设我想要一个特定的类(纹理:IComponent)来震动,同时也要移动到某一点。我想过可能像这样使用装饰器模式:

创建纹理=>将其包裹在摇动中(持续5秒)=>将其包裹在翻译中(持续10秒)

但是存在一些问题。

首先,Shake包装器可以单独使用静态静态纹理,但与Translate结合使用会失败,因为它会将纹理恢复到原来的位置并且不会正常翻译。

其次,虽然翻译需要10秒才能完成,但振动只需5秒钟,所以之后我不知道如何从链内自动移除Shake包装,最后当它翻译时也会移除完成后,留下原始纹理。

第三,装饰器模式隐藏了包装对象的任何特定功能,因此在使用Shake包装Texture后,我将无法调用Texture的setPixelColor(),除非我之前还创建了第二个对Texture的直接引用包裹它。

欢迎任何关于如何优雅地应对这种挑战的建议。感谢。

注意:实际上,我很可能将这些效果应用于所有IComponent创建对象的2%。

2 个答案:

答案 0 :(得分:1)

沿着以下方向可能是另一种方法。

在你的例子中,摇动和平移都是动画效果。所有动画都需要某种形式的持续时间并应用于组件。这可能导致以下第一次采取:

public interface IAnimationEffect
{
    IComponent targetComponent;
    int Duration { get; set; }
}

动画的持续时间似乎是一种很好的初始方法,但是,当您需要组合动画时,每个动画需要在与其他动画相同的时间段内操纵其封装的IComponent的BoundingRect。为了能够像这样堆叠动画,需要指示动画效果的绘制方法来绘制动画的特定帧。

IAnimationEffect接口的改进版本是:

public interface IAnimationEffect
{
    IComponent targetComponent;
    int StartFrame { get; set; }
    int EndFrame { get; set; }
    void CalculateFrame(int frame);
}

无论哪个班级负责绘制您的IComponents(现在让我们称之为DrawingEngine),现在还负责保存所有适用的动画效果的内部列表。它还需要具有某种时间轴和每秒帧数的渲染逻辑,以便可以执行特定动画帧的计算。

public class ShakeAnimationEffect : IAnimationEffect
{
    public IComponent TargetComponent { get; set; }
    public int StartFrame { get; set; }
    public int EndFrame { get; set; }
    // Some shake specific properties can be added, to control the type of vibration etc 
    // (could be a rotating vibration an updown shake), but these really should have dedicated classes of their own.

    public void CalculateFrame(int frame)
    {
        // your maths manipulations for calculating the bounds of the IComponent go here
    }
}

public class TranslateAnimation : IAnimationEffect
{
    public IComponent targetComponent { get; set; }
    public int StartFrame { get; set; }
    public int EndFrame { get; set; }
    public int TranslateX { get; set; } 
    public int TranslateY { get; set; }

    public void CalculateFrame(int frame)
    {
        // your maths manipulations for calculating the bounds of the IComponent go here
    }
}

如上所述,您的AnimationEffects类对绘制IComponents没有任何责任,这仍然是DrawingEngine的责任。

在DrawingLoop之前需要直接引入的是用于运行所有动画效果的循环,所有这一切都是更新恰好具有与之关联的动画效果的对象的边界。然后像往常一样继续绘制对象。

假设你有这样的东西:

        // suppose your drawing engine has the following:
       List<IComponent> components = new List<IComponent>();

        //now just add the following 
       List<IAnimationEffect> animationEffects = new List<IAnimationEffect>(); 

        // create some animation effects and register them to your list
        animationEffects.Add(new ShakeAnimationEffect
          {
              TargetComponent = Lorry,
              StartFrame = 0,
              EndFrame = 150, //At 30 frames per second, this would be a 5sec animation
          }
        );

        //sample pseudecode
        RunAnimationCalculations();
        RunDrawingLoop();

RunAnimationCalculations将执行的操作是遍历animationEffects集合并运行Calculate传入当前帧,以便在随后绘制帧之前更新IComponent的边界。

祝你好运!

答案 1 :(得分:0)

你可以这样:

public Shake : ITransformation 
{
}

public Translate : ITransformation
{
}

IComponent接口还定义了应用于它的转换集合:

public interface IComponent
{
    public IEnumerable<ITransformation> GetTransformation();
}

Component

的一些具体实现中
public MyCompo : IComponennt 
{
    public Draw(Color tint) {

        //apply here transformations available in list of GetTransformation()

        .... 
        // draw
    }
}

只是一个想法。

希望这有帮助。